smspva-sdk 0.1.0a1__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.
- smspva_sdk-0.1.0a1/LICENSE +21 -0
- smspva_sdk-0.1.0a1/MANIFEST.in +3 -0
- smspva_sdk-0.1.0a1/METHODS.md +76 -0
- smspva_sdk-0.1.0a1/PKG-INFO +135 -0
- smspva_sdk-0.1.0a1/README.md +116 -0
- smspva_sdk-0.1.0a1/pyproject.toml +34 -0
- smspva_sdk-0.1.0a1/setup.cfg +4 -0
- smspva_sdk-0.1.0a1/src/smspva/__init__.py +6 -0
- smspva_sdk-0.1.0a1/src/smspva/_client.py +43 -0
- smspva_sdk-0.1.0a1/src/smspva/_contracts.json +1804 -0
- smspva_sdk-0.1.0a1/src/smspva/_polling.py +58 -0
- smspva_sdk-0.1.0a1/src/smspva/_resources.py +485 -0
- smspva_sdk-0.1.0a1/src/smspva/_transport.py +158 -0
- smspva_sdk-0.1.0a1/src/smspva/errors.py +39 -0
- smspva_sdk-0.1.0a1/src/smspva/py.typed +0 -0
- smspva_sdk-0.1.0a1/src/smspva/types.py +437 -0
- smspva_sdk-0.1.0a1/src/smspva_sdk.egg-info/PKG-INFO +135 -0
- smspva_sdk-0.1.0a1/src/smspva_sdk.egg-info/SOURCES.txt +19 -0
- smspva_sdk-0.1.0a1/src/smspva_sdk.egg-info/dependency_links.txt +1 -0
- smspva_sdk-0.1.0a1/src/smspva_sdk.egg-info/requires.txt +2 -0
- smspva_sdk-0.1.0a1/src/smspva_sdk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SMSPVA
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# SDK method index
|
|
2
|
+
|
|
3
|
+
All six packages cover these same 56 methods. Both Python clients use the Python names. PHP and Java use the JavaScript camelCase method names, with their own resource access syntax. Go capitalizes the first letter (for example, `GetBalance`); C# also appends `Async` (`GetBalanceAsync`). See each package README for request types and working usage. Parameters retain their API spelling.
|
|
4
|
+
|
|
5
|
+
## activation
|
|
6
|
+
|
|
7
|
+
| Python (sync and async) | JavaScript / TypeScript | HTTP |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `client.activation.get_balance` | `client.activation.getBalance` | `GET /activation/balance` |
|
|
10
|
+
| `client.activation.get_user_info` | `client.activation.getUserInfo` | `GET /activation/userinfo` |
|
|
11
|
+
| `client.activation.get_operators` | `client.activation.getOperators` | `GET /activation/operators/{country}` |
|
|
12
|
+
| `client.activation.get_conversions` | `client.activation.getConversions` | `GET /activation/conversions/{service}` |
|
|
13
|
+
| `client.activation.get_message_type_settings` | `client.activation.getMessageTypeSettings` | `GET /activation/countryservicesettings` |
|
|
14
|
+
| `client.activation.get_all_number_counts` | `client.activation.getAllNumberCounts` | `GET /activation/countnumbers` |
|
|
15
|
+
| `client.activation.get_country_number_counts` | `client.activation.getCountryNumberCounts` | `GET /activation/countnumbers/{country}` |
|
|
16
|
+
| `client.activation.get_service_price` | `client.activation.getServicePrice` | `GET /activation/serviceprice/{country}/{service}` |
|
|
17
|
+
| `client.activation.get_country_operator_prices` | `client.activation.getCountryOperatorPrices` | `GET /activation/serviceprice/{country}` |
|
|
18
|
+
| `client.activation.get_service_prices` | `client.activation.getServicePrices` | `GET /activation/serviceprices/{service}` |
|
|
19
|
+
| `client.activation.get_all_service_prices` | `client.activation.getAllServicePrices` | `GET /activation/servicesprices` |
|
|
20
|
+
| `client.activation.get_number` | `client.activation.getNumber` | `GET /activation/number/{country}/{service}` |
|
|
21
|
+
| `client.activation.get_number_by_operator` | `client.activation.getNumberByOperator` | `GET /activation/number/{country}/{service}/{operator}` |
|
|
22
|
+
| `client.activation.receive_sms` | `client.activation.receiveSms` | `GET /activation/sms/{orderid}` |
|
|
23
|
+
| `client.activation.get_orders` | `client.activation.getOrders` | `GET /activation/orders` |
|
|
24
|
+
| `client.activation.get_number_status` | `client.activation.getNumberStatus` | `GET /activation/numberstatus/{number}/{service}` |
|
|
25
|
+
| `client.activation.clear_sms` | `client.activation.clearSms` | `PUT /activation/clearsms/{orderid}` |
|
|
26
|
+
| `client.activation.stop_sms` | `client.activation.stopSms` | `PUT /activation/stopsms/{orderid}` |
|
|
27
|
+
| `client.activation.cancel_order` | `client.activation.cancelOrder` | `PUT /activation/cancelorder/{orderid}` |
|
|
28
|
+
| `client.activation.block_number` | `client.activation.blockNumber` | `PUT /activation/blocknumber/{orderid}` |
|
|
29
|
+
|
|
30
|
+
## rental
|
|
31
|
+
|
|
32
|
+
| Python (sync and async) | JavaScript / TypeScript | HTTP |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `client.rental.get_countries` | `client.rental.getCountries` | `GET /api/rent.php?method=getcountries` |
|
|
35
|
+
| `client.rental.get_default_services` | `client.rental.getDefaultServices` | `GET /api/rent.php?method=get_default_services` |
|
|
36
|
+
| `client.rental.get_countries_by_service` | `client.rental.getCountriesByService` | `GET /api/rent.php?method=get_country_by_service` |
|
|
37
|
+
| `client.rental.get_data` | `client.rental.getData` | `GET /api/rent.php?method=getdata` |
|
|
38
|
+
| `client.rental.get_data_with_providers` | `client.rental.getDataWithProviders` | `GET /api/rent.php?method=getdataWithProviders` |
|
|
39
|
+
| `client.rental.get_count` | `client.rental.getCount` | `GET /api/rent.php?method=getcount` |
|
|
40
|
+
| `client.rental.get_count_with_providers` | `client.rental.getCountWithProviders` | `GET /api/rent.php?method=getCountWithProviders` |
|
|
41
|
+
| `client.rental.get_multi_service_count` | `client.rental.getMultiServiceCount` | `GET /api/rent.php?method=get_count_multi` |
|
|
42
|
+
| `client.rental.get_canada_paypal_days` | `client.rental.getCanadaPaypalDays` | `GET /api/rent.php?method=get_days_canada_paypal` |
|
|
43
|
+
| `client.rental.get_minimum_paypal_days` | `client.rental.getMinimumPaypalDays` | `GET /api/rent.php?method=get_min_paypal_days` |
|
|
44
|
+
| `client.rental.create` | `client.rental.create` | `GET /api/rent.php?method=create` |
|
|
45
|
+
| `client.rental.create_multi` | `client.rental.createMulti` | `GET /api/rent.php?method=create_multi` |
|
|
46
|
+
| `client.rental.add_service_to_order` | `client.rental.addServiceToOrder` | `GET /api/rent.php?method=add_service_to_order` |
|
|
47
|
+
| `client.rental.get_orders` | `client.rental.getOrders` | `GET /api/rent.php?method=orders` |
|
|
48
|
+
| `client.rental.activate` | `client.rental.activate` | `GET /api/rent.php?method=activate` |
|
|
49
|
+
| `client.rental.get_sms` | `client.rental.getSms` | `GET /api/rent.php?method=sms` |
|
|
50
|
+
| `client.rental.prolong` | `client.rental.prolong` | `GET /api/rent.php?method=prolong` |
|
|
51
|
+
| `client.rental.prolong_by_days` | `client.rental.prolongByDays` | `GET /api/rent.php?method=prolong_max` |
|
|
52
|
+
| `client.rental.delete_order` | `client.rental.deleteOrder` | `GET /api/rent.php?method=delete` |
|
|
53
|
+
| `client.rental.get_history` | `client.rental.getHistory` | `GET /api/rent.php?method=get_rent_history` |
|
|
54
|
+
| `client.rental.get_restore_cost` | `client.rental.getRestoreCost` | `GET /api/rent.php?method=restore_user_precalc` |
|
|
55
|
+
| `client.rental.restore` | `client.rental.restore` | `GET /api/rent.php?method=restore_user` |
|
|
56
|
+
|
|
57
|
+
## email
|
|
58
|
+
|
|
59
|
+
| Python (sync and async) | JavaScript / TypeScript | HTTP |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `client.email.get_providers` | `client.email.getProviders` | `GET /api/rent_email.php?method=get_providers` |
|
|
62
|
+
| `client.email.get_data` | `client.email.getData` | `GET /api/rent_email.php?method=get_data` |
|
|
63
|
+
| `client.email.get_count` | `client.email.getCount` | `GET /api/rent_email.php?method=get_count` |
|
|
64
|
+
| `client.email.create_order` | `client.email.createOrder` | `GET /api/rent_email.php?method=create_order` |
|
|
65
|
+
| `client.email.get_orders` | `client.email.getOrders` | `GET /api/rent_email.php?method=get_orders` |
|
|
66
|
+
| `client.email.get_mails` | `client.email.getMails` | `GET /api/rent_email.php?method=get_mails` |
|
|
67
|
+
| `client.email.get_mail_body` | `client.email.getMailBody` | `GET /api/rent_email.php?method=get_mail_body` |
|
|
68
|
+
| `client.email.buy_alias` | `client.email.buyAlias` | `GET /api/rent_email.php?method=buy_alias` |
|
|
69
|
+
| `client.email.get_mails_by_alias` | `client.email.getMailsByAlias` | `GET /api/rent_email.php?method=get_mails_by_alias` |
|
|
70
|
+
| `client.email.prolong_order` | `client.email.prolongOrder` | `GET /api/rent_email.php?method=prolong_order` |
|
|
71
|
+
| `client.email.cancel_order` | `client.email.cancelOrder` | `GET /api/rent_email.php?method=cancel_order` |
|
|
72
|
+
| `client.email.get_archived_orders` | `client.email.getArchivedOrders` | `GET /api/rent_email.php?method=get_archived_orders` |
|
|
73
|
+
| `client.email.calc_restore_order` | `client.email.calcRestoreOrder` | `GET /api/rent_email.php?method=calc_restore_order` |
|
|
74
|
+
| `client.email.restore_order` | `client.email.restoreOrder` | `GET /api/rent_email.php?method=restore_order` |
|
|
75
|
+
|
|
76
|
+
Activation also provides `wait_for_sms` / `waitForSms`. It polls an existing order until a caller-supplied expiry, without purchasing or cancelling an order.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smspva-sdk
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Typed sync and async clients for SMSPVA Activation V2, Rental and Private Email
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/SMSPVA/smspva-python
|
|
7
|
+
Project-URL: Documentation, https://docs.smspva.com
|
|
8
|
+
Project-URL: Repository, https://github.com/SMSPVA/smspva-python
|
|
9
|
+
Project-URL: Issues, https://github.com/SMSPVA/smspva-python/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: httpx<1,>=0.28
|
|
17
|
+
Requires-Dist: typing_extensions>=4.12
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# SMSPVA Python SDK
|
|
21
|
+
|
|
22
|
+
[](https://github.com/SMSPVA/smspva-python/actions/workflows/ci.yml) [](LICENSE) 
|
|
23
|
+
|
|
24
|
+
Python 3.10+, synchronous `SMSPVA` and asynchronous `AsyncSMSPVA`, with the same 56 typed methods. Covers Activation V2 (20), Rental (22) and Private Email (14). Alternative API is excluded. This is an alpha release. PyPI publication is pending.
|
|
25
|
+
|
|
26
|
+
Install from source:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
git clone https://github.com/SMSPVA/smspva-python.git
|
|
30
|
+
cd smspva-python
|
|
31
|
+
python -m pip install .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import os
|
|
36
|
+
from smspva import SMSPVA
|
|
37
|
+
|
|
38
|
+
with SMSPVA(os.environ["SMSPVA_API_KEY"]) as client:
|
|
39
|
+
print(client.activation.get_balance())
|
|
40
|
+
print(client.rental.get_countries())
|
|
41
|
+
print(client.email.get_providers())
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import asyncio
|
|
46
|
+
import os
|
|
47
|
+
from smspva import AsyncSMSPVA
|
|
48
|
+
|
|
49
|
+
async def main():
|
|
50
|
+
async with AsyncSMSPVA(os.environ["SMSPVA_API_KEY"]) as client:
|
|
51
|
+
result = await client.activation.get_balance()
|
|
52
|
+
print(result)
|
|
53
|
+
|
|
54
|
+
asyncio.run(main())
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use the client as a context manager, or call `close()` / `await aclose()` yourself. Async uses HTTPX `AsyncClient`, not worker threads.
|
|
58
|
+
|
|
59
|
+
## Requests and results
|
|
60
|
+
|
|
61
|
+
- Activation V2 sends the key in the `apikey` header. Rental and Email use the `apikey` query parameter. Public methods send no key or cookies. Optional-key rental methods use the client key unless you pass `use_api_key=False`.
|
|
62
|
+
- Returned dictionaries preserve wire field names and values, including string prices, timestamps, and IDs. Types follow the source schemas; a field not marked required remains optional.
|
|
63
|
+
- `is_pending(result)` recognises Activation SMS HTTP 202 / `SMS_NOT_YET_RECEIVED`. `is_no_stock(result)` recognises Email creation `status=2`. Both return the original response instead of raising a generic error.
|
|
64
|
+
- All documented optional parameters are available, including `partnerkey`, `partner_key`, `provider`, `extend`, pagination and search. Query and path values are encoded. Email `pcode=None` or `pcode=""` is omitted entirely.
|
|
65
|
+
- `rental.get_multi_service_count` requires explicit `dtype` and `dcount` because the docs warn that backend defaults are unreliable. The wire method remains `get_count_multi`.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# Public reference data, without an API key.
|
|
69
|
+
with SMSPVA() as client:
|
|
70
|
+
services = client.email.get_data(pcode="CODE_FROM_GET_PROVIDERS")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Paid example (run only when you intend to create a rental):
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from smspva import is_no_stock
|
|
77
|
+
|
|
78
|
+
with SMSPVA(os.environ["SMSPVA_API_KEY"]) as client:
|
|
79
|
+
result = client.email.create_order(
|
|
80
|
+
scode="CODE_FROM_GET_DATA", dtype="week", dcount=1
|
|
81
|
+
)
|
|
82
|
+
if is_no_stock(result):
|
|
83
|
+
print("No mailbox available; refresh counts before trying again.")
|
|
84
|
+
else:
|
|
85
|
+
print(result)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Waiting for SMS
|
|
89
|
+
|
|
90
|
+
Save an expiry once when the number is created. `wait_for_sms` polls only Receive SMS, never creates another order, and stops on terminal errors, expiry or a transport failure. A new SMS retrieval can trigger payment.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import time
|
|
94
|
+
|
|
95
|
+
# number_result is the successful response from your own get_number request.
|
|
96
|
+
expires_at = time.time() + number_result["data"]["orderExpireIn"]
|
|
97
|
+
result = client.activation.wait_for_sms(
|
|
98
|
+
orderid=number_result["data"]["orderId"], expires_at=expires_at
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Use `await client.activation.wait_for_sms(...)` with `AsyncSMSPVA`. The helper waits at least five seconds between pending responses; this is a helper policy, not a claim about an API-wide rate limit. Request deadlines are bounded by the saved expiry. HTTPX timeouts apply to individual network operations; the async polling helper also enforces an overall remaining deadline.
|
|
103
|
+
|
|
104
|
+
## Errors and timeouts
|
|
105
|
+
|
|
106
|
+
`ApiError` retains `family`, `operation`, `http_status`, `code` and the original `response`. `ProtocolError` means the body was not a usable JSON envelope. `TransportError` and `RequestTimeoutError` mean the request outcome may be unknown (`outcome_unknown` for state-changing/paid methods). No requests are retried automatically; inspect current orders before repeating a paid operation.
|
|
107
|
+
|
|
108
|
+
Exception messages omit request URLs and keys. Query credentials may still appear if you enable HTTPX request logging; redact those logs. Do not put account keys in browser code.
|
|
109
|
+
|
|
110
|
+
The default timeout is 30 seconds per HTTPX network operation. Override at client construction or per call with `timeout=...`. Advanced transports, proxies and test doubles can be supplied through `transport=httpx.HTTPTransport(...)` or `httpx.AsyncHTTPTransport(...)`; environment proxies/cookies are not inherited. Redirects are never followed.
|
|
111
|
+
|
|
112
|
+
The generated method index is in `METHODS.md`. Response TypedDicts and input aliases are available in `smspva.types`; the package includes `py.typed`.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT. See [LICENSE](LICENSE).
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
python -m pip install -e . build mypy
|
|
122
|
+
python -m unittest discover -s tests -v
|
|
123
|
+
python -m mypy
|
|
124
|
+
python -m build
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Tests use synthetic fixtures and local transports; they do not buy numbers or call the production API.
|
|
128
|
+
|
|
129
|
+
## Documentation
|
|
130
|
+
|
|
131
|
+
[API documentation](https://docs.smspva.com)
|
|
132
|
+
|
|
133
|
+
The new documentation is being prepared in English, Spanish and Chinese. Localized links will be added when it is deployed.
|
|
134
|
+
|
|
135
|
+
The SDK covers Activation V2, Rental and Private Email. Alternative API is not supported.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# SMSPVA Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://github.com/SMSPVA/smspva-python/actions/workflows/ci.yml) [](LICENSE) 
|
|
4
|
+
|
|
5
|
+
Python 3.10+, synchronous `SMSPVA` and asynchronous `AsyncSMSPVA`, with the same 56 typed methods. Covers Activation V2 (20), Rental (22) and Private Email (14). Alternative API is excluded. This is an alpha release. PyPI publication is pending.
|
|
6
|
+
|
|
7
|
+
Install from source:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
git clone https://github.com/SMSPVA/smspva-python.git
|
|
11
|
+
cd smspva-python
|
|
12
|
+
python -m pip install .
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import os
|
|
17
|
+
from smspva import SMSPVA
|
|
18
|
+
|
|
19
|
+
with SMSPVA(os.environ["SMSPVA_API_KEY"]) as client:
|
|
20
|
+
print(client.activation.get_balance())
|
|
21
|
+
print(client.rental.get_countries())
|
|
22
|
+
print(client.email.get_providers())
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import asyncio
|
|
27
|
+
import os
|
|
28
|
+
from smspva import AsyncSMSPVA
|
|
29
|
+
|
|
30
|
+
async def main():
|
|
31
|
+
async with AsyncSMSPVA(os.environ["SMSPVA_API_KEY"]) as client:
|
|
32
|
+
result = await client.activation.get_balance()
|
|
33
|
+
print(result)
|
|
34
|
+
|
|
35
|
+
asyncio.run(main())
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use the client as a context manager, or call `close()` / `await aclose()` yourself. Async uses HTTPX `AsyncClient`, not worker threads.
|
|
39
|
+
|
|
40
|
+
## Requests and results
|
|
41
|
+
|
|
42
|
+
- Activation V2 sends the key in the `apikey` header. Rental and Email use the `apikey` query parameter. Public methods send no key or cookies. Optional-key rental methods use the client key unless you pass `use_api_key=False`.
|
|
43
|
+
- Returned dictionaries preserve wire field names and values, including string prices, timestamps, and IDs. Types follow the source schemas; a field not marked required remains optional.
|
|
44
|
+
- `is_pending(result)` recognises Activation SMS HTTP 202 / `SMS_NOT_YET_RECEIVED`. `is_no_stock(result)` recognises Email creation `status=2`. Both return the original response instead of raising a generic error.
|
|
45
|
+
- All documented optional parameters are available, including `partnerkey`, `partner_key`, `provider`, `extend`, pagination and search. Query and path values are encoded. Email `pcode=None` or `pcode=""` is omitted entirely.
|
|
46
|
+
- `rental.get_multi_service_count` requires explicit `dtype` and `dcount` because the docs warn that backend defaults are unreliable. The wire method remains `get_count_multi`.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# Public reference data, without an API key.
|
|
50
|
+
with SMSPVA() as client:
|
|
51
|
+
services = client.email.get_data(pcode="CODE_FROM_GET_PROVIDERS")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Paid example (run only when you intend to create a rental):
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from smspva import is_no_stock
|
|
58
|
+
|
|
59
|
+
with SMSPVA(os.environ["SMSPVA_API_KEY"]) as client:
|
|
60
|
+
result = client.email.create_order(
|
|
61
|
+
scode="CODE_FROM_GET_DATA", dtype="week", dcount=1
|
|
62
|
+
)
|
|
63
|
+
if is_no_stock(result):
|
|
64
|
+
print("No mailbox available; refresh counts before trying again.")
|
|
65
|
+
else:
|
|
66
|
+
print(result)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Waiting for SMS
|
|
70
|
+
|
|
71
|
+
Save an expiry once when the number is created. `wait_for_sms` polls only Receive SMS, never creates another order, and stops on terminal errors, expiry or a transport failure. A new SMS retrieval can trigger payment.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import time
|
|
75
|
+
|
|
76
|
+
# number_result is the successful response from your own get_number request.
|
|
77
|
+
expires_at = time.time() + number_result["data"]["orderExpireIn"]
|
|
78
|
+
result = client.activation.wait_for_sms(
|
|
79
|
+
orderid=number_result["data"]["orderId"], expires_at=expires_at
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Use `await client.activation.wait_for_sms(...)` with `AsyncSMSPVA`. The helper waits at least five seconds between pending responses; this is a helper policy, not a claim about an API-wide rate limit. Request deadlines are bounded by the saved expiry. HTTPX timeouts apply to individual network operations; the async polling helper also enforces an overall remaining deadline.
|
|
84
|
+
|
|
85
|
+
## Errors and timeouts
|
|
86
|
+
|
|
87
|
+
`ApiError` retains `family`, `operation`, `http_status`, `code` and the original `response`. `ProtocolError` means the body was not a usable JSON envelope. `TransportError` and `RequestTimeoutError` mean the request outcome may be unknown (`outcome_unknown` for state-changing/paid methods). No requests are retried automatically; inspect current orders before repeating a paid operation.
|
|
88
|
+
|
|
89
|
+
Exception messages omit request URLs and keys. Query credentials may still appear if you enable HTTPX request logging; redact those logs. Do not put account keys in browser code.
|
|
90
|
+
|
|
91
|
+
The default timeout is 30 seconds per HTTPX network operation. Override at client construction or per call with `timeout=...`. Advanced transports, proxies and test doubles can be supplied through `transport=httpx.HTTPTransport(...)` or `httpx.AsyncHTTPTransport(...)`; environment proxies/cookies are not inherited. Redirects are never followed.
|
|
92
|
+
|
|
93
|
+
The generated method index is in `METHODS.md`. Response TypedDicts and input aliases are available in `smspva.types`; the package includes `py.typed`.
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT. See [LICENSE](LICENSE).
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
python -m pip install -e . build mypy
|
|
103
|
+
python -m unittest discover -s tests -v
|
|
104
|
+
python -m mypy
|
|
105
|
+
python -m build
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Tests use synthetic fixtures and local transports; they do not buy numbers or call the production API.
|
|
109
|
+
|
|
110
|
+
## Documentation
|
|
111
|
+
|
|
112
|
+
[API documentation](https://docs.smspva.com)
|
|
113
|
+
|
|
114
|
+
The new documentation is being prepared in English, Spanish and Chinese. Localized links will be added when it is deployed.
|
|
115
|
+
|
|
116
|
+
The SDK covers Activation V2, Rental and Private Email. Alternative API is not supported.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smspva-sdk"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "Typed sync and async clients for SMSPVA Activation V2, Rental and Private Email"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = ["httpx>=0.28,<1", "typing_extensions>=4.12"]
|
|
14
|
+
classifiers = ["Development Status :: 3 - Alpha", "Typing :: Typed", "Programming Language :: Python :: 3"]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages.find]
|
|
17
|
+
where = ["src"]
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.package-data]
|
|
20
|
+
smspva = ["py.typed", "_contracts.json"]
|
|
21
|
+
|
|
22
|
+
[tool.mypy]
|
|
23
|
+
python_version = "3.10"
|
|
24
|
+
strict = true
|
|
25
|
+
files = ["src/smspva"]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
include-package-data = true
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/SMSPVA/smspva-python"
|
|
32
|
+
Documentation = "https://docs.smspva.com"
|
|
33
|
+
Repository = "https://github.com/SMSPVA/smspva-python"
|
|
34
|
+
Issues = "https://github.com/SMSPVA/smspva-python/issues"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from ._client import SMSPVA, AsyncSMSPVA
|
|
2
|
+
from ._polling import is_pending, is_no_stock
|
|
3
|
+
from .errors import SMSPVAError, ApiError, ProtocolError, TransportError, RequestTimeoutError, PollingTimeoutError, ValidationError
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0a1"
|
|
6
|
+
__all__ = ["SMSPVA", "AsyncSMSPVA", "is_pending", "is_no_stock", "SMSPVAError", "ApiError", "ProtocolError", "TransportError", "RequestTimeoutError", "PollingTimeoutError", "ValidationError"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from ._transport import Transport, AsyncTransport
|
|
7
|
+
from ._resources import Activation, Rental, Email, AsyncActivation, AsyncRental, AsyncEmail
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SMSPVA:
|
|
11
|
+
def __init__(self, api_key: str | None = None, *, timeout: float = 30, activation_base_url: str | None = None,
|
|
12
|
+
rental_base_url: str | None = None, email_base_url: str | None = None, transport: httpx.BaseTransport | None = None):
|
|
13
|
+
self._transport = Transport(api_key, timeout, {"activation": activation_base_url, "rental": rental_base_url, "email": email_base_url}, transport)
|
|
14
|
+
self.activation = Activation(self._transport)
|
|
15
|
+
self.rental = Rental(self._transport)
|
|
16
|
+
self.email = Email(self._transport)
|
|
17
|
+
|
|
18
|
+
def close(self) -> None:
|
|
19
|
+
self._transport.close()
|
|
20
|
+
|
|
21
|
+
def __enter__(self) -> SMSPVA:
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def __exit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None:
|
|
25
|
+
self.close()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AsyncSMSPVA:
|
|
29
|
+
def __init__(self, api_key: str | None = None, *, timeout: float = 30, activation_base_url: str | None = None,
|
|
30
|
+
rental_base_url: str | None = None, email_base_url: str | None = None, transport: httpx.AsyncBaseTransport | None = None):
|
|
31
|
+
self._transport = AsyncTransport(api_key, timeout, {"activation": activation_base_url, "rental": rental_base_url, "email": email_base_url}, transport)
|
|
32
|
+
self.activation = AsyncActivation(self._transport)
|
|
33
|
+
self.rental = AsyncRental(self._transport)
|
|
34
|
+
self.email = AsyncEmail(self._transport)
|
|
35
|
+
|
|
36
|
+
async def aclose(self) -> None:
|
|
37
|
+
await self._transport.close()
|
|
38
|
+
|
|
39
|
+
async def __aenter__(self) -> AsyncSMSPVA:
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
async def __aexit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None:
|
|
43
|
+
await self.aclose()
|