arplyx 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.
arplyx-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: arplyx
3
+ Version: 0.1.0
4
+ Summary: SDK oficial de Arplyx: enviá mensajes y broadcasts de WhatsApp desde Python.
5
+ Author-email: Arplyx <info@arplyx.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://docs.arplyx.com/api/
8
+ Project-URL: Documentation, https://docs.arplyx.com/api/
9
+ Keywords: arplyx,whatsapp,whatsapp-api,sdk,mensajeria,broadcasts
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.25
16
+
17
+ # arplyx (Python SDK)
18
+
19
+ SDK oficial de **Arplyx** para Python: enviá mensajes y broadcasts de WhatsApp y consultá estados con una API tipada.
20
+
21
+ ## Instalación
22
+
23
+ ```bash
24
+ pip install arplyx
25
+ ```
26
+
27
+ Requiere Python 3.8+.
28
+
29
+ ## Uso
30
+
31
+ ```python
32
+ from arplyx import Arplyx, ArplyxError
33
+
34
+ client = Arplyx(api_key="ak_live_...")
35
+
36
+ # Enviar un texto
37
+ msg = client.send_message(
38
+ external_id="pedido-10045",
39
+ to="+5491155551234",
40
+ text="Tu pedido fue confirmado.",
41
+ channel="whatsapp_direct",
42
+ whatsapp_account_id="TU_ACCOUNT_ID",
43
+ )
44
+ print(msg.message_id, msg.status)
45
+
46
+ # Consultar estado (por message_id o por tu external_id)
47
+ status = client.get_message("pedido-10045")
48
+ print(status.status) # pending | queued | sent | delivered | read | failed
49
+ ```
50
+
51
+ ### Plantillas (Meta Official)
52
+
53
+ ```python
54
+ client.send_message(
55
+ external_id="pedido-10045",
56
+ to="+5491155551234",
57
+ channel="whatsapp_meta",
58
+ template={"name": "pedido_confirmado", "language": "es_AR", "body": ["Juan", "10045"]},
59
+ )
60
+ ```
61
+
62
+ ### Broadcast a una lista (planes Basic/Pro)
63
+
64
+ ```python
65
+ client.send_broadcast(
66
+ external_id="promo-junio-2026",
67
+ list_id="TU_LIST_ID",
68
+ text="Este mes 2x1 en turnos.",
69
+ channel="whatsapp_direct",
70
+ whatsapp_account_id="TU_ACCOUNT_ID",
71
+ )
72
+ ```
73
+
74
+ ### Listar cuentas de WhatsApp
75
+
76
+ ```python
77
+ for acc in client.list_whatsapp_accounts():
78
+ print(acc.id, acc.type, acc.status) # acc.id se usa como whatsapp_account_id
79
+ ```
80
+
81
+ ## Manejo de errores
82
+
83
+ ```python
84
+ try:
85
+ client.send_message(external_id="x", to="+549...", text="hola")
86
+ except ArplyxError as e:
87
+ print(e.status) # 400, 401, 409, 422, 429, 500...
88
+ print(e.code) # 'validation_error', 'conflict', 'quota_exceeded'...
89
+ print(e.details) # detalles por campo si es validation_error
90
+ ```
91
+
92
+ ## Idempotencia
93
+
94
+ Todos los envíos usan tu `external_id` como clave de idempotencia: reintentar con el mismo `external_id` y payload no duplica el envío. Es seguro reintentar ante timeouts.
95
+
96
+ ## Configuración
97
+
98
+ | Parámetro | Requerido | Default |
99
+ |---|---|---|
100
+ | `api_key` | Sí | — |
101
+ | `base_url` | No | `https://api.arplyx.com` |
102
+ | `timeout` | No | `10.0` |
103
+ | `session` | No | `requests.Session()` nueva |
104
+
105
+ Docs de la API: https://docs.arplyx.com/api/ · OpenAPI: https://api.arplyx.com/openapi.yaml
arplyx-0.1.0/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # arplyx (Python SDK)
2
+
3
+ SDK oficial de **Arplyx** para Python: enviá mensajes y broadcasts de WhatsApp y consultá estados con una API tipada.
4
+
5
+ ## Instalación
6
+
7
+ ```bash
8
+ pip install arplyx
9
+ ```
10
+
11
+ Requiere Python 3.8+.
12
+
13
+ ## Uso
14
+
15
+ ```python
16
+ from arplyx import Arplyx, ArplyxError
17
+
18
+ client = Arplyx(api_key="ak_live_...")
19
+
20
+ # Enviar un texto
21
+ msg = client.send_message(
22
+ external_id="pedido-10045",
23
+ to="+5491155551234",
24
+ text="Tu pedido fue confirmado.",
25
+ channel="whatsapp_direct",
26
+ whatsapp_account_id="TU_ACCOUNT_ID",
27
+ )
28
+ print(msg.message_id, msg.status)
29
+
30
+ # Consultar estado (por message_id o por tu external_id)
31
+ status = client.get_message("pedido-10045")
32
+ print(status.status) # pending | queued | sent | delivered | read | failed
33
+ ```
34
+
35
+ ### Plantillas (Meta Official)
36
+
37
+ ```python
38
+ client.send_message(
39
+ external_id="pedido-10045",
40
+ to="+5491155551234",
41
+ channel="whatsapp_meta",
42
+ template={"name": "pedido_confirmado", "language": "es_AR", "body": ["Juan", "10045"]},
43
+ )
44
+ ```
45
+
46
+ ### Broadcast a una lista (planes Basic/Pro)
47
+
48
+ ```python
49
+ client.send_broadcast(
50
+ external_id="promo-junio-2026",
51
+ list_id="TU_LIST_ID",
52
+ text="Este mes 2x1 en turnos.",
53
+ channel="whatsapp_direct",
54
+ whatsapp_account_id="TU_ACCOUNT_ID",
55
+ )
56
+ ```
57
+
58
+ ### Listar cuentas de WhatsApp
59
+
60
+ ```python
61
+ for acc in client.list_whatsapp_accounts():
62
+ print(acc.id, acc.type, acc.status) # acc.id se usa como whatsapp_account_id
63
+ ```
64
+
65
+ ## Manejo de errores
66
+
67
+ ```python
68
+ try:
69
+ client.send_message(external_id="x", to="+549...", text="hola")
70
+ except ArplyxError as e:
71
+ print(e.status) # 400, 401, 409, 422, 429, 500...
72
+ print(e.code) # 'validation_error', 'conflict', 'quota_exceeded'...
73
+ print(e.details) # detalles por campo si es validation_error
74
+ ```
75
+
76
+ ## Idempotencia
77
+
78
+ Todos los envíos usan tu `external_id` como clave de idempotencia: reintentar con el mismo `external_id` y payload no duplica el envío. Es seguro reintentar ante timeouts.
79
+
80
+ ## Configuración
81
+
82
+ | Parámetro | Requerido | Default |
83
+ |---|---|---|
84
+ | `api_key` | Sí | — |
85
+ | `base_url` | No | `https://api.arplyx.com` |
86
+ | `timeout` | No | `10.0` |
87
+ | `session` | No | `requests.Session()` nueva |
88
+
89
+ Docs de la API: https://docs.arplyx.com/api/ · OpenAPI: https://api.arplyx.com/openapi.yaml
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "arplyx"
7
+ version = "0.1.0"
8
+ description = "SDK oficial de Arplyx: enviá mensajes y broadcasts de WhatsApp desde Python."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Arplyx", email = "info@arplyx.com" }]
13
+ keywords = ["arplyx", "whatsapp", "whatsapp-api", "sdk", "mensajeria", "broadcasts"]
14
+ dependencies = ["requests>=2.25"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://docs.arplyx.com/api/"
23
+ Documentation = "https://docs.arplyx.com/api/"
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
arplyx-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ """SDK oficial de Arplyx para Python."""
2
+
3
+ from .client import Arplyx
4
+ from .errors import ArplyxError
5
+ from .types import (
6
+ BroadcastResponse,
7
+ MessageResponse,
8
+ MessageStatus,
9
+ WhatsAppAccount,
10
+ )
11
+
12
+ __version__ = "0.1.0"
13
+ __all__ = [
14
+ "Arplyx",
15
+ "ArplyxError",
16
+ "MessageResponse",
17
+ "BroadcastResponse",
18
+ "MessageStatus",
19
+ "WhatsAppAccount",
20
+ ]
@@ -0,0 +1,157 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ import requests
4
+
5
+ from .errors import ArplyxError
6
+ from .types import (
7
+ BroadcastResponse,
8
+ MessageResponse,
9
+ MessageStatus,
10
+ WhatsAppAccount,
11
+ )
12
+
13
+ DEFAULT_BASE_URL = "https://api.arplyx.com"
14
+
15
+
16
+ class Arplyx:
17
+ """Cliente de la API de Arplyx.
18
+
19
+ Ejemplo::
20
+
21
+ from arplyx import Arplyx
22
+
23
+ client = Arplyx(api_key="ak_live_...")
24
+ msg = client.send_message(
25
+ external_id="pedido-10045",
26
+ to="+5491155551234",
27
+ text="Tu pedido fue confirmado",
28
+ channel="whatsapp_direct",
29
+ whatsapp_account_id="...",
30
+ )
31
+ print(msg.message_id, msg.status)
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ api_key: str,
37
+ base_url: str = DEFAULT_BASE_URL,
38
+ timeout: float = 10.0,
39
+ session: Optional[requests.Session] = None,
40
+ ) -> None:
41
+ if not api_key:
42
+ raise ValueError("Arplyx: falta api_key.")
43
+ self.api_key = api_key
44
+ self.base_url = base_url.rstrip("/")
45
+ self.timeout = timeout
46
+ self._session = session or requests.Session()
47
+
48
+ def _request(self, method: str, path: str, json: Optional[dict] = None) -> Any:
49
+ resp = self._session.request(
50
+ method,
51
+ f"{self.base_url}{path}",
52
+ json=json,
53
+ headers={"x-api-key": self.api_key, "Content-Type": "application/json"},
54
+ timeout=self.timeout,
55
+ )
56
+ data: Any = None
57
+ if resp.content:
58
+ try:
59
+ data = resp.json()
60
+ except ValueError:
61
+ data = resp.text
62
+ if not resp.ok:
63
+ body = data if isinstance(data, dict) else {"error": data}
64
+ raise ArplyxError(resp.status_code, body)
65
+ return data
66
+
67
+ def send_message(
68
+ self,
69
+ external_id: str,
70
+ to: str,
71
+ *,
72
+ text: Optional[str] = None,
73
+ template: Optional[Dict[str, Any]] = None,
74
+ channel: Optional[str] = None,
75
+ whatsapp_account_id: Optional[str] = None,
76
+ metadata: Optional[Dict[str, Any]] = None,
77
+ ttl_seconds: Optional[int] = None,
78
+ ) -> MessageResponse:
79
+ """Envía un mensaje de WhatsApp (texto o plantilla).
80
+
81
+ Indicá exactamente uno de ``text`` o ``template``. ``template`` es un
82
+ dict ``{"name", "language", "body": [...], "header": [...]}``.
83
+ """
84
+ if (text is None) == (template is None):
85
+ raise ValueError(
86
+ "send_message: indicá exactamente uno de `text` o `template`."
87
+ )
88
+
89
+ if text is not None:
90
+ content: Dict[str, Any] = {"type": "text", "text": text}
91
+ else:
92
+ tpl: Dict[str, Any] = {
93
+ "name": template["name"],
94
+ "language": template["language"],
95
+ }
96
+ components: Dict[str, Any] = {}
97
+ if template.get("body"):
98
+ components["body"] = template["body"]
99
+ if template.get("header"):
100
+ components["header"] = template["header"]
101
+ if components:
102
+ tpl["components"] = components
103
+ content = {"type": "template", "template": tpl}
104
+
105
+ payload: Dict[str, Any] = {
106
+ "externalId": external_id,
107
+ "to": {"type": "phone", "phoneE164": to},
108
+ "content": content,
109
+ }
110
+ if channel is not None:
111
+ payload["channel"] = channel
112
+ if whatsapp_account_id is not None:
113
+ payload["whatsappAccountId"] = whatsapp_account_id
114
+ if metadata is not None:
115
+ payload["metadata"] = metadata
116
+ if ttl_seconds is not None:
117
+ payload["ttlSeconds"] = ttl_seconds
118
+
119
+ return MessageResponse._from_api(self._request("POST", "/messages", payload))
120
+
121
+ def send_broadcast(
122
+ self,
123
+ external_id: str,
124
+ list_id: str,
125
+ text: str,
126
+ channel: str,
127
+ *,
128
+ whatsapp_account_id: Optional[str] = None,
129
+ ttl_seconds: Optional[int] = None,
130
+ ) -> BroadcastResponse:
131
+ """Envía un texto a todos los contactos de una lista (planes Basic/Pro)."""
132
+ payload: Dict[str, Any] = {
133
+ "externalId": external_id,
134
+ "listId": list_id,
135
+ "channel": channel,
136
+ "content": {"type": "text", "text": text},
137
+ }
138
+ if whatsapp_account_id is not None:
139
+ payload["whatsappAccountId"] = whatsapp_account_id
140
+ if ttl_seconds is not None:
141
+ payload["ttlSeconds"] = ttl_seconds
142
+
143
+ return BroadcastResponse._from_api(
144
+ self._request("POST", "/messages/broadcast", payload)
145
+ )
146
+
147
+ def list_whatsapp_accounts(self) -> List[WhatsAppAccount]:
148
+ """Lista las cuentas de WhatsApp del cliente."""
149
+ data = self._request("GET", "/whatsapp-accounts")
150
+ return [WhatsAppAccount._from_api(a) for a in data["accounts"]]
151
+
152
+ def get_message(self, id_or_external_id: str) -> MessageStatus:
153
+ """Estado de un mensaje por su ``message_id`` o tu ``external_id``."""
154
+ from urllib.parse import quote
155
+
156
+ data = self._request("GET", f"/messages/{quote(id_or_external_id, safe='')}")
157
+ return MessageStatus._from_api(data)
@@ -0,0 +1,29 @@
1
+ from typing import Any, List, Optional
2
+
3
+
4
+ class ArplyxError(Exception):
5
+ """Error devuelto por la API de Arplyx (respuesta HTTP no-2xx).
6
+
7
+ Atributos:
8
+ status: código HTTP.
9
+ code: código de error de Arplyx (campo ``error`` del cuerpo), p. ej.
10
+ ``validation_error``, ``conflict``, ``quota_exceeded``,
11
+ ``invalid_whatsapp_account``, ``templates_not_available``.
12
+ body: cuerpo crudo de la respuesta.
13
+ details: detalles por campo si ``code == "validation_error"``.
14
+ """
15
+
16
+ def __init__(self, status: int, body: Any) -> None:
17
+ self.status = status
18
+ self.body = body
19
+ code: Optional[str] = None
20
+ message: Optional[str] = None
21
+ details: Optional[List[dict]] = None
22
+ if isinstance(body, dict):
23
+ code = body.get("error")
24
+ message = body.get("message")
25
+ if isinstance(body.get("details"), list):
26
+ details = body["details"]
27
+ self.code = code or f"http_{status}"
28
+ self.details = details
29
+ super().__init__(f"Arplyx {status} ({self.code}): {message or self.code}")
@@ -0,0 +1,92 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+
5
+ @dataclass
6
+ class MessageResponse:
7
+ message_id: str
8
+ external_id: str
9
+ status: str
10
+ queued: bool
11
+
12
+ @staticmethod
13
+ def _from_api(d: dict) -> "MessageResponse":
14
+ return MessageResponse(
15
+ message_id=d["messageId"],
16
+ external_id=d["externalId"],
17
+ status=d["status"],
18
+ queued=d["queued"],
19
+ )
20
+
21
+
22
+ @dataclass
23
+ class BroadcastResponse:
24
+ broadcast_id: str
25
+ external_id: str
26
+ list_id: str
27
+ message_count: int
28
+ status: str
29
+
30
+ @staticmethod
31
+ def _from_api(d: dict) -> "BroadcastResponse":
32
+ return BroadcastResponse(
33
+ broadcast_id=d["broadcastId"],
34
+ external_id=d["externalId"],
35
+ list_id=d["listId"],
36
+ message_count=d["messageCount"],
37
+ status=d["status"],
38
+ )
39
+
40
+
41
+ @dataclass
42
+ class MessageStatus:
43
+ message_id: str
44
+ external_id: str
45
+ recipient: str
46
+ channel: str
47
+ status: str
48
+ attempt_count: int
49
+ last_error_code: Optional[str]
50
+ broadcast_id: Optional[str]
51
+ created_at: str
52
+ sent_at: Optional[str]
53
+ delivered_at: Optional[str]
54
+ failed_at: Optional[str]
55
+
56
+ @staticmethod
57
+ def _from_api(d: dict) -> "MessageStatus":
58
+ return MessageStatus(
59
+ message_id=d["messageId"],
60
+ external_id=d["externalId"],
61
+ recipient=d["recipient"],
62
+ channel=d["channel"],
63
+ status=d["status"],
64
+ attempt_count=d["attemptCount"],
65
+ last_error_code=d.get("lastErrorCode"),
66
+ broadcast_id=d.get("broadcastId"),
67
+ created_at=d["createdAt"],
68
+ sent_at=d.get("sentAt"),
69
+ delivered_at=d.get("deliveredAt"),
70
+ failed_at=d.get("failedAt"),
71
+ )
72
+
73
+
74
+ @dataclass
75
+ class WhatsAppAccount:
76
+ id: str
77
+ type: str
78
+ status: str
79
+ phone_number: Optional[str]
80
+ display_name: Optional[str]
81
+ connected_at: Optional[str]
82
+
83
+ @staticmethod
84
+ def _from_api(d: dict) -> "WhatsAppAccount":
85
+ return WhatsAppAccount(
86
+ id=d["id"],
87
+ type=d["type"],
88
+ status=d["status"],
89
+ phone_number=d.get("phoneNumber"),
90
+ display_name=d.get("displayName"),
91
+ connected_at=d.get("connectedAt"),
92
+ )
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: arplyx
3
+ Version: 0.1.0
4
+ Summary: SDK oficial de Arplyx: enviá mensajes y broadcasts de WhatsApp desde Python.
5
+ Author-email: Arplyx <info@arplyx.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://docs.arplyx.com/api/
8
+ Project-URL: Documentation, https://docs.arplyx.com/api/
9
+ Keywords: arplyx,whatsapp,whatsapp-api,sdk,mensajeria,broadcasts
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.25
16
+
17
+ # arplyx (Python SDK)
18
+
19
+ SDK oficial de **Arplyx** para Python: enviá mensajes y broadcasts de WhatsApp y consultá estados con una API tipada.
20
+
21
+ ## Instalación
22
+
23
+ ```bash
24
+ pip install arplyx
25
+ ```
26
+
27
+ Requiere Python 3.8+.
28
+
29
+ ## Uso
30
+
31
+ ```python
32
+ from arplyx import Arplyx, ArplyxError
33
+
34
+ client = Arplyx(api_key="ak_live_...")
35
+
36
+ # Enviar un texto
37
+ msg = client.send_message(
38
+ external_id="pedido-10045",
39
+ to="+5491155551234",
40
+ text="Tu pedido fue confirmado.",
41
+ channel="whatsapp_direct",
42
+ whatsapp_account_id="TU_ACCOUNT_ID",
43
+ )
44
+ print(msg.message_id, msg.status)
45
+
46
+ # Consultar estado (por message_id o por tu external_id)
47
+ status = client.get_message("pedido-10045")
48
+ print(status.status) # pending | queued | sent | delivered | read | failed
49
+ ```
50
+
51
+ ### Plantillas (Meta Official)
52
+
53
+ ```python
54
+ client.send_message(
55
+ external_id="pedido-10045",
56
+ to="+5491155551234",
57
+ channel="whatsapp_meta",
58
+ template={"name": "pedido_confirmado", "language": "es_AR", "body": ["Juan", "10045"]},
59
+ )
60
+ ```
61
+
62
+ ### Broadcast a una lista (planes Basic/Pro)
63
+
64
+ ```python
65
+ client.send_broadcast(
66
+ external_id="promo-junio-2026",
67
+ list_id="TU_LIST_ID",
68
+ text="Este mes 2x1 en turnos.",
69
+ channel="whatsapp_direct",
70
+ whatsapp_account_id="TU_ACCOUNT_ID",
71
+ )
72
+ ```
73
+
74
+ ### Listar cuentas de WhatsApp
75
+
76
+ ```python
77
+ for acc in client.list_whatsapp_accounts():
78
+ print(acc.id, acc.type, acc.status) # acc.id se usa como whatsapp_account_id
79
+ ```
80
+
81
+ ## Manejo de errores
82
+
83
+ ```python
84
+ try:
85
+ client.send_message(external_id="x", to="+549...", text="hola")
86
+ except ArplyxError as e:
87
+ print(e.status) # 400, 401, 409, 422, 429, 500...
88
+ print(e.code) # 'validation_error', 'conflict', 'quota_exceeded'...
89
+ print(e.details) # detalles por campo si es validation_error
90
+ ```
91
+
92
+ ## Idempotencia
93
+
94
+ Todos los envíos usan tu `external_id` como clave de idempotencia: reintentar con el mismo `external_id` y payload no duplica el envío. Es seguro reintentar ante timeouts.
95
+
96
+ ## Configuración
97
+
98
+ | Parámetro | Requerido | Default |
99
+ |---|---|---|
100
+ | `api_key` | Sí | — |
101
+ | `base_url` | No | `https://api.arplyx.com` |
102
+ | `timeout` | No | `10.0` |
103
+ | `session` | No | `requests.Session()` nueva |
104
+
105
+ Docs de la API: https://docs.arplyx.com/api/ · OpenAPI: https://api.arplyx.com/openapi.yaml
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/arplyx/__init__.py
4
+ src/arplyx/client.py
5
+ src/arplyx/errors.py
6
+ src/arplyx/types.py
7
+ src/arplyx.egg-info/PKG-INFO
8
+ src/arplyx.egg-info/SOURCES.txt
9
+ src/arplyx.egg-info/dependency_links.txt
10
+ src/arplyx.egg-info/requires.txt
11
+ src/arplyx.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.25
@@ -0,0 +1 @@
1
+ arplyx