python-termii 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ """
2
+ This module defines the `NumberService` class, which provides functionality for sending messages
3
+ directly to a phone number through Termii's Number Messaging API.
4
+
5
+ The service validates the recipient’s phone number format before dispatching messages
6
+ and uses the provided `RequestHandler` instance to make HTTP requests to the Termii API.
7
+
8
+ References:
9
+ - https://developer.termii.com/number
10
+ """
11
+
12
+ from termii_py.http import RequestHandler
13
+ from termii_py.value_object import PhoneNumber
14
+
15
+
16
+ class NumberService:
17
+ """
18
+ Provides functionality for sending messages to individual phone numbers
19
+ using Termii’s Number Messaging API endpoint.
20
+
21
+ This class ensures that phone numbers are properly validated before
22
+ sending and abstracts the HTTP request handling via the injected `RequestHandler`.
23
+
24
+ Attributes:
25
+ http (RequestHandler): The HTTP request handler instance used to communicate
26
+ with Termii’s API endpoints.
27
+
28
+ References:
29
+ - https://developer.termii.com/number#:~:text=Join%20Loop-,Number%20API,-This%20API%20allows
30
+ """
31
+
32
+ def __init__(self, http: RequestHandler):
33
+ """
34
+ Initializes the `NumberService` with an HTTP client for API communication.
35
+
36
+ Args:
37
+ http (RequestHandler): The request handler responsible for making
38
+ authenticated HTTP requests to Termii’s API.
39
+
40
+ References:
41
+ - https://developer.termii.com/number#:~:text=Join%20Loop-,Number%20API,-This%20API%20allows
42
+ """
43
+
44
+ self.http = http
45
+
46
+ def send_message(self, sent_to: str, message: str):
47
+ """
48
+ Sends an SMS message directly to a specific phone number using Termii’s
49
+ Number Messaging API endpoint.
50
+
51
+ This method validates the provided phone number using the `PhoneNumber`
52
+ value object before constructing the payload and dispatching the message.
53
+
54
+ Args:
55
+ sent_to (str): The recipient’s phone number in international format (e.g., "2348012345678").
56
+ message (str): The text message to send.
57
+
58
+ Raises:
59
+ ValueError: If the provided phone number is invalid.
60
+
61
+ Returns:
62
+ dict: The JSON response returned by the Termii API.
63
+
64
+ References:
65
+ - https://developer.termii.com/number#:~:text=to%20customers%20location.-,Send%20Message,-Endpoint%20%3A%20https
66
+ """
67
+
68
+ PhoneNumber(sent_to)
69
+
70
+ payload = {
71
+ "to": sent_to,
72
+ "sms": message,
73
+ }
74
+
75
+ return self.http.post("/api/sms/number/send", json=payload)
@@ -0,0 +1,137 @@
1
+ """
2
+ This module defines the PhonebookService class, which provides methods to interact with Termii's phonebook API
3
+ endpoints. It allows users to fetch, create, update, and delete phonebooks through the Termii API. Each method
4
+ corresponds to a specific API endpoint and handles the necessary HTTP requests and payloads.
5
+
6
+ References:
7
+ - https://developer.termii.com/phonebook
8
+
9
+ Classes:
10
+ PhonebookService: A service class that provides methods to manage phonebooks via Termii's API.
11
+ """
12
+ from termii_py.http import RequestHandler
13
+
14
+
15
+ class PhonebookService:
16
+ """
17
+ Provides methods to manage phonebooks through Termii's API.
18
+
19
+ The class includes methods to fetch all phonebooks, create a new phonebook, update an existing phonebook, and
20
+ delete a phonebook. Each method constructs the appropriate API request and handles the response.
21
+
22
+ Attributes:
23
+ http (object): An HTTP client instance responsible for making API requests to Termii.
24
+
25
+ """
26
+
27
+ def __init__(self, http: RequestHandler):
28
+ """
29
+ Initializes the PhonebookService instance.
30
+
31
+ Args:
32
+ http (object): An HTTP client instance (e.g., `requests.Session`) used to perform
33
+ network requests to the Termii API.
34
+ """
35
+
36
+ self.http = http
37
+
38
+ def fetch_phonebooks(self):
39
+ """
40
+ Fetches all phonebooks associated with the authenticated Termii account.
41
+ This method sends a GET request to the `/api/phonebooks` endpoint and returns the response containing the list of phonebooks.
42
+
43
+ Raise:
44
+ ValueError: If the API request fails or returns an error response.
45
+
46
+ Returns:
47
+ A response object containing the list of phonebooks and associated metadata.
48
+
49
+ References:
50
+ - https://developer.termii.com/phonebook#fetch-phonebooks:~:text=phonebooks%20as%20needed.-,Fetch%20Phonebooks,-This%20endpoint%20returns
51
+
52
+ """
53
+
54
+ return self.http.fetch("/api/phonebooks")
55
+
56
+ def create_phonebooks(self, phonebook_name: str, description: str = None):
57
+ """
58
+ Creates a new phonebook with the specified name and optional description.
59
+
60
+ This method sends a POST request to the `/api/phonebooks` endpoint with the provided phonebook name and description in the request body.
61
+
62
+ Args:
63
+ phonebook_name (str): The name of the phonebook to be created. This field is required.
64
+ description (str, optional): A brief description of the phonebook. This field is optional and can be left blank.
65
+
66
+ Raise:
67
+ ValueError: If the `phonebook_name` parameter is not provided or is empty. This field is required to create a phonebook.
68
+
69
+ Returns:
70
+ A response object containing the details of the newly created phonebook, including its unique identifier and any associated metadata.
71
+
72
+ References:
73
+ - https://developer.termii.com/phonebook#create--a-phonebook:~:text=12%2C%0A%20%20%22empty%22%3A%20false%0A%7D-,Create%20a%20Phonebook,-This%20endpoint%20creates
74
+ """
75
+
76
+ payload = {
77
+ "phonebook_name": phonebook_name,
78
+ "description": description,
79
+ }
80
+
81
+ return self.http.post("/api/phonebooks", json=payload)
82
+
83
+ def update_phonebook(self, phonebook_id, phonebook_name: str, description: str):
84
+ """
85
+ Updates the details of an existing phonebook identified by its unique ID.
86
+ This method sends a PATCH request to the `/api/phonebooks/{phonebook_id}` endpoint with the updated phonebook name and description in the request body.
87
+
88
+ Args:
89
+ phonebook_id (str): The unique identifier of the phonebook to be updated. This field is required to specify which phonebook to update.
90
+ phonebook_name (str): The new name for the phonebook. This field is required to update the phonebook's name.
91
+ description (str): The new description for the phonebook. This field is required to update the phonebook's description.
92
+
93
+ Raise:
94
+ ValueError: If the `phonebook_id` parameter is not provided or is empty. This field is required to
95
+ identify which phonebook to update. Additionally, if either the `phonebook_name` or `description`
96
+ parameters are not provided or are empty, a ValueError will be raised since both fields are
97
+ required to update the phonebook's details.
98
+
99
+ Returns:
100
+ A response object containing the updated details of the phonebook, including its unique identifier, new name, new description, and any associated metadata.
101
+
102
+ References:
103
+ - https://developer.termii.com/phonebook#update-phonebook:~:text=successfully%22%2C%0A%20%20%20%20%22status%22%3A%20%22success%22%0A%20%20%7D-,Update%20Phonebook,-This%20endpoint%20updates
104
+ """
105
+
106
+ if not phonebook_id:
107
+ raise ValueError("phonebook_id is required to update a phonebook.")
108
+
109
+ payload = {
110
+ "phonebook_name": phonebook_name,
111
+ "description": description,
112
+ }
113
+
114
+ return self.http.patch(f"/api/phonebooks/{phonebook_id}", json=payload)
115
+
116
+ def delete_phonebook(self, phonebook_id):
117
+ """
118
+ Deletes an existing phonebook identified by its unique ID.
119
+ This method sends a DELETE request to the `/api/phonebooks/{phonebook_id}` endpoint to remove the specified phonebook from the Termii account. Once deleted, the phonebook and all associated contacts will be permanently removed.
120
+
121
+ Args:
122
+ phonebook_id (str): The unique identifier of the phonebook to be deleted. This field is required to specify which phonebook to delete.
123
+
124
+ Raise:
125
+ ValueError: If the `phonebook_id` parameter is not provided or is empty. This field is required to identify which phonebook to delete.
126
+
127
+ Returns:
128
+ A response object indicating the success or failure of the delete operation, including any relevant status messages or error details.
129
+
130
+ References:
131
+ - https://developer.termii.com/phonebook#delete-phonebook:~:text=0%2C%0A%20%20%20%20%20%20%22numberOfCampaigns%22%3A%200%0A%20%20%7D-,Delete%20phonebook,-This%20endpoint%20deletes
132
+ """
133
+
134
+ if not phonebook_id:
135
+ raise ValueError("phonebook_id is required to delete a phonebook.")
136
+
137
+ return self.http.delete(f"/api/phonebooks/{phonebook_id}")
@@ -0,0 +1,78 @@
1
+ """
2
+ SenderIDService class for managing sender IDs.
3
+ Classes:
4
+ SenderIDService
5
+ Methods:
6
+ fetch_id: Retrieves a list of sender IDs based on query parameters.
7
+ request_id: Requests a new sender ID.
8
+ """
9
+ from termii_py.http import RequestHandler
10
+
11
+
12
+ class SenderIDService:
13
+ """
14
+ Provides methods for interacting with sender IDs.
15
+
16
+ Attributes:
17
+ http: An instance of the HTTP client.
18
+
19
+ References:
20
+ https://developer.termii.com/sender-id
21
+ """
22
+
23
+ def __init__(self, http: RequestHandler):
24
+ """
25
+ Initializes a SenderIDService instance.
26
+
27
+ Args:
28
+ http: An instance of the HTTP client.
29
+ """
30
+
31
+ self.http = http
32
+
33
+ def fetch_id(self, name: str = None, status: str = None):
34
+ """
35
+ Retrieves a list of sender IDs based on query parameters.
36
+
37
+ Args:
38
+ name (str, optional): The name of the sender ID. Defaults to None.
39
+ status (str, optional): The status of the sender ID. Defaults to None.
40
+
41
+ Returns:
42
+ The response from the API.
43
+
44
+ References:
45
+ https://developer.termii.com/sender-id#:~:text=request%20methods%2C%20respectively.-,Fetch%20Sender%20ID,-The%20Fetch%20Sender
46
+ """
47
+
48
+ params = {}
49
+ if name:
50
+ params["name"] = name
51
+
52
+ if status:
53
+ params["status"] = status
54
+
55
+ return self.http.fetch("/api/sender-id", params=params)
56
+
57
+ def request_id(self, sender_id: str, usecase: str, company: str):
58
+ """
59
+ Requests a new sender ID.
60
+
61
+ Args:
62
+ sender_id (str): The desired sender ID.
63
+ usecase (str): The use case for the sender ID.
64
+ company (str): The company name.
65
+
66
+ References:
67
+ https://developer.termii.com/sender-id#:~:text=5%2C%0A%20%20%20%20%22empty%22%3A%20false%0A%7D-,Request%20Sender%20ID,-The%20Request%20Sender
68
+ Returns:
69
+ The response from the API.
70
+ """
71
+
72
+ payload = {
73
+ "sender_id": sender_id,
74
+ "usecase": usecase,
75
+ "company": company
76
+ }
77
+
78
+ return self.http.post("/api/sender-id/request", json=payload)
@@ -0,0 +1,99 @@
1
+ """
2
+ This module defines the `TemplateService` class, which provides functionality for sending messages
3
+ using predefined templates via Termii's Template Messaging API.
4
+
5
+ The service validates the recipient’s phone number format before dispatching messages
6
+ and uses the provided HTTP client instance to make requests to the Termii API.
7
+
8
+ References:
9
+ - https://developer.termii.com/templates
10
+
11
+ Classes:
12
+ TemplateService: Handles message dispatch operations via Termii's Template Messaging API.
13
+ """
14
+ from termii_py.http import RequestHandler
15
+ from termii_py.value_object import PhoneNumber
16
+
17
+
18
+ class TemplateService:
19
+ """
20
+ Provides methods for sending messages using predefined templates via Termii's Template Messaging API.
21
+
22
+ The class ensures data validation before making HTTP requests to the API. Each method constructs the appropriate
23
+ request payload and sends it using the injected HTTP client.
24
+
25
+ Attributes:
26
+ http: An instance of the HTTP client.
27
+ """
28
+
29
+ def __init__(self, http: RequestHandler):
30
+ """
31
+ Initializes a SenderIDService instance.
32
+
33
+ Args:
34
+ http: An instance of the HTTP client.
35
+ """
36
+
37
+ self.http = http
38
+
39
+ def send_message(self, sent_to: str, device_id: str, template_id: str, data: dict, caption: str = None,
40
+ url: str = None):
41
+ """
42
+
43
+ Sends a message using a predefined template via Termii's Template Messaging API.
44
+
45
+ Args:
46
+ sent_to (str): The recipient’s phone number in international format. e.g., "2348012345678".
47
+ device_id (str): The device ID associated with the template.
48
+ (Represents the Device ID for Whatsapp. It can be Alphanumeric. It should be passed
49
+ when the message is sent via whatsapp (It can be found on the manage device page on
50
+ your Termii dashboard))
51
+ template_id (str): The ID of the message template to use.
52
+ data (dict): A dictionary containing placeholder values to populate the template.
53
+ Represents the variables used in your WhatsApp template. These key-value pairs will dynamically
54
+ populate the placeholders in your approved template message. You can find the required keys for
55
+ each template on the Device Subscription page of your dashboard.
56
+ (Example: {"studname": "Victor", "average": "30" })
57
+ caption (str, optional): The caption for the media (if sending media). Defaults to None.
58
+ url (str, optional): The URL of the media to send (if sending media). Defaults to None.
59
+
60
+ Raises:
61
+ ValueError: If the provided phone number is invalid.
62
+
63
+ Returns:
64
+ dict: The JSON response returned by the Termii API.
65
+
66
+ References:
67
+ https://developer.termii.com/templates
68
+
69
+ """
70
+
71
+ if caption is not None and url is None:
72
+ raise ValueError("If caption is provided, url must also be provided to send media.")
73
+
74
+ if caption is None and url is not None:
75
+ raise ValueError("If url is provided, caption must also be provided to send media.")
76
+
77
+ endpoint = "/api/send/template"
78
+
79
+ PhoneNumber(sent_to)
80
+
81
+ if not isinstance(data, dict):
82
+ raise ValueError("The 'data' parameter must be a dictionary containing template placeholder values.")
83
+
84
+ payload = {
85
+ "phone_number": sent_to,
86
+ "device_id": device_id,
87
+ "template_id": template_id,
88
+ "data": data,
89
+ }
90
+
91
+ if caption is not None and url is not None:
92
+ endpoint = "/api/send/template/media"
93
+
94
+ payload['media'] = {
95
+ "caption": caption,
96
+ "url": url
97
+ }
98
+
99
+ return self.http.post(endpoint, json=payload)
File without changes
@@ -0,0 +1,11 @@
1
+ """
2
+ Custom exception class for Termii client configuration errors.
3
+ Classes:
4
+ ClientConfigError
5
+ """
6
+
7
+
8
+ class ClientConfigError(Exception):
9
+ """Raised when Termii configuration is invalid or missing."""
10
+ pass
11
+
@@ -0,0 +1,5 @@
1
+ """
2
+
3
+ """
4
+
5
+ from .phone_number import PhoneNumber
@@ -0,0 +1,74 @@
1
+ """
2
+ This module defines the `PhoneNumber` value object, responsible for validating and encapsulating
3
+ a properly formatted Nigerian phone number (MSISDN format).
4
+
5
+ It ensures that phone numbers conform to the international dialing format beginning with
6
+ Nigeria’s country code `234`, followed by 10 digits.
7
+
8
+ Example:
9
+ >>> PhoneNumber("2348031234567")
10
+ <PhoneNumber: 2348031234567>
11
+
12
+ Classes:
13
+ PhoneNumber: Represents a validated phone number conforming to Termii’s expected format.
14
+ """
15
+ import re
16
+
17
+
18
+ class PhoneNumber:
19
+ """
20
+ Represents and validates a phone number in international (MSISDN) format.
21
+
22
+ This class enforces that all phone numbers begin with the Nigerian country code `234`
23
+ and are followed by exactly 10 digits, ensuring compatibility with Termii's messaging API
24
+ and other telecom standards.
25
+
26
+ Attributes:
27
+ phone_number (str): The validated phone number string in MSISDN format.
28
+
29
+ Raises:
30
+ ValueError: If the provided phone number does not match the expected format.
31
+ """
32
+
33
+ def __init__(self, phone_number: str):
34
+ """
35
+ Initializes a new `PhoneNumber` instance after validating its format.
36
+
37
+ Args:
38
+ phone_number (str): A phone number string in MSISDN format (e.g., "2348031234567").
39
+
40
+ Raises:
41
+ ValueError: If the provided phone number is invalid or not in the expected format.
42
+
43
+ Example:
44
+ >>> valid = PhoneNumber("2349012345678")
45
+ >>> invalid = PhoneNumber("09012345678") # Raises ValueError
46
+ """
47
+ if not self.is_valid_phone_number(phone_number):
48
+ raise ValueError(f"Invalid phone number: {phone_number}")
49
+ self.phone_number = phone_number
50
+
51
+ @staticmethod
52
+ def is_valid_phone_number(phone_number) -> bool:
53
+ """
54
+ Validates whether the given string is a valid Nigerian phone number in MSISDN format.
55
+
56
+ A valid phone number must:
57
+ - Begin with "234" (Nigeria's country code)
58
+ - Contain exactly 13 digits total (234 + 10 digits)
59
+ - Contain only numeric characters
60
+
61
+ Args:
62
+ phone_number (str): The phone number string to validate.
63
+
64
+ Returns:
65
+ bool: True if the phone number is valid, False otherwise.
66
+
67
+ Example:
68
+ >>> PhoneNumber.is_valid_phone_number("2348023456789")
69
+ True
70
+ >>> PhoneNumber.is_valid_phone_number("08023456789")
71
+ False
72
+ """
73
+
74
+ return bool(re.match(r"^234\d{10}$", phone_number))