Metadata-Version: 2.4
Name: django-mqtt-client
Version: 0.1.1
Summary: Asynchronous MQTT client library for Python with optional Django integration
Author-email: Your Name <your.email@example.com>
Maintainer-email: Your Name <your.email@example.com>
License: MIT
Project-URL: Homepage, https://github.com/yourusername/django-mqtt-client
Project-URL: Documentation, https://github.com/yourusername/django-mqtt-client#readme
Project-URL: Repository, https://github.com/yourusername/django-mqtt-client
Project-URL: Bug Tracker, https://github.com/yourusername/django-mqtt-client/issues
Project-URL: Changelog, https://github.com/yourusername/django-mqtt-client/releases
Keywords: django,mqtt,publisher,subscriber,client,async,iot,messaging,broker,standalone,python
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Framework :: Django
Classifier: Framework :: Django :: 3.2
Classifier: Framework :: Django :: 4.0
Classifier: Framework :: Django :: 4.1
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Communications
Classifier: Topic :: Internet
Classifier: Topic :: System :: Networking
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=3.2
Requires-Dist: paho-mqtt>=2.1.0
Requires-Dist: aio-pika>=9.4.0
Provides-Extra: django
Requires-Dist: Django>=3.2; extra == "django"
Provides-Extra: dev
Requires-Dist: Django>=3.2; extra == "dev"
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-django>=4.5; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: black>=23.0; extra == "dev"
Requires-Dist: flake8>=6.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: isort>=5.12; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx>=5.0; extra == "docs"
Requires-Dist: sphinx-rtd-theme>=1.2; extra == "docs"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: pytest-django>=4.5; extra == "test"
Requires-Dist: pytest-asyncio>=0.21; extra == "test"
Requires-Dist: pytest-cov>=4.0; extra == "test"
Dynamic: license-file

# django-mqtt-client

Unified inter-service event bus utility for Django services (MQTT + RabbitMQ).

## Objectives

- Standardize inter-service messaging using one RabbitMQ envelope schema.
- Support request/response/event patterns with topic conventions.
- Ensure reliable publish/consume with retries, backoff, and worker heartbeats.
- Persist envelope metadata in DB (`message_type`, `message_status`, `source_service`, `reply_to`, `parent_request_id`, `chain_depth`).
- Guarantee message identity with `message_id` (auto-generated/fallback from `OutboundEvent.id` when missing on outbound).
- Keep MQTT inbound compatible for EdgeX events while enforcing schema for RabbitMQ.

## What it does

- Outbound flow: DB `OutboundEvent` -> broker publish
- Inbound flow: broker consume -> DB `InboundEvent`
- Shared RabbitMQ envelope validation via `RabbitMqSchemaValidator`
- Retry + backoff for outbound publish failures
- Worker heartbeat/status updates via runtime config table

## Install

```bash
pip install django-mqtt-client
```

## Django setup

```python
INSTALLED_APPS = [
    # ...
    "django_mqtt_client",
]
```

Run migrations:

```bash
python manage.py migrate
```

## Key modules

- `django_mqtt_client/event_bus.py`
  - async worker for MQTT and RabbitMQ
- `django_mqtt_client/rabbitmq_schema.py`
  - envelope normalize/validate for RabbitMQ messages
- `django_mqtt_client/exceptions.py`
  - broker-neutral exceptions

## RabbitMQ envelope

Schema: `interservice.envelope.v1`

```json
{
  "schema": "interservice.envelope.v1",
  "message_id": "uuid",
  "message_type": "request",
  "topic": "edgenexus.request.machine.read",
  "source_service": "ebmr",
  "request_id": "req-100",
  "parent_request_id": "req-99",
  "chain_depth": 1,
  "reply_to": "ebmr.response.machine.read",
  "payload": {
    "machine_id": "M1"
  }
}
```

### Message type rules

- `request`: requires `request_id`, `reply_to`
- `response`: requires `request_id`, `status` (`success` or `error`)
- `event`: requires `event_type`

### `message_id` behavior

- Envelope includes `message_id`.
- Outbound RabbitMQ: if `message_id` is missing, utility auto-generates a UUID.
- Outbound fallback: if DB row has no envelope `message_id`, `OutboundEvent.id` is used before validation.
- Inbound RabbitMQ: `message_id` must be present after normalization; invalid messages are dropped.

### Topic rules

- Request: `<target_service>.request.<entity>.<action>[.<subaction>...]`
- Response: `<requesting_service>.response.<entity>.<action>[.<subaction>...]`
- Event: `event.<domain>.<entity>.<action>[.<subaction>...]`

Examples:

- `edgenexus.request.machine.read`
- `ebmr.response.machine.read`
- `event.operational.machine.status.changed`

### Message samples

Request:

```json
{
  "message_type": "request",
  "topic": "edgenexus.request.machine.read",
  "request_id": "req-100",
  "reply_to": "ebmr.response.machine.read",
  "payload": {
    "machine_id": "M1"
  }
}
```

Response:

```json
{
  "message_type": "response",
  "topic": "ebmr.response.machine.read",
  "request_id": "req-100",
  "status": "success",
  "payload": {
    "machine_id": "M1",
    "state": "running"
  }
}
```

Event:

```json
{
  "message_type": "event",
  "topic": "event.operational.machine.status.changed",
  "event_type": "machine.status.changed",
  "payload": {
    "machine_id": "M1",
    "status": "running"
  }
}
```

Chaining sample:

```json
{
  "message_type": "request",
  "topic": "edgenexus.request.batch.start",
  "request_id": "req-200",
  "parent_request_id": "req-100",
  "chain_depth": 1,
  "reply_to": "ebmr.response.batch.start",
  "payload": {
    "batch_id": "B-001"
  }
}
```

## Runtime DB config

Worker reads active row from `EventBusRuntimeConfig` (`config_key`, `is_active`).

Broker-specific child settings:

- `EventBusMqttSettings`
- `EventBusRabbitMqSettings`

Shared connection fields are in runtime config (`host`, `port`, `username`, `password`, `connect_timeout`, `retry_interval_seconds`).

## Event table fields

### OutboundEvent

- transport fields:
  - `status` (`pending/published/invalid/error`) for publish lifecycle
  - `published`, `published_at`, `retry_count`, `next_retry_at`, `last_error`
- envelope-related fields:
  - `topic`
  - `request_id`
  - `event_type`
  - `message_type`
  - `message_status` (mapped to envelope `status`)
  - `source_service`
  - `parent_request_id`
  - `chain_depth`
  - `reply_to`
  - `payload`
  - `id` is used as fallback `message_id` if payload/message row does not provide one

### InboundEvent

- `topic`
- `request_id`
- `message_type`
- `message_status` (envelope/business status if present)
- `reply_to`
- `source_service`
- `parent_request_id`
- `chain_depth`
- `payload`
- processing fields:
  - `status` (`pending/processed/error`) for local processing lifecycle
  - `processed`, `processed_at`, `last_error`

## EVENT_BUS_CONFIG example


## Worker

Start:

```bash
python manage.py event_bus_worker --config-key default
```

Optional override:

```bash
python manage.py event_bus_worker --config-key default --broker-type rabbitmq
```

Status:

```bash
python manage.py event_bus_status
```

## Behavior notes

- Broker connect failure retries every configured retry interval.
- Outbound invalid payload/envelope is marked `status=invalid`.
- Outbound publish errors use exponential backoff until `max_retry_count`.
- If `InboundEvent.message_id` exists, duplicate inbound message IDs are ignored.

## Tests

Run from repo root:

```bash
pytest -q
```

If your environment does not include coverage plugins from `pyproject.toml`, run:

```bash
pytest -q -o addopts=''
```
