comms-sdk 1.0.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.
- comms_sdk-1.0.0/PKG-INFO +25 -0
- comms_sdk-1.0.0/README.md +16 -0
- comms_sdk-1.0.0/pyproject.toml +12 -0
- comms_sdk-1.0.0/setup.cfg +4 -0
- comms_sdk-1.0.0/src/comms_sdk/__init__.py +13 -0
- comms_sdk-1.0.0/src/comms_sdk/v1/__init__.py +3 -0
- comms_sdk-1.0.0/src/comms_sdk/v1/comms_sdk.py +139 -0
- comms_sdk-1.0.0/src/comms_sdk/v1/models.py +75 -0
- comms_sdk-1.0.0/src/comms_sdk/v1/utils.py +80 -0
- comms_sdk-1.0.0/src/comms_sdk.egg-info/PKG-INFO +25 -0
- comms_sdk-1.0.0/src/comms_sdk.egg-info/SOURCES.txt +12 -0
- comms_sdk-1.0.0/src/comms_sdk.egg-info/dependency_links.txt +1 -0
- comms_sdk-1.0.0/src/comms_sdk.egg-info/requires.txt +1 -0
- comms_sdk-1.0.0/src/comms_sdk.egg-info/top_level.txt +1 -0
comms_sdk-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: comms_sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: EgoSMS Python SDK
|
|
5
|
+
Author-email: Pahappa Limited <systems@pahappa.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests
|
|
9
|
+
|
|
10
|
+
# Usage
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
# install the package
|
|
14
|
+
pip install egosms_sdk
|
|
15
|
+
# Or for dev "pip install ."
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# import in your project
|
|
19
|
+
from egosms_sdk.v1 import EgoSmsSDK, MessagePriority
|
|
20
|
+
|
|
21
|
+
# use
|
|
22
|
+
EgoSmsSDK.authenticate("username", "password")
|
|
23
|
+
EgoSmsSDK.send_sms("0712345678", "Message to send")
|
|
24
|
+
# send_sms(self, numbers: List[str] | str, message: str, sender_id: Optional[str] = None, priority: MessagePriority = MessagePriority.HIGHEST)
|
|
25
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Usage
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
# install the package
|
|
5
|
+
pip install egosms_sdk
|
|
6
|
+
# Or for dev "pip install ."
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# import in your project
|
|
10
|
+
from egosms_sdk.v1 import EgoSmsSDK, MessagePriority
|
|
11
|
+
|
|
12
|
+
# use
|
|
13
|
+
EgoSmsSDK.authenticate("username", "password")
|
|
14
|
+
EgoSmsSDK.send_sms("0712345678", "Message to send")
|
|
15
|
+
# send_sms(self, numbers: List[str] | str, message: str, sender_id: Optional[str] = None, priority: MessagePriority = MessagePriority.HIGHEST)
|
|
16
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = ["setuptools"]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "comms_sdk"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [{name = "Pahappa Limited", email = "systems@pahappa.com"}]
|
|
9
|
+
description = "EgoSMS Python SDK"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
dependencies = ["requests"]
|
|
12
|
+
readme = "README.md"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pahappa Comms platform Python SDK
|
|
3
|
+
|
|
4
|
+
A Python SDK for integrating with the Comms platform API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__author__ = "Pahappa Limited"
|
|
9
|
+
__email__ = "systems@pahappa.com"
|
|
10
|
+
|
|
11
|
+
from .v1 import CommsSDK, MessagePriority
|
|
12
|
+
|
|
13
|
+
__all__ = ['CommsSDK', 'MessagePriority']
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
import requests
|
|
4
|
+
from .models import ApiRequest, ApiResponse, ApiResponseCode, MessageModel, MessagePriority, UserData
|
|
5
|
+
from .utils import NumberValidator, Validator
|
|
6
|
+
|
|
7
|
+
class CommsSDK:
|
|
8
|
+
API_URL = "https://comms.egosms.co/api/v1/json/"
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self._api_key: Optional[str] = None
|
|
12
|
+
self._user_name: Optional[str] = None
|
|
13
|
+
self._sender_id: str = "EgoSMS"
|
|
14
|
+
self._is_authenticated: bool = False
|
|
15
|
+
self._client = requests.Session()
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def api_key(self) -> Optional[str]:
|
|
19
|
+
return self._api_key
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def user_name(self) -> Optional[str]:
|
|
23
|
+
return self._user_name
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def sender_id(self) -> str:
|
|
27
|
+
return self._sender_id
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def is_authenticated(self) -> bool:
|
|
31
|
+
return self._is_authenticated
|
|
32
|
+
|
|
33
|
+
def set_authenticated(self):
|
|
34
|
+
self._is_authenticated = True
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def authenticate(cls, user_name: str, api_key: str):
|
|
38
|
+
sdk = cls()
|
|
39
|
+
sdk._user_name = user_name
|
|
40
|
+
sdk._api_key = api_key
|
|
41
|
+
Validator.validate_credentials(sdk)
|
|
42
|
+
return sdk
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def use_sandbox():
|
|
46
|
+
CommsSDK.API_URL = "https://comms-test.pahappa.net/api/v1/json"
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def use_live_server():
|
|
50
|
+
CommsSDK.API_URL = "https://comms.egosms.co/api/v1/json"
|
|
51
|
+
|
|
52
|
+
def with_sender_id(self, sender_id: str):
|
|
53
|
+
self._sender_id = sender_id
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def send_sms(self, numbers: str | List[str], message: str, sender_id: Optional[str] = None, priority: MessagePriority = MessagePriority.HIGHEST) -> bool:
|
|
57
|
+
if isinstance(numbers, str):
|
|
58
|
+
numbers = [numbers]
|
|
59
|
+
|
|
60
|
+
api_response = self.query_send_sms(numbers, message, sender_id or self._sender_id, priority)
|
|
61
|
+
|
|
62
|
+
if api_response is None:
|
|
63
|
+
print("Failed to get a response from the server.")
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
if api_response.Status == ApiResponseCode.OK.value:
|
|
67
|
+
print("SMS sent successfully.")
|
|
68
|
+
print(f"MessageFollowUpUniqueCode: {api_response.MsgFollowUpUniqueCode}")
|
|
69
|
+
return True
|
|
70
|
+
elif api_response.Status == ApiResponseCode.FAILED.value:
|
|
71
|
+
print(f"Failed: {api_response.Message}")
|
|
72
|
+
return False
|
|
73
|
+
else:
|
|
74
|
+
raise RuntimeError(f"Unexpected response status: {api_response.Status}")
|
|
75
|
+
|
|
76
|
+
def query_send_sms(self, numbers: List[str], message: str, sender_id: str, priority: MessagePriority) -> Optional[ApiResponse]:
|
|
77
|
+
if self._sdk_not_authenticated():
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
if not numbers:
|
|
81
|
+
raise ValueError("Numbers list cannot be empty")
|
|
82
|
+
if not message:
|
|
83
|
+
raise ValueError("Message cannot be empty")
|
|
84
|
+
if len(message) == 1:
|
|
85
|
+
raise ValueError("Message cannot be a single character")
|
|
86
|
+
|
|
87
|
+
if not sender_id or sender_id.strip() == "":
|
|
88
|
+
sender_id = self._sender_id
|
|
89
|
+
if sender_id and len(sender_id) > 11:
|
|
90
|
+
print("Warning: Sender ID length exceeds 11 characters. Some networks may truncate or reject messages.")
|
|
91
|
+
|
|
92
|
+
numbers = NumberValidator.validate_numbers(numbers)
|
|
93
|
+
if not numbers:
|
|
94
|
+
print("No valid phone numbers provided. Please check inputs.", file=sys.stderr)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
api_request = ApiRequest(method="SendSms", userdata=UserData(self._user_name, self._api_key))
|
|
98
|
+
message_models = []
|
|
99
|
+
for num in numbers:
|
|
100
|
+
message_model = MessageModel(number=num, message=message, senderid=sender_id, priority=priority.value)
|
|
101
|
+
message_models.append(message_model)
|
|
102
|
+
api_request.msgdata = message_models
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
res = self._client.post(CommsSDK.API_URL, json=api_request.to_dict())
|
|
106
|
+
return ApiResponse(**res.json())
|
|
107
|
+
except Exception as e:
|
|
108
|
+
print(f"Failed to send SMS: {e}", file=sys.stderr)
|
|
109
|
+
try:
|
|
110
|
+
print(f"Request: {api_request.__dict__}", file=sys.stderr)
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def _sdk_not_authenticated(self) -> bool:
|
|
116
|
+
if not self._is_authenticated:
|
|
117
|
+
print("SDK is not authenticated. Please authenticate before performing actions.", file=sys.stderr)
|
|
118
|
+
print("Attempting to re-authenticate with provided credentials...", file=sys.stderr)
|
|
119
|
+
return not Validator.validate_credentials(self)
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
def __str__(self) -> str:
|
|
123
|
+
return f"SDK({self._user_name} => {self._api_key})"
|
|
124
|
+
|
|
125
|
+
def query_balance(self) -> Optional[ApiResponse]:
|
|
126
|
+
if self._sdk_not_authenticated():
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
api_request = ApiRequest(method="Balance", userdata=UserData(self._user_name, self._api_key))
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
res = self._client.post(CommsSDK.API_URL, json=api_request.to_dict())
|
|
133
|
+
return ApiResponse(**res.json())
|
|
134
|
+
except Exception as e:
|
|
135
|
+
raise RuntimeError(f"Failed to get balance: {e}") from e
|
|
136
|
+
|
|
137
|
+
def get_balance(self) -> Optional[float]:
|
|
138
|
+
response = self.query_balance()
|
|
139
|
+
return float(response.Balance) if response and response.Balance else None
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from dataclasses import dataclass, asdict
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
class ApiResponseCode(Enum):
|
|
7
|
+
OK = "OK"
|
|
8
|
+
FAILED = "Failed"
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
def from_json(cls, json_string: str):
|
|
12
|
+
for code in cls:
|
|
13
|
+
if code.value.lower() == json_string.lower():
|
|
14
|
+
return code
|
|
15
|
+
raise ValueError(f"Unknown value: {json_string}")
|
|
16
|
+
|
|
17
|
+
class MessagePriority(Enum):
|
|
18
|
+
HIGHEST = "0"
|
|
19
|
+
HIGH = "1"
|
|
20
|
+
MEDIUM = "2"
|
|
21
|
+
LOW = "3"
|
|
22
|
+
LOWEST = "4"
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_value(cls, text: str):
|
|
26
|
+
for priority in cls:
|
|
27
|
+
if priority.value == text:
|
|
28
|
+
return priority
|
|
29
|
+
raise ValueError(f"Unknown priority value: {text}")
|
|
30
|
+
|
|
31
|
+
class JSONSerializable:
|
|
32
|
+
def to_dict(self):
|
|
33
|
+
return asdict(self)
|
|
34
|
+
|
|
35
|
+
def to_json(self):
|
|
36
|
+
return json.dumps(self.to_dict())
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class UserData(JSONSerializable):
|
|
40
|
+
username: str
|
|
41
|
+
apikey: str
|
|
42
|
+
|
|
43
|
+
def to_dict(self):
|
|
44
|
+
return {
|
|
45
|
+
"username": self.username,
|
|
46
|
+
"password": self.apikey # Maps to "password" in JSON
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class MessageModel(JSONSerializable):
|
|
51
|
+
number: str
|
|
52
|
+
message: str
|
|
53
|
+
senderid: str
|
|
54
|
+
priority: MessagePriority
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ApiRequest(JSONSerializable):
|
|
58
|
+
method: str
|
|
59
|
+
userdata: UserData
|
|
60
|
+
msgdata: Optional[List[MessageModel]] = None
|
|
61
|
+
def to_dict(self):
|
|
62
|
+
return {
|
|
63
|
+
"method": self.method,
|
|
64
|
+
"userdata": self.userdata.to_dict(),
|
|
65
|
+
"msgdata": [msg.to_dict() for msg in self.msgdata] if self.msgdata else None
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class ApiResponse(JSONSerializable):
|
|
70
|
+
Status: str
|
|
71
|
+
Message: Optional[str] = None
|
|
72
|
+
Cost: Optional[int] = None
|
|
73
|
+
Currency: Optional[str] = None
|
|
74
|
+
MsgFollowUpUniqueCode: Optional[str] = None
|
|
75
|
+
Balance: Optional[str] = None
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List, Set
|
|
3
|
+
|
|
4
|
+
from .models import ApiRequest, ApiResponse, ApiResponseCode, UserData
|
|
5
|
+
import requests
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
class NumberValidator:
|
|
9
|
+
_regex = r"^\+?(0|\d{3})\d{9}$"
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def validate_numbers(numbers: List[str]) -> List[str]:
|
|
13
|
+
if not numbers:
|
|
14
|
+
print("Number list cannot be null or empty", file=sys.stderr)
|
|
15
|
+
return []
|
|
16
|
+
|
|
17
|
+
cleansed_numbers: Set[str] = set()
|
|
18
|
+
for number in numbers:
|
|
19
|
+
if not number or not number.strip():
|
|
20
|
+
print(f"Number ({number}) cannot be null or empty!", file=sys.stderr)
|
|
21
|
+
continue
|
|
22
|
+
|
|
23
|
+
number = number.strip().replace("-", "").replace(" ", "")
|
|
24
|
+
if re.match(NumberValidator._regex, number):
|
|
25
|
+
if number.startswith("0"):
|
|
26
|
+
number = "256" + number[1:]
|
|
27
|
+
elif number.startswith("+"):
|
|
28
|
+
number = number[1:]
|
|
29
|
+
cleansed_numbers.add(number)
|
|
30
|
+
else:
|
|
31
|
+
print(f"Number ({number}) is not valid!", file=sys.stderr)
|
|
32
|
+
return list(cleansed_numbers)
|
|
33
|
+
|
|
34
|
+
class Validator:
|
|
35
|
+
@staticmethod
|
|
36
|
+
def validate_credentials(sdk) -> bool:
|
|
37
|
+
if sdk is None:
|
|
38
|
+
raise ValueError("CommsSDK instance cannot be null")
|
|
39
|
+
|
|
40
|
+
if sdk.api_key is None and sdk.user_name is None:
|
|
41
|
+
raise ValueError("API Key and Username must be provided")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if not Validator._is_valid_credential(sdk):
|
|
45
|
+
print(" _ \n" +
|
|
46
|
+
" /\\ _|_ |_ _ ._ _|_ o _ _. _|_ o _ ._ |_ _. o | _ _| | | \n" +
|
|
47
|
+
" /--\\ |_| |_ | | (/_ | | |_ | (_ (_| |_ | (_) | | | (_| | | (/_ (_| o o \n" +
|
|
48
|
+
" \n" +
|
|
49
|
+
"\n")
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
print("Validated using an api key")
|
|
53
|
+
sdk.set_authenticated()
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _is_valid_credential(sdk) -> bool:
|
|
58
|
+
client = requests.Session()
|
|
59
|
+
api_request = ApiRequest(method="Balance",userdata=UserData(sdk.user_name, sdk.api_key))
|
|
60
|
+
req = api_request.to_dict()
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
res = client.post(sdk.API_URL, json=req)
|
|
64
|
+
# res.raise_for_status() # Raise an exception for HTTP errors
|
|
65
|
+
|
|
66
|
+
api_response = ApiResponse(**res.json())
|
|
67
|
+
|
|
68
|
+
if api_response.Status == ApiResponseCode.OK.value:
|
|
69
|
+
print("Credentials validated successfully.")
|
|
70
|
+
return True
|
|
71
|
+
elif api_response.Status == ApiResponseCode.FAILED.value:
|
|
72
|
+
raise Exception(api_response.Message)
|
|
73
|
+
else:
|
|
74
|
+
return False
|
|
75
|
+
except requests.exceptions.RequestException as e:
|
|
76
|
+
print(f"Error validating credentials: {e}", file=sys.stderr)
|
|
77
|
+
return False
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"Error validating credentials: {e}", file=sys.stderr)
|
|
80
|
+
return False
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: comms_sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: EgoSMS Python SDK
|
|
5
|
+
Author-email: Pahappa Limited <systems@pahappa.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests
|
|
9
|
+
|
|
10
|
+
# Usage
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
# install the package
|
|
14
|
+
pip install egosms_sdk
|
|
15
|
+
# Or for dev "pip install ."
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# import in your project
|
|
19
|
+
from egosms_sdk.v1 import EgoSmsSDK, MessagePriority
|
|
20
|
+
|
|
21
|
+
# use
|
|
22
|
+
EgoSmsSDK.authenticate("username", "password")
|
|
23
|
+
EgoSmsSDK.send_sms("0712345678", "Message to send")
|
|
24
|
+
# send_sms(self, numbers: List[str] | str, message: str, sender_id: Optional[str] = None, priority: MessagePriority = MessagePriority.HIGHEST)
|
|
25
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/comms_sdk/__init__.py
|
|
4
|
+
src/comms_sdk.egg-info/PKG-INFO
|
|
5
|
+
src/comms_sdk.egg-info/SOURCES.txt
|
|
6
|
+
src/comms_sdk.egg-info/dependency_links.txt
|
|
7
|
+
src/comms_sdk.egg-info/requires.txt
|
|
8
|
+
src/comms_sdk.egg-info/top_level.txt
|
|
9
|
+
src/comms_sdk/v1/__init__.py
|
|
10
|
+
src/comms_sdk/v1/comms_sdk.py
|
|
11
|
+
src/comms_sdk/v1/models.py
|
|
12
|
+
src/comms_sdk/v1/utils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
comms_sdk
|