ombala 0.1.0__py3-none-any.whl
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.
- ombala/__init__.py +3 -0
- ombala/client.py +79 -0
- ombala/exceptions.py +20 -0
- ombala/models.py +48 -0
- ombala/resources/__init__.py +0 -0
- ombala/resources/credits.py +19 -0
- ombala/resources/messages.py +73 -0
- ombala/resources/senders.py +30 -0
- ombala-0.1.0.dist-info/METADATA +112 -0
- ombala-0.1.0.dist-info/RECORD +11 -0
- ombala-0.1.0.dist-info/WHEEL +4 -0
ombala/__init__.py
ADDED
ombala/client.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from ombala.exceptions import (
|
|
8
|
+
AuthenticationError,
|
|
9
|
+
OmbalaError,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
ServerError,
|
|
12
|
+
ValidationError,
|
|
13
|
+
)
|
|
14
|
+
from ombala.resources.credits import CreditsResource
|
|
15
|
+
from ombala.resources.messages import MessagesResource
|
|
16
|
+
from ombala.resources.senders import SendersResource
|
|
17
|
+
|
|
18
|
+
BASE_URL = "https://api.useombala.ao"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def error(status_code: int, response_data: dict[str, Any] | None) -> OmbalaError:
|
|
22
|
+
message = str(response_data) if response_data else f"HTTP {status_code}"
|
|
23
|
+
if status_code == 401:
|
|
24
|
+
return AuthenticationError(message, status_code, response_data)
|
|
25
|
+
if status_code == 429:
|
|
26
|
+
return RateLimitError(message, status_code, response_data)
|
|
27
|
+
if 400 <= status_code < 500:
|
|
28
|
+
return ValidationError(message, status_code, response_data)
|
|
29
|
+
if 500 <= status_code < 600:
|
|
30
|
+
return ServerError(message, status_code, response_data)
|
|
31
|
+
return OmbalaError(message, status_code, response_data)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Ombala:
|
|
35
|
+
def __init__(self, token: str, base_url: str = BASE_URL, timeout: int = 30) -> None:
|
|
36
|
+
self._client = httpx.Client(
|
|
37
|
+
base_url=base_url,
|
|
38
|
+
headers={
|
|
39
|
+
"Authorization": f"Token {token}",
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
timeout=timeout,
|
|
43
|
+
)
|
|
44
|
+
self.messages = MessagesResource(self)
|
|
45
|
+
self.senders = SendersResource(self)
|
|
46
|
+
self.credits = CreditsResource(self)
|
|
47
|
+
|
|
48
|
+
def _request(self, method: str, path: str, **kwargs: Any) -> httpx.Response:
|
|
49
|
+
try:
|
|
50
|
+
response = self._client.request(method, path, **kwargs)
|
|
51
|
+
except httpx.HTTPError as exc:
|
|
52
|
+
raise OmbalaError(f"Request failed: {exc}") from exc
|
|
53
|
+
|
|
54
|
+
if response.is_error:
|
|
55
|
+
try:
|
|
56
|
+
data = response.json()
|
|
57
|
+
except Exception:
|
|
58
|
+
data = None
|
|
59
|
+
raise error(response.status_code, data)
|
|
60
|
+
|
|
61
|
+
return response
|
|
62
|
+
|
|
63
|
+
def get(self, path: str, **kwargs: Any) -> httpx.Response:
|
|
64
|
+
return self._request("GET", path, **kwargs)
|
|
65
|
+
|
|
66
|
+
def post(self, path: str, **kwargs: Any) -> httpx.Response:
|
|
67
|
+
return self._request("POST", path, **kwargs)
|
|
68
|
+
|
|
69
|
+
def delete(self, path: str, **kwargs: Any) -> httpx.Response:
|
|
70
|
+
return self._request("DELETE", path, **kwargs)
|
|
71
|
+
|
|
72
|
+
def close(self) -> None:
|
|
73
|
+
self._client.close()
|
|
74
|
+
|
|
75
|
+
def __enter__(self) -> Ombala:
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
def __exit__(self, *args: Any) -> None:
|
|
79
|
+
self.close()
|
ombala/exceptions.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class OmbalaError(Exception):
|
|
5
|
+
def __init__(self, message: str, status_code: int | None = None,response_data: dict[str, Any] | None = None) -> None:
|
|
6
|
+
self.status_code = status_code
|
|
7
|
+
self.response_data = response_data
|
|
8
|
+
super().__init__(message)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthenticationError(OmbalaError): ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ValidationError(OmbalaError): ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RateLimitError(OmbalaError): ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ServerError(OmbalaError): ...
|
ombala/models.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SendSMSRequest(BaseModel):
|
|
8
|
+
message: str
|
|
9
|
+
from_: str = Field(alias="from", serialization_alias="from")
|
|
10
|
+
to: str
|
|
11
|
+
schedule: str | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Message(BaseModel):
|
|
15
|
+
id: str
|
|
16
|
+
message: str
|
|
17
|
+
from_: str | None = Field(None, alias="from")
|
|
18
|
+
to: str | None = None
|
|
19
|
+
status: str | None = None
|
|
20
|
+
created_at: datetime | None = Field(None, alias="createdAt")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SenderCreate(BaseModel):
|
|
24
|
+
name: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Sender(BaseModel):
|
|
28
|
+
id: str
|
|
29
|
+
name: str
|
|
30
|
+
status: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CreditBalance(BaseModel):
|
|
34
|
+
balance: float | None = None
|
|
35
|
+
currency: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Recharge(BaseModel):
|
|
39
|
+
id: str | None = None
|
|
40
|
+
amount: float | None = None
|
|
41
|
+
date: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PaginatedResponse(BaseModel):
|
|
45
|
+
data: list[Any]
|
|
46
|
+
page: int | None = None
|
|
47
|
+
total: int | None = None
|
|
48
|
+
total_pages: int | None = None
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ombala.client import Ombala, AsyncOmbala
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CreditsResource:
|
|
10
|
+
def __init__(self, client: Ombala | AsyncOmbala) -> None:
|
|
11
|
+
self._client = client
|
|
12
|
+
|
|
13
|
+
def balance(self) -> dict[str, Any]:
|
|
14
|
+
resp = self._client.get("/v1/credits")
|
|
15
|
+
return resp.json()
|
|
16
|
+
|
|
17
|
+
def recharges(self) -> dict[str, Any]:
|
|
18
|
+
resp = self._client.get("/v1/recharges")
|
|
19
|
+
return resp.json()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from ombala.models import SendSMSRequest
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ombala.client import Ombala, AsyncOmbala
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MessagesResource:
|
|
12
|
+
def __init__(self, client: Ombala | AsyncOmbala) -> None:
|
|
13
|
+
self._client = client
|
|
14
|
+
|
|
15
|
+
def send(self, message: str, from_: str,to: str, schedule: str | None = None) -> dict[str, Any]:
|
|
16
|
+
body = SendSMSRequest(message=message, from_=from_, to=to, schedule=schedule)
|
|
17
|
+
resp = self._client.post(
|
|
18
|
+
"/v1/messages",
|
|
19
|
+
content=body.model_dump_json(by_alias=True),
|
|
20
|
+
)
|
|
21
|
+
return resp.json()
|
|
22
|
+
|
|
23
|
+
def list(self, page: int | None = None) -> dict[str, Any]:
|
|
24
|
+
params: dict[str, Any] = {}
|
|
25
|
+
if page is not None:
|
|
26
|
+
params["page"] = page
|
|
27
|
+
resp = self._client.get("/v1/messages", params=params)
|
|
28
|
+
return resp.json()
|
|
29
|
+
|
|
30
|
+
def get(self, message_id: str, id: str | None = None) -> dict[str, Any]:
|
|
31
|
+
params: dict[str, Any] = {"message_id": message_id}
|
|
32
|
+
if id is not None:
|
|
33
|
+
params["id"] = id
|
|
34
|
+
resp = self._client.get("/v1/messages/one", params=params)
|
|
35
|
+
return resp.json()
|
|
36
|
+
|
|
37
|
+
def delete(self, message_id: str) -> None:
|
|
38
|
+
self._client.delete(f"/v1/messages/{message_id}")
|
|
39
|
+
|
|
40
|
+
def list_recipients(
|
|
41
|
+
self,
|
|
42
|
+
page: int | None = None,
|
|
43
|
+
) -> dict[str, Any]:
|
|
44
|
+
params: dict[str, Any] = {}
|
|
45
|
+
if page is not None:
|
|
46
|
+
params["page"] = page
|
|
47
|
+
resp = self._client.get("/v1/messages/recipients", params=params)
|
|
48
|
+
return resp.json()
|
|
49
|
+
|
|
50
|
+
def list_by_date_range(
|
|
51
|
+
self,
|
|
52
|
+
start: str,
|
|
53
|
+
end: str,
|
|
54
|
+
page: int | None = None,
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
params: dict[str, Any] = {"start": start, "end": end}
|
|
57
|
+
if page is not None:
|
|
58
|
+
params["page"] = page
|
|
59
|
+
resp = self._client.get("/v1/messages/date", params=params)
|
|
60
|
+
return resp.json()
|
|
61
|
+
|
|
62
|
+
def list_by_recipient(
|
|
63
|
+
self,
|
|
64
|
+
phone_number: str | None = None,
|
|
65
|
+
page: int | None = None,
|
|
66
|
+
) -> dict[str, Any]:
|
|
67
|
+
params: dict[str, Any] = {}
|
|
68
|
+
if phone_number is not None:
|
|
69
|
+
params["phone_number"] = phone_number
|
|
70
|
+
if page is not None:
|
|
71
|
+
params["page"] = page
|
|
72
|
+
resp = self._client.get("/v1/messages/recipient", params=params)
|
|
73
|
+
return resp.json()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ombala.client import Ombala, AsyncOmbala
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SendersResource:
|
|
10
|
+
def __init__(self, client: Ombala | AsyncOmbala) -> None:
|
|
11
|
+
self._client = client
|
|
12
|
+
|
|
13
|
+
def create(self, name: str) -> dict[str, Any]:
|
|
14
|
+
resp = self._client.post("/v1/senders/", json={"name": name})
|
|
15
|
+
return resp.json()
|
|
16
|
+
|
|
17
|
+
def list(self) -> dict[str, Any]:
|
|
18
|
+
resp = self._client.get("/v1/senders")
|
|
19
|
+
return resp.json()
|
|
20
|
+
|
|
21
|
+
def list_approved(self) -> dict[str, Any]:
|
|
22
|
+
resp = self._client.get("/v1/senders/approved")
|
|
23
|
+
return resp.json()
|
|
24
|
+
|
|
25
|
+
def list_pending(self) -> dict[str, Any]:
|
|
26
|
+
resp = self._client.get("/v1/senders/pending")
|
|
27
|
+
return resp.json()
|
|
28
|
+
|
|
29
|
+
def delete(self, sender_id: str) -> None:
|
|
30
|
+
self._client.delete(f"/v1/senders/{sender_id}")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ombala
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SDK em Python para a API de envios de SMS em Angola
|
|
5
|
+
Project-URL: Homepage, https://www.useombala.ao/
|
|
6
|
+
Project-URL: Repository, https://github.com/omarscode/ombala
|
|
7
|
+
Project-URL: Documentation, https://github.com/omarscode/ombala#README
|
|
8
|
+
Project-URL: Tracker, https://github.com/omarscode/ombala/issues
|
|
9
|
+
Author-email: Omar Rodrigues <omarscode007@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: angola,api,ombala,sdk,sms
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Communications :: Telephony
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: httpx>=0.27.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.5.0; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Ombala SDK Python
|
|
35
|
+
|
|
36
|
+
SDK Python para a [API Ombala](https://api.useombala.ao) — envio de SMS em Angola.
|
|
37
|
+
|
|
38
|
+
## Instalação
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install ombala
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Uso
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from ombala import Ombala
|
|
48
|
+
|
|
49
|
+
client = Ombala("Token a9eb6ea6-5777-4848-a9ed-8cbffc74a503")
|
|
50
|
+
|
|
51
|
+
# Enviar SMS
|
|
52
|
+
client.messages.send(
|
|
53
|
+
message="Olá, tudo bem?",
|
|
54
|
+
from_="MINHALOJA",
|
|
55
|
+
to="921939411",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Listar mensagens
|
|
59
|
+
client.messages.list(page=1)
|
|
60
|
+
|
|
61
|
+
# Ver saldo
|
|
62
|
+
client.credits.balance()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Modo assíncrono
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import asyncio
|
|
69
|
+
from ombala import AsyncOmbala
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
async with AsyncOmbala("Token ...") as client:
|
|
73
|
+
resp = await client.messages.send(
|
|
74
|
+
message="Olá, tudo bem?",
|
|
75
|
+
from_="MINHALOJA",
|
|
76
|
+
to="921939411",
|
|
77
|
+
)
|
|
78
|
+
print(resp)
|
|
79
|
+
|
|
80
|
+
asyncio.run(main())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### Mensagens
|
|
86
|
+
|
|
87
|
+
| Método | Descrição |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `messages.send(message, from_, to, schedule?)` | Enviar SMS |
|
|
90
|
+
| `messages.list(page?)` | Listar mensagens |
|
|
91
|
+
| `messages.get(message_id, id?)` | Obter mensagem por ID |
|
|
92
|
+
| `messages.delete(message_id)` | Apagar registo de envio |
|
|
93
|
+
| `messages.list_recipients(page?)` | Listar destinatários |
|
|
94
|
+
| `messages.list_by_date_range(start, end, page?)` | Listar mensagens por intervalo de datas |
|
|
95
|
+
| `messages.list_by_recipient(phone_number?, page?)` | Listar mensagens de um número |
|
|
96
|
+
|
|
97
|
+
### Remetentes
|
|
98
|
+
|
|
99
|
+
| Método | Descrição |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `senders.create(name)` | Criar remetente |
|
|
102
|
+
| `senders.list()` | Listar remetentes |
|
|
103
|
+
| `senders.list_approved()` | Listar remetentes aprovados |
|
|
104
|
+
| `senders.list_pending()` | Listar remetentes pendentes |
|
|
105
|
+
| `senders.delete(sender_id)` | Apagar remetente |
|
|
106
|
+
|
|
107
|
+
### Créditos
|
|
108
|
+
|
|
109
|
+
| Método | Descrição |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `credits.balance()` | Mostrar saldo |
|
|
112
|
+
| `credits.recharges()` | Histórico de carregamentos |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
ombala/__init__.py,sha256=evXj8g4BMZtK2C3M56vMO9JtQQNFFBMj0p1bJUwjUqU,83
|
|
2
|
+
ombala/client.py,sha256=JBzFsVBuy8v1NT4WMNOuJZveJx0UEQ_oRIjaF-69hPU,2535
|
|
3
|
+
ombala/exceptions.py,sha256=YSfzUChw7omqQuEtk3-US6rlQyIqDpyJlhnSWJGyJyA,460
|
|
4
|
+
ombala/models.py,sha256=TcHeXoKsV-2yq2As-HacTx7IllNe_chz-pBg2Ar7opU,975
|
|
5
|
+
ombala/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
ombala/resources/credits.py,sha256=sXpg08mMIu1EPL97R3SgZtDThERGgKY7EVT21nll6GU,496
|
|
7
|
+
ombala/resources/messages.py,sha256=QTxPY16w7Q2zYA-TYvOUAYs0U30jTPm3iRsrgr8Ee18,2379
|
|
8
|
+
ombala/resources/senders.py,sha256=IEwA4qpaO-4Ei2cHwshMpe923LpbspyImklAmuvvPog,885
|
|
9
|
+
ombala-0.1.0.dist-info/METADATA,sha256=p1dZQY2kqaBIhihI4kxvvW8zD6mXiGoBlDW6xPnSeiI,3212
|
|
10
|
+
ombala-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
11
|
+
ombala-0.1.0.dist-info/RECORD,,
|