rocketmq-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rocketmq_sdk-0.1.0/.dockerignore +19 -0
- rocketmq_sdk-0.1.0/.github/workflows/ci.yml +40 -0
- rocketmq_sdk-0.1.0/.github/workflows/publish.yml +36 -0
- rocketmq_sdk-0.1.0/.gitignore +10 -0
- rocketmq_sdk-0.1.0/Dockerfile +18 -0
- rocketmq_sdk-0.1.0/LICENSE +21 -0
- rocketmq_sdk-0.1.0/Makefile +32 -0
- rocketmq_sdk-0.1.0/PKG-INFO +94 -0
- rocketmq_sdk-0.1.0/README.md +83 -0
- rocketmq_sdk-0.1.0/examples/custom_serializer.py +71 -0
- rocketmq_sdk-0.1.0/examples/exchange_routing.py +64 -0
- rocketmq_sdk-0.1.0/examples/publisher.py +51 -0
- rocketmq_sdk-0.1.0/pyproject.toml +85 -0
- rocketmq_sdk-0.1.0/src/rocketmq/__init__.py +75 -0
- rocketmq_sdk-0.1.0/src/rocketmq/amqp.py +222 -0
- rocketmq_sdk-0.1.0/src/rocketmq/client.py +435 -0
- rocketmq_sdk-0.1.0/src/rocketmq/error_codes.py +31 -0
- rocketmq_sdk-0.1.0/src/rocketmq/error_parser.py +215 -0
- rocketmq_sdk-0.1.0/src/rocketmq/errors.py +126 -0
- rocketmq_sdk-0.1.0/src/rocketmq/proto.py +188 -0
- rocketmq_sdk-0.1.0/src/rocketmq/py.typed +0 -0
- rocketmq_sdk-0.1.0/src/rocketmq/schema.py +89 -0
- rocketmq_sdk-0.1.0/src/rocketmq/serializer.py +66 -0
- rocketmq_sdk-0.1.0/tests/__init__.py +1 -0
- rocketmq_sdk-0.1.0/tests/test_client.py +318 -0
- rocketmq_sdk-0.1.0/tests/test_error_parser.py +162 -0
- rocketmq_sdk-0.1.0/tests/test_proto.py +152 -0
- rocketmq_sdk-0.1.0/tests/test_schema.py +55 -0
- rocketmq_sdk-0.1.0/tests/test_serializer.py +54 -0
- rocketmq_sdk-0.1.0/uv.lock +730 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
check:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v6
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
run: uv python install ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync
|
|
29
|
+
|
|
30
|
+
- name: Format check (ruff)
|
|
31
|
+
run: uv run ruff format --check src/ tests/ examples/
|
|
32
|
+
|
|
33
|
+
- name: Lint (ruff)
|
|
34
|
+
run: uv run ruff check src/ tests/ examples/
|
|
35
|
+
|
|
36
|
+
- name: Type check (pyright)
|
|
37
|
+
run: uv run pyright src/ tests/
|
|
38
|
+
|
|
39
|
+
- name: Test (pytest)
|
|
40
|
+
run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Trigger on GitHub releases (recommended over tag-push for PyPI)
|
|
4
|
+
on:
|
|
5
|
+
release:
|
|
6
|
+
types: [published]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: rocketmq.py
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read # Required for actions/checkout to clone the repo
|
|
14
|
+
id-token: write # Required for trusted publishing (OIDC)
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v6
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
|
|
24
|
+
- name: Set up Python
|
|
25
|
+
run: uv python install 3.12
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --no-dev --frozen
|
|
29
|
+
|
|
30
|
+
- name: Build package
|
|
31
|
+
run: uv build
|
|
32
|
+
|
|
33
|
+
- name: Publish to PyPI
|
|
34
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
35
|
+
# Uses trusted publishing (OIDC) — no API token needed.
|
|
36
|
+
# Configure at: https://pypi.org/manage/project/rocketmq/settings/publishing/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
FROM python:3.12-slim AS base
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Install uv for fast dependency resolution
|
|
6
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
7
|
+
|
|
8
|
+
# Copy dependency files first for layer caching
|
|
9
|
+
COPY pyproject.toml uv.lock ./
|
|
10
|
+
|
|
11
|
+
# Install production deps only (no dev group)
|
|
12
|
+
RUN uv sync --no-dev --frozen
|
|
13
|
+
|
|
14
|
+
# Copy source code
|
|
15
|
+
COPY src/ src/
|
|
16
|
+
|
|
17
|
+
# Default command: Python shell with rocketmq importable
|
|
18
|
+
CMD ["uv", "run", "python", "-c", "import rocketmq; print(f'rocketmq {rocketmq.__version__}')"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Edilson Pateguana
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.DEFAULT_GOAL := help
|
|
2
|
+
.PHONY: help install fmt lint typecheck test check all clean
|
|
3
|
+
|
|
4
|
+
help: ## Show this help
|
|
5
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
|
6
|
+
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
|
|
7
|
+
|
|
8
|
+
install: ## Install deps + editable package
|
|
9
|
+
uv sync
|
|
10
|
+
|
|
11
|
+
fmt: ## Format code (ruff)
|
|
12
|
+
uv run ruff format src/ tests/ examples/
|
|
13
|
+
|
|
14
|
+
lint: ## Lint code (ruff)
|
|
15
|
+
uv run ruff check src/ tests/ examples/
|
|
16
|
+
|
|
17
|
+
lint-fix: ## Lint + auto-fix (ruff)
|
|
18
|
+
uv run ruff check --fix src/ tests/ examples/
|
|
19
|
+
|
|
20
|
+
typecheck: ## Type-check (pyright)
|
|
21
|
+
uv run pyright src/ tests/
|
|
22
|
+
|
|
23
|
+
test: ## Run tests (pytest)
|
|
24
|
+
uv run pytest tests/ -v
|
|
25
|
+
|
|
26
|
+
check: lint typecheck test ## Run lint + typecheck + test
|
|
27
|
+
|
|
28
|
+
all: fmt lint-fix typecheck test ## Format + fix + typecheck + test
|
|
29
|
+
|
|
30
|
+
clean: ## Remove caches and build artifacts
|
|
31
|
+
rm -rf .venv .pytest_cache .ruff_cache dist build __pycache__
|
|
32
|
+
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rocketmq-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Schema-aware AMQP client for the RocketMQ broker — Pydantic + aio-pika.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: aio-pika<10.0,>=9.0
|
|
9
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# rocketmq.py
|
|
13
|
+
|
|
14
|
+
Schema-aware AMQP client for the RocketMQ broker - **Pydantic v2 + aio-pika**.
|
|
15
|
+
|
|
16
|
+
Python port of the [`rocketmq.js`](../rocketmq.js) TypeScript SDK.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
Requires [uv](https://docs.astral.sh/uv/).
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv sync # install deps + editable package
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import asyncio
|
|
30
|
+
from rocketmq import connect
|
|
31
|
+
from rocketmq.schema import BaseSchema
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Order(BaseSchema):
|
|
35
|
+
id: str
|
|
36
|
+
customer_id: str
|
|
37
|
+
qty: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def main():
|
|
41
|
+
mq = await connect(url="amqp://localhost")
|
|
42
|
+
orders = await mq.queue("orders", Order)
|
|
43
|
+
|
|
44
|
+
await orders.send(Order(id="1", customer_id="c1", qty=5))
|
|
45
|
+
await orders.consume(lambda msg: print(msg))
|
|
46
|
+
|
|
47
|
+
await asyncio.sleep(2)
|
|
48
|
+
await mq.close()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
asyncio.run(main())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
make help # show all commands
|
|
58
|
+
make fmt # format (ruff)
|
|
59
|
+
make lint # lint (ruff)
|
|
60
|
+
make typecheck # type-check (pyright)
|
|
61
|
+
make test # run tests (pytest)
|
|
62
|
+
make check # lint + typecheck + test
|
|
63
|
+
make all # format + fix + typecheck + test
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Architecture
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
src/rocketmq/
|
|
70
|
+
├── __init__.py # Public re-exports
|
|
71
|
+
├── py.typed # PEP 561 marker
|
|
72
|
+
├── amqp.py # Thin aio-pika wrapper
|
|
73
|
+
├── client.py # RocketMQ + connect() + QueueHandle
|
|
74
|
+
├── schema.py # BaseSchema + Proto annotation
|
|
75
|
+
├── proto.py # Pydantic model -> proto3 string
|
|
76
|
+
├── serializer.py # Serializer Protocol + JsonSerializer
|
|
77
|
+
├── errors.py # Error hierarchy
|
|
78
|
+
├── error_codes.py # BrokerErrorCode enum
|
|
79
|
+
└── error_parser.py # Broker JSON error parsing
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Cross-Language Type Compatibility
|
|
83
|
+
|
|
84
|
+
Python types map to proto3 types that match the JS SDK:
|
|
85
|
+
|
|
86
|
+
| Python | TypeScript (Zod) | Proto3 |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `str` | `z.string()` | `string` |
|
|
89
|
+
| `int` | `z.number()` | `double` |
|
|
90
|
+
| `float` | `z.number()` | `double` |
|
|
91
|
+
| `bool` | `z.boolean()` | `bool` |
|
|
92
|
+
| `bytes` | `z.object(...)` | `bytes` |
|
|
93
|
+
| `datetime` | `z.date()` | `Timestamp` |
|
|
94
|
+
| `Annotated[int, Proto("int32")]` | `z.number().int()` | `int32` |
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# rocketmq.py
|
|
2
|
+
|
|
3
|
+
Schema-aware AMQP client for the RocketMQ broker - **Pydantic v2 + aio-pika**.
|
|
4
|
+
|
|
5
|
+
Python port of the [`rocketmq.js`](../rocketmq.js) TypeScript SDK.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Requires [uv](https://docs.astral.sh/uv/).
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv sync # install deps + editable package
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import asyncio
|
|
19
|
+
from rocketmq import connect
|
|
20
|
+
from rocketmq.schema import BaseSchema
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Order(BaseSchema):
|
|
24
|
+
id: str
|
|
25
|
+
customer_id: str
|
|
26
|
+
qty: int
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def main():
|
|
30
|
+
mq = await connect(url="amqp://localhost")
|
|
31
|
+
orders = await mq.queue("orders", Order)
|
|
32
|
+
|
|
33
|
+
await orders.send(Order(id="1", customer_id="c1", qty=5))
|
|
34
|
+
await orders.consume(lambda msg: print(msg))
|
|
35
|
+
|
|
36
|
+
await asyncio.sleep(2)
|
|
37
|
+
await mq.close()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
asyncio.run(main())
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
make help # show all commands
|
|
47
|
+
make fmt # format (ruff)
|
|
48
|
+
make lint # lint (ruff)
|
|
49
|
+
make typecheck # type-check (pyright)
|
|
50
|
+
make test # run tests (pytest)
|
|
51
|
+
make check # lint + typecheck + test
|
|
52
|
+
make all # format + fix + typecheck + test
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Architecture
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
src/rocketmq/
|
|
59
|
+
├── __init__.py # Public re-exports
|
|
60
|
+
├── py.typed # PEP 561 marker
|
|
61
|
+
├── amqp.py # Thin aio-pika wrapper
|
|
62
|
+
├── client.py # RocketMQ + connect() + QueueHandle
|
|
63
|
+
├── schema.py # BaseSchema + Proto annotation
|
|
64
|
+
├── proto.py # Pydantic model -> proto3 string
|
|
65
|
+
├── serializer.py # Serializer Protocol + JsonSerializer
|
|
66
|
+
├── errors.py # Error hierarchy
|
|
67
|
+
├── error_codes.py # BrokerErrorCode enum
|
|
68
|
+
└── error_parser.py # Broker JSON error parsing
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Cross-Language Type Compatibility
|
|
72
|
+
|
|
73
|
+
Python types map to proto3 types that match the JS SDK:
|
|
74
|
+
|
|
75
|
+
| Python | TypeScript (Zod) | Proto3 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `str` | `z.string()` | `string` |
|
|
78
|
+
| `int` | `z.number()` | `double` |
|
|
79
|
+
| `float` | `z.number()` | `double` |
|
|
80
|
+
| `bool` | `z.boolean()` | `bool` |
|
|
81
|
+
| `bytes` | `z.object(...)` | `bytes` |
|
|
82
|
+
| `datetime` | `z.date()` | `Timestamp` |
|
|
83
|
+
| `Annotated[int, Proto("int32")]` | `z.number().int()` | `int32` |
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example: Custom serializer.
|
|
3
|
+
|
|
4
|
+
Demonstrates plugging in a custom serializer (MessagePack in this case)
|
|
5
|
+
while keeping the same schema-aware API.
|
|
6
|
+
|
|
7
|
+
Run::
|
|
8
|
+
|
|
9
|
+
pip install msgpack
|
|
10
|
+
python examples/custom_serializer.py
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
|
|
15
|
+
from rocketmq import connect
|
|
16
|
+
from rocketmq.schema import BaseSchema
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MsgpackSerializer:
|
|
20
|
+
"""MessagePack serializer — drop-in replacement for JsonSerializer.
|
|
21
|
+
|
|
22
|
+
Usage::
|
|
23
|
+
|
|
24
|
+
mq = await connect(url="amqp://localhost", serializer=MsgpackSerializer())
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def content_type(self) -> str:
|
|
29
|
+
return "application/x-msgpack"
|
|
30
|
+
|
|
31
|
+
def serialize(self, value: object) -> bytes:
|
|
32
|
+
from typing import cast
|
|
33
|
+
|
|
34
|
+
import msgpack
|
|
35
|
+
|
|
36
|
+
# WHY: packb is typed as -> bytes | None in stubs, but never
|
|
37
|
+
# returns None for dict/list inputs.
|
|
38
|
+
return cast("bytes", msgpack.packb(value, use_bin_type=True))
|
|
39
|
+
|
|
40
|
+
def deserialize(self, data: bytes) -> object:
|
|
41
|
+
import msgpack
|
|
42
|
+
|
|
43
|
+
return msgpack.unpackb(data, raw=False)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SensorReading(BaseSchema):
|
|
47
|
+
sensor_id: str
|
|
48
|
+
temperature: float
|
|
49
|
+
humidity: float
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def main() -> None:
|
|
53
|
+
mq = await connect(url="amqp://localhost", serializer=MsgpackSerializer())
|
|
54
|
+
readings = await mq.queue("sensor-readings", SensorReading)
|
|
55
|
+
|
|
56
|
+
await readings.send(
|
|
57
|
+
SensorReading(
|
|
58
|
+
sensor_id="s-42",
|
|
59
|
+
temperature=23.5,
|
|
60
|
+
humidity=61.2,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
_ = await readings.consume(lambda msg: print(f"Sensor {msg.sensor_id}: {msg.temperature}°C"))
|
|
65
|
+
|
|
66
|
+
await asyncio.sleep(2)
|
|
67
|
+
await mq.close()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example: Exchange routing with Pydantic schemas.
|
|
3
|
+
|
|
4
|
+
Demonstrates declaring an exchange, binding queues with routing keys,
|
|
5
|
+
and publishing messages that route to specific consumers.
|
|
6
|
+
|
|
7
|
+
Run::
|
|
8
|
+
|
|
9
|
+
python examples/exchange_routing.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
|
|
14
|
+
from rocketmq import connect
|
|
15
|
+
from rocketmq.schema import BaseSchema
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OrderEvent(BaseSchema):
|
|
19
|
+
order_id: str
|
|
20
|
+
action: str
|
|
21
|
+
amount: float
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def main() -> None:
|
|
25
|
+
mq = await connect(url="amqp://localhost")
|
|
26
|
+
|
|
27
|
+
# Declare exchange + queues
|
|
28
|
+
await mq.assert_exchange("orders.topic", "direct")
|
|
29
|
+
await mq.assert_queue("orders.created", OrderEvent)
|
|
30
|
+
await mq.assert_queue("orders.cancelled", OrderEvent)
|
|
31
|
+
await mq.bind_queue("orders.created", "orders.topic", "created")
|
|
32
|
+
await mq.bind_queue("orders.cancelled", "orders.topic", "cancelled")
|
|
33
|
+
|
|
34
|
+
# Publish to different routing keys
|
|
35
|
+
await mq.publish(
|
|
36
|
+
"orders.topic",
|
|
37
|
+
"created",
|
|
38
|
+
{
|
|
39
|
+
"order_id": "ord-001",
|
|
40
|
+
"action": "created",
|
|
41
|
+
"amount": 99.90,
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
await mq.publish(
|
|
46
|
+
"orders.topic",
|
|
47
|
+
"cancelled",
|
|
48
|
+
{
|
|
49
|
+
"order_id": "ord-002",
|
|
50
|
+
"action": "cancelled",
|
|
51
|
+
"amount": 45.00,
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Consume from specific queues
|
|
56
|
+
_ = await mq.consume("orders.created", OrderEvent, lambda msg: print(f"NEW: {msg}"))
|
|
57
|
+
_ = await mq.consume("orders.cancelled", OrderEvent, lambda msg: print(f"CANCEL: {msg}"))
|
|
58
|
+
|
|
59
|
+
await asyncio.sleep(2)
|
|
60
|
+
await mq.close()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example: Pydantic model schema — publish + consume.
|
|
3
|
+
|
|
4
|
+
Demonstrates defining a message schema as a Pydantic model, declaring
|
|
5
|
+
a queue with schema metadata, publishing a typed message, and consuming
|
|
6
|
+
with automatic deserialization.
|
|
7
|
+
|
|
8
|
+
Run::
|
|
9
|
+
|
|
10
|
+
python examples/publisher.py
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
from typing import Annotated
|
|
15
|
+
|
|
16
|
+
from rocketmq import connect
|
|
17
|
+
from rocketmq.schema import BaseSchema, Proto
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Notification(BaseSchema):
|
|
21
|
+
id: Annotated[int, Proto("int32")]
|
|
22
|
+
content: str
|
|
23
|
+
timestamp: float
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def main() -> None:
|
|
27
|
+
mq = await connect(url="amqp://localhost")
|
|
28
|
+
queue = await mq.queue("pending-notifications", Notification)
|
|
29
|
+
|
|
30
|
+
message = Notification(
|
|
31
|
+
id=1,
|
|
32
|
+
content="Hello from Python SDK",
|
|
33
|
+
timestamp=1717520000.0,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Publish
|
|
37
|
+
await queue.send(message)
|
|
38
|
+
|
|
39
|
+
# Consume
|
|
40
|
+
async def on_message(msg: Notification) -> None:
|
|
41
|
+
print(f"Received: id={msg.id} content={msg.content}")
|
|
42
|
+
|
|
43
|
+
_ = await queue.consume(on_message)
|
|
44
|
+
|
|
45
|
+
# Keep alive to let messages arrive, then close.
|
|
46
|
+
await asyncio.sleep(2)
|
|
47
|
+
await mq.close()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rocketmq-sdk"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Schema-aware AMQP client for the RocketMQ broker — Pydantic + aio-pika."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"aio-pika>=9.0,<10.0",
|
|
10
|
+
"pydantic>=2.0,<3.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["hatchling"]
|
|
15
|
+
build-backend = "hatchling.build"
|
|
16
|
+
|
|
17
|
+
[tool.hatch.build.targets.wheel]
|
|
18
|
+
packages = ["src/rocketmq"]
|
|
19
|
+
|
|
20
|
+
# --------------------------------------------------------------------------
|
|
21
|
+
# ruff — lint + format (replaces flake8, isort, black, pyupgrade)
|
|
22
|
+
# --------------------------------------------------------------------------
|
|
23
|
+
[tool.ruff]
|
|
24
|
+
target-version = "py311"
|
|
25
|
+
line-length = 100
|
|
26
|
+
src = ["src", "tests"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff.lint]
|
|
29
|
+
select = [
|
|
30
|
+
"E", # pycodestyle errors
|
|
31
|
+
"W", # pycodestyle warnings
|
|
32
|
+
"F", # pyflakes
|
|
33
|
+
"I", # isort
|
|
34
|
+
"UP", # pyupgrade
|
|
35
|
+
"B", # flake8-bugbear
|
|
36
|
+
"SIM", # flake8-simplify
|
|
37
|
+
"RUF", # ruff-specific rules
|
|
38
|
+
"TCH", # type-checking imports
|
|
39
|
+
"PT", # flake8-pytest-style
|
|
40
|
+
"PIE", # flake8-pie
|
|
41
|
+
"C4", # flake8-comprehensions
|
|
42
|
+
"T20", # flake8-print (catch stray prints)
|
|
43
|
+
]
|
|
44
|
+
ignore = [
|
|
45
|
+
"T201", # allow print() in examples
|
|
46
|
+
"UP007", # allow Union[X, Y] alongside X | Y in runtime contexts
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint.isort]
|
|
50
|
+
known-first-party = ["rocketmq"]
|
|
51
|
+
force-single-line = false
|
|
52
|
+
lines-after-imports = 2
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint.per-file-ignores]
|
|
55
|
+
"tests/**" = ["T201"] # allow print() in tests
|
|
56
|
+
"examples/**" = ["T201"] # allow print() in examples
|
|
57
|
+
|
|
58
|
+
# --------------------------------------------------------------------------
|
|
59
|
+
# pyright — static type checking
|
|
60
|
+
# --------------------------------------------------------------------------
|
|
61
|
+
[tool.pyright]
|
|
62
|
+
pythonVersion = "3.11"
|
|
63
|
+
venvPath = "."
|
|
64
|
+
venv = ".venv"
|
|
65
|
+
typeCheckingMode = "standard"
|
|
66
|
+
include = ["src", "tests", "examples"]
|
|
67
|
+
reportMissingTypeStubs = false
|
|
68
|
+
|
|
69
|
+
# --------------------------------------------------------------------------
|
|
70
|
+
# pytest
|
|
71
|
+
# --------------------------------------------------------------------------
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
asyncio_mode = "auto"
|
|
74
|
+
testpaths = ["tests"]
|
|
75
|
+
|
|
76
|
+
[dependency-groups]
|
|
77
|
+
dev = [
|
|
78
|
+
"pytest>=8.0",
|
|
79
|
+
"pytest-asyncio>=0.24",
|
|
80
|
+
"pyright>=1.1.410",
|
|
81
|
+
"ruff>=0.15.16",
|
|
82
|
+
]
|
|
83
|
+
examples = [
|
|
84
|
+
"msgpack>=1.0,<2.0",
|
|
85
|
+
]
|