textverified 0.1.0a0__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,288 @@
1
+ """
2
+ TextVerified Python Client
3
+
4
+ This package provides a Python interface to the TextVerified API.
5
+ You can either use the TextVerified class directly to manage multiple credentials
6
+ or access API endpoints statically.
7
+
8
+ Example usage:
9
+ from textverified import services, verifications
10
+ # or
11
+ from textverified import TextVerified
12
+ client = TextVerified(api_key="...", api_username="...")
13
+ """
14
+
15
+ import os
16
+ import sys
17
+ from typing import Optional
18
+
19
+ # Import the main TextVerified class and API modules
20
+ from .textverified import TextVerified, BearerToken
21
+ from .account_api import AccountAPI
22
+ from .billing_cycle_api import BillingCycleAPI
23
+ from .reservations_api import ReservationsAPI
24
+ from .sales_api import SalesAPI
25
+ from .services_api import ServicesAPI
26
+ from .sms_api import SMSApi
27
+ from .verifications_api import VerificationsAPI
28
+ from .wake_api import WakeAPI
29
+ from .paginated_list import PaginatedList
30
+ from .exceptions import TextVerifiedError
31
+
32
+ # Import generated enums
33
+ from .data import *
34
+
35
+ # Configurable, lazy-initialized static instance
36
+ _static_instance: Optional[TextVerified] = None
37
+
38
+
39
+ def _get_static_instance() -> TextVerified:
40
+ """Get or create the static TextVerified instance."""
41
+ global _static_instance
42
+ if _static_instance is None:
43
+ api_key = os.environ.get("TEXTVERIFIED_API_KEY")
44
+ api_username = os.environ.get("TEXTVERIFIED_API_USERNAME")
45
+ base_url = os.environ.get("TEXTVERIFIED_BASE_URL", "https://www.textverified.com")
46
+ user_agent = os.environ.get("TEXTVERIFIED_USER_AGENT", "TextVerified-Python-Client/0.1.0")
47
+
48
+ if not api_key or not api_username:
49
+ raise ValueError(
50
+ "TextVerified static instance not configured. "
51
+ "Either call configure() or set TEXTVERIFIED_API_KEY and TEXTVERIFIED_API_USERNAME environment variables."
52
+ )
53
+
54
+ _static_instance = TextVerified(
55
+ api_key=api_key, api_username=api_username, base_url=base_url, user_agent=user_agent
56
+ )
57
+
58
+ return _static_instance
59
+
60
+
61
+ def configure(
62
+ api_key: str,
63
+ api_username: str,
64
+ base_url: str = "https://www.textverified.com",
65
+ user_agent: str = "TextVerified-Python-Client/0.1.0",
66
+ ) -> None:
67
+ """Configure the static TextVerified instance."""
68
+ global _static_instance
69
+ _static_instance = TextVerified(
70
+ api_key=api_key, api_username=api_username, base_url=base_url, user_agent=user_agent
71
+ )
72
+
73
+
74
+ # Lazy property implementation using __getattr__ at module level
75
+ class _LazyAPI:
76
+ """
77
+ Lazy wrapper for API endpoints that creates the static TextVerified instance
78
+ only when an API endpoint is actually accessed.
79
+ """
80
+
81
+ def __init__(self, attr_name: str, doc: str = None):
82
+ self.attr_name = attr_name
83
+ if doc:
84
+ self.__doc__ = doc
85
+
86
+ def __getattr__(self, name):
87
+ """Forward attribute access to the actual API endpoint."""
88
+ api_endpoint = getattr(_get_static_instance(), self.attr_name)
89
+ return getattr(api_endpoint, name)
90
+
91
+ def __call__(self, *args, **kwargs):
92
+ """Forward method calls to the actual API endpoint."""
93
+ api_endpoint = getattr(_get_static_instance(), self.attr_name)
94
+ return api_endpoint(*args, **kwargs)
95
+
96
+
97
+ # Create lazy API wrappers with documentation
98
+ account = _LazyAPI(
99
+ "account",
100
+ """
101
+ Static access to account management functionality.
102
+
103
+ Provides methods for retrieving account information, balance, and account settings.
104
+ This is a static wrapper around the AccountAPI class that uses the globally
105
+ configured TextVerified instance.
106
+
107
+ Example:
108
+ from textverified import account
109
+
110
+ # Get account information
111
+ account_info = account.me()
112
+
113
+ # Get account balance
114
+ balance = account.balance()
115
+ """,
116
+ )
117
+
118
+ billing_cycles = _LazyAPI(
119
+ "billing_cycles",
120
+ """
121
+ Static access to billing cycle management functionality.
122
+
123
+ Provides methods for managing billing cycles, invoices, and payment history.
124
+ This is a static wrapper around the BillingCycleAPI class that uses the globally
125
+ configured TextVerified instance.
126
+
127
+ Example:
128
+ from textverified import billing_cycles
129
+
130
+ # List all billing cycles
131
+ cycles = billing_cycles.list()
132
+
133
+ # Get specific billing cycle
134
+ cycle = billing_cycles.get(cycle_id)
135
+ """,
136
+ )
137
+
138
+ reservations = _LazyAPI(
139
+ "reservations",
140
+ """
141
+ Static access to phone number reservation functionality.
142
+
143
+ Provides methods for creating, managing, and releasing phone number reservations.
144
+ This is a static wrapper around the ReservationsAPI class that uses the globally
145
+ configured TextVerified instance.
146
+
147
+ Example:
148
+ from textverified import reservations
149
+
150
+ # Create a new reservation
151
+ reservation = reservations.create(service_id=1, area_code="555")
152
+
153
+ # List all reservations
154
+ all_reservations = reservations.list()
155
+ """,
156
+ )
157
+
158
+ sales = _LazyAPI(
159
+ "sales",
160
+ """
161
+ Static access to sales and transaction functionality.
162
+
163
+ Provides methods for retrieving sales history, transaction details, and revenue analytics.
164
+ This is a static wrapper around the SalesAPI class that uses the globally
165
+ configured TextVerified instance.
166
+
167
+ Example:
168
+ from textverified import sales
169
+
170
+ # Get sales history
171
+ sales_history = sales.list()
172
+
173
+ # Get specific sale details
174
+ sale = sales.get(sale_id)
175
+ """,
176
+ )
177
+
178
+ services = _LazyAPI(
179
+ "services",
180
+ """
181
+ Static access to service management functionality.
182
+
183
+ Provides methods for listing available services, getting service pricing,
184
+ and retrieving service-specific information.
185
+ This is a static wrapper around the ServicesAPI class that uses the globally
186
+ configured TextVerified instance.
187
+
188
+ Example:
189
+ from textverified import services
190
+
191
+ # Get all available services
192
+ available_services = services.list()
193
+
194
+ # Get pricing for rentals
195
+ pricing = services.pricing_rentals()
196
+ """,
197
+ )
198
+
199
+ verifications = _LazyAPI(
200
+ "verifications",
201
+ """
202
+ Static access to phone verification functionality.
203
+
204
+ Provides methods for creating verification requests, checking verification status,
205
+ and managing the verification process.
206
+ This is a static wrapper around the VerificationsAPI class that uses the globally
207
+ configured TextVerified instance.
208
+
209
+ Example:
210
+ from textverified import verifications
211
+
212
+ # Create a new verification
213
+ verification = verifications.create(service_id=1)
214
+
215
+ # Check verification status
216
+ status = verifications.get(verification_id)
217
+ """,
218
+ )
219
+
220
+ wake_requests = _LazyAPI(
221
+ "wake_requests",
222
+ """
223
+ Static access to wake request functionality.
224
+
225
+ Provides methods for creating and managing wake requests for phone numbers.
226
+ This is a static wrapper around the WakeAPI class that uses the globally
227
+ configured TextVerified instance.
228
+
229
+ Example:
230
+ from textverified import wake_requests
231
+
232
+ # Create a wake request
233
+ wake = wake_requests.create(phone_number="+1234567890")
234
+
235
+ # List wake requests
236
+ wakes = wake_requests.list()
237
+ """,
238
+ )
239
+
240
+ sms = _LazyAPI(
241
+ "sms",
242
+ """
243
+ Static access to SMS management functionality.
244
+
245
+ Provides methods for sending, receiving, and managing SMS messages.
246
+ This is a static wrapper around the SMSApi class that uses the globally
247
+ configured TextVerified instance.
248
+
249
+ Example:
250
+ from textverified import sms
251
+
252
+ # Get SMS messages
253
+ messages = sms.list()
254
+
255
+ # Get specific SMS
256
+ message = sms.get(message_id)
257
+ """,
258
+ )
259
+
260
+
261
+ # Available for import:
262
+ __all__ = [
263
+ # Main classes
264
+ "TextVerified",
265
+ "BearerToken",
266
+ "PaginatedList",
267
+ "TextVerifiedError",
268
+ # Configuration
269
+ "configure",
270
+ # Static API access
271
+ "account",
272
+ "billing_cycles",
273
+ "reservations",
274
+ "sales",
275
+ "services",
276
+ "verifications",
277
+ "wake_requests",
278
+ "sms",
279
+ # API classes (for direct instantiation if needed)
280
+ "AccountAPI",
281
+ "BillingCycleAPI",
282
+ "ReservationsAPI",
283
+ "SalesAPI",
284
+ "ServicesAPI",
285
+ "SMSApi",
286
+ "VerificationsAPI",
287
+ "WakeAPI",
288
+ ] + data.__all__
@@ -0,0 +1,32 @@
1
+ from .action import _Action, _ActionPerformer
2
+ from dataclasses import dataclass
3
+ from .data import Account
4
+
5
+
6
+ class AccountAPI:
7
+ """API endpoints related to account management."""
8
+
9
+ def __init__(self, client: _ActionPerformer):
10
+ self.client = client
11
+
12
+ def me(self) -> Account:
13
+ """
14
+ Returns:
15
+ Account: The current account details.
16
+ """
17
+ action = _Action(method="GET", href="/api/pub/v2/account/me")
18
+ response = self.client._perform_action(action)
19
+ return Account.from_api(response.data)
20
+
21
+ @property
22
+ def balance(self) -> float:
23
+ """Get the current account balance."""
24
+ details = self.me()
25
+ return details.current_balance
26
+
27
+ @property
28
+ def username(self) -> str:
29
+ """Get the current account username."""
30
+ # Realistically, this is the only request we could cache
31
+ details = self.me()
32
+ return details.username
textverified/action.py ADDED
@@ -0,0 +1,47 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Dict, Union
3
+ from requests.structures import CaseInsensitiveDict
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class _ActionResponse:
8
+ """Internal Protocol for API responses."""
9
+
10
+ data: Any
11
+ headers: "CaseInsensitiveDict[str, Union[str, int]]"
12
+
13
+
14
+ class _ActionPerformer:
15
+ """Internal Protocol for objects that can perform API actions."""
16
+
17
+ def _perform_action(self, action: "_Action", **kwargs) -> _ActionResponse:
18
+ """
19
+ Perform an API action and return the result.
20
+ :param action: The action to perform
21
+ :return: Dictionary containing the API response
22
+ """
23
+ pass
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class _Action:
28
+ """Single API action. Often returned by the API but also used internally."""
29
+
30
+ method: str
31
+ href: str
32
+
33
+ def to_api(self) -> dict:
34
+ """
35
+ Convert the Action instance to an API-compatible dictionary.
36
+ :return: Dictionary representation of the Action for API requests.
37
+ """
38
+ return {"method": self.method, "href": self.href}
39
+
40
+ @classmethod
41
+ def from_api(cls, data: Dict[str, Any]) -> "_Action":
42
+ """
43
+ Create an Action instance from API data.
44
+ :param data: Dictionary containing the API data.
45
+ :return: Action instance.
46
+ """
47
+ return cls(method=data["method"], href=data["href"])
@@ -0,0 +1,178 @@
1
+ from .action import _ActionPerformer, _Action
2
+ from typing import List, Union
3
+ from .data import (
4
+ BillingCycleCompact,
5
+ BillingCycleExpanded,
6
+ BillingCycleUpdateRequest,
7
+ BillingCycleRenewalInvoicePreview,
8
+ BillingCycleRenewalInvoice,
9
+ )
10
+ from .paginated_list import PaginatedList
11
+
12
+
13
+ class BillingCycleAPI:
14
+ """API endpoints related to billing cycles.
15
+ Renewable rentals are associated with billing cycles and can be manually assigned on rental creation or automatically assigned.
16
+ A single billing cycle can have multiple renewable rentals.
17
+ Billing cycles can be updated, renewed, and queried for invoices."""
18
+
19
+ def __init__(self, client: _ActionPerformer):
20
+ self.client = client
21
+
22
+ def list(self) -> PaginatedList[BillingCycleCompact]:
23
+ """Fetch all billing cycles associated with this account."""
24
+ action = _Action(method="GET", href="/api/pub/v2/billing-cycles")
25
+ response = self.client._perform_action(action)
26
+
27
+ return PaginatedList(
28
+ request_json=response.data, parse_item=BillingCycleCompact.from_api, api_context=self.client
29
+ )
30
+
31
+ def get(self, billing_cycle_id: str) -> BillingCycleExpanded:
32
+ """Get the details of a billing cycle by ID.
33
+
34
+ Args:
35
+ billing_cycle_id (str): The ID of the billing cycle to retrieve.
36
+
37
+ Returns:
38
+ BillingCycleExpanded: The detailed information about the billing cycle.
39
+ """
40
+ action = _Action(method="GET", href=f"/api/pub/v2/billing-cycles/{billing_cycle_id}")
41
+ response = self.client._perform_action(action)
42
+
43
+ return BillingCycleExpanded.from_api(response.data)
44
+
45
+ def update(
46
+ self,
47
+ billing_cycle: Union[str, BillingCycleCompact, BillingCycleExpanded],
48
+ data: BillingCycleUpdateRequest = None,
49
+ *,
50
+ reminders_enabled: bool = None,
51
+ nickname: str = None,
52
+ ) -> bool:
53
+ """Update a billing cycle.
54
+
55
+ Args:
56
+ billing_cycle (Union[str, BillingCycleCompact, BillingCycleExpanded]): The ID or instance of the billing cycle to update.
57
+ data (BillingCycleUpdateRequest, optional): Data to update. Overwritten by kwargs. Defaults to None.
58
+ reminders_enabled (bool, optional): Whether email reminders are enabled. Defaults to None.
59
+ nickname (str, optional): Your custom string to identify the billing cycle. Defaults to None.
60
+
61
+ Raises:
62
+ ValueError: If billing_cycle is not a valid ID or instance, or if no fields are provided to update.
63
+
64
+ Returns:
65
+ bool: True if the update was successful, False otherwise.
66
+ """
67
+ billing_cycle_id = (
68
+ billing_cycle.id
69
+ if isinstance(billing_cycle, (BillingCycleCompact, BillingCycleExpanded))
70
+ else billing_cycle
71
+ )
72
+
73
+ update_request = (
74
+ BillingCycleUpdateRequest(
75
+ reminders_enabled=reminders_enabled if reminders_enabled is not None else data.reminders_enabled,
76
+ nickname=nickname or data.nickname,
77
+ )
78
+ if data
79
+ else BillingCycleUpdateRequest(reminders_enabled=reminders_enabled, nickname=nickname)
80
+ )
81
+
82
+ if not billing_cycle_id or not isinstance(billing_cycle_id, str):
83
+ raise ValueError("billing_cycle must be a valid ID or instance of BillingCycleCompact/Expanded.")
84
+
85
+ if not update_request or (not update_request.reminders_enabled and not update_request.nickname):
86
+ raise ValueError("At least one field must be updated: reminders_enabled or nickname.")
87
+
88
+ action = _Action(method="POST", href=f"/api/pub/v2/billing-cycles/{billing_cycle_id}")
89
+ response = self.client._perform_action(action, json=update_request.to_api())
90
+
91
+ return True
92
+
93
+ def invoices(
94
+ self, billing_cycle_id: Union[str, BillingCycleCompact, BillingCycleExpanded]
95
+ ) -> PaginatedList[BillingCycleRenewalInvoice]:
96
+ """Get invoices for a specific billing cycle.
97
+
98
+ Args:
99
+ billing_cycle_id (Union[str, BillingCycleCompact, BillingCycleExpanded]): The ID or instance of the billing cycle to retrieve invoices for.
100
+
101
+ Raises:
102
+ ValueError: If billing_cycle_id is not a valid ID or instance.
103
+
104
+ Returns:
105
+ PaginatedList[BillingCycleRenewalInvoice]: A paginated list of billing cycle renewal invoices.
106
+ """
107
+
108
+ billing_cycle_id = (
109
+ billing_cycle_id.id
110
+ if isinstance(billing_cycle_id, (BillingCycleCompact, BillingCycleExpanded))
111
+ else billing_cycle_id
112
+ )
113
+
114
+ if not billing_cycle_id or not isinstance(billing_cycle_id, str):
115
+ raise ValueError("billing_cycle_id must be a valid ID or instance of BillingCycleCompact/Expanded.")
116
+
117
+ action = _Action(method="GET", href=f"/api/pub/v2/billing-cycles/{billing_cycle_id}/invoices")
118
+ response = self.client._perform_action(action)
119
+
120
+ return PaginatedList(
121
+ request_json=response.data, parse_item=BillingCycleRenewalInvoice.from_api, api_context=self.client
122
+ )
123
+
124
+ def preview(
125
+ self, billing_cycle_id: Union[str, BillingCycleCompact, BillingCycleExpanded]
126
+ ) -> BillingCycleRenewalInvoice:
127
+ """Preview the next billing cycle invoice.
128
+
129
+ Args:
130
+ billing_cycle_id (Union[str, BillingCycleCompact, BillingCycleExpanded]): The ID or instance of the billing cycle to preview.
131
+
132
+ Raises:
133
+ ValueError: If billing_cycle_id is not a valid ID or instance.
134
+
135
+ Returns:
136
+ BillingCycleRenewalInvoice: The preview of the next billing cycle invoice.
137
+ """
138
+
139
+ billing_cycle_id = (
140
+ billing_cycle_id.id
141
+ if isinstance(billing_cycle_id, (BillingCycleCompact, BillingCycleExpanded))
142
+ else billing_cycle_id
143
+ )
144
+
145
+ if not billing_cycle_id or not isinstance(billing_cycle_id, str):
146
+ raise ValueError("billing_cycle_id must be a valid ID or instance of BillingCycleCompact/Expanded.")
147
+
148
+ action = _Action(method="POST", href=f"/api/pub/v2/billing-cycles/{billing_cycle_id}/next-invoice")
149
+ response = self.client._perform_action(action)
150
+ return BillingCycleRenewalInvoicePreview.from_api(response.data)
151
+
152
+ def renew(self, billing_cycle_id: Union[str, BillingCycleCompact, BillingCycleExpanded]) -> bool:
153
+ """Renew the active rentals on your billing cycle.
154
+ Will not renew overdue rentals. To renew overdue rentals, you must explicitly call `textverified.reservations.renew_overdue_renewable_reservation` on each overdue rental.
155
+
156
+ Args:
157
+ billing_cycle_id (Union[str, BillingCycleCompact, BillingCycleExpanded]): The ID or instance of the billing cycle to renew.
158
+
159
+ Raises:
160
+ ValueError: If billing_cycle_id is not a valid ID or instance.
161
+
162
+ Returns:
163
+ bool: True if the billing cycle was renewed successfully, False otherwise.
164
+ """
165
+
166
+ billing_cycle_id = (
167
+ billing_cycle_id.id
168
+ if isinstance(billing_cycle_id, (BillingCycleCompact, BillingCycleExpanded))
169
+ else billing_cycle_id
170
+ )
171
+
172
+ if not billing_cycle_id or not isinstance(billing_cycle_id, str):
173
+ raise ValueError("billing_cycle_id must be a valid ID or instance of BillingCycleCompact/Expanded.")
174
+
175
+ action = _Action(method="POST", href=f"/api/pub/v2/billing-cycles/{billing_cycle_id}/renew")
176
+ self.client._perform_action(action)
177
+
178
+ return True
@@ -0,0 +1,9 @@
1
+ import inspect
2
+ from . import dtypes
3
+ from .dtypes import *
4
+
5
+ __all__ = [
6
+ name
7
+ for name, obj in inspect.getmembers(dtypes, inspect.isclass)
8
+ if obj.__module__ == dtypes.__name__ and not name.startswith("_")
9
+ ]