ccai-python 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.
- ccai_python-1.0.0/PKG-INFO +83 -0
- ccai_python-1.0.0/README.md +63 -0
- ccai_python-1.0.0/pyproject.toml +52 -0
- ccai_python-1.0.0/setup.cfg +4 -0
- ccai_python-1.0.0/src/ccai_python/__init__.py +21 -0
- ccai_python-1.0.0/src/ccai_python/ccai.py +149 -0
- ccai_python-1.0.0/src/ccai_python/examples/async_example.py +248 -0
- ccai_python-1.0.0/src/ccai_python/examples/basic_example.py +79 -0
- ccai_python-1.0.0/src/ccai_python/sms/__init__.py +10 -0
- ccai_python-1.0.0/src/ccai_python/sms/sms.py +215 -0
- ccai_python-1.0.0/src/ccai_python.egg-info/PKG-INFO +83 -0
- ccai_python-1.0.0/src/ccai_python.egg-info/SOURCES.txt +15 -0
- ccai_python-1.0.0/src/ccai_python.egg-info/dependency_links.txt +1 -0
- ccai_python-1.0.0/src/ccai_python.egg-info/requires.txt +2 -0
- ccai_python-1.0.0/src/ccai_python.egg-info/top_level.txt +3 -0
- ccai_python-1.0.0/tests/test_ccai.py +88 -0
- ccai_python-1.0.0/tests/test_sms.py +237 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ccai-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for CloudContactAI API
|
|
5
|
+
Author: CloudContactAI LLC
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cloudcontactai/ccai-python
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/cloudcontactai/ccai-python/issues
|
|
9
|
+
Keywords: sms,api,client,cloud,contact,ai,cloudcontactai
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: requests>=2.31.0
|
|
19
|
+
Requires-Dist: pydantic>=2.5.0
|
|
20
|
+
|
|
21
|
+
# CCAI Python Client
|
|
22
|
+
|
|
23
|
+
A Python client for interacting with the CloudContactAI API.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install ccai-python
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from ccai_python import CCAI
|
|
35
|
+
|
|
36
|
+
# Initialize the client
|
|
37
|
+
ccai = CCAI(
|
|
38
|
+
client_id="YOUR-CLIENT-ID",
|
|
39
|
+
api_key="YOUR-API-KEY"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Send a single SMS
|
|
43
|
+
response = ccai.sms.send_single(
|
|
44
|
+
first_name="John",
|
|
45
|
+
last_name="Doe",
|
|
46
|
+
phone="+15551234567",
|
|
47
|
+
message="Hello ${first_name}, this is a test message!",
|
|
48
|
+
title="Test Campaign"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
print(f"Message sent with ID: {response.id}")
|
|
52
|
+
|
|
53
|
+
# Send to multiple recipients
|
|
54
|
+
accounts = [
|
|
55
|
+
{"first_name": "John", "last_name": "Doe", "phone": "+15551234567"},
|
|
56
|
+
{"first_name": "Jane", "last_name": "Smith", "phone": "+15559876543"}
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
campaign_response = ccai.sms.send(
|
|
60
|
+
accounts=accounts,
|
|
61
|
+
message="Hello ${first_name} ${last_name}, this is a test message!",
|
|
62
|
+
title="Bulk Test Campaign"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
print(f"Campaign sent with ID: {campaign_response.campaign_id}")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Features
|
|
69
|
+
|
|
70
|
+
- Send SMS messages to single or multiple recipients
|
|
71
|
+
- Variable substitution in messages
|
|
72
|
+
- Async support
|
|
73
|
+
- Type hints for better IDE integration
|
|
74
|
+
- Comprehensive error handling
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
- Python 3.10 or higher
|
|
79
|
+
- `requests` library
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# CCAI Python Client
|
|
2
|
+
|
|
3
|
+
A Python client for interacting with the CloudContactAI API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install ccai-python
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from ccai_python import CCAI
|
|
15
|
+
|
|
16
|
+
# Initialize the client
|
|
17
|
+
ccai = CCAI(
|
|
18
|
+
client_id="YOUR-CLIENT-ID",
|
|
19
|
+
api_key="YOUR-API-KEY"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Send a single SMS
|
|
23
|
+
response = ccai.sms.send_single(
|
|
24
|
+
first_name="John",
|
|
25
|
+
last_name="Doe",
|
|
26
|
+
phone="+15551234567",
|
|
27
|
+
message="Hello ${first_name}, this is a test message!",
|
|
28
|
+
title="Test Campaign"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
print(f"Message sent with ID: {response.id}")
|
|
32
|
+
|
|
33
|
+
# Send to multiple recipients
|
|
34
|
+
accounts = [
|
|
35
|
+
{"first_name": "John", "last_name": "Doe", "phone": "+15551234567"},
|
|
36
|
+
{"first_name": "Jane", "last_name": "Smith", "phone": "+15559876543"}
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
campaign_response = ccai.sms.send(
|
|
40
|
+
accounts=accounts,
|
|
41
|
+
message="Hello ${first_name} ${last_name}, this is a test message!",
|
|
42
|
+
title="Bulk Test Campaign"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
print(f"Campaign sent with ID: {campaign_response.campaign_id}")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- Send SMS messages to single or multiple recipients
|
|
51
|
+
- Variable substitution in messages
|
|
52
|
+
- Async support
|
|
53
|
+
- Type hints for better IDE integration
|
|
54
|
+
- Comprehensive error handling
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
- Python 3.10 or higher
|
|
59
|
+
- `requests` library
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ccai-python"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Python client for CloudContactAI API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "CloudContactAI LLC"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["sms", "api", "client", "cloud", "contact", "ai", "cloudcontactai"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"requests>=2.31.0",
|
|
26
|
+
"pydantic>=2.5.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
"Homepage" = "https://github.com/cloudcontactai/ccai-python"
|
|
31
|
+
"Bug Tracker" = "https://github.com/cloudcontactai/ccai-python/issues"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools]
|
|
34
|
+
package-dir = {"" = "src"}
|
|
35
|
+
|
|
36
|
+
[tool.pytest]
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
python_version = "3.10"
|
|
41
|
+
warn_return_any = true
|
|
42
|
+
warn_unused_configs = true
|
|
43
|
+
disallow_untyped_defs = true
|
|
44
|
+
disallow_incomplete_defs = true
|
|
45
|
+
|
|
46
|
+
[tool.black]
|
|
47
|
+
line-length = 88
|
|
48
|
+
target-version = ["py310"]
|
|
49
|
+
|
|
50
|
+
[tool.isort]
|
|
51
|
+
profile = "black"
|
|
52
|
+
line_length = 88
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main export file for the CCAI Python module
|
|
3
|
+
|
|
4
|
+
:license: MIT
|
|
5
|
+
:copyright: 2025 CloudContactAI LLC
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .ccai import CCAI, Account, CCAIConfig
|
|
9
|
+
from .sms.sms import SMS, SMSCampaign, SMSResponse, SMSOptions
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'CCAI',
|
|
13
|
+
'SMS',
|
|
14
|
+
'Account',
|
|
15
|
+
'CCAIConfig',
|
|
16
|
+
'SMSCampaign',
|
|
17
|
+
'SMSResponse',
|
|
18
|
+
'SMSOptions'
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
__version__ = '1.0.0'
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ccai.py - A Python module for interacting with the Cloud Contact AI API
|
|
3
|
+
This module provides functionality to send SMS messages through the CCAI platform.
|
|
4
|
+
|
|
5
|
+
:license: MIT
|
|
6
|
+
:copyright: 2025 CloudContactAI LLC
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, Optional, TypedDict, cast
|
|
10
|
+
import requests
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from .sms.sms import SMS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Account(BaseModel):
|
|
17
|
+
"""Account model representing a recipient"""
|
|
18
|
+
first_name: str = Field(..., description="Recipient's first name")
|
|
19
|
+
last_name: str = Field(..., description="Recipient's last name")
|
|
20
|
+
phone: str = Field(..., description="Recipient's phone number in E.164 format")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CCAIConfig(BaseModel):
|
|
24
|
+
"""Configuration for the CCAI client"""
|
|
25
|
+
client_id: str = Field(..., description="Client ID for authentication")
|
|
26
|
+
api_key: str = Field(..., description="API key for authentication")
|
|
27
|
+
base_url: str = Field(
|
|
28
|
+
default="https://core.cloudcontactai.com/api",
|
|
29
|
+
description="Base URL for the API"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class APIError(Exception):
|
|
34
|
+
"""Exception raised for API errors"""
|
|
35
|
+
def __init__(self, status_code: int, message: str):
|
|
36
|
+
self.status_code = status_code
|
|
37
|
+
self.message = message
|
|
38
|
+
super().__init__(f"API Error: {status_code} - {message}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CCAI:
|
|
42
|
+
"""
|
|
43
|
+
Main client for interacting with the CloudContactAI API
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
client_id: str,
|
|
49
|
+
api_key: str,
|
|
50
|
+
base_url: Optional[str] = None
|
|
51
|
+
) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Create a new CCAI client instance
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
client_id: Client ID for authentication
|
|
57
|
+
api_key: API key for authentication
|
|
58
|
+
base_url: Optional base URL for the API
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ValueError: If client_id or api_key is not provided
|
|
62
|
+
"""
|
|
63
|
+
if not client_id:
|
|
64
|
+
raise ValueError("Client ID is required")
|
|
65
|
+
if not api_key:
|
|
66
|
+
raise ValueError("API Key is required")
|
|
67
|
+
|
|
68
|
+
self._config = CCAIConfig(
|
|
69
|
+
client_id=client_id,
|
|
70
|
+
api_key=api_key,
|
|
71
|
+
base_url=base_url or "https://core.cloudcontactai.com/api"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Initialize the SMS service
|
|
75
|
+
self.sms = SMS(self)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def client_id(self) -> str:
|
|
79
|
+
"""Get the client ID"""
|
|
80
|
+
return self._config.client_id
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def api_key(self) -> str:
|
|
84
|
+
"""Get the API key"""
|
|
85
|
+
return self._config.api_key
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def base_url(self) -> str:
|
|
89
|
+
"""Get the base URL"""
|
|
90
|
+
return self._config.base_url
|
|
91
|
+
|
|
92
|
+
def request(
|
|
93
|
+
self,
|
|
94
|
+
method: str,
|
|
95
|
+
endpoint: str,
|
|
96
|
+
data: Optional[Dict[str, Any]] = None,
|
|
97
|
+
timeout: int = 30
|
|
98
|
+
) -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
Make an authenticated API request to the CCAI API
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
method: HTTP method (GET, POST, etc.)
|
|
104
|
+
endpoint: API endpoint
|
|
105
|
+
data: Request data
|
|
106
|
+
timeout: Request timeout in seconds
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
API response as a dictionary
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
APIError: If the API returns an error
|
|
113
|
+
requests.RequestException: For network-related errors
|
|
114
|
+
"""
|
|
115
|
+
url = f"{self.base_url}{endpoint}"
|
|
116
|
+
|
|
117
|
+
headers = {
|
|
118
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
"Accept": "*/*"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
response = requests.request(
|
|
125
|
+
method=method.upper(),
|
|
126
|
+
url=url,
|
|
127
|
+
headers=headers,
|
|
128
|
+
json=data,
|
|
129
|
+
timeout=timeout
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Raise an exception for HTTP errors
|
|
133
|
+
response.raise_for_status()
|
|
134
|
+
|
|
135
|
+
return cast(Dict[str, Any], response.json())
|
|
136
|
+
except requests.HTTPError as e:
|
|
137
|
+
# Handle API errors with response
|
|
138
|
+
if e.response is not None:
|
|
139
|
+
try:
|
|
140
|
+
error_data = e.response.json()
|
|
141
|
+
error_message = str(error_data)
|
|
142
|
+
except (ValueError, TypeError):
|
|
143
|
+
error_message = e.response.text or str(e)
|
|
144
|
+
|
|
145
|
+
raise APIError(e.response.status_code, error_message)
|
|
146
|
+
raise
|
|
147
|
+
except requests.RequestException as e:
|
|
148
|
+
# Handle network errors
|
|
149
|
+
raise APIError(0, f"Network error: {str(e)}")
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async example using the CCAI Python client with asyncio
|
|
3
|
+
|
|
4
|
+
:license: MIT
|
|
5
|
+
:copyright: 2025 CloudContactAI LLC
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from typing import Dict, Any, List
|
|
10
|
+
|
|
11
|
+
import aiohttp
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
# Import the synchronous client for type definitions
|
|
15
|
+
from ccai_python import Account, SMSResponse, SMSOptions
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AsyncCCAI:
|
|
19
|
+
"""
|
|
20
|
+
Async version of the CCAI client for CloudContactAI API
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
client_id: str,
|
|
26
|
+
api_key: str,
|
|
27
|
+
base_url: str = "https://core.cloudcontactai.com/api"
|
|
28
|
+
) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Create a new async CCAI client instance
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
client_id: Client ID for authentication
|
|
34
|
+
api_key: API key for authentication
|
|
35
|
+
base_url: Base URL for the API
|
|
36
|
+
"""
|
|
37
|
+
if not client_id:
|
|
38
|
+
raise ValueError("Client ID is required")
|
|
39
|
+
if not api_key:
|
|
40
|
+
raise ValueError("API Key is required")
|
|
41
|
+
|
|
42
|
+
self._client_id = client_id
|
|
43
|
+
self._api_key = api_key
|
|
44
|
+
self._base_url = base_url
|
|
45
|
+
self.sms = AsyncSMS(self)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def client_id(self) -> str:
|
|
49
|
+
"""Get the client ID"""
|
|
50
|
+
return self._client_id
|
|
51
|
+
|
|
52
|
+
async def request(
|
|
53
|
+
self,
|
|
54
|
+
method: str,
|
|
55
|
+
endpoint: str,
|
|
56
|
+
data: Dict[str, Any] = None,
|
|
57
|
+
timeout: int = 30
|
|
58
|
+
) -> Dict[str, Any]:
|
|
59
|
+
"""
|
|
60
|
+
Make an authenticated API request to the CCAI API
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
method: HTTP method
|
|
64
|
+
endpoint: API endpoint
|
|
65
|
+
data: Request data
|
|
66
|
+
timeout: Request timeout in seconds
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
API response as a dictionary
|
|
70
|
+
"""
|
|
71
|
+
url = f"{self._base_url}{endpoint}"
|
|
72
|
+
|
|
73
|
+
headers = {
|
|
74
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
"Accept": "*/*"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async with aiohttp.ClientSession() as session:
|
|
80
|
+
async with session.request(
|
|
81
|
+
method=method.upper(),
|
|
82
|
+
url=url,
|
|
83
|
+
headers=headers,
|
|
84
|
+
json=data,
|
|
85
|
+
timeout=aiohttp.ClientTimeout(total=timeout)
|
|
86
|
+
) as response:
|
|
87
|
+
# Raise an exception for HTTP errors
|
|
88
|
+
response.raise_for_status()
|
|
89
|
+
|
|
90
|
+
# Parse the response as JSON
|
|
91
|
+
return await response.json()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class AsyncSMS:
|
|
95
|
+
"""
|
|
96
|
+
Async SMS service for sending messages through the CCAI API
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, ccai: AsyncCCAI) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Create a new async SMS service instance
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
ccai: The parent AsyncCCAI instance
|
|
105
|
+
"""
|
|
106
|
+
self._ccai = ccai
|
|
107
|
+
|
|
108
|
+
async def send(
|
|
109
|
+
self,
|
|
110
|
+
accounts: List[Account],
|
|
111
|
+
message: str,
|
|
112
|
+
title: str,
|
|
113
|
+
options: SMSOptions = None
|
|
114
|
+
) -> SMSResponse:
|
|
115
|
+
"""
|
|
116
|
+
Send an SMS message to one or more recipients asynchronously
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
accounts: List of recipient accounts
|
|
120
|
+
message: Message content
|
|
121
|
+
title: Campaign title
|
|
122
|
+
options: Optional settings for the SMS send operation
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
API response
|
|
126
|
+
"""
|
|
127
|
+
# Validate inputs
|
|
128
|
+
if not accounts:
|
|
129
|
+
raise ValueError("At least one account is required")
|
|
130
|
+
if not message:
|
|
131
|
+
raise ValueError("Message is required")
|
|
132
|
+
if not title:
|
|
133
|
+
raise ValueError("Campaign title is required")
|
|
134
|
+
|
|
135
|
+
# Prepare the endpoint and data
|
|
136
|
+
endpoint = f"/clients/{self._ccai.client_id}/campaigns/direct"
|
|
137
|
+
|
|
138
|
+
# Convert Account objects to dictionaries with camelCase keys for API compatibility
|
|
139
|
+
accounts_data = [
|
|
140
|
+
{
|
|
141
|
+
"firstName": account.first_name,
|
|
142
|
+
"lastName": account.last_name,
|
|
143
|
+
"phone": account.phone
|
|
144
|
+
}
|
|
145
|
+
for account in accounts
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
campaign_data = {
|
|
149
|
+
"accounts": accounts_data,
|
|
150
|
+
"message": message,
|
|
151
|
+
"title": title
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Make the API request
|
|
155
|
+
timeout = options.timeout if options else 30
|
|
156
|
+
response_data = await self._ccai.request(
|
|
157
|
+
method="post",
|
|
158
|
+
endpoint=endpoint,
|
|
159
|
+
data=campaign_data,
|
|
160
|
+
timeout=timeout
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Convert response to SMSResponse object
|
|
164
|
+
return SMSResponse(**response_data)
|
|
165
|
+
|
|
166
|
+
async def send_single(
|
|
167
|
+
self,
|
|
168
|
+
first_name: str,
|
|
169
|
+
last_name: str,
|
|
170
|
+
phone: str,
|
|
171
|
+
message: str,
|
|
172
|
+
title: str,
|
|
173
|
+
options: SMSOptions = None
|
|
174
|
+
) -> SMSResponse:
|
|
175
|
+
"""
|
|
176
|
+
Send a single SMS message to one recipient asynchronously
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
first_name: Recipient's first name
|
|
180
|
+
last_name: Recipient's last name
|
|
181
|
+
phone: Recipient's phone number (E.164 format)
|
|
182
|
+
message: Message content
|
|
183
|
+
title: Campaign title
|
|
184
|
+
options: Optional settings for the SMS send operation
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
API response
|
|
188
|
+
"""
|
|
189
|
+
account = Account(
|
|
190
|
+
first_name=first_name,
|
|
191
|
+
last_name=last_name,
|
|
192
|
+
phone=phone
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return await self.send([account], message, title, options)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def main() -> None:
|
|
199
|
+
"""Example of using the async CCAI client"""
|
|
200
|
+
# Create a new async CCAI client
|
|
201
|
+
ccai = AsyncCCAI(
|
|
202
|
+
client_id="YOUR-CLIENT-ID",
|
|
203
|
+
api_key="API-KEY-TOKEN"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Example recipients
|
|
207
|
+
accounts = [
|
|
208
|
+
Account(
|
|
209
|
+
first_name="John",
|
|
210
|
+
last_name="Doe",
|
|
211
|
+
phone="+15551234567" # Use E.164 format
|
|
212
|
+
)
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
# Message with variable placeholders
|
|
216
|
+
message = "Hello ${first_name} ${last_name}, this is a test message!"
|
|
217
|
+
title = "Async Test Campaign"
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
# Send SMS to multiple recipients
|
|
221
|
+
print('Sending campaign to multiple recipients asynchronously...')
|
|
222
|
+
campaign_response = await ccai.sms.send(
|
|
223
|
+
accounts=accounts,
|
|
224
|
+
message=message,
|
|
225
|
+
title=title
|
|
226
|
+
)
|
|
227
|
+
print('SMS campaign sent successfully!')
|
|
228
|
+
print(campaign_response.model_dump())
|
|
229
|
+
|
|
230
|
+
# Send SMS to a single recipient
|
|
231
|
+
print('\nSending message to a single recipient asynchronously...')
|
|
232
|
+
single_response = await ccai.sms.send_single(
|
|
233
|
+
first_name="Jane",
|
|
234
|
+
last_name="Smith",
|
|
235
|
+
phone="+15559876543",
|
|
236
|
+
message="Hi ${first_name}, thanks for your interest!",
|
|
237
|
+
title="Single Async Message Test"
|
|
238
|
+
)
|
|
239
|
+
print('Single SMS sent successfully!')
|
|
240
|
+
print(single_response.model_dump())
|
|
241
|
+
|
|
242
|
+
print('\nAll messages sent successfully!')
|
|
243
|
+
except Exception as error:
|
|
244
|
+
print(f'Error sending SMS: {str(error)}')
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if __name__ == "__main__":
|
|
248
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Basic example using the CCAI Python client
|
|
3
|
+
|
|
4
|
+
:license: MIT
|
|
5
|
+
:copyright: 2025 CloudContactAI LLC
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ccai_python import CCAI, Account, SMSResponse
|
|
9
|
+
|
|
10
|
+
# Create a new CCAI client
|
|
11
|
+
ccai = CCAI(
|
|
12
|
+
client_id="YOUR-CLIENT-ID",
|
|
13
|
+
api_key="API-KEY-TOKEN"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Example recipients
|
|
17
|
+
accounts = [
|
|
18
|
+
Account(
|
|
19
|
+
first_name="John",
|
|
20
|
+
last_name="Doe",
|
|
21
|
+
phone="+15551234567" # Use E.164 format
|
|
22
|
+
)
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Alternative dictionary format
|
|
26
|
+
dict_accounts = [
|
|
27
|
+
{
|
|
28
|
+
"first_name": "John",
|
|
29
|
+
"last_name": "Doe",
|
|
30
|
+
"phone": "+15551234567" # Use E.164 format
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# Message with variable placeholders
|
|
35
|
+
message = "Hello ${first_name} ${last_name}, this is a test message!"
|
|
36
|
+
title = "Test Campaign"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def send_messages() -> dict:
|
|
40
|
+
"""Example of sending SMS messages"""
|
|
41
|
+
try:
|
|
42
|
+
# Method 1: Send SMS to multiple recipients
|
|
43
|
+
print('Sending campaign to multiple recipients...')
|
|
44
|
+
campaign_response: SMSResponse = ccai.sms.send(
|
|
45
|
+
accounts=accounts,
|
|
46
|
+
message=message,
|
|
47
|
+
title=title
|
|
48
|
+
)
|
|
49
|
+
print('SMS campaign sent successfully!')
|
|
50
|
+
print(campaign_response.model_dump())
|
|
51
|
+
|
|
52
|
+
# Method 2: Send SMS to a single recipient
|
|
53
|
+
print('\nSending message to a single recipient...')
|
|
54
|
+
single_response: SMSResponse = ccai.sms.send_single(
|
|
55
|
+
first_name="Jane",
|
|
56
|
+
last_name="Smith",
|
|
57
|
+
phone="+15559876543",
|
|
58
|
+
message="Hi ${first_name}, thanks for your interest!",
|
|
59
|
+
title="Single Message Test"
|
|
60
|
+
)
|
|
61
|
+
print('Single SMS sent successfully!')
|
|
62
|
+
print(single_response.model_dump())
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"campaign_response": campaign_response.model_dump(),
|
|
66
|
+
"single_response": single_response.model_dump()
|
|
67
|
+
}
|
|
68
|
+
except Exception as error:
|
|
69
|
+
print(f'Error sending SMS: {str(error)}')
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
try:
|
|
75
|
+
results = send_messages()
|
|
76
|
+
print('\nAll messages sent successfully!')
|
|
77
|
+
print(f'\nResults: {results}')
|
|
78
|
+
except Exception:
|
|
79
|
+
print('\nFailed to send one or more messages.')
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
sms.py - SMS service for the CCAI API
|
|
3
|
+
Handles sending SMS messages through the Cloud Contact AI platform.
|
|
4
|
+
|
|
5
|
+
:license: MIT
|
|
6
|
+
:copyright: 2025 CloudContactAI LLC
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Protocol, TypedDict, Union, cast
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Account(BaseModel):
|
|
14
|
+
"""Account model representing a recipient"""
|
|
15
|
+
first_name: str = Field(..., description="Recipient's first name")
|
|
16
|
+
last_name: str = Field(..., description="Recipient's last name")
|
|
17
|
+
phone: str = Field(..., description="Recipient's phone number in E.164 format")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SMSCampaign(BaseModel):
|
|
21
|
+
"""SMS campaign data model"""
|
|
22
|
+
accounts: List[Account] = Field(..., description="List of recipient accounts")
|
|
23
|
+
message: str = Field(..., description="Message content with optional variables")
|
|
24
|
+
title: str = Field(..., description="Campaign title")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SMSResponse(BaseModel):
|
|
28
|
+
"""Response from the SMS API"""
|
|
29
|
+
id: Optional[str] = Field(None, description="Message ID")
|
|
30
|
+
status: Optional[str] = Field(None, description="Message status")
|
|
31
|
+
campaign_id: Optional[str] = Field(None, description="Campaign ID")
|
|
32
|
+
messages_sent: Optional[int] = Field(None, description="Number of messages sent")
|
|
33
|
+
timestamp: Optional[str] = Field(None, description="Timestamp of the operation")
|
|
34
|
+
|
|
35
|
+
# Allow additional fields
|
|
36
|
+
model_config = {
|
|
37
|
+
"extra": "allow",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SMSOptions(BaseModel):
|
|
42
|
+
"""Options for SMS operations"""
|
|
43
|
+
timeout: Optional[int] = Field(None, description="Request timeout in seconds")
|
|
44
|
+
retries: Optional[int] = Field(None, description="Number of retry attempts")
|
|
45
|
+
on_progress: Optional[Callable[[str], None]] = Field(
|
|
46
|
+
None,
|
|
47
|
+
description="Callback for tracking progress"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CCAIProtocol(Protocol):
|
|
52
|
+
"""Protocol defining the required methods for the CCAI client"""
|
|
53
|
+
@property
|
|
54
|
+
def client_id(self) -> str:
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def request(
|
|
58
|
+
self,
|
|
59
|
+
method: str,
|
|
60
|
+
endpoint: str,
|
|
61
|
+
data: Optional[Dict[str, Any]] = None,
|
|
62
|
+
timeout: Optional[int] = None
|
|
63
|
+
) -> Dict[str, Any]:
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SMS:
|
|
68
|
+
"""
|
|
69
|
+
SMS service for sending messages through the CCAI API
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, ccai: CCAIProtocol) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Create a new SMS service instance
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
ccai: The parent CCAI instance
|
|
78
|
+
"""
|
|
79
|
+
self._ccai = ccai
|
|
80
|
+
|
|
81
|
+
def send(
|
|
82
|
+
self,
|
|
83
|
+
accounts: List[Union[Account, Dict[str, str]]],
|
|
84
|
+
message: str,
|
|
85
|
+
title: str,
|
|
86
|
+
options: Optional[SMSOptions] = None
|
|
87
|
+
) -> SMSResponse:
|
|
88
|
+
"""
|
|
89
|
+
Send an SMS message to one or more recipients
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
accounts: List of recipient accounts
|
|
93
|
+
message: Message content (can include ${first_name} and ${last_name} variables)
|
|
94
|
+
title: Campaign title
|
|
95
|
+
options: Optional settings for the SMS send operation
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
API response
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: If required parameters are missing or invalid
|
|
102
|
+
"""
|
|
103
|
+
# Validate inputs
|
|
104
|
+
if not accounts:
|
|
105
|
+
raise ValueError("At least one account is required")
|
|
106
|
+
if not message:
|
|
107
|
+
raise ValueError("Message is required")
|
|
108
|
+
if not title:
|
|
109
|
+
raise ValueError("Campaign title is required")
|
|
110
|
+
|
|
111
|
+
# Convert dict accounts to Account objects if needed
|
|
112
|
+
normalized_accounts: List[Account] = []
|
|
113
|
+
for idx, account in enumerate(accounts):
|
|
114
|
+
if isinstance(account, dict):
|
|
115
|
+
try:
|
|
116
|
+
# Convert dictionary keys from snake_case to camelCase if needed
|
|
117
|
+
account_data = {}
|
|
118
|
+
for key, value in account.items():
|
|
119
|
+
if key == "first_name":
|
|
120
|
+
account_data["first_name"] = value
|
|
121
|
+
elif key == "lastName":
|
|
122
|
+
account_data["last_name"] = value
|
|
123
|
+
elif key == "firstName":
|
|
124
|
+
account_data["first_name"] = value
|
|
125
|
+
elif key == "last_name":
|
|
126
|
+
account_data["last_name"] = value
|
|
127
|
+
else:
|
|
128
|
+
account_data[key] = value
|
|
129
|
+
|
|
130
|
+
normalized_accounts.append(Account(**account_data))
|
|
131
|
+
except Exception as e:
|
|
132
|
+
raise ValueError(f"Invalid account at index {idx}: {str(e)}")
|
|
133
|
+
else:
|
|
134
|
+
normalized_accounts.append(account)
|
|
135
|
+
|
|
136
|
+
# Notify progress if callback provided
|
|
137
|
+
if options and options.on_progress:
|
|
138
|
+
options.on_progress("Preparing to send SMS")
|
|
139
|
+
|
|
140
|
+
# Prepare the endpoint and data
|
|
141
|
+
endpoint = f"/clients/{self._ccai.client_id}/campaigns/direct"
|
|
142
|
+
|
|
143
|
+
# Convert Account objects to dictionaries with camelCase keys for API compatibility
|
|
144
|
+
accounts_data = [
|
|
145
|
+
{
|
|
146
|
+
"firstName": account.first_name,
|
|
147
|
+
"lastName": account.last_name,
|
|
148
|
+
"phone": account.phone
|
|
149
|
+
}
|
|
150
|
+
for account in normalized_accounts
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
campaign_data = {
|
|
154
|
+
"accounts": accounts_data,
|
|
155
|
+
"message": message,
|
|
156
|
+
"title": title
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
# Notify progress if callback provided
|
|
161
|
+
if options and options.on_progress:
|
|
162
|
+
options.on_progress("Sending SMS")
|
|
163
|
+
|
|
164
|
+
# Make the API request
|
|
165
|
+
timeout = options.timeout if options else None
|
|
166
|
+
response_data = self._ccai.request(
|
|
167
|
+
method="post",
|
|
168
|
+
endpoint=endpoint,
|
|
169
|
+
data=campaign_data,
|
|
170
|
+
timeout=timeout
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Notify progress if callback provided
|
|
174
|
+
if options and options.on_progress:
|
|
175
|
+
options.on_progress("SMS sent successfully")
|
|
176
|
+
|
|
177
|
+
# Convert response to SMSResponse object
|
|
178
|
+
return SMSResponse(**response_data)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
# Notify progress if callback provided
|
|
181
|
+
if options and options.on_progress:
|
|
182
|
+
options.on_progress("SMS sending failed")
|
|
183
|
+
|
|
184
|
+
raise e
|
|
185
|
+
|
|
186
|
+
def send_single(
|
|
187
|
+
self,
|
|
188
|
+
first_name: str,
|
|
189
|
+
last_name: str,
|
|
190
|
+
phone: str,
|
|
191
|
+
message: str,
|
|
192
|
+
title: str,
|
|
193
|
+
options: Optional[SMSOptions] = None
|
|
194
|
+
) -> SMSResponse:
|
|
195
|
+
"""
|
|
196
|
+
Send a single SMS message to one recipient
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
first_name: Recipient's first name
|
|
200
|
+
last_name: Recipient's last name
|
|
201
|
+
phone: Recipient's phone number (E.164 format)
|
|
202
|
+
message: Message content (can include ${first_name} and ${last_name} variables)
|
|
203
|
+
title: Campaign title
|
|
204
|
+
options: Optional settings for the SMS send operation
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
API response
|
|
208
|
+
"""
|
|
209
|
+
account = Account(
|
|
210
|
+
first_name=first_name,
|
|
211
|
+
last_name=last_name,
|
|
212
|
+
phone=phone
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return self.send([account], message, title, options)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ccai-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for CloudContactAI API
|
|
5
|
+
Author: CloudContactAI LLC
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cloudcontactai/ccai-python
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/cloudcontactai/ccai-python/issues
|
|
9
|
+
Keywords: sms,api,client,cloud,contact,ai,cloudcontactai
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: requests>=2.31.0
|
|
19
|
+
Requires-Dist: pydantic>=2.5.0
|
|
20
|
+
|
|
21
|
+
# CCAI Python Client
|
|
22
|
+
|
|
23
|
+
A Python client for interacting with the CloudContactAI API.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install ccai-python
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from ccai_python import CCAI
|
|
35
|
+
|
|
36
|
+
# Initialize the client
|
|
37
|
+
ccai = CCAI(
|
|
38
|
+
client_id="YOUR-CLIENT-ID",
|
|
39
|
+
api_key="YOUR-API-KEY"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Send a single SMS
|
|
43
|
+
response = ccai.sms.send_single(
|
|
44
|
+
first_name="John",
|
|
45
|
+
last_name="Doe",
|
|
46
|
+
phone="+15551234567",
|
|
47
|
+
message="Hello ${first_name}, this is a test message!",
|
|
48
|
+
title="Test Campaign"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
print(f"Message sent with ID: {response.id}")
|
|
52
|
+
|
|
53
|
+
# Send to multiple recipients
|
|
54
|
+
accounts = [
|
|
55
|
+
{"first_name": "John", "last_name": "Doe", "phone": "+15551234567"},
|
|
56
|
+
{"first_name": "Jane", "last_name": "Smith", "phone": "+15559876543"}
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
campaign_response = ccai.sms.send(
|
|
60
|
+
accounts=accounts,
|
|
61
|
+
message="Hello ${first_name} ${last_name}, this is a test message!",
|
|
62
|
+
title="Bulk Test Campaign"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
print(f"Campaign sent with ID: {campaign_response.campaign_id}")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Features
|
|
69
|
+
|
|
70
|
+
- Send SMS messages to single or multiple recipients
|
|
71
|
+
- Variable substitution in messages
|
|
72
|
+
- Async support
|
|
73
|
+
- Type hints for better IDE integration
|
|
74
|
+
- Comprehensive error handling
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
- Python 3.10 or higher
|
|
79
|
+
- `requests` library
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/ccai_python/__init__.py
|
|
4
|
+
src/ccai_python/ccai.py
|
|
5
|
+
src/ccai_python.egg-info/PKG-INFO
|
|
6
|
+
src/ccai_python.egg-info/SOURCES.txt
|
|
7
|
+
src/ccai_python.egg-info/dependency_links.txt
|
|
8
|
+
src/ccai_python.egg-info/requires.txt
|
|
9
|
+
src/ccai_python.egg-info/top_level.txt
|
|
10
|
+
src/ccai_python/examples/async_example.py
|
|
11
|
+
src/ccai_python/examples/basic_example.py
|
|
12
|
+
src/ccai_python/sms/__init__.py
|
|
13
|
+
src/ccai_python/sms/sms.py
|
|
14
|
+
tests/test_ccai.py
|
|
15
|
+
tests/test_sms.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the CCAI client
|
|
3
|
+
|
|
4
|
+
:license: MIT
|
|
5
|
+
:copyright: 2025 CloudContactAI LLC
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
from unittest.mock import patch, MagicMock
|
|
10
|
+
|
|
11
|
+
from ccai_python import CCAI, Account
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestCCAI(unittest.TestCase):
|
|
15
|
+
"""Test cases for the CCAI client"""
|
|
16
|
+
|
|
17
|
+
def setUp(self):
|
|
18
|
+
"""Set up test fixtures"""
|
|
19
|
+
self.client_id = "test-client-id"
|
|
20
|
+
self.api_key = "test-api-key"
|
|
21
|
+
self.ccai = CCAI(client_id=self.client_id, api_key=self.api_key)
|
|
22
|
+
|
|
23
|
+
def test_initialization(self):
|
|
24
|
+
"""Test client initialization"""
|
|
25
|
+
self.assertEqual(self.ccai.client_id, self.client_id)
|
|
26
|
+
self.assertEqual(self.ccai.api_key, self.api_key)
|
|
27
|
+
self.assertEqual(self.ccai.base_url, "https://core.cloudcontactai.com/api")
|
|
28
|
+
|
|
29
|
+
# Test custom base URL
|
|
30
|
+
custom_url = "https://custom.api.example.com"
|
|
31
|
+
ccai = CCAI(client_id=self.client_id, api_key=self.api_key, base_url=custom_url)
|
|
32
|
+
self.assertEqual(ccai.base_url, custom_url)
|
|
33
|
+
|
|
34
|
+
def test_initialization_validation(self):
|
|
35
|
+
"""Test validation during initialization"""
|
|
36
|
+
with self.assertRaises(ValueError):
|
|
37
|
+
CCAI(client_id="", api_key=self.api_key)
|
|
38
|
+
|
|
39
|
+
with self.assertRaises(ValueError):
|
|
40
|
+
CCAI(client_id=self.client_id, api_key="")
|
|
41
|
+
|
|
42
|
+
@patch('requests.request')
|
|
43
|
+
def test_request(self, mock_request):
|
|
44
|
+
"""Test the request method"""
|
|
45
|
+
# Mock response
|
|
46
|
+
mock_response = MagicMock()
|
|
47
|
+
mock_response.json.return_value = {"status": "success"}
|
|
48
|
+
mock_response.raise_for_status.return_value = None
|
|
49
|
+
mock_request.return_value = mock_response
|
|
50
|
+
|
|
51
|
+
# Test GET request
|
|
52
|
+
result = self.ccai.request("get", "/test-endpoint")
|
|
53
|
+
self.assertEqual(result, {"status": "success"})
|
|
54
|
+
|
|
55
|
+
# Verify request was made correctly
|
|
56
|
+
mock_request.assert_called_with(
|
|
57
|
+
method="GET",
|
|
58
|
+
url="https://core.cloudcontactai.com/api/test-endpoint",
|
|
59
|
+
headers={
|
|
60
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"Accept": "*/*"
|
|
63
|
+
},
|
|
64
|
+
json=None,
|
|
65
|
+
timeout=30
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Test POST request with data
|
|
69
|
+
data = {"key": "value"}
|
|
70
|
+
result = self.ccai.request("post", "/test-endpoint", data=data)
|
|
71
|
+
self.assertEqual(result, {"status": "success"})
|
|
72
|
+
|
|
73
|
+
# Verify request was made correctly
|
|
74
|
+
mock_request.assert_called_with(
|
|
75
|
+
method="POST",
|
|
76
|
+
url="https://core.cloudcontactai.com/api/test-endpoint",
|
|
77
|
+
headers={
|
|
78
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
"Accept": "*/*"
|
|
81
|
+
},
|
|
82
|
+
json=data,
|
|
83
|
+
timeout=30
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == '__main__':
|
|
88
|
+
unittest.main()
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the SMS service
|
|
3
|
+
|
|
4
|
+
:license: MIT
|
|
5
|
+
:copyright: 2025 CloudContactAI LLC
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
from unittest.mock import patch, MagicMock
|
|
10
|
+
|
|
11
|
+
from ccai_python import CCAI, Account, SMSOptions
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestSMS(unittest.TestCase):
|
|
15
|
+
"""Test cases for the SMS service"""
|
|
16
|
+
|
|
17
|
+
def setUp(self):
|
|
18
|
+
"""Set up test fixtures"""
|
|
19
|
+
self.client_id = "test-client-id"
|
|
20
|
+
self.api_key = "test-api-key"
|
|
21
|
+
self.ccai = CCAI(client_id=self.client_id, api_key=self.api_key)
|
|
22
|
+
|
|
23
|
+
# Sample account
|
|
24
|
+
self.account = Account(
|
|
25
|
+
first_name="John",
|
|
26
|
+
last_name="Doe",
|
|
27
|
+
phone="+15551234567"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Sample message and title
|
|
31
|
+
self.message = "Hello ${first_name}, this is a test message!"
|
|
32
|
+
self.title = "Test Campaign"
|
|
33
|
+
|
|
34
|
+
@patch.object(CCAI, 'request')
|
|
35
|
+
def test_send(self, mock_request):
|
|
36
|
+
"""Test sending SMS to multiple recipients"""
|
|
37
|
+
# Mock response
|
|
38
|
+
mock_request.return_value = {
|
|
39
|
+
"id": "msg-123",
|
|
40
|
+
"status": "sent",
|
|
41
|
+
"campaign_id": "camp-456",
|
|
42
|
+
"messages_sent": 1,
|
|
43
|
+
"timestamp": "2025-06-06T12:00:00Z"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Send SMS
|
|
47
|
+
response = self.ccai.sms.send(
|
|
48
|
+
accounts=[self.account],
|
|
49
|
+
message=self.message,
|
|
50
|
+
title=self.title
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Verify response
|
|
54
|
+
self.assertEqual(response.id, "msg-123")
|
|
55
|
+
self.assertEqual(response.status, "sent")
|
|
56
|
+
self.assertEqual(response.campaign_id, "camp-456")
|
|
57
|
+
self.assertEqual(response.messages_sent, 1)
|
|
58
|
+
self.assertEqual(response.timestamp, "2025-06-06T12:00:00Z")
|
|
59
|
+
|
|
60
|
+
# Verify request was made correctly
|
|
61
|
+
mock_request.assert_called_with(
|
|
62
|
+
method="post",
|
|
63
|
+
endpoint=f"/clients/{self.client_id}/campaigns/direct",
|
|
64
|
+
data={
|
|
65
|
+
"accounts": [
|
|
66
|
+
{
|
|
67
|
+
"firstName": "John",
|
|
68
|
+
"lastName": "Doe",
|
|
69
|
+
"phone": "+15551234567"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"message": self.message,
|
|
73
|
+
"title": self.title
|
|
74
|
+
},
|
|
75
|
+
timeout=None
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@patch.object(CCAI, 'request')
|
|
79
|
+
def test_send_with_dict_accounts(self, mock_request):
|
|
80
|
+
"""Test sending SMS with dictionary accounts"""
|
|
81
|
+
# Mock response
|
|
82
|
+
mock_request.return_value = {"id": "msg-123", "status": "sent"}
|
|
83
|
+
|
|
84
|
+
# Send SMS with dictionary accounts
|
|
85
|
+
response = self.ccai.sms.send(
|
|
86
|
+
accounts=[{
|
|
87
|
+
"first_name": "John",
|
|
88
|
+
"last_name": "Doe",
|
|
89
|
+
"phone": "+15551234567"
|
|
90
|
+
}],
|
|
91
|
+
message=self.message,
|
|
92
|
+
title=self.title
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Verify response
|
|
96
|
+
self.assertEqual(response.id, "msg-123")
|
|
97
|
+
self.assertEqual(response.status, "sent")
|
|
98
|
+
|
|
99
|
+
# Verify request was made correctly
|
|
100
|
+
mock_request.assert_called_with(
|
|
101
|
+
method="post",
|
|
102
|
+
endpoint=f"/clients/{self.client_id}/campaigns/direct",
|
|
103
|
+
data={
|
|
104
|
+
"accounts": [
|
|
105
|
+
{
|
|
106
|
+
"firstName": "John",
|
|
107
|
+
"lastName": "Doe",
|
|
108
|
+
"phone": "+15551234567"
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"message": self.message,
|
|
112
|
+
"title": self.title
|
|
113
|
+
},
|
|
114
|
+
timeout=None
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@patch.object(CCAI, 'request')
|
|
118
|
+
def test_send_single(self, mock_request):
|
|
119
|
+
"""Test sending SMS to a single recipient"""
|
|
120
|
+
# Mock response
|
|
121
|
+
mock_request.return_value = {"id": "msg-123", "status": "sent"}
|
|
122
|
+
|
|
123
|
+
# Send SMS to a single recipient
|
|
124
|
+
response = self.ccai.sms.send_single(
|
|
125
|
+
first_name="Jane",
|
|
126
|
+
last_name="Smith",
|
|
127
|
+
phone="+15559876543",
|
|
128
|
+
message="Hi ${first_name}, thanks for your interest!",
|
|
129
|
+
title="Single Message Test"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Verify response
|
|
133
|
+
self.assertEqual(response.id, "msg-123")
|
|
134
|
+
self.assertEqual(response.status, "sent")
|
|
135
|
+
|
|
136
|
+
# Verify request was made correctly
|
|
137
|
+
mock_request.assert_called_with(
|
|
138
|
+
method="post",
|
|
139
|
+
endpoint=f"/clients/{self.client_id}/campaigns/direct",
|
|
140
|
+
data={
|
|
141
|
+
"accounts": [
|
|
142
|
+
{
|
|
143
|
+
"firstName": "Jane",
|
|
144
|
+
"lastName": "Smith",
|
|
145
|
+
"phone": "+15559876543"
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"message": "Hi ${first_name}, thanks for your interest!",
|
|
149
|
+
"title": "Single Message Test"
|
|
150
|
+
},
|
|
151
|
+
timeout=None
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
@patch.object(CCAI, 'request')
|
|
155
|
+
def test_send_with_options(self, mock_request):
|
|
156
|
+
"""Test sending SMS with options"""
|
|
157
|
+
# Mock response
|
|
158
|
+
mock_request.return_value = {"id": "msg-123", "status": "sent"}
|
|
159
|
+
|
|
160
|
+
# Create progress tracking callback
|
|
161
|
+
progress_updates = []
|
|
162
|
+
def track_progress(status: str):
|
|
163
|
+
progress_updates.append(status)
|
|
164
|
+
|
|
165
|
+
# Create options
|
|
166
|
+
options = SMSOptions(
|
|
167
|
+
timeout=60,
|
|
168
|
+
retries=3,
|
|
169
|
+
on_progress=track_progress
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Send SMS with options
|
|
173
|
+
response = self.ccai.sms.send(
|
|
174
|
+
accounts=[self.account],
|
|
175
|
+
message=self.message,
|
|
176
|
+
title=self.title,
|
|
177
|
+
options=options
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Verify response
|
|
181
|
+
self.assertEqual(response.id, "msg-123")
|
|
182
|
+
self.assertEqual(response.status, "sent")
|
|
183
|
+
|
|
184
|
+
# Verify progress updates
|
|
185
|
+
self.assertEqual(progress_updates, [
|
|
186
|
+
"Preparing to send SMS",
|
|
187
|
+
"Sending SMS",
|
|
188
|
+
"SMS sent successfully"
|
|
189
|
+
])
|
|
190
|
+
|
|
191
|
+
# Verify request was made correctly
|
|
192
|
+
mock_request.assert_called_with(
|
|
193
|
+
method="post",
|
|
194
|
+
endpoint=f"/clients/{self.client_id}/campaigns/direct",
|
|
195
|
+
data={
|
|
196
|
+
"accounts": [
|
|
197
|
+
{
|
|
198
|
+
"firstName": "John",
|
|
199
|
+
"lastName": "Doe",
|
|
200
|
+
"phone": "+15551234567"
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"message": self.message,
|
|
204
|
+
"title": self.title
|
|
205
|
+
},
|
|
206
|
+
timeout=60
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def test_validation(self):
|
|
210
|
+
"""Test input validation"""
|
|
211
|
+
# Test empty accounts
|
|
212
|
+
with self.assertRaises(ValueError):
|
|
213
|
+
self.ccai.sms.send(
|
|
214
|
+
accounts=[],
|
|
215
|
+
message=self.message,
|
|
216
|
+
title=self.title
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Test empty message
|
|
220
|
+
with self.assertRaises(ValueError):
|
|
221
|
+
self.ccai.sms.send(
|
|
222
|
+
accounts=[self.account],
|
|
223
|
+
message="",
|
|
224
|
+
title=self.title
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Test empty title
|
|
228
|
+
with self.assertRaises(ValueError):
|
|
229
|
+
self.ccai.sms.send(
|
|
230
|
+
accounts=[self.account],
|
|
231
|
+
message=self.message,
|
|
232
|
+
title=""
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == '__main__':
|
|
237
|
+
unittest.main()
|