Metadata-Version: 2.3
Name: whatsapp-models
Version: 0.2.6
Summary: Pydantic v2 models for the WhatsApp Business API — outgoing messages, incoming webhooks, and template management.
Author: Gabriel Chaves
Author-email: Gabriel Chaves <gabriel.chaves@olist.com>
License: MIT License
         
         Copyright (c) 2026 Gabriel Chaves
         
         Permission is hereby granted, free of charge, to any person obtaining a copy
         of this software and associated documentation files (the "Software"), to deal
         in the Software without restriction, including without limitation the rights
         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is
         furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in all
         copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.
Requires-Dist: pydantic>=2.12.5
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# whatsapp-models

Biblioteca Python de modelos de dados ([Pydantic v2](https://docs.pydantic.dev/latest/)) para as APIs do WhatsApp Business da Meta.

## Instalação

```bash
pip install whatsapp-models
# ou com uv
uv add whatsapp-models
```

## Módulos

| Módulo | Descrição |
|---|---|
| `messages` | Modelos de envio (`POST /messages`) |
| `message_templates` | Criação e envio de templates |
| `webhooks` | Payloads recebidos via webhook |
| `phone_numbers` | Gerenciamento de números |
| `media` | Upload e referência de mídia |

---

## Exemplos

### Envio de mensagens

#### Mensagem de texto

```python
from whatsapp_models import TextMessage

msg = TextMessage(to="+5511999999999", text={"body": "Olá!"})
payload = msg.model_dump()
# POST /messages — body: payload
```

#### Mensagem de mídia (imagem, vídeo, documento, áudio, sticker)

```python
from whatsapp_models import ImageMessage, DocumentMessage

# Por ID de mídia previamente enviada
image = ImageMessage(to="+5511999999999", image={"id": "media_id_abc", "caption": "Foto do evento"})

# Por URL hospedada
doc = DocumentMessage(
    to="+5511999999999",
    document={"link": "https://example.com/relatorio.pdf", "filename": "relatorio.pdf"},
)
```

#### Mensagem interativa — botões de resposta rápida

```python
from whatsapp_models import InteractiveMessage

msg = InteractiveMessage(
    to="+5511999999999",
    interactive={
        "type": "button",
        "body": {"text": "Confirme sua presença:"},
        "action": {
            "buttons": [
                {"type": "reply", "reply": {"id": "sim", "title": "Sim"}},
                {"type": "reply", "reply": {"id": "nao", "title": "Não"}},
            ]
        },
    },
)
```

#### Mensagem interativa — lista

```python
from whatsapp_models import InteractiveMessage

msg = InteractiveMessage(
    to="+5511999999999",
    interactive={
        "type": "list",
        "body": {"text": "Escolha um departamento:"},
        "action": {
            "button": "Ver opções",
            "sections": [
                {
                    "title": "Suporte",
                    "rows": [
                        {"id": "tecnico", "title": "Suporte Técnico"},
                        {"id": "financeiro", "title": "Financeiro"},
                    ],
                }
            ],
        },
    },
)
```

#### Mensagem via template

```python
from whatsapp_models import TemplateMessage

msg = TemplateMessage(
    to="+5511999999999",
    template={
        "name": "hello_world",
        "language": {"code": "pt_BR"},
        "components": [
            {
                "type": "body",
                "parameters": [{"type": "text", "text": "João"}],
            }
        ],
    },
)
```

#### Discriminated union — `OutgoingMessage`

Útil para serializar ou deserializar qualquer mensagem de saída pelo campo `type`:

```python
from pydantic import TypeAdapter
from whatsapp_models import OutgoingMessage

adapter = TypeAdapter(OutgoingMessage)
msg = adapter.validate_python({
    "to": "+5511999999999",
    "type": "text",
    "text": {"body": "Olá!"},
})
# msg é uma instância de TextMessage
```

---

### Templates

#### Criação de template

```python
from whatsapp_models import (
    CreateTemplateRequest,
    TemplateCategory,
    HeaderComponent,
    BodyComponent,
    FooterComponent,
    HeaderFormat,
)

request = CreateTemplateRequest(
    name="confirmacao_pedido",
    language="pt_BR",
    category=TemplateCategory.UTILITY,
    components=[
        HeaderComponent(format=HeaderFormat.TEXT, text="Pedido confirmado"),
        BodyComponent(text="Olá {{1}}, seu pedido #{{2}} foi confirmado."),
        FooterComponent(text="Dúvidas? Fale conosco."),
    ],
)
```

---

### Webhooks

#### Deserializar notificação recebida

```python
from whatsapp_models import WebhookNotification

payload = { ... }  # dict recebido no endpoint
notification = WebhookNotification.model_validate(payload)

for entry in notification.entry:
    for change in entry.changes:
        for message in change.value.messages:
            print(type(message).__name__, message.type)
```

#### Mensagem direta — text

```python
from whatsapp_models.webhooks.messages import IncomingTextMessage

if isinstance(message, IncomingTextMessage):
    print(message.from_, message.text.body)
```

#### Mensagem de grupo

Mensagens de grupo possuem `group_id` no payload e são resolvidas automaticamente para o tipo `IncomingGroup*` correspondente:

```python
from whatsapp_models.webhooks.messages import IncomingGroupTextMessage

if isinstance(message, IncomingGroupTextMessage):
    print(f"Grupo {message.group_id}: {message.text.body}")
```

#### Status de entrega

```python
from whatsapp_models.webhooks.statuses import DeliveryStatus

for status in change.value.statuses:
    if status.status == DeliveryStatus.failed:
        print(f"Falha ao entregar {status.id}: {status.errors}")
```

---

### Números de telefone

```python
from whatsapp_models import PhoneNumber, QualityRating

pn = PhoneNumber(
    id="pn_id_1",
    display_phone_number="+55 11 99999-9999",
    verified_name="Minha Empresa",
    quality_rating=QualityRating.GREEN,
)
```

---

### Mídia

```python
from whatsapp_models import MediaObject

# Referência por ID (após upload)
ref = MediaObject(id="media_id_abc")

# Referência por URL
ref = MediaObject(link="https://example.com/audio.ogg", filename="audio.ogg")
```

---

## Convenções

- Todos os modelos herdam de `pydantic.BaseModel` com `validate_by_name=True` e `validate_by_alias=True`
- Campos opcionais usam `field: Type | None = None`
- Enums usam `StrEnum` — serializam como string pura
- Discriminated unions usam `Field(discriminator="type")` para parse direto sem tentativa e erro

## Desenvolvimento

```bash
uv sync
uv run pytest
uv run ruff check . && uv run ruff format .
```
