bridgexapi 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.
- bridgexapi-0.1.0/LICENSE +1 -0
- bridgexapi-0.1.0/PKG-INFO +43 -0
- bridgexapi-0.1.0/README.md +23 -0
- bridgexapi-0.1.0/bridgexapi/__init__.py +40 -0
- bridgexapi-0.1.0/bridgexapi/client.py +133 -0
- bridgexapi-0.1.0/bridgexapi/exceptions.py +50 -0
- bridgexapi-0.1.0/bridgexapi/models.py +60 -0
- bridgexapi-0.1.0/bridgexapi/routes.py +19 -0
- bridgexapi-0.1.0/bridgexapi/validators.py +102 -0
- bridgexapi-0.1.0/bridgexapi/version.py +1 -0
- bridgexapi-0.1.0/bridgexapi.egg-info/PKG-INFO +43 -0
- bridgexapi-0.1.0/bridgexapi.egg-info/SOURCES.txt +15 -0
- bridgexapi-0.1.0/bridgexapi.egg-info/dependency_links.txt +1 -0
- bridgexapi-0.1.0/bridgexapi.egg-info/requires.txt +1 -0
- bridgexapi-0.1.0/bridgexapi.egg-info/top_level.txt +1 -0
- bridgexapi-0.1.0/pyproject.toml +35 -0
- bridgexapi-0.1.0/setup.cfg +4 -0
bridgexapi-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Copyright (c) BridgeXAPI. All rights reserved.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bridgexapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the BridgeXAPI SMS API
|
|
5
|
+
Author: BridgeXAPI
|
|
6
|
+
Project-URL: Homepage, https://dashboard.bridgexapi.io
|
|
7
|
+
Project-URL: Documentation, https://dashboard.bridgexapi.io
|
|
8
|
+
Project-URL: Source, https://dashboard.bridgexapi.io
|
|
9
|
+
Keywords: sms,api,messaging,bridgexapi,python-sdk
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Communications
|
|
14
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: requests>=2.31.0
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# BridgeXAPI Python SDK
|
|
22
|
+
|
|
23
|
+
Official Python SDK for the BridgeXAPI SMS API.
|
|
24
|
+
|
|
25
|
+
The BridgeXAPI Python SDK provides a simple and secure way to send SMS through the BridgeXAPI infrastructure using one API key, one endpoint, and route-based delivery selection.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Official Python SDK for BridgeXAPI SMS
|
|
30
|
+
- Simple sync client
|
|
31
|
+
- Local validation before requests are sent
|
|
32
|
+
- Support for route IDs `1` through `5`
|
|
33
|
+
- Typed route constants via `Route`
|
|
34
|
+
- Structured response models
|
|
35
|
+
- Clean Python exceptions
|
|
36
|
+
- Server-side integration ready
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
### Local development
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install -e .
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# BridgeXAPI Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the BridgeXAPI SMS API.
|
|
4
|
+
|
|
5
|
+
The BridgeXAPI Python SDK provides a simple and secure way to send SMS through the BridgeXAPI infrastructure using one API key, one endpoint, and route-based delivery selection.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Official Python SDK for BridgeXAPI SMS
|
|
10
|
+
- Simple sync client
|
|
11
|
+
- Local validation before requests are sent
|
|
12
|
+
- Support for route IDs `1` through `5`
|
|
13
|
+
- Typed route constants via `Route`
|
|
14
|
+
- Structured response models
|
|
15
|
+
- Clean Python exceptions
|
|
16
|
+
- Server-side integration ready
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Local development
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install -e .
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .client import BridgeXAPI
|
|
2
|
+
from .version import __version__
|
|
3
|
+
from .routes import Route
|
|
4
|
+
from .models import SMSBatchResponse, SMSMessageStatus
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
BridgeXAPIError,
|
|
7
|
+
ApiRequestError,
|
|
8
|
+
AuthenticationError,
|
|
9
|
+
ValidationError,
|
|
10
|
+
InvalidRouteError,
|
|
11
|
+
UnauthorizedRouteError,
|
|
12
|
+
InsufficientBalanceError,
|
|
13
|
+
VendorSendError,
|
|
14
|
+
InvalidNumberError,
|
|
15
|
+
MixedCountryBatchError,
|
|
16
|
+
MessageTooLongError,
|
|
17
|
+
UnicodeNotAllowedError,
|
|
18
|
+
InvalidCallerIDError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"BridgeXAPI",
|
|
23
|
+
"__version__",
|
|
24
|
+
"Route",
|
|
25
|
+
"SMSBatchResponse",
|
|
26
|
+
"SMSMessageStatus",
|
|
27
|
+
"BridgeXAPIError",
|
|
28
|
+
"ApiRequestError",
|
|
29
|
+
"AuthenticationError",
|
|
30
|
+
"ValidationError",
|
|
31
|
+
"InvalidRouteError",
|
|
32
|
+
"UnauthorizedRouteError",
|
|
33
|
+
"InsufficientBalanceError",
|
|
34
|
+
"VendorSendError",
|
|
35
|
+
"InvalidNumberError",
|
|
36
|
+
"MixedCountryBatchError",
|
|
37
|
+
"MessageTooLongError",
|
|
38
|
+
"UnicodeNotAllowedError",
|
|
39
|
+
"InvalidCallerIDError",
|
|
40
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from typing import Iterable, Optional, Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
ApiRequestError,
|
|
7
|
+
AuthenticationError,
|
|
8
|
+
InsufficientBalanceError,
|
|
9
|
+
InvalidRouteError,
|
|
10
|
+
UnauthorizedRouteError,
|
|
11
|
+
ValidationError,
|
|
12
|
+
VendorSendError,
|
|
13
|
+
)
|
|
14
|
+
from .models import SMSBatchResponse
|
|
15
|
+
from .validators import (
|
|
16
|
+
validate_route_id,
|
|
17
|
+
validate_caller_id,
|
|
18
|
+
validate_numbers,
|
|
19
|
+
validate_message,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BridgeXAPI:
|
|
24
|
+
"""
|
|
25
|
+
Official sync client for the BridgeXAPI SMS endpoint.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
api_key: str,
|
|
31
|
+
*,
|
|
32
|
+
base_url: str = "https://hi.bridgexapi.io",
|
|
33
|
+
timeout: float = 30.0,
|
|
34
|
+
session: Optional[requests.Session] = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
if not api_key or not isinstance(api_key, str):
|
|
37
|
+
raise AuthenticationError("api_key is required.")
|
|
38
|
+
|
|
39
|
+
self.api_key = api_key.strip()
|
|
40
|
+
self.base_url = base_url.rstrip("/")
|
|
41
|
+
self.timeout = timeout
|
|
42
|
+
self.session = session or requests.Session()
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def endpoint(self) -> str:
|
|
46
|
+
return f"{self.base_url}/api/v1/send_sms"
|
|
47
|
+
|
|
48
|
+
def send_sms(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
route_id: int,
|
|
52
|
+
caller_id: str,
|
|
53
|
+
numbers: Iterable[str],
|
|
54
|
+
message: str,
|
|
55
|
+
) -> SMSBatchResponse:
|
|
56
|
+
payload = {
|
|
57
|
+
"route_id": validate_route_id(route_id),
|
|
58
|
+
"caller_id": validate_caller_id(caller_id),
|
|
59
|
+
"numbers": validate_numbers(numbers),
|
|
60
|
+
"message": validate_message(message),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
response = self.session.post(
|
|
64
|
+
self.endpoint,
|
|
65
|
+
headers={
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
"X-API-KEY": self.api_key,
|
|
68
|
+
},
|
|
69
|
+
json=payload,
|
|
70
|
+
timeout=self.timeout,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
data = self._decode_json(response)
|
|
74
|
+
self._raise_for_error(response.status_code, data)
|
|
75
|
+
|
|
76
|
+
return SMSBatchResponse.from_dict(data)
|
|
77
|
+
|
|
78
|
+
def send_one(
|
|
79
|
+
self,
|
|
80
|
+
*,
|
|
81
|
+
route_id: int,
|
|
82
|
+
caller_id: str,
|
|
83
|
+
number: str,
|
|
84
|
+
message: str,
|
|
85
|
+
) -> SMSBatchResponse:
|
|
86
|
+
return self.send_sms(
|
|
87
|
+
route_id=route_id,
|
|
88
|
+
caller_id=caller_id,
|
|
89
|
+
numbers=[number],
|
|
90
|
+
message=message,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def _decode_json(self, response: requests.Response) -> dict[str, Any]:
|
|
94
|
+
try:
|
|
95
|
+
return response.json()
|
|
96
|
+
except ValueError as exc:
|
|
97
|
+
raise ApiRequestError(
|
|
98
|
+
f"BridgeXAPI returned a non-JSON response (status {response.status_code})."
|
|
99
|
+
) from exc
|
|
100
|
+
|
|
101
|
+
def _raise_for_error(self, status_code: int, data: dict[str, Any]) -> None:
|
|
102
|
+
if 200 <= status_code < 300:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
detail = data.get("detail")
|
|
106
|
+
|
|
107
|
+
if isinstance(detail, list):
|
|
108
|
+
first = detail[0] if detail else {}
|
|
109
|
+
msg = first.get("msg") or "Validation error."
|
|
110
|
+
raise ValidationError(msg)
|
|
111
|
+
|
|
112
|
+
if isinstance(detail, str):
|
|
113
|
+
if "Missing API Key" in detail or "Invalid or inactive API Key" in detail:
|
|
114
|
+
raise AuthenticationError(detail)
|
|
115
|
+
|
|
116
|
+
if "Invalid route_id" in detail:
|
|
117
|
+
raise InvalidRouteError(detail)
|
|
118
|
+
|
|
119
|
+
if "not authorized for the Casino route" in detail:
|
|
120
|
+
raise UnauthorizedRouteError(detail)
|
|
121
|
+
|
|
122
|
+
if "Insufficient balance" in detail:
|
|
123
|
+
raise InsufficientBalanceError(detail)
|
|
124
|
+
|
|
125
|
+
if "Vendor send failed" in detail:
|
|
126
|
+
raise VendorSendError(detail)
|
|
127
|
+
|
|
128
|
+
if "Pricing failed" in detail:
|
|
129
|
+
raise ValidationError(detail)
|
|
130
|
+
|
|
131
|
+
raise ApiRequestError(detail)
|
|
132
|
+
|
|
133
|
+
raise ApiRequestError(f"BridgeXAPI request failed with status {status_code}.")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class BridgeXAPIError(Exception):
|
|
2
|
+
"""Base exception for all BridgeXAPI SDK errors."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ApiRequestError(BridgeXAPIError):
|
|
6
|
+
"""Generic API request error."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuthenticationError(ApiRequestError):
|
|
10
|
+
"""Missing, invalid, or inactive API key."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ValidationError(ApiRequestError):
|
|
14
|
+
"""Payload validation failed."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InvalidRouteError(ValidationError):
|
|
18
|
+
"""Invalid route_id supplied."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UnauthorizedRouteError(ApiRequestError):
|
|
22
|
+
"""User is not allowed to use the selected route."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InsufficientBalanceError(ApiRequestError):
|
|
26
|
+
"""User balance is too low for this request."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class VendorSendError(ApiRequestError):
|
|
30
|
+
"""Vendor gateway failed to accept the SMS request."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class InvalidNumberError(ValidationError):
|
|
34
|
+
"""Phone number format is invalid."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MixedCountryBatchError(ValidationError):
|
|
38
|
+
"""Numbers from multiple countries were provided in one request."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MessageTooLongError(ValidationError):
|
|
42
|
+
"""Message exceeds the maximum allowed length."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class UnicodeNotAllowedError(ValidationError):
|
|
46
|
+
"""Message contains non-ASCII characters."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class InvalidCallerIDError(ValidationError):
|
|
50
|
+
"""Caller ID is invalid."""
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from dataclasses import dataclass, asdict
|
|
2
|
+
from typing import Optional, List, Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(slots=True)
|
|
6
|
+
class SMSMessageStatus:
|
|
7
|
+
bx_message_id: Optional[str]
|
|
8
|
+
msisdn: str
|
|
9
|
+
status: str
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def from_dict(cls, data: dict) -> "SMSMessageStatus":
|
|
13
|
+
return cls(
|
|
14
|
+
bx_message_id=data.get("bx_message_id"),
|
|
15
|
+
msisdn=str(data.get("msisdn", "")),
|
|
16
|
+
status=str(data.get("status", "")),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def to_dict(self) -> dict:
|
|
20
|
+
return asdict(self)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(slots=True)
|
|
24
|
+
class SMSBatchResponse:
|
|
25
|
+
status: str
|
|
26
|
+
message: str
|
|
27
|
+
order_id: Optional[int]
|
|
28
|
+
route_id: int
|
|
29
|
+
count: int
|
|
30
|
+
messages: List[SMSMessageStatus]
|
|
31
|
+
cost: Optional[float]
|
|
32
|
+
balance_after: Optional[float]
|
|
33
|
+
raw: dict[str, Any]
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_dict(cls, data: dict) -> "SMSBatchResponse":
|
|
37
|
+
return cls(
|
|
38
|
+
status=str(data.get("status", "")),
|
|
39
|
+
message=str(data.get("message", "")),
|
|
40
|
+
order_id=data.get("order_id"),
|
|
41
|
+
route_id=int(data.get("route_id", 0)),
|
|
42
|
+
count=int(data.get("count", 0)),
|
|
43
|
+
messages=[SMSMessageStatus.from_dict(x) for x in data.get("messages", [])],
|
|
44
|
+
cost=float(data["cost"]) if data.get("cost") is not None else None,
|
|
45
|
+
balance_after=float(data["balance_after"]) if data.get("balance_after") is not None else None,
|
|
46
|
+
raw=data,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def to_dict(self) -> dict:
|
|
50
|
+
return {
|
|
51
|
+
"status": self.status,
|
|
52
|
+
"message": self.message,
|
|
53
|
+
"order_id": self.order_id,
|
|
54
|
+
"route_id": self.route_id,
|
|
55
|
+
"count": self.count,
|
|
56
|
+
"messages": [m.to_dict() for m in self.messages],
|
|
57
|
+
"cost": self.cost,
|
|
58
|
+
"balance_after": self.balance_after,
|
|
59
|
+
"raw": self.raw,
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Route(IntEnum):
|
|
5
|
+
"""
|
|
6
|
+
BridgeXAPI route identifiers.
|
|
7
|
+
|
|
8
|
+
Each route represents a different SMS delivery path.
|
|
9
|
+
Route 1 is typically the primary route, while routes 2–4
|
|
10
|
+
are backup or alternative routes depending on availability.
|
|
11
|
+
|
|
12
|
+
Route 5 is restricted to authorized iGaming clients.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
ROUTE_1 = 1
|
|
16
|
+
ROUTE_2 = 2
|
|
17
|
+
ROUTE_3 = 3
|
|
18
|
+
ROUTE_4 = 4
|
|
19
|
+
CASINO = 5
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Iterable, List
|
|
3
|
+
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
InvalidCallerIDError,
|
|
6
|
+
InvalidNumberError,
|
|
7
|
+
InvalidRouteError,
|
|
8
|
+
MessageTooLongError,
|
|
9
|
+
MixedCountryBatchError,
|
|
10
|
+
UnicodeNotAllowedError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
VALID_ROUTES = {1, 2, 3, 4, 5}
|
|
14
|
+
NUMBER_RE = re.compile(r"^\d{10,15}$")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def normalize_number(number: str) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Normalize user input into BridgeXAPI format.
|
|
20
|
+
Removes spaces, dashes, parentheses and other non-digits.
|
|
21
|
+
"""
|
|
22
|
+
if not isinstance(number, str):
|
|
23
|
+
raise InvalidNumberError("Phone number must be a string.")
|
|
24
|
+
return re.sub(r"\D", "", number.strip())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_route_id(route_id: int) -> int:
|
|
28
|
+
if route_id not in VALID_ROUTES:
|
|
29
|
+
raise InvalidRouteError("route_id must be between 1 and 5.")
|
|
30
|
+
return route_id
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def validate_caller_id(caller_id: str) -> str:
|
|
34
|
+
if not isinstance(caller_id, str):
|
|
35
|
+
raise InvalidCallerIDError("caller_id must be a string.")
|
|
36
|
+
|
|
37
|
+
caller_id = caller_id.strip()
|
|
38
|
+
|
|
39
|
+
if not (3 <= len(caller_id) <= 11):
|
|
40
|
+
raise InvalidCallerIDError("caller_id must be between 3 and 11 characters.")
|
|
41
|
+
|
|
42
|
+
return caller_id
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def validate_message(message: str) -> str:
|
|
46
|
+
if not isinstance(message, str):
|
|
47
|
+
raise MessageTooLongError("message must be a string.")
|
|
48
|
+
|
|
49
|
+
if len(message) > 158:
|
|
50
|
+
raise MessageTooLongError("Message must contain at most 158 characters.")
|
|
51
|
+
|
|
52
|
+
if not message.isascii():
|
|
53
|
+
raise UnicodeNotAllowedError("Message must contain only ASCII characters.")
|
|
54
|
+
|
|
55
|
+
return message
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def validate_numbers(numbers: Iterable[str]) -> List[str]:
|
|
59
|
+
numbers = list(numbers)
|
|
60
|
+
|
|
61
|
+
if not numbers:
|
|
62
|
+
raise InvalidNumberError("numbers must contain at least one phone number.")
|
|
63
|
+
|
|
64
|
+
normalized = []
|
|
65
|
+
|
|
66
|
+
for raw in numbers:
|
|
67
|
+
if not isinstance(raw, str):
|
|
68
|
+
raise InvalidNumberError("Each number must be a string.")
|
|
69
|
+
|
|
70
|
+
if raw.strip().startswith("+"):
|
|
71
|
+
raise InvalidNumberError("Number must not start with '+'.")
|
|
72
|
+
|
|
73
|
+
cleaned = normalize_number(raw)
|
|
74
|
+
|
|
75
|
+
if not NUMBER_RE.fullmatch(cleaned):
|
|
76
|
+
raise InvalidNumberError("Each number must be 10 to 15 digits, digits only.")
|
|
77
|
+
|
|
78
|
+
normalized.append(cleaned)
|
|
79
|
+
|
|
80
|
+
validate_same_country(normalized)
|
|
81
|
+
return normalized
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def validate_same_country(numbers: List[str]) -> None:
|
|
85
|
+
"""
|
|
86
|
+
BridgeXAPI business rule:
|
|
87
|
+
all numbers in one request must belong to the same country.
|
|
88
|
+
|
|
89
|
+
Current SDK heuristic:
|
|
90
|
+
compare first 3, 2 or 1 digits against the first number.
|
|
91
|
+
"""
|
|
92
|
+
if len(numbers) <= 1:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
ref3, ref2, ref1 = numbers[0][:3], numbers[0][:2], numbers[0][:1]
|
|
96
|
+
|
|
97
|
+
for number in numbers[1:]:
|
|
98
|
+
cur3, cur2, cur1 = number[:3], number[:2], number[:1]
|
|
99
|
+
if not (cur3 == ref3 or cur2 == ref2 or cur1 == ref1):
|
|
100
|
+
raise MixedCountryBatchError(
|
|
101
|
+
"All numbers in one request must belong to the same country."
|
|
102
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bridgexapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the BridgeXAPI SMS API
|
|
5
|
+
Author: BridgeXAPI
|
|
6
|
+
Project-URL: Homepage, https://dashboard.bridgexapi.io
|
|
7
|
+
Project-URL: Documentation, https://dashboard.bridgexapi.io
|
|
8
|
+
Project-URL: Source, https://dashboard.bridgexapi.io
|
|
9
|
+
Keywords: sms,api,messaging,bridgexapi,python-sdk
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Communications
|
|
14
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: requests>=2.31.0
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# BridgeXAPI Python SDK
|
|
22
|
+
|
|
23
|
+
Official Python SDK for the BridgeXAPI SMS API.
|
|
24
|
+
|
|
25
|
+
The BridgeXAPI Python SDK provides a simple and secure way to send SMS through the BridgeXAPI infrastructure using one API key, one endpoint, and route-based delivery selection.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Official Python SDK for BridgeXAPI SMS
|
|
30
|
+
- Simple sync client
|
|
31
|
+
- Local validation before requests are sent
|
|
32
|
+
- Support for route IDs `1` through `5`
|
|
33
|
+
- Typed route constants via `Route`
|
|
34
|
+
- Structured response models
|
|
35
|
+
- Clean Python exceptions
|
|
36
|
+
- Server-side integration ready
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
### Local development
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install -e .
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
bridgexapi/__init__.py
|
|
5
|
+
bridgexapi/client.py
|
|
6
|
+
bridgexapi/exceptions.py
|
|
7
|
+
bridgexapi/models.py
|
|
8
|
+
bridgexapi/routes.py
|
|
9
|
+
bridgexapi/validators.py
|
|
10
|
+
bridgexapi/version.py
|
|
11
|
+
bridgexapi.egg-info/PKG-INFO
|
|
12
|
+
bridgexapi.egg-info/SOURCES.txt
|
|
13
|
+
bridgexapi.egg-info/dependency_links.txt
|
|
14
|
+
bridgexapi.egg-info/requires.txt
|
|
15
|
+
bridgexapi.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.31.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bridgexapi
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bridgexapi"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the BridgeXAPI SMS API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "BridgeXAPI" }
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"requests>=2.31.0"
|
|
16
|
+
]
|
|
17
|
+
keywords = ["sms", "api", "messaging", "bridgexapi", "python-sdk"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Topic :: Communications",
|
|
23
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://dashboard.bridgexapi.io"
|
|
28
|
+
Documentation = "https://dashboard.bridgexapi.io"
|
|
29
|
+
Source = "https://dashboard.bridgexapi.io"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
include-package-data = true
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
include = ["bridgexapi*"]
|