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 ADDED
@@ -0,0 +1,3 @@
1
+ from ombala.client import Ombala, AsyncOmbala
2
+
3
+ __all__ = ["Ombala", "AsyncOmbala"]
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any