django-mimsms 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.
- django_mimsms-0.1.0/PKG-INFO +156 -0
- django_mimsms-0.1.0/README.md +121 -0
- django_mimsms-0.1.0/pyproject.toml +100 -0
- django_mimsms-0.1.0/setup.cfg +4 -0
- django_mimsms-0.1.0/src/django_mimsms/__init__.py +3 -0
- django_mimsms-0.1.0/src/django_mimsms/client.py +280 -0
- django_mimsms-0.1.0/src/django_mimsms/config.py +16 -0
- django_mimsms-0.1.0/src/django_mimsms/django.py +52 -0
- django_mimsms-0.1.0/src/django_mimsms/exceptions.py +56 -0
- django_mimsms-0.1.0/src/django_mimsms/models.py +95 -0
- django_mimsms-0.1.0/src/django_mimsms/transport.py +95 -0
- django_mimsms-0.1.0/src/django_mimsms/version.py +1 -0
- django_mimsms-0.1.0/src/django_mimsms.egg-info/PKG-INFO +156 -0
- django_mimsms-0.1.0/src/django_mimsms.egg-info/SOURCES.txt +19 -0
- django_mimsms-0.1.0/src/django_mimsms.egg-info/dependency_links.txt +1 -0
- django_mimsms-0.1.0/src/django_mimsms.egg-info/requires.txt +19 -0
- django_mimsms-0.1.0/src/django_mimsms.egg-info/top_level.txt +1 -0
- django_mimsms-0.1.0/tests/test_client.py +266 -0
- django_mimsms-0.1.0/tests/test_config.py +31 -0
- django_mimsms-0.1.0/tests/test_django.py +52 -0
- django_mimsms-0.1.0/tests/test_transport.py +103 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-mimsms
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-ready Django & Python package for MiMSMS.com bulk SMS API. Features async support, type safety, and seamless Bangladesh SMS gateway integration.
|
|
5
|
+
Author: Mimsms Integration Team
|
|
6
|
+
Project-URL: Homepage, https://github.com/sharf-shawon/django-mimsms
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/sharf-shawon/django-mimsms/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/sharf-shawon/django-mimsms#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/sharf-shawon/django-mimsms
|
|
10
|
+
Keywords: django-mimsms,mimsms sms api,django sms integration,bulk sms bangladesh,mimsms.com python,dynamic sms api,sms gateway django
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 5.0
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: django>=5.0
|
|
19
|
+
Requires-Dist: httpx>=0.27.0
|
|
20
|
+
Requires-Dist: pydantic>=2.6.0
|
|
21
|
+
Requires-Dist: rich>=15.0.0
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
24
|
+
Requires-Dist: pytest-django>=4.8.0; extra == "test"
|
|
25
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "test"
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
|
|
27
|
+
Requires-Dist: respx>=0.20.0; extra == "test"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.9.0; extra == "dev"
|
|
31
|
+
Requires-Dist: build; extra == "dev"
|
|
32
|
+
Requires-Dist: rich>=13.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: django-mimsms[test]; extra == "dev"
|
|
34
|
+
Requires-Dist: python-semantic-release>=9.0.0; extra == "dev"
|
|
35
|
+
|
|
36
|
+
# django-mimsms: MiMSMS SMS API Integration for Django & Python
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/django-mimsms/)
|
|
39
|
+
[](https://opensource.org/licenses/MIT)
|
|
40
|
+
[](https://github.com/mimsms/django-mimsms/actions)
|
|
41
|
+
|
|
42
|
+
Seamlessly integrate **MiMSMS.com SMS API** into your Django and Python applications. This package provides a robust, type-safe, and asynchronous client for sending SMS, bulk messages, and tracking delivery reports in Bangladesh.
|
|
43
|
+
|
|
44
|
+
## Key Features
|
|
45
|
+
|
|
46
|
+
- **Strict Type Safety**: Fully typed with `mypy` strict checks and Pydantic v2 validation.
|
|
47
|
+
- **Async Support**: Efficient non-blocking I/O using `httpx`.
|
|
48
|
+
- **Django Integration**: Configuration via standard Django settings.
|
|
49
|
+
- **100% Test Coverage**: Comprehensive test suite with deterministic network isolation (`respx`).
|
|
50
|
+
- **Multiple API Support**: Single SMS, Bulk SMS (One-to-Many), and Dynamic SMS (DSMS).
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
Install the package via `pip` or `uv`:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Using pip
|
|
58
|
+
pip install django-mimsms
|
|
59
|
+
|
|
60
|
+
# Using uv
|
|
61
|
+
uv add django-mimsms
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Django Integration
|
|
65
|
+
|
|
66
|
+
### 1. Add to `INSTALLED_APPS`
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# settings.py
|
|
70
|
+
INSTALLED_APPS = [
|
|
71
|
+
...,
|
|
72
|
+
"django_mimsms",
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Configure Settings
|
|
77
|
+
|
|
78
|
+
Add your MiMSMS credentials to your `settings.py`:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# settings.py
|
|
82
|
+
MIMSMS_API_KEY = "your_api_key"
|
|
83
|
+
MIMSMS_SENDER_ID = "your_sender_id"
|
|
84
|
+
MIMSMS_USERNAME = "your_username"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Usage
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from django_mimsms import send_sms
|
|
91
|
+
|
|
92
|
+
# Send a simple SMS
|
|
93
|
+
response = send_sms(
|
|
94
|
+
to="88017XXXXXXXX",
|
|
95
|
+
message="Hello from Django!",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
print(response.trxnId)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Plain Python Usage
|
|
102
|
+
|
|
103
|
+
If you're not using Django, you can use the `MiMSMSClient` directly:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import asyncio
|
|
107
|
+
from django_mimsms import MiMSMSClient
|
|
108
|
+
|
|
109
|
+
async def main():
|
|
110
|
+
client = MiMSMSClient(
|
|
111
|
+
api_key="your_api_key",
|
|
112
|
+
username="your_username",
|
|
113
|
+
sender_id="your_sender_id"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
response = await client.send_sms(
|
|
117
|
+
to="88017XXXXXXXX",
|
|
118
|
+
message="Hello from Python!",
|
|
119
|
+
)
|
|
120
|
+
print(response.trxnId)
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
asyncio.run(main())
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Advanced Features
|
|
127
|
+
|
|
128
|
+
### Bulk SMS
|
|
129
|
+
Send the same message to multiple recipients:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from django_mimsms import send_bulk_sms
|
|
133
|
+
|
|
134
|
+
send_bulk_sms(
|
|
135
|
+
numbers=["88017XXXXXXXX", "88018XXXXXXXX"],
|
|
136
|
+
message="Bulk message testing",
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Dynamic SMS
|
|
141
|
+
Send different messages to different recipients in one call:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from django_mimsms import send_dynamic_sms
|
|
145
|
+
|
|
146
|
+
messages = [
|
|
147
|
+
{"to": "88017XXXXXXXX", "message": "Hi Alice!"},
|
|
148
|
+
{"to": "88018XXXXXXXX", "message": "Hi Bob!"},
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
send_dynamic_sms(messages)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# django-mimsms: MiMSMS SMS API Integration for Django & Python
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/django-mimsms/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/mimsms/django-mimsms/actions)
|
|
6
|
+
|
|
7
|
+
Seamlessly integrate **MiMSMS.com SMS API** into your Django and Python applications. This package provides a robust, type-safe, and asynchronous client for sending SMS, bulk messages, and tracking delivery reports in Bangladesh.
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
- **Strict Type Safety**: Fully typed with `mypy` strict checks and Pydantic v2 validation.
|
|
12
|
+
- **Async Support**: Efficient non-blocking I/O using `httpx`.
|
|
13
|
+
- **Django Integration**: Configuration via standard Django settings.
|
|
14
|
+
- **100% Test Coverage**: Comprehensive test suite with deterministic network isolation (`respx`).
|
|
15
|
+
- **Multiple API Support**: Single SMS, Bulk SMS (One-to-Many), and Dynamic SMS (DSMS).
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Install the package via `pip` or `uv`:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Using pip
|
|
23
|
+
pip install django-mimsms
|
|
24
|
+
|
|
25
|
+
# Using uv
|
|
26
|
+
uv add django-mimsms
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Django Integration
|
|
30
|
+
|
|
31
|
+
### 1. Add to `INSTALLED_APPS`
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
# settings.py
|
|
35
|
+
INSTALLED_APPS = [
|
|
36
|
+
...,
|
|
37
|
+
"django_mimsms",
|
|
38
|
+
]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Configure Settings
|
|
42
|
+
|
|
43
|
+
Add your MiMSMS credentials to your `settings.py`:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# settings.py
|
|
47
|
+
MIMSMS_API_KEY = "your_api_key"
|
|
48
|
+
MIMSMS_SENDER_ID = "your_sender_id"
|
|
49
|
+
MIMSMS_USERNAME = "your_username"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Usage
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from django_mimsms import send_sms
|
|
56
|
+
|
|
57
|
+
# Send a simple SMS
|
|
58
|
+
response = send_sms(
|
|
59
|
+
to="88017XXXXXXXX",
|
|
60
|
+
message="Hello from Django!",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
print(response.trxnId)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Plain Python Usage
|
|
67
|
+
|
|
68
|
+
If you're not using Django, you can use the `MiMSMSClient` directly:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
from django_mimsms import MiMSMSClient
|
|
73
|
+
|
|
74
|
+
async def main():
|
|
75
|
+
client = MiMSMSClient(
|
|
76
|
+
api_key="your_api_key",
|
|
77
|
+
username="your_username",
|
|
78
|
+
sender_id="your_sender_id"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
response = await client.send_sms(
|
|
82
|
+
to="88017XXXXXXXX",
|
|
83
|
+
message="Hello from Python!",
|
|
84
|
+
)
|
|
85
|
+
print(response.trxnId)
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Advanced Features
|
|
92
|
+
|
|
93
|
+
### Bulk SMS
|
|
94
|
+
Send the same message to multiple recipients:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from django_mimsms import send_bulk_sms
|
|
98
|
+
|
|
99
|
+
send_bulk_sms(
|
|
100
|
+
numbers=["88017XXXXXXXX", "88018XXXXXXXX"],
|
|
101
|
+
message="Bulk message testing",
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Dynamic SMS
|
|
106
|
+
Send different messages to different recipients in one call:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from django_mimsms import send_dynamic_sms
|
|
110
|
+
|
|
111
|
+
messages = [
|
|
112
|
+
{"to": "88017XXXXXXXX", "message": "Hi Alice!"},
|
|
113
|
+
{"to": "88018XXXXXXXX", "message": "Hi Bob!"},
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
send_dynamic_sms(messages)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-mimsms"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Mimsms Integration Team" },
|
|
10
|
+
]
|
|
11
|
+
description = "Production-ready Django & Python package for MiMSMS.com bulk SMS API. Features async support, type safety, and seamless Bangladesh SMS gateway integration."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.12"
|
|
14
|
+
keywords = [
|
|
15
|
+
"django-mimsms",
|
|
16
|
+
"mimsms sms api",
|
|
17
|
+
"django sms integration",
|
|
18
|
+
"bulk sms bangladesh",
|
|
19
|
+
"mimsms.com python",
|
|
20
|
+
"dynamic sms api",
|
|
21
|
+
"sms gateway django",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Framework :: Django",
|
|
28
|
+
"Framework :: Django :: 5.0",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"django>=5.0",
|
|
32
|
+
"httpx>=0.27.0",
|
|
33
|
+
"pydantic>=2.6.0",
|
|
34
|
+
"rich>=15.0.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
"Homepage" = "https://github.com/sharf-shawon/django-mimsms"
|
|
39
|
+
"Bug Tracker" = "https://github.com/sharf-shawon/django-mimsms/issues"
|
|
40
|
+
"Documentation" = "https://github.com/sharf-shawon/django-mimsms#readme"
|
|
41
|
+
"Repository" = "https://github.com/sharf-shawon/django-mimsms"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
test = [
|
|
45
|
+
"pytest>=8.0.0",
|
|
46
|
+
"pytest-django>=4.8.0",
|
|
47
|
+
"pytest-cov>=4.1.0",
|
|
48
|
+
"pytest-asyncio>=0.23.0",
|
|
49
|
+
"respx>=0.20.0",
|
|
50
|
+
]
|
|
51
|
+
dev = [
|
|
52
|
+
"ruff>=0.3.0",
|
|
53
|
+
"mypy>=1.9.0",
|
|
54
|
+
"build",
|
|
55
|
+
"rich>=13.0.0",
|
|
56
|
+
"django-mimsms[test]",
|
|
57
|
+
"python-semantic-release>=9.0.0",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
[tool.semantic_release]
|
|
65
|
+
vcs_provider = "github"
|
|
66
|
+
version_variable = [
|
|
67
|
+
"src/django_mimsms/version.py:__version__",
|
|
68
|
+
]
|
|
69
|
+
version_toml = [
|
|
70
|
+
"pyproject.toml:project.version",
|
|
71
|
+
]
|
|
72
|
+
branch = "main"
|
|
73
|
+
upload_to_pypi = false
|
|
74
|
+
upload_to_release = true
|
|
75
|
+
build_command = "python -m pip install build && python -m build"
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
line-length = 120
|
|
79
|
+
target-version = "py312"
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint]
|
|
82
|
+
select = ["E", "F", "I", "N", "UP", "B", "C4", "DTZ", "SIM", "TID"]
|
|
83
|
+
|
|
84
|
+
[tool.mypy]
|
|
85
|
+
python_version = "3.12"
|
|
86
|
+
strict = true
|
|
87
|
+
ignore_missing_imports = true
|
|
88
|
+
plugins = ["pydantic.mypy"]
|
|
89
|
+
|
|
90
|
+
[tool.pytest.ini_options]
|
|
91
|
+
minversion = "8.0"
|
|
92
|
+
addopts = "-ra -q --cov=src/django_mimsms --cov-report=term-missing --cov-fail-under=100"
|
|
93
|
+
testpaths = [
|
|
94
|
+
"tests",
|
|
95
|
+
]
|
|
96
|
+
pythonpath = [".", "src"]
|
|
97
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
98
|
+
|
|
99
|
+
[tool.setuptools.packages.find]
|
|
100
|
+
where = ["src"]
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import ValidationError
|
|
4
|
+
|
|
5
|
+
from django_mimsms.config import MiMSMSConfig
|
|
6
|
+
from django_mimsms.exceptions import MiMSMSResponseParseError, MiMSMSValidationError
|
|
7
|
+
from django_mimsms.models import (
|
|
8
|
+
BulkSmsRequest,
|
|
9
|
+
DlrRequest,
|
|
10
|
+
DlrResponse,
|
|
11
|
+
DynamicSmsItem,
|
|
12
|
+
DynamicSmsRequest,
|
|
13
|
+
MiMSMSBaseRequest,
|
|
14
|
+
MiMSMSResponse,
|
|
15
|
+
SingleSmsRequest,
|
|
16
|
+
)
|
|
17
|
+
from django_mimsms.transport import Transport
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MiMSMSClient:
|
|
21
|
+
"""Core client for the MiMSMS API."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
username: str,
|
|
26
|
+
apikey: str,
|
|
27
|
+
sender_name: str,
|
|
28
|
+
**options: Any,
|
|
29
|
+
) -> None:
|
|
30
|
+
self.config = MiMSMSConfig(username=username, apikey=apikey, sender_name=sender_name, **options)
|
|
31
|
+
self.transport = Transport(self.config)
|
|
32
|
+
|
|
33
|
+
def send_sms(
|
|
34
|
+
self,
|
|
35
|
+
number: str,
|
|
36
|
+
message: str,
|
|
37
|
+
transaction_type: str | None = None,
|
|
38
|
+
campaign_id: str | None = None,
|
|
39
|
+
) -> MiMSMSResponse:
|
|
40
|
+
"""Send a single SMS via JSON POST."""
|
|
41
|
+
try:
|
|
42
|
+
payload = SingleSmsRequest(
|
|
43
|
+
username=self.config.username,
|
|
44
|
+
apikey=self.config.apikey,
|
|
45
|
+
mobile_number=number,
|
|
46
|
+
sender_name=self.config.sender_name,
|
|
47
|
+
transaction_type=transaction_type or self.config.default_transaction_type,
|
|
48
|
+
message=message,
|
|
49
|
+
campaign_id=campaign_id or self.config.campaign_id,
|
|
50
|
+
)
|
|
51
|
+
except ValidationError as e:
|
|
52
|
+
raise MiMSMSValidationError(str(e)) from e
|
|
53
|
+
|
|
54
|
+
data = self.transport.request(
|
|
55
|
+
"POST",
|
|
56
|
+
"/api/SmsSending/SMS",
|
|
57
|
+
json=payload.model_dump(by_alias=True, exclude_none=True),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
return MiMSMSResponse.model_validate(data)
|
|
62
|
+
except ValidationError as e:
|
|
63
|
+
raise MiMSMSResponseParseError(str(e)) from e
|
|
64
|
+
|
|
65
|
+
def send_sms_get(
|
|
66
|
+
self,
|
|
67
|
+
number: str,
|
|
68
|
+
message: str,
|
|
69
|
+
transaction_type: str | None = None,
|
|
70
|
+
campaign_id: str | None = None,
|
|
71
|
+
) -> MiMSMSResponse:
|
|
72
|
+
"""Send a single SMS via query-string GET."""
|
|
73
|
+
try:
|
|
74
|
+
payload = SingleSmsRequest(
|
|
75
|
+
username=self.config.username,
|
|
76
|
+
apikey=self.config.apikey,
|
|
77
|
+
mobile_number=number,
|
|
78
|
+
sender_name=self.config.sender_name,
|
|
79
|
+
transaction_type=transaction_type or self.config.default_transaction_type,
|
|
80
|
+
message=message,
|
|
81
|
+
campaign_id=campaign_id or self.config.campaign_id,
|
|
82
|
+
)
|
|
83
|
+
except ValidationError as e:
|
|
84
|
+
raise MiMSMSValidationError(str(e)) from e
|
|
85
|
+
|
|
86
|
+
data = self.transport.request(
|
|
87
|
+
"GET",
|
|
88
|
+
"/api/SmsSending/Send",
|
|
89
|
+
params=payload.model_dump(by_alias=True, exclude_none=True),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
return MiMSMSResponse.model_validate(data)
|
|
94
|
+
except ValidationError as e:
|
|
95
|
+
raise MiMSMSResponseParseError(str(e)) from e
|
|
96
|
+
|
|
97
|
+
def send_one_to_many(
|
|
98
|
+
self,
|
|
99
|
+
numbers: list[str] | str,
|
|
100
|
+
message: str,
|
|
101
|
+
transaction_type: str | None = None,
|
|
102
|
+
campaign_id: str | None = None,
|
|
103
|
+
) -> MiMSMSResponse:
|
|
104
|
+
"""Send bulk SMS via JSON POST."""
|
|
105
|
+
numbers_str = ",".join(numbers) if isinstance(numbers, list) else numbers
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
payload = BulkSmsRequest(
|
|
109
|
+
username=self.config.username,
|
|
110
|
+
apikey=self.config.apikey,
|
|
111
|
+
mobile_number=numbers_str,
|
|
112
|
+
sender_name=self.config.sender_name,
|
|
113
|
+
transaction_type=transaction_type or self.config.default_transaction_type,
|
|
114
|
+
message=message,
|
|
115
|
+
campaign_id=campaign_id or self.config.campaign_id,
|
|
116
|
+
)
|
|
117
|
+
except ValidationError as e:
|
|
118
|
+
raise MiMSMSValidationError(str(e)) from e
|
|
119
|
+
|
|
120
|
+
data = self.transport.request(
|
|
121
|
+
"POST",
|
|
122
|
+
"/api/SmsSending/OneToMany",
|
|
123
|
+
json=payload.model_dump(by_alias=True, exclude_none=True),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
return MiMSMSResponse.model_validate(data)
|
|
128
|
+
except ValidationError as e:
|
|
129
|
+
raise MiMSMSResponseParseError(str(e)) from e
|
|
130
|
+
|
|
131
|
+
def send_one_to_many_get(
|
|
132
|
+
self,
|
|
133
|
+
numbers: list[str] | str,
|
|
134
|
+
message: str,
|
|
135
|
+
transaction_type: str | None = None,
|
|
136
|
+
campaign_id: str | None = None,
|
|
137
|
+
) -> MiMSMSResponse:
|
|
138
|
+
"""Send bulk SMS via query-string GET."""
|
|
139
|
+
numbers_str = ",".join(numbers) if isinstance(numbers, list) else numbers
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
payload = BulkSmsRequest(
|
|
143
|
+
username=self.config.username,
|
|
144
|
+
apikey=self.config.apikey,
|
|
145
|
+
mobile_number=numbers_str,
|
|
146
|
+
sender_name=self.config.sender_name,
|
|
147
|
+
transaction_type=transaction_type or self.config.default_transaction_type,
|
|
148
|
+
message=message,
|
|
149
|
+
campaign_id=campaign_id or self.config.campaign_id,
|
|
150
|
+
)
|
|
151
|
+
except ValidationError as e:
|
|
152
|
+
raise MiMSMSValidationError(str(e)) from e
|
|
153
|
+
|
|
154
|
+
data = self.transport.request(
|
|
155
|
+
"GET",
|
|
156
|
+
"/api/SmsSending/SendOneToMany",
|
|
157
|
+
params=payload.model_dump(by_alias=True, exclude_none=True),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
return MiMSMSResponse.model_validate(data)
|
|
162
|
+
except ValidationError as e:
|
|
163
|
+
raise MiMSMSResponseParseError(str(e)) from e
|
|
164
|
+
|
|
165
|
+
def send_dynamic_sms(
|
|
166
|
+
self,
|
|
167
|
+
messages: list[dict[str, str]],
|
|
168
|
+
transaction_type: str = "D",
|
|
169
|
+
) -> MiMSMSResponse:
|
|
170
|
+
"""Send dynamic SMS via JSON POST."""
|
|
171
|
+
try:
|
|
172
|
+
sms_data = [DynamicSmsItem(mobile_number=m["number"], message=m["text"]) for m in messages]
|
|
173
|
+
payload = DynamicSmsRequest(
|
|
174
|
+
username=self.config.username,
|
|
175
|
+
apikey=self.config.apikey,
|
|
176
|
+
sender_name=self.config.sender_name,
|
|
177
|
+
sms_data=sms_data,
|
|
178
|
+
transaction_type=transaction_type,
|
|
179
|
+
)
|
|
180
|
+
except ValidationError as e:
|
|
181
|
+
raise MiMSMSValidationError(str(e)) from e
|
|
182
|
+
|
|
183
|
+
data = self.transport.request(
|
|
184
|
+
"POST",
|
|
185
|
+
"/api/SmsSending/DSMS",
|
|
186
|
+
json=payload.model_dump(by_alias=True, exclude_none=True),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
return MiMSMSResponse.model_validate(data)
|
|
191
|
+
except ValidationError as e:
|
|
192
|
+
raise MiMSMSResponseParseError(str(e)) from e
|
|
193
|
+
|
|
194
|
+
def check_balance(self) -> float:
|
|
195
|
+
"""Check account balance via JSON POST."""
|
|
196
|
+
payload = MiMSMSBaseRequest(
|
|
197
|
+
username=self.config.username,
|
|
198
|
+
apikey=self.config.apikey,
|
|
199
|
+
)
|
|
200
|
+
data = self.transport.request(
|
|
201
|
+
"POST",
|
|
202
|
+
"/api/SmsSending/balanceCheck",
|
|
203
|
+
json=payload.model_dump(by_alias=True, exclude_none=True),
|
|
204
|
+
)
|
|
205
|
+
try:
|
|
206
|
+
val = float(data.get("responseResult", 0.0))
|
|
207
|
+
import math
|
|
208
|
+
|
|
209
|
+
return val if not math.isnan(val) else 0.0
|
|
210
|
+
except (ValueError, TypeError):
|
|
211
|
+
return 0.0
|
|
212
|
+
|
|
213
|
+
def check_balance_get(self) -> float:
|
|
214
|
+
"""Check account balance via query-string GET."""
|
|
215
|
+
payload = MiMSMSBaseRequest(
|
|
216
|
+
username=self.config.username,
|
|
217
|
+
apikey=self.config.apikey,
|
|
218
|
+
)
|
|
219
|
+
data = self.transport.request(
|
|
220
|
+
"GET",
|
|
221
|
+
"/api/SmsSending/balanceCheck",
|
|
222
|
+
params=payload.model_dump(by_alias=True, exclude_none=True),
|
|
223
|
+
)
|
|
224
|
+
try:
|
|
225
|
+
val = float(data.get("responseResult", 0.0))
|
|
226
|
+
import math
|
|
227
|
+
|
|
228
|
+
return val if not math.isnan(val) else 0.0
|
|
229
|
+
except (ValueError, TypeError):
|
|
230
|
+
return 0.0
|
|
231
|
+
|
|
232
|
+
def check_dlr(self, trxn_id: str, number: str) -> DlrResponse:
|
|
233
|
+
"""Check delivery report for a transaction."""
|
|
234
|
+
try:
|
|
235
|
+
payload = DlrRequest(
|
|
236
|
+
username=self.config.username,
|
|
237
|
+
apikey=self.config.apikey,
|
|
238
|
+
mobile_number=number,
|
|
239
|
+
trxn_id=trxn_id,
|
|
240
|
+
)
|
|
241
|
+
except ValidationError as e:
|
|
242
|
+
raise MiMSMSValidationError(str(e)) from e
|
|
243
|
+
|
|
244
|
+
data = self.transport.request(
|
|
245
|
+
"POST",
|
|
246
|
+
"/api/SmsSending/DlrApi",
|
|
247
|
+
json=payload.model_dump(by_alias=True, exclude_none=True),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
return DlrResponse.model_validate(data)
|
|
252
|
+
except ValidationError as e:
|
|
253
|
+
raise MiMSMSResponseParseError(str(e)) from e
|
|
254
|
+
|
|
255
|
+
def _get_auth_params(self) -> dict[str, str]:
|
|
256
|
+
"""Get authentication parameters."""
|
|
257
|
+
return {
|
|
258
|
+
"UserName": self.config.username,
|
|
259
|
+
"ApiKey": self.config.apikey,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
def close(self) -> None:
|
|
263
|
+
"""Close the underlying transport."""
|
|
264
|
+
self.transport.close()
|
|
265
|
+
|
|
266
|
+
async def aclose(self) -> None:
|
|
267
|
+
"""Close the underlying transport asynchronously."""
|
|
268
|
+
await self.transport.aclose()
|
|
269
|
+
|
|
270
|
+
def __enter__(self) -> "MiMSMSClient":
|
|
271
|
+
return self
|
|
272
|
+
|
|
273
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
274
|
+
self.close()
|
|
275
|
+
|
|
276
|
+
async def __aenter__(self) -> "MiMSMSClient":
|
|
277
|
+
return self
|
|
278
|
+
|
|
279
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
280
|
+
await self.aclose()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MiMSMSConfig(BaseModel):
|
|
5
|
+
"""Configuration model for MiMSMS integration."""
|
|
6
|
+
|
|
7
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
8
|
+
|
|
9
|
+
username: str
|
|
10
|
+
apikey: str
|
|
11
|
+
sender_name: str
|
|
12
|
+
base_url: str = "https://api.mimsms.com"
|
|
13
|
+
timeout: float = 30.0
|
|
14
|
+
verify_ssl: bool = True
|
|
15
|
+
default_transaction_type: str = "T"
|
|
16
|
+
campaign_id: str | None = None
|