sv-number 0.2.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.
@@ -0,0 +1,46 @@
1
+ # Dependencies
2
+ node_modules/
3
+ .pnp.*
4
+ .yarn/
5
+ .pnpm-store/
6
+
7
+ # Build
8
+ dist/
9
+ build/
10
+ *.tsbuildinfo
11
+ *.egg-info/
12
+ .eggs/
13
+ wheels/
14
+
15
+ # Tests and coverage
16
+ coverage/
17
+ .nyc_output/
18
+ .pytest_cache/
19
+ .mypy_cache/
20
+ .ruff_cache/
21
+ htmlcov/
22
+ .coverage
23
+ .coverage.*
24
+
25
+ # Python
26
+ __pycache__/
27
+ *.py[cod]
28
+ *$py.class
29
+ .venv/
30
+ venv/
31
+ env/
32
+
33
+ # Secrets
34
+ .env
35
+ .env.*
36
+ !.env.example
37
+ AGENTS.md
38
+
39
+ # GitBook / HonKit
40
+ _book/
41
+
42
+ # OS and editors
43
+ .DS_Store
44
+ Thumbs.db
45
+ .idea/
46
+ *.swp
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 api-kit contributors
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.5
2
+ Name: sv-number
3
+ Version: 0.2.0
4
+ Summary: Python client for the SV Number SMS verification API
5
+ Project-URL: Repository, https://github.com/TysikGG/api-kit
6
+ Project-URL: Documentation, https://sms-verification-number.com/en/api-sms-activate/
7
+ License-Expression: ISC
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: httpx>=0.27
11
+ Description-Content-Type: text/markdown
12
+
13
+ # sv-number
14
+
15
+ [![PyPI version](https://img.shields.io/pypi/v/sv-number.svg)](https://pypi.org/project/sv-number/)
16
+ [![Python versions](https://img.shields.io/pypi/pyversions/sv-number.svg)](https://pypi.org/project/sv-number/)
17
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
18
+
19
+ Modern, typed Python client for the [SV Number SMS Verification Service](https://sms-verification-number.com/en/api-sms-activate/). Rent temporary numbers to receive verification SMS across hundreds of platforms. Built on [HTTPX](https://www.python-httpx.org).
20
+
21
+ Official API documentation: <https://sms-verification-number.com/en/api-sms-activate/>
22
+
23
+ ---
24
+
25
+ ## Features
26
+
27
+ - 📱 **Virtual Number Rental** — Order temporary phone numbers across global countries and services.
28
+ - ⏳ **Automated Polling** — `wait_for_code()` polls the activation status until an OTP arrives or timeout expires.
29
+ - 🏷️ **Price Catalog & Services** — Fetch live rates and availability via `get_prices()` and `get_services_and_cost()`.
30
+ - 🐍 **Modern Python** — Type-annotated, Google-style docstrings, context manager support (`with SvNumber(...) as client:`).
31
+ - 🚨 **Structured Errors** — Clear `SvNumberError` exceptions with API status strings (`NO_NUMBERS`, `BAD_KEY`, `NO_BALANCE`).
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install sv-number
39
+ ```
40
+
41
+ Requires Python 3.10 or higher.
42
+
43
+ ---
44
+
45
+ ## Quick Start (Safe Check)
46
+
47
+ The quick check only inspects your account balance and service catalogs. It does not order numbers or spend credits.
48
+
49
+ ```python
50
+ import os
51
+ from sv_number import SvNumber
52
+
53
+ api_key = os.environ["SV_NUMBER_API_KEY"]
54
+
55
+ with SvNumber(api_key) as client:
56
+ balance = client.get_balance()
57
+ services = client.get_services_and_cost(country=0)
58
+ print(f"Balance: ${balance}")
59
+ print(f"Available services in country 0: {len(services)}")
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Usage Guide
65
+
66
+ ### 1. Renting a Number and Receiving an SMS
67
+
68
+ ```python
69
+ import os
70
+ from sv_number import SvNumber, SvNumberError
71
+
72
+ api_key = os.environ["SV_NUMBER_API_KEY"]
73
+
74
+ with SvNumber(api_key) as client:
75
+ # 1. Order a number for Telegram ('tg') in country 0
76
+ order = client.get_number("tg", country=0)
77
+ print(f"Rented: {order['phone_number']} (ID: {order['activation_id']})")
78
+
79
+ # 2. Inform the provider that the application has triggered the SMS
80
+ client.ready(order["activation_id"])
81
+
82
+ # 3. Poll for the incoming OTP code
83
+ try:
84
+ code = client.wait_for_code(order["activation_id"], timeout=120.0, interval=3.0)
85
+ print(f"Received SMS code: {code}")
86
+
87
+ # 4. Confirm completion
88
+ client.complete(order["activation_id"])
89
+ except SvNumberError as err:
90
+ if err.code == "TIMEOUT":
91
+ print("Code timed out, canceling order...")
92
+ client.cancel(order["activation_id"])
93
+ else:
94
+ raise
95
+ ```
96
+
97
+ ### 2. Checking Prices
98
+
99
+ ```python
100
+ with SvNumber(os.environ["SV_NUMBER_API_KEY"]) as client:
101
+ prices = client.get_prices(country=0)
102
+ print("Prices for country 0:", prices)
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Error Handling
108
+
109
+ When the API returns an error, the client raises an `SvNumberError`:
110
+
111
+ ```python
112
+ from sv_number import SvNumber, SvNumberError
113
+
114
+ try:
115
+ with SvNumber("invalid_key") as client:
116
+ client.get_balance()
117
+ except SvNumberError as err:
118
+ print(f"SV Number API error: {err.code}")
119
+ ```
120
+
121
+ ---
122
+
123
+ ## License
124
+
125
+ [ISC](LICENSE) © api-kit contributors
@@ -0,0 +1,113 @@
1
+ # sv-number
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/sv-number.svg)](https://pypi.org/project/sv-number/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/sv-number.svg)](https://pypi.org/project/sv-number/)
5
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
6
+
7
+ Modern, typed Python client for the [SV Number SMS Verification Service](https://sms-verification-number.com/en/api-sms-activate/). Rent temporary numbers to receive verification SMS across hundreds of platforms. Built on [HTTPX](https://www.python-httpx.org).
8
+
9
+ Official API documentation: <https://sms-verification-number.com/en/api-sms-activate/>
10
+
11
+ ---
12
+
13
+ ## Features
14
+
15
+ - 📱 **Virtual Number Rental** — Order temporary phone numbers across global countries and services.
16
+ - ⏳ **Automated Polling** — `wait_for_code()` polls the activation status until an OTP arrives or timeout expires.
17
+ - 🏷️ **Price Catalog & Services** — Fetch live rates and availability via `get_prices()` and `get_services_and_cost()`.
18
+ - 🐍 **Modern Python** — Type-annotated, Google-style docstrings, context manager support (`with SvNumber(...) as client:`).
19
+ - 🚨 **Structured Errors** — Clear `SvNumberError` exceptions with API status strings (`NO_NUMBERS`, `BAD_KEY`, `NO_BALANCE`).
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install sv-number
27
+ ```
28
+
29
+ Requires Python 3.10 or higher.
30
+
31
+ ---
32
+
33
+ ## Quick Start (Safe Check)
34
+
35
+ The quick check only inspects your account balance and service catalogs. It does not order numbers or spend credits.
36
+
37
+ ```python
38
+ import os
39
+ from sv_number import SvNumber
40
+
41
+ api_key = os.environ["SV_NUMBER_API_KEY"]
42
+
43
+ with SvNumber(api_key) as client:
44
+ balance = client.get_balance()
45
+ services = client.get_services_and_cost(country=0)
46
+ print(f"Balance: ${balance}")
47
+ print(f"Available services in country 0: {len(services)}")
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Usage Guide
53
+
54
+ ### 1. Renting a Number and Receiving an SMS
55
+
56
+ ```python
57
+ import os
58
+ from sv_number import SvNumber, SvNumberError
59
+
60
+ api_key = os.environ["SV_NUMBER_API_KEY"]
61
+
62
+ with SvNumber(api_key) as client:
63
+ # 1. Order a number for Telegram ('tg') in country 0
64
+ order = client.get_number("tg", country=0)
65
+ print(f"Rented: {order['phone_number']} (ID: {order['activation_id']})")
66
+
67
+ # 2. Inform the provider that the application has triggered the SMS
68
+ client.ready(order["activation_id"])
69
+
70
+ # 3. Poll for the incoming OTP code
71
+ try:
72
+ code = client.wait_for_code(order["activation_id"], timeout=120.0, interval=3.0)
73
+ print(f"Received SMS code: {code}")
74
+
75
+ # 4. Confirm completion
76
+ client.complete(order["activation_id"])
77
+ except SvNumberError as err:
78
+ if err.code == "TIMEOUT":
79
+ print("Code timed out, canceling order...")
80
+ client.cancel(order["activation_id"])
81
+ else:
82
+ raise
83
+ ```
84
+
85
+ ### 2. Checking Prices
86
+
87
+ ```python
88
+ with SvNumber(os.environ["SV_NUMBER_API_KEY"]) as client:
89
+ prices = client.get_prices(country=0)
90
+ print("Prices for country 0:", prices)
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Error Handling
96
+
97
+ When the API returns an error, the client raises an `SvNumberError`:
98
+
99
+ ```python
100
+ from sv_number import SvNumber, SvNumberError
101
+
102
+ try:
103
+ with SvNumber("invalid_key") as client:
104
+ client.get_balance()
105
+ except SvNumberError as err:
106
+ print(f"SV Number API error: {err.code}")
107
+ ```
108
+
109
+ ---
110
+
111
+ ## License
112
+
113
+ [ISC](LICENSE) © api-kit contributors
@@ -0,0 +1,21 @@
1
+ """Safe verification script: checks account balance without renting numbers."""
2
+
3
+ import os
4
+
5
+ from sv_number import SvNumber
6
+
7
+
8
+ def main() -> None:
9
+ api_key = os.environ.get("SV_NUMBER_API_KEY")
10
+ if not api_key:
11
+ raise SystemExit("Please set SV_NUMBER_API_KEY environment variable")
12
+
13
+ with SvNumber(api_key) as client:
14
+ balance = client.get_balance()
15
+ services = client.get_services_and_cost(country=0)
16
+ print(f"Account balance: ${balance}")
17
+ print(f"Available services in country 0: {len(services)}")
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()
@@ -0,0 +1,44 @@
1
+ # Publish trigger
2
+ # SV Number API client
3
+ [build-system]
4
+ requires = ["hatchling"]
5
+ build-backend = "hatchling.build"
6
+
7
+ [project]
8
+ name = "sv-number"
9
+ version = "0.2.0"
10
+ description = "Python client for the SV Number SMS verification API"
11
+ readme = "README.md"
12
+ requires-python = ">=3.10"
13
+ license = "ISC"
14
+ dependencies = [
15
+ "httpx>=0.27",
16
+ ]
17
+
18
+ [project.urls]
19
+ Repository = "https://github.com/TysikGG/api-kit"
20
+ Documentation = "https://sms-verification-number.com/en/api-sms-activate/"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "pytest>=8",
25
+ "pytest-httpx>=0.30",
26
+ "ruff>=0.8",
27
+ ]
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["src/sv_number"]
31
+
32
+ [tool.pytest.ini_options]
33
+ markers = [
34
+ "live: calls the real SV Number API and needs SV_NUMBER_API_KEY",
35
+ ]
36
+ testpaths = ["tests"]
37
+
38
+ [tool.ruff]
39
+ target-version = "py310"
40
+ line-length = 100
41
+ src = ["src", "tests", "examples"]
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,14 @@
1
+ """SV Number SMS verification API client."""
2
+
3
+ from .client import REF_KEY, SvNumber
4
+ from .errors import SvNumberError
5
+ from .types import ActivationStatus, OrderedNumber, ServiceCostItem
6
+
7
+ __all__ = [
8
+ "ActivationStatus",
9
+ "OrderedNumber",
10
+ "REF_KEY",
11
+ "ServiceCostItem",
12
+ "SvNumber",
13
+ "SvNumberError",
14
+ ]
@@ -0,0 +1,234 @@
1
+ """Client for the SV Number SMS verification API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from collections.abc import Mapping
7
+ from typing import Any
8
+
9
+ import httpx
10
+
11
+ from .errors import SvNumberError, assert_not_api_error
12
+ from .parse import parse_access_number, parse_balance, parse_status
13
+ from .types import ActivationStatus, OrderedNumber, ServiceCostItem
14
+
15
+ _DEFAULT_BASE_URL = "https://sms-verification-number.com"
16
+ _DEFAULT_TIMEOUT = 30.0
17
+
18
+ REF_KEY = "226"
19
+
20
+
21
+ class SvNumber:
22
+ """Client for the SV Number SMS verification service.
23
+
24
+ Args:
25
+ api_key: SV Number API key.
26
+ base_url: Custom API base URL (defaults to `https://sms-verification-number.com`).
27
+ timeout: Request timeout in seconds (defaults to 30.0).
28
+ lang: Language preference (e.g. `en`, `ru`).
29
+
30
+ Examples:
31
+ >>> from sv_number import SvNumber
32
+ >>> with SvNumber("your_api_key") as client:
33
+ ... balance = client.get_balance()
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ api_key: str,
39
+ *,
40
+ base_url: str = _DEFAULT_BASE_URL,
41
+ timeout: float = _DEFAULT_TIMEOUT,
42
+ lang: str = "en",
43
+ ) -> None:
44
+ if not api_key or not api_key.strip():
45
+ raise SvNumberError("BAD_KEY", "API key is required")
46
+
47
+ self._api_key = api_key.strip()
48
+ self._lang = lang
49
+ self._http = httpx.Client(
50
+ base_url=base_url.rstrip("/"),
51
+ timeout=timeout,
52
+ headers={
53
+ "Accept": "application/json, text/plain",
54
+ },
55
+ )
56
+
57
+ def close(self) -> None:
58
+ """Close the underlying HTTP client."""
59
+ self._http.close()
60
+
61
+ def __enter__(self) -> SvNumber:
62
+ return self
63
+
64
+ def __exit__(self, *exc: object) -> None:
65
+ self.close()
66
+
67
+ def call(self, action: str, params: Mapping[str, Any] | None = None) -> Any:
68
+ """Execute a low-level call to /stubs/handler_api.
69
+
70
+ Automatically attaches ``api_key``, ``ref_key``, and ``lang``.
71
+ """
72
+ query: dict[str, str] = {
73
+ "api_key": self._api_key,
74
+ "ref_key": REF_KEY,
75
+ "lang": self._lang,
76
+ "action": action,
77
+ }
78
+
79
+ if params:
80
+ for key, val in params.items():
81
+ if val is not None and val != "":
82
+ query[key] = str(val)
83
+
84
+ try:
85
+ res = self._http.get("/stubs/handler_api", params=query)
86
+ except httpx.HTTPError as err:
87
+ raise SvNumberError("NETWORK", str(err)) from err
88
+
89
+ if res.is_error:
90
+ raise SvNumberError(f"HTTP_{res.status_code}", res.text)
91
+
92
+ # Attempt to parse json, otherwise return text
93
+ try:
94
+ data = res.json()
95
+ except ValueError:
96
+ data = res.text
97
+
98
+ assert_not_api_error(data)
99
+ return data
100
+
101
+ def get_balance(self) -> float:
102
+ """Retrieve current balance in USD.
103
+
104
+ Returns:
105
+ Balance amount.
106
+
107
+ Examples:
108
+ >>> with SvNumber("key") as client:
109
+ ... print(client.get_balance())
110
+ """
111
+ data = self.call("getBalance")
112
+ return parse_balance(data)
113
+
114
+ def get_number(
115
+ self,
116
+ service: str,
117
+ *,
118
+ country: str | int = 0,
119
+ operator: str | None = None,
120
+ ) -> OrderedNumber:
121
+ """Rent a temporary phone number.
122
+
123
+ Args:
124
+ service: Service identifier (e.g. ``wa``, ``tg``).
125
+ country: Country code or ID (defaults to 0).
126
+ operator: Specific mobile operator name.
127
+
128
+ Returns:
129
+ Dictionary with ``activation_id`` and ``phone_number``.
130
+
131
+ Examples:
132
+ >>> with SvNumber("key") as client:
133
+ ... order = client.get_number("wa", country=0)
134
+ ... print(order["phone_number"])
135
+ """
136
+ params: dict[str, Any] = {"service": service, "country": country}
137
+ if operator:
138
+ params["operator"] = operator
139
+
140
+ data = self.call("getNumber", params)
141
+ return parse_access_number(str(data))
142
+
143
+ def get_status(self, activation_id: str | int) -> ActivationStatus:
144
+ """Check current status of an activation.
145
+
146
+ Args:
147
+ activation_id: Activation ID.
148
+
149
+ Returns:
150
+ Dictionary with ``status`` (and optional ``code``).
151
+
152
+ Examples:
153
+ >>> with SvNumber("key") as client:
154
+ ... status = client.get_status(activation_id)
155
+ """
156
+ data = self.call("getStatus", {"id": activation_id})
157
+ return parse_status(str(data))
158
+
159
+ def set_status(self, activation_id: str | int, status: int) -> str:
160
+ """Update activation status.
161
+
162
+ Args:
163
+ activation_id: Activation ID.
164
+ status: Status integer (1 = ready, 6 = complete, 8 = cancel).
165
+ """
166
+ data = self.call("setStatus", {"id": activation_id, "status": status})
167
+ return str(data)
168
+
169
+ def ready(self, activation_id: str | int) -> str:
170
+ """Signal that the SMS has been requested (status=1)."""
171
+ return self.set_status(activation_id, 1)
172
+
173
+ def complete(self, activation_id: str | int) -> str:
174
+ """Finish the activation successfully (status=6)."""
175
+ return self.set_status(activation_id, 6)
176
+
177
+ def cancel(self, activation_id: str | int) -> str:
178
+ """Cancel the activation (status=8)."""
179
+ return self.set_status(activation_id, 8)
180
+
181
+ def wait_for_code(
182
+ self,
183
+ activation_id: str | int,
184
+ *,
185
+ timeout: float = 120.0,
186
+ interval: float = 3.0,
187
+ ) -> str:
188
+ """Poll get_status until an SMS code arrives or timeout expires.
189
+
190
+ Args:
191
+ activation_id: Activation ID.
192
+ timeout: Maximum wait time in seconds (defaults to 120.0).
193
+ interval: Delay between checks in seconds (defaults to 3.0).
194
+
195
+ Returns:
196
+ Extracted verification code string.
197
+ """
198
+ start_time = time.monotonic()
199
+
200
+ while True:
201
+ res = self.get_status(activation_id)
202
+ if res.get("status") == "STATUS_OK" and res.get("code"):
203
+ return res["code"]
204
+
205
+ if res.get("status") == "STATUS_CANCEL":
206
+ raise SvNumberError("STATUS_CANCEL", "Activation cancelled")
207
+
208
+ if time.monotonic() - start_time >= timeout:
209
+ raise SvNumberError("TIMEOUT", "Timed out waiting for SMS code")
210
+
211
+ time.sleep(interval)
212
+
213
+ def get_prices(self, country: str | int | None = None) -> dict[str, Any]:
214
+ """Fetch price matrix grouped by country and service."""
215
+ params = {"country": country} if country is not None else {}
216
+ data = self.call("getPrices", params)
217
+ return data if isinstance(data, dict) else {}
218
+
219
+ def get_services_and_cost(
220
+ self,
221
+ *,
222
+ country: str | int = 0,
223
+ operator: str | None = None,
224
+ service: str | None = None,
225
+ ) -> list[ServiceCostItem]:
226
+ """Get services with price and quantity for a country."""
227
+ params: dict[str, Any] = {"country": country}
228
+ if operator:
229
+ params["operator"] = operator
230
+ if service:
231
+ params["service"] = service
232
+
233
+ data = self.call("getServicesAndCost", params)
234
+ return data if isinstance(data, list) else []
@@ -0,0 +1,51 @@
1
+ """Error types for the SV Number API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ KNOWN_ERROR_CODES = {
8
+ "BAD_KEY",
9
+ "BAD_ACTION",
10
+ "BAD_SERVICE",
11
+ "BAD_STATUS",
12
+ "NO_NUMBERS",
13
+ "NO_BALANCE",
14
+ "WRONG_ACTIVATION_ID",
15
+ "STATUS_CANCEL",
16
+ "ERROR_SQL",
17
+ }
18
+
19
+
20
+ class SvNumberError(Exception):
21
+ """Exception raised for SV Number API errors.
22
+
23
+ Args:
24
+ code: Error code string (e.g. `BAD_KEY`, `NO_NUMBERS`).
25
+ raw: Raw response body, if available.
26
+ """
27
+
28
+ def __init__(self, code: str, raw: Any = None) -> None:
29
+ super().__init__(raw if isinstance(raw, str) and raw.strip() else code)
30
+ self.code = code
31
+ self.raw = raw
32
+
33
+ def __repr__(self) -> str:
34
+ return f"SvNumberError(code={self.code!r}, raw={self.raw!r})"
35
+
36
+
37
+ def assert_not_api_error(data: Any) -> None:
38
+ """Check text data for known error codes."""
39
+ if not isinstance(data, str):
40
+ return
41
+
42
+ text = data.strip()
43
+ if not text:
44
+ raise SvNumberError("EMPTY_RESPONSE", data)
45
+
46
+ code = text.split(":", 1)[0].strip() if ":" in text else text
47
+ if text.startswith("ACCESS_") or text.startswith("STATUS_"):
48
+ return
49
+
50
+ if text in KNOWN_ERROR_CODES or code in KNOWN_ERROR_CODES:
51
+ raise SvNumberError(text if text in KNOWN_ERROR_CODES else code, text)
@@ -0,0 +1,53 @@
1
+ """Response parsers for SV Number."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .errors import SvNumberError
8
+ from .types import ActivationStatus, OrderedNumber
9
+
10
+
11
+ def parse_balance(raw: Any) -> float:
12
+ """Parse balance from raw number or string."""
13
+ if isinstance(raw, (int, float)):
14
+ return float(raw)
15
+
16
+ if isinstance(raw, str):
17
+ text = raw.strip()
18
+ if text.startswith("ACCESS_BALANCE:"):
19
+ try:
20
+ return float(text.split(":", 1)[1])
21
+ except ValueError:
22
+ pass
23
+ try:
24
+ return float(text)
25
+ except ValueError:
26
+ pass
27
+
28
+ raise SvNumberError("UNEXPECTED_RESPONSE", raw)
29
+
30
+
31
+ def parse_access_number(raw: str) -> OrderedNumber:
32
+ """Parse ACCESS_NUMBER response."""
33
+ parts = str(raw).strip().split(":")
34
+ if len(parts) < 3 or parts[0] != "ACCESS_NUMBER":
35
+ raise SvNumberError("UNEXPECTED_RESPONSE", raw)
36
+
37
+ return {
38
+ "activation_id": parts[1],
39
+ "phone_number": ":".join(parts[2:]),
40
+ }
41
+
42
+
43
+ def parse_status(raw: str) -> ActivationStatus:
44
+ """Parse STATUS_ response."""
45
+ parts = str(raw).strip().split(":", 1)
46
+ status = parts[0]
47
+ if not status.startswith("STATUS_"):
48
+ raise SvNumberError("UNEXPECTED_RESPONSE", raw)
49
+
50
+ res: ActivationStatus = {"status": status}
51
+ if len(parts) > 1:
52
+ res["code"] = parts[1]
53
+ return res
@@ -0,0 +1,28 @@
1
+ """Type definitions for the SV Number API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TypedDict
6
+
7
+
8
+ class OrderedNumber(TypedDict):
9
+ """Details of an ordered virtual number."""
10
+
11
+ activation_id: str
12
+ phone_number: str
13
+
14
+
15
+ class ActivationStatus(TypedDict, total=False):
16
+ """Status of an activation."""
17
+
18
+ status: str
19
+ code: str
20
+
21
+
22
+ class ServiceCostItem(TypedDict):
23
+ """Service pricing and availability item."""
24
+
25
+ id: str
26
+ name: str
27
+ price: float
28
+ quantity: int
@@ -0,0 +1,104 @@
1
+ """Unit tests for the SV Number Python client."""
2
+
3
+ from typing import Any
4
+
5
+ import pytest
6
+ from pytest_httpx import HTTPXMock
7
+
8
+ from sv_number import REF_KEY, SvNumber, SvNumberError
9
+
10
+
11
+ def test_missing_api_key() -> None:
12
+ with pytest.raises(SvNumberError, match="API key is required"):
13
+ SvNumber("")
14
+
15
+
16
+ def test_get_balance_decimal(httpx_mock: HTTPXMock) -> None:
17
+ def callback(request: Any) -> Any:
18
+ assert request.url.params.get("ref_key") == REF_KEY
19
+ assert request.url.params.get("action") == "getBalance"
20
+ return httpx_mock.Response(status_code=200, text="18.25")
21
+
22
+ httpx_mock.add_response(
23
+ url="https://sms-verification-number.com/stubs/handler_api?api_key=test_key&ref_key=226&lang=en&action=getBalance",
24
+ method="GET",
25
+ text="18.25",
26
+ )
27
+
28
+ with SvNumber("test_key") as client:
29
+ balance = client.get_balance()
30
+ assert balance == 18.25
31
+
32
+
33
+ def test_get_balance_access_prefix(httpx_mock: HTTPXMock) -> None:
34
+ httpx_mock.add_response(
35
+ url="https://sms-verification-number.com/stubs/handler_api?api_key=test_key&ref_key=226&lang=en&action=getBalance",
36
+ method="GET",
37
+ text="ACCESS_BALANCE:25.50",
38
+ )
39
+
40
+ with SvNumber("test_key") as client:
41
+ balance = client.get_balance()
42
+ assert balance == 25.50
43
+
44
+
45
+ def test_get_number_embeds_ref_key(httpx_mock: HTTPXMock) -> None:
46
+ def callback(request: Any) -> Any:
47
+ assert request.url.params.get("ref_key") == REF_KEY
48
+ assert request.url.params.get("service") == "wa"
49
+ assert request.url.params.get("country") == "0"
50
+ return httpx_mock.Response(status_code=200, text="ACCESS_NUMBER:9988:1234567890")
51
+
52
+ httpx_mock.add_response(
53
+ url="https://sms-verification-number.com/stubs/handler_api?api_key=test_key&ref_key=226&lang=en&action=getNumber&service=wa&country=0",
54
+ method="GET",
55
+ text="ACCESS_NUMBER:9988:1234567890",
56
+ )
57
+
58
+ with SvNumber("test_key") as client:
59
+ order = client.get_number("wa", country=0)
60
+ assert order["activation_id"] == "9988"
61
+ assert order["phone_number"] == "1234567890"
62
+
63
+
64
+ def test_get_status(httpx_mock: HTTPXMock) -> None:
65
+ httpx_mock.add_response(
66
+ url="https://sms-verification-number.com/stubs/handler_api?api_key=test_key&ref_key=226&lang=en&action=getStatus&id=9988",
67
+ method="GET",
68
+ text="STATUS_OK:445566",
69
+ )
70
+
71
+ with SvNumber("test_key") as client:
72
+ status = client.get_status("9988")
73
+ assert status["status"] == "STATUS_OK"
74
+ assert status["code"] == "445566"
75
+
76
+
77
+ def test_wait_for_code(httpx_mock: HTTPXMock) -> None:
78
+ httpx_mock.add_response(
79
+ url="https://sms-verification-number.com/stubs/handler_api?api_key=test_key&ref_key=226&lang=en&action=getStatus&id=9988",
80
+ method="GET",
81
+ text="STATUS_WAIT_CODE",
82
+ )
83
+ httpx_mock.add_response(
84
+ url="https://sms-verification-number.com/stubs/handler_api?api_key=test_key&ref_key=226&lang=en&action=getStatus&id=9988",
85
+ method="GET",
86
+ text="STATUS_OK:778899",
87
+ )
88
+
89
+ with SvNumber("test_key") as client:
90
+ code = client.wait_for_code("9988", timeout=5.0, interval=0.01)
91
+ assert code == "778899"
92
+
93
+
94
+ def test_api_error_raised(httpx_mock: HTTPXMock) -> None:
95
+ httpx_mock.add_response(
96
+ url="https://sms-verification-number.com/stubs/handler_api?api_key=test_key&ref_key=226&lang=en&action=getNumber&service=tg&country=0",
97
+ method="GET",
98
+ text="NO_NUMBERS",
99
+ )
100
+
101
+ with SvNumber("test_key") as client:
102
+ with pytest.raises(SvNumberError) as exc_info:
103
+ client.get_number("tg")
104
+ assert exc_info.value.code == "NO_NUMBERS"
@@ -0,0 +1,20 @@
1
+ """Live API tests for SV Number."""
2
+
3
+ import os
4
+
5
+ import pytest
6
+
7
+ from sv_number import SvNumber
8
+
9
+ api_key = os.environ.get("SV_NUMBER_API_KEY")
10
+
11
+
12
+ @pytest.mark.live
13
+ @pytest.mark.skipif(
14
+ not api_key,
15
+ reason="SV_NUMBER_API_KEY environment variable not set",
16
+ )
17
+ def test_live_balance() -> None:
18
+ with SvNumber(api_key) as client:
19
+ balance = client.get_balance()
20
+ assert isinstance(balance, float)
@@ -0,0 +1,295 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.10"
4
+ resolution-markers = [
5
+ "python_full_version >= '3.11'",
6
+ "python_full_version < '3.11'",
7
+ ]
8
+
9
+ [[package]]
10
+ name = "anyio"
11
+ version = "4.15.1"
12
+ source = { registry = "https://pypi.org/simple" }
13
+ dependencies = [
14
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
15
+ { name = "idna" },
16
+ { name = "typing-extensions", marker = "python_full_version < '3.15'" },
17
+ ]
18
+ sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
19
+ wheels = [
20
+ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
21
+ ]
22
+
23
+ [[package]]
24
+ name = "certifi"
25
+ version = "2026.10.23"
26
+ source = { registry = "https://pypi.org/simple" }
27
+ sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" }
28
+ wheels = [
29
+ { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" },
30
+ ]
31
+
32
+ [[package]]
33
+ name = "colorama"
34
+ version = "0.4.6"
35
+ source = { registry = "https://pypi.org/simple" }
36
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
37
+ wheels = [
38
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
39
+ ]
40
+
41
+ [[package]]
42
+ name = "exceptiongroup"
43
+ version = "1.3.1"
44
+ source = { registry = "https://pypi.org/simple" }
45
+ dependencies = [
46
+ { name = "typing-extensions" },
47
+ ]
48
+ sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
49
+ wheels = [
50
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
51
+ ]
52
+
53
+ [[package]]
54
+ name = "h11"
55
+ version = "0.16.0"
56
+ source = { registry = "https://pypi.org/simple" }
57
+ sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
58
+ wheels = [
59
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
60
+ ]
61
+
62
+ [[package]]
63
+ name = "httpcore"
64
+ version = "1.0.9"
65
+ source = { registry = "https://pypi.org/simple" }
66
+ dependencies = [
67
+ { name = "certifi" },
68
+ { name = "h11" },
69
+ ]
70
+ sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
71
+ wheels = [
72
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
73
+ ]
74
+
75
+ [[package]]
76
+ name = "httpx"
77
+ version = "0.28.1"
78
+ source = { registry = "https://pypi.org/simple" }
79
+ dependencies = [
80
+ { name = "anyio" },
81
+ { name = "certifi" },
82
+ { name = "httpcore" },
83
+ { name = "idna" },
84
+ ]
85
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
86
+ wheels = [
87
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
88
+ ]
89
+
90
+ [[package]]
91
+ name = "idna"
92
+ version = "3.20"
93
+ source = { registry = "https://pypi.org/simple" }
94
+ sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" }
95
+ wheels = [
96
+ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
97
+ ]
98
+
99
+ [[package]]
100
+ name = "iniconfig"
101
+ version = "2.3.1"
102
+ source = { registry = "https://pypi.org/simple" }
103
+ sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" }
104
+ wheels = [
105
+ { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" },
106
+ ]
107
+
108
+ [[package]]
109
+ name = "packaging"
110
+ version = "26.3"
111
+ source = { registry = "https://pypi.org/simple" }
112
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
113
+ wheels = [
114
+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
115
+ ]
116
+
117
+ [[package]]
118
+ name = "pluggy"
119
+ version = "1.7.0"
120
+ source = { registry = "https://pypi.org/simple" }
121
+ sdist = { url = "https://files.pythonhosted.org/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" }
122
+ wheels = [
123
+ { url = "https://files.pythonhosted.org/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" },
124
+ ]
125
+
126
+ [[package]]
127
+ name = "pygments"
128
+ version = "2.21.0"
129
+ source = { registry = "https://pypi.org/simple" }
130
+ sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
131
+ wheels = [
132
+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
133
+ ]
134
+
135
+ [[package]]
136
+ name = "pytest"
137
+ version = "9.1.1"
138
+ source = { registry = "https://pypi.org/simple" }
139
+ dependencies = [
140
+ { name = "colorama", marker = "sys_platform == 'win32'" },
141
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
142
+ { name = "iniconfig" },
143
+ { name = "packaging" },
144
+ { name = "pluggy" },
145
+ { name = "pygments" },
146
+ { name = "tomli", marker = "python_full_version < '3.11'" },
147
+ ]
148
+ sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
149
+ wheels = [
150
+ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
151
+ ]
152
+
153
+ [[package]]
154
+ name = "pytest-httpx"
155
+ version = "0.36.2"
156
+ source = { registry = "https://pypi.org/simple" }
157
+ dependencies = [
158
+ { name = "httpx" },
159
+ { name = "pytest" },
160
+ ]
161
+ sdist = { url = "https://files.pythonhosted.org/packages/4e/42/f53c58570e80d503ade9dd42ce57f2915d14bcbe25f6308138143950d1d6/pytest_httpx-0.36.2.tar.gz", hash = "sha256:05a56527484f7f4e8c856419ea379b8dc359c36801c4992fdb330f294c690356", size = 57683, upload-time = "2026-04-09T13:57:19.837Z" }
162
+ wheels = [
163
+ { url = "https://files.pythonhosted.org/packages/1e/55/1fa65f8e4fceb19dd6daa867c162ad845d547f6058cd92b4b02384a44777/pytest_httpx-0.36.2-py3-none-any.whl", hash = "sha256:d42ebd5679442dc7bfb0c48e0767b6562e9bc4534d805127b0084171886a5e22", size = 20315, upload-time = "2026-04-09T13:57:18.587Z" },
164
+ ]
165
+
166
+ [[package]]
167
+ name = "ruff"
168
+ version = "0.17.2"
169
+ source = { registry = "https://pypi.org/simple" }
170
+ sdist = { url = "https://files.pythonhosted.org/packages/ae/58/690cf56da80c8f2c8bf12042b6017beac2c34fe961f65420bcb3fb7fac38/ruff-0.17.2.tar.gz", hash = "sha256:84d632402d14c23b488f941db23712d6736a013225162062d34558c3f3c780eb", size = 5333400, upload-time = "2026-10-22T18:38:00.181Z" }
171
+ wheels = [
172
+ { url = "https://files.pythonhosted.org/packages/98/b8/8b6d69c57e8fe1cc6af1aa7493b72a1a5b5a8afe34694e302025441729d4/ruff-0.17.2-py3-none-linux_armv6l.whl", hash = "sha256:96a65e755d8dba094707fb23ab602111d9a31ec2835b3a6992a1914a8fcb211e", size = 10183857, upload-time = "2026-10-22T18:37:14.956Z" },
173
+ { url = "https://files.pythonhosted.org/packages/e8/61/12cc2ff7b2fa90a268ce1e2162c23aaf76e601d22623f6a31b0eeaa764b0/ruff-0.17.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:39c5b55c960515cdb32be16e69efb5734c589c06a0eb38a22694524a336b889d", size = 10210904, upload-time = "2026-10-22T18:37:17.985Z" },
174
+ { url = "https://files.pythonhosted.org/packages/e8/7c/b8850a8e4ad29f64a827dd6c6902db4a9aa538fb2b9eef4e26084001a3bb/ruff-0.17.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:92a80d2f44584dabb0189b3a6c393e37e525e04942838482010575cb81945be7", size = 9408439, upload-time = "2026-10-22T18:37:20.964Z" },
175
+ { url = "https://files.pythonhosted.org/packages/b7/b6/6a64fae86ca5e5e29d5adc311e5481de726cedf9aee607435a16c31c442a/ruff-0.17.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7d0906677d89bbf3cf73cde64b5bbe2ab35df3ae43e49ee2627301e91265bac", size = 10047078, upload-time = "2026-10-22T18:37:23.717Z" },
176
+ { url = "https://files.pythonhosted.org/packages/10/19/23d376547715175aa1bd46df48df6aacc7c6439d9daf19cac15975180aa5/ruff-0.17.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cfd783590edf2b77317882631900f6a13ea2e8eacefe6ebf2370d2715299160", size = 9990478, upload-time = "2026-10-22T18:37:26.584Z" },
177
+ { url = "https://files.pythonhosted.org/packages/93/6c/34cda32cbe13adac5c6b291a307a5570d8ebd37c0c86339f0fe628e55632/ruff-0.17.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122258f6ecde0e0dd1bb60b24263c3edde4338caf723fca711cc729f9bfd414a", size = 10673624, upload-time = "2026-10-22T18:37:29.767Z" },
178
+ { url = "https://files.pythonhosted.org/packages/bf/10/5d762a4dbc851d8bb47863cf3a28a61bf31c804d393b70d891fac899f5d1/ruff-0.17.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f8fba429d6ac4128ab29cfb8fa6240d2fa671d04adf7f84d3b22fc7b6b6bea4", size = 11351797, upload-time = "2026-10-22T18:37:32.535Z" },
179
+ { url = "https://files.pythonhosted.org/packages/3f/04/a0e92a81faf5c81c1c98d4374c8401734ccf2b8cca7d0170cb0fec08c6e2/ruff-0.17.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:583bd67bb9e7061dd09bc46a0a884bae2bbd214dbcd48e509813612619683869", size = 10949414, upload-time = "2026-10-22T18:37:35.501Z" },
180
+ { url = "https://files.pythonhosted.org/packages/c3/d8/59894cf630d2d86882457a2a6a0a1533410c5c805bfbac6f6eb4b5aef69d/ruff-0.17.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c60f93fec7142dba5b8f487f94bf2f6231dae24dc11e4c48648347c8b0720004", size = 10528000, upload-time = "2026-10-22T18:37:37.88Z" },
181
+ { url = "https://files.pythonhosted.org/packages/ab/95/d6e30c51e851d37e10762ad91fa1e76ddaad998589b5667962a3cfbc9edd/ruff-0.17.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c555a877f8f41447e318101cfefea9bf12dfa9f82eedf90c376e57e069f917d0", size = 10571779, upload-time = "2026-10-22T18:37:40.444Z" },
182
+ { url = "https://files.pythonhosted.org/packages/f4/5a/a7e1d74fb464250137d3d4eca645d323536533b672004d04ee0e46e0caf3/ruff-0.17.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ba405cbd5ec7bda1f555d5e52be1fd68d02608d4d95d24591bdac92cfacb579f", size = 10051707, upload-time = "2026-10-22T18:37:42.812Z" },
183
+ { url = "https://files.pythonhosted.org/packages/6e/f2/02fca29ca7999380f0350a7212fe755c11ab9c22d61327356cce8decdbc6/ruff-0.17.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8c21b4ab24485358f17291a4f8dc5399b834e963b8efedd920fe4fdb02081bb4", size = 9998227, upload-time = "2026-10-22T18:37:45.224Z" },
184
+ { url = "https://files.pythonhosted.org/packages/d1/00/55aa604a093dfdc1526e570fae0bb7d37a2221c7b4728d5f080b2add16ba/ruff-0.17.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5875a16b0de70b43cb81eabea5ae5e8f5d1b2fd1b5e6ed07f80cfe5019d0bd5a", size = 10377692, upload-time = "2026-10-22T18:37:47.704Z" },
185
+ { url = "https://files.pythonhosted.org/packages/e4/5a/a7a640f47bb906ec8fa233ea84319d503e6dee02b59e3901bd312ebd1f9b/ruff-0.17.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2d086be452eed686aa51271ecf2cbe5037956517cc6ee39a17b4489ac12f8bbe", size = 10790587, upload-time = "2026-10-22T18:37:50.191Z" },
186
+ { url = "https://files.pythonhosted.org/packages/2c/d4/c3b74e1b4808438ea7f14bb914478025e2d51261f60774a54a0fa222ec46/ruff-0.17.2-py3-none-win32.whl", hash = "sha256:6bce7d1f5165701ae18f5df85c65afe78cddfa6c5fb4b9bdb0014d60388f3edd", size = 9870537, upload-time = "2026-10-22T18:37:52.486Z" },
187
+ { url = "https://files.pythonhosted.org/packages/1c/9a/c7da96c2c96d20704cf9027f7a5121da272a8c05f1d1531ba949e3b3df04/ruff-0.17.2-py3-none-win_amd64.whl", hash = "sha256:1b59d447f197bef9ef48a22ba1a6016137753c6099e9bac9c36eb566e422424c", size = 10282826, upload-time = "2026-10-22T18:37:55.2Z" },
188
+ { url = "https://files.pythonhosted.org/packages/81/60/a3604c79cb02777b6cb84da894104021986f06c4bc41375102cc95049a32/ruff-0.17.2-py3-none-win_arm64.whl", hash = "sha256:195ad918027f817c119310ca89a42d347887dfc28934aaa869325e9c5dc9d962", size = 9827036, upload-time = "2026-10-22T18:37:58.031Z" },
189
+ ]
190
+
191
+ [[package]]
192
+ name = "sv-number"
193
+ version = "0.2.0"
194
+ source = { editable = "." }
195
+ dependencies = [
196
+ { name = "httpx" },
197
+ ]
198
+
199
+ [package.dev-dependencies]
200
+ dev = [
201
+ { name = "pytest" },
202
+ { name = "pytest-httpx" },
203
+ { name = "ruff" },
204
+ ]
205
+
206
+ [package.metadata]
207
+ requires-dist = [{ name = "httpx", specifier = ">=0.27" }]
208
+
209
+ [package.metadata.requires-dev]
210
+ dev = [
211
+ { name = "pytest", specifier = ">=8" },
212
+ { name = "pytest-httpx", specifier = ">=0.30" },
213
+ { name = "ruff", specifier = ">=0.8" },
214
+ ]
215
+
216
+ [[package]]
217
+ name = "tomli"
218
+ version = "2.5.0"
219
+ source = { registry = "https://pypi.org/simple" }
220
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/78/9ad63712633ed3ab5cc1a648d863d7e7da371e9425e209555a0fe711b695/tomli-2.5.0.tar.gz", hash = "sha256:264507556cd8b8c8e7c6ee037cdf443a463f03f4c958e57195e3d369711b8ff6", size = 17662, upload-time = "2026-10-07T12:23:37.892Z" }
221
+ wheels = [
222
+ { url = "https://files.pythonhosted.org/packages/22/a6/ab99b60ee52acd949684febabc3005d0045d0f66bebd9cdebd67372d26dd/tomli-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4dc1c1781f2f716de763d1e9a7b34c6a894e167e291c7c5d16c72f7a9538545", size = 163901, upload-time = "2026-10-07T12:22:15.601Z" },
223
+ { url = "https://files.pythonhosted.org/packages/bc/00/ee01b7ed4579180fff07142d290257f25ba786f23f3ec6005f620933c2f5/tomli-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eff8babca5a7999bc137acbc7482a8b7e17ffca5075ab41f5d770ab408c7bfef", size = 163756, upload-time = "2026-10-07T12:22:16.957Z" },
224
+ { url = "https://files.pythonhosted.org/packages/72/c2/4efebf65372f6583185f79799312109dddb61102d47e5c33dcfd1a297aca/tomli-2.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86665cee9c4835b7a7f1e8ec2c719b5258d4dc782887aded5a8ae7352a96843b", size = 268038, upload-time = "2026-10-07T12:22:18.135Z" },
225
+ { url = "https://files.pythonhosted.org/packages/53/07/5850468e925d898abb36038666f9c333a94d2a223e802a8ba5b6d319d23f/tomli-2.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e369fd63331746182360977b1892bfc215476a30d61612d732425311639f56", size = 276422, upload-time = "2026-10-07T12:22:19.567Z" },
226
+ { url = "https://files.pythonhosted.org/packages/b4/87/f293984cdcf83c054196d4fd3dad44fc68ae55b4b8c44bc76cef360c3150/tomli-2.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ad1ea345759240d6463efa0ed1c704402752e49aa21476620738d74d72d8aa1", size = 272616, upload-time = "2026-10-07T12:22:20.794Z" },
227
+ { url = "https://files.pythonhosted.org/packages/ce/ce/db582886b3c1219d3fec93ebd669332482e5aee7a91e0f7838d84f2d1759/tomli-2.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96243987194634bd411066ce40c952e108f86af04db533ecd8ac3ff2a85b1885", size = 276593, upload-time = "2026-10-07T12:22:22.12Z" },
228
+ { url = "https://files.pythonhosted.org/packages/bf/72/7619b87dea4261fc27dd7b54c4461c129c1f7d9bb7ba3aec89c797a431b8/tomli-2.5.0-cp311-cp311-win32.whl", hash = "sha256:610b27d99f28ec5f191c7064a48f3ddb179a1fe6ca73d571483ae859f57b605e", size = 101830, upload-time = "2026-10-07T12:22:23.651Z" },
229
+ { url = "https://files.pythonhosted.org/packages/1e/74/220106da34502304b6751a2a9b8a9fbca6c3fd47e737a2e2e3da7c61c9db/tomli-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c804ae44fe7b4bab5da295e4f980a1ff04670bca9d23fe0a4e887e08ebd741a8", size = 112742, upload-time = "2026-10-07T12:22:24.972Z" },
230
+ { url = "https://files.pythonhosted.org/packages/27/99/7d9c8b41837a7773613e169504147375c157a290167aa59ad74a085f521f/tomli-2.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:cfac177ebd6236003846ea339981f71457cb6eb748f23381eb257e45092e3980", size = 109332, upload-time = "2026-10-07T12:22:26.117Z" },
231
+ { url = "https://files.pythonhosted.org/packages/52/ed/7baa86f87493646a594de388c7c1c40a39dd0461f7e9c0359cbeefc91fe8/tomli-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f4a40d03fb9f63424f0979855bdeaf44dd7696b8d59501822c10ed30ba532df", size = 164854, upload-time = "2026-10-07T12:22:27.444Z" },
232
+ { url = "https://files.pythonhosted.org/packages/a5/b1/44c0341f2224397855723c7a8a39f718ea6fcbcc3dacc66e5aeca0f334e3/tomli-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ebf8d19b17bd0daeb7b7dec81a946a439b753942fd0210d6e96c532249eea6b", size = 164074, upload-time = "2026-10-07T12:22:28.679Z" },
233
+ { url = "https://files.pythonhosted.org/packages/23/04/e2d5b7d3fba47adedb23de616c16d428ea076c79a3d8e1d95d649ffe197e/tomli-2.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf0b5e8e0f68ebb494356e577c06c139161efd8d3b9050f93b39b7c26cc54ff0", size = 274274, upload-time = "2026-10-07T12:22:29.804Z" },
234
+ { url = "https://files.pythonhosted.org/packages/43/90/6090e706ff27a6f89f4a40578e3324b95c3cd8c4150868aabf33a8f414c3/tomli-2.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cf74416bdc94ae458b14e37286c1073081850ac8459a00d0c5efef5d44294c6", size = 286435, upload-time = "2026-10-07T12:22:31.297Z" },
235
+ { url = "https://files.pythonhosted.org/packages/0a/9e/a2c40768df16c408f22430afb0a73e9d7e5f79c950884954649d1146b74d/tomli-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:61ea1ebe1e55a34ea8199cc8dbff398d35027b82271c8ac4802fd3a1fd5b1bcc", size = 278119, upload-time = "2026-10-07T12:22:32.601Z" },
236
+ { url = "https://files.pythonhosted.org/packages/12/25/3c0cb485b98e9cfac495629b1c93c87ccf0b72fbe9d2689fd8fe62c6d5a3/tomli-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed53f7e89bb04f6d9e8e7799112360b0c4d5cbff067de0814c98c37c39b920f7", size = 286177, upload-time = "2026-10-07T12:22:33.745Z" },
237
+ { url = "https://files.pythonhosted.org/packages/77/8b/0144c65f0e37e51c18d04ae15c21b19431c165002d0131fe9aa8b0b8b1e8/tomli-2.5.0-cp312-cp312-win32.whl", hash = "sha256:e7ad033e27a516a233bea839cdb77b80146facb3b4f40bf02cd0cac165cdd5c2", size = 102760, upload-time = "2026-10-07T12:22:34.887Z" },
238
+ { url = "https://files.pythonhosted.org/packages/de/32/5d6d8f42fc9a05fce69354e00ff256484192f5f2fc9a2165718fa0de61ec/tomli-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd05de8c1698f8413dd7d869492693a0bf2211543b787ac78cd5e7536af1a6d7", size = 112722, upload-time = "2026-10-07T12:22:36.162Z" },
239
+ { url = "https://files.pythonhosted.org/packages/30/65/df18032218db0fb9b769fb23c8039a051f15c811993995ea04c350273a32/tomli-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:069435bd5480429b98c5e5afb02ab21c219b6f0064680671c6dc0d46817346ea", size = 109534, upload-time = "2026-10-07T12:22:37.296Z" },
240
+ { url = "https://files.pythonhosted.org/packages/42/e5/51736d70da209350969e15aca5c5ab6e2ce1ea87a0a892a6c13aec172a86/tomli-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:943276cf269e0071948d9ff697159c1735e623c1151d88abb09b74659ef0cbea", size = 163328, upload-time = "2026-10-07T12:22:38.373Z" },
241
+ { url = "https://files.pythonhosted.org/packages/ec/55/086f80dab4ab497602644274e6dea7ec5dd0b4e262e443a8ad3bb7edee2d/tomli-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:463b16086865b97facd8d0b3fb4cb7c544e3f58d2a69dc3113d6db9653fdb043", size = 162246, upload-time = "2026-10-07T12:22:39.673Z" },
242
+ { url = "https://files.pythonhosted.org/packages/aa/eb/3ecc94459f3635c92321f4e7bde571323fdb2267c50e19e3188a281eae3b/tomli-2.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1245a6638fc4bb0a60af38a7d45413db34a13842027c77597c712c998c62fdf0", size = 272655, upload-time = "2026-10-07T12:22:41.08Z" },
243
+ { url = "https://files.pythonhosted.org/packages/c0/d7/494fd1f0c37a621f1ad9975c2efadb523e8101f144ed6edb2e7fe64738f2/tomli-2.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d8bac3d603c97e6854424e5b2b5b741bdbde387e09f162fb0446812b4a8362b", size = 283595, upload-time = "2026-10-07T12:22:42.222Z" },
244
+ { url = "https://files.pythonhosted.org/packages/70/51/bb8d62b1317e6640866f6949b2d5855e5300f2c99d46de1cd245570bba65/tomli-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:21e4cae4114aba25aa0d4f85cdf486d290fb35c0954d7bba536248da64d43066", size = 276253, upload-time = "2026-10-07T12:22:43.625Z" },
245
+ { url = "https://files.pythonhosted.org/packages/66/f4/f46bd7f0763cd47de2db697dca9257c6a4adfd1a93b018cc75c8190ed5a8/tomli-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bbaefc84548d754be821bba7c4141c4787dda182f9e77f2f87b71213529efa7b", size = 283582, upload-time = "2026-10-07T12:22:44.983Z" },
246
+ { url = "https://files.pythonhosted.org/packages/ac/03/70f2bcb2923a6db37818d917e124270a7f4cfd38ea576f5aa753a91c0ef5/tomli-2.5.0-cp313-cp313-win32.whl", hash = "sha256:abdbf6313b8d9efe157edeb7ab6eae4de064b1300ad31abf73755154b30abe68", size = 102628, upload-time = "2026-10-07T12:22:46.508Z" },
247
+ { url = "https://files.pythonhosted.org/packages/dc/98/d52024bb5b0ff68b4f0d276d867f634c84a67319a7e9f6b7708a37742333/tomli-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd4dc129784e0c5335bd4e61dfcc4487499a013419e655cf2da1d091b7e0efdc", size = 113301, upload-time = "2026-10-07T12:22:47.647Z" },
248
+ { url = "https://files.pythonhosted.org/packages/6f/f2/540db3a70572a8c23a28aba3e9c358ce0ffffbafc990905c1343aa265b31/tomli-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:69491c143d2fe063046e0301e62a810bed338fa4d1ce0fd870c27dc1e09b0d84", size = 109744, upload-time = "2026-10-07T12:22:48.925Z" },
249
+ { url = "https://files.pythonhosted.org/packages/e4/49/caf6b307766eb9567664a8707e9d6be5fcc0e8903f18781c6677a60d80c7/tomli-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d3182ee2d887e507bd67319a0a61105d1dd33facc111329559a233b772c1a105", size = 162899, upload-time = "2026-10-07T12:22:50.088Z" },
250
+ { url = "https://files.pythonhosted.org/packages/d3/c8/68cfce773a2733a49c74f99d627fb461bd990756860099eac25617889585/tomli-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:521345fd1f19d45b8df87657aaa38b6f2ca3800059fadf428e7ebf479a383646", size = 162080, upload-time = "2026-10-07T12:22:51.558Z" },
251
+ { url = "https://files.pythonhosted.org/packages/7e/b2/e5bb8651fdad593f670501a7d718b1a7f73f064d44dea15e04c04dfef45d/tomli-2.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95c7614e705bfe2b04b27aa124adec59752d15813df37e2156747cab3a006b", size = 273380, upload-time = "2026-10-07T12:22:52.918Z" },
252
+ { url = "https://files.pythonhosted.org/packages/8d/d2/9e2d7f8b1dfe0e2b34c245986ebd55c4c553ea4ce6c47c443b332673253f/tomli-2.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac2027d37c3afbdf4bdd377f2676f6f1d2122a5be1f1137b49dced590b37e75", size = 283228, upload-time = "2026-10-07T12:22:54.173Z" },
253
+ { url = "https://files.pythonhosted.org/packages/ba/df/ec7b876b7b1a2718bd74a3743c076fff565b04029ba33e8f61fac262739f/tomli-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c414be4ed9d3cac80c42e348fa5a956117d1a48227f48026e31f59cb4a7671eb", size = 277189, upload-time = "2026-10-07T12:22:55.342Z" },
254
+ { url = "https://files.pythonhosted.org/packages/7d/7b/e192d9eed0b9cb80da799f4d77052297fb9a2c3cc9b19f571f56ea88add6/tomli-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9b03d7dc168353b4132965bde20feceabaa470e570c6f59660dfae59b1f9eeb3", size = 283632, upload-time = "2026-10-07T12:22:56.735Z" },
255
+ { url = "https://files.pythonhosted.org/packages/84/50/ff94454e75461d75623e47401ed323d65c10aab8fe9033242c20cd2fdf32/tomli-2.5.0-cp314-cp314-win32.whl", hash = "sha256:6f041843c4d3a37245c0c056fd955b186bf8b1fb85690cbe40b81230891dc34b", size = 103535, upload-time = "2026-10-07T12:22:58.084Z" },
256
+ { url = "https://files.pythonhosted.org/packages/54/0b/bdacf05f963bd6026ebf6eeb0beda847d1d60e03e440725c64a4e08a0afd/tomli-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:f4b653094e18f9031102d3a1da5c729c8f222d85225b18037dac621695e46e1a", size = 114621, upload-time = "2026-10-07T12:22:59.2Z" },
257
+ { url = "https://files.pythonhosted.org/packages/61/99/53f438fa6ae4f9d4ed0ddde3e7242b3bdc34b48c8f9948b72b9e9b127676/tomli-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3f89d10c1ff6a38d992c27fc8a4816af71a909e08a40ec66934240b1e74347c3", size = 111572, upload-time = "2026-10-07T12:23:00.479Z" },
258
+ { url = "https://files.pythonhosted.org/packages/b9/20/1f88f19427d380a40e90a770e087489eaafe4aeee070ae88ed2bbec00acd/tomli-2.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e9e15b4a6c7dd6b85b5fbab29488a73f1f70de516942308daa266bf0e0aeb0d4", size = 171814, upload-time = "2026-10-07T12:23:01.914Z" },
259
+ { url = "https://files.pythonhosted.org/packages/d0/56/cbe5079c9f9a54b9b3e27fc82f08f3cb36edee75561679f53d2380c801d6/tomli-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e12bbcd32897272fb05929110362ae9ff4c1b9bb26bd9e971e71dcd3275b4c3d", size = 171324, upload-time = "2026-10-07T12:23:03.18Z" },
260
+ { url = "https://files.pythonhosted.org/packages/2b/30/1d53fd3b0f1cb3ba542e345ec32c26aefdddc4e829e4f3429af8a4f27782/tomli-2.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20aa36de8f2cf87237143bc1fa1aae8d6612c09118f4da21c6a684db5dd1f6f9", size = 297441, upload-time = "2026-10-07T12:23:04.345Z" },
261
+ { url = "https://files.pythonhosted.org/packages/66/d9/0800acb6a111686f764c1b91ef15cc42a20a66a46013bb42220f1d2c61c1/tomli-2.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22185fad8a1e622f064e78008018a0dd3323550dcb479cb7a1d296888d74024f", size = 307476, upload-time = "2026-10-07T12:23:05.671Z" },
262
+ { url = "https://files.pythonhosted.org/packages/e8/63/30a8f3cd51b5bec37f04744bad0b0dc6160df84aad4f27b0e9283d66f221/tomli-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:984012f71908165449a951de2050d52f276bfe3aa5d5f570f63ddad814370374", size = 296113, upload-time = "2026-10-07T12:23:07.202Z" },
263
+ { url = "https://files.pythonhosted.org/packages/ab/18/0b9ffc597e69c5a1e20a7823cb60d54b39a9f54e91edcb8574f022186758/tomli-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f79203b3965b4000e91808aaa7c040206093f2b8bf86f455982f2274c9ccf442", size = 307725, upload-time = "2026-10-07T12:23:08.508Z" },
264
+ { url = "https://files.pythonhosted.org/packages/ab/c7/18f8baae0b5607a60e8e19b4a7fedee43a8ff6458e3896dcbbadeeac9c22/tomli-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:91294a9fb94a75542f6e46e4a2ae709bd8d9b51134098cae5cf3bea5478b6d03", size = 108546, upload-time = "2026-10-07T12:23:09.956Z" },
265
+ { url = "https://files.pythonhosted.org/packages/72/34/4cca9739254130627bde87500b3f2b512154fe2f278efa7e2a5e10ad4bcb/tomli-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f15e3e0b835a6d68b10c86bf80a3149780498d6911c93c3ffd1861d19f9200f1", size = 117814, upload-time = "2026-10-07T12:23:11.486Z" },
266
+ { url = "https://files.pythonhosted.org/packages/7d/fb/afa530d47dd80a78fce43beac6bc6e00f84558eafcffbc6f37b21e80d056/tomli-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6664b7ae7af7294256c53960a6103077f4914cec8ff98479c352f622c6f6b2f0", size = 115188, upload-time = "2026-10-07T12:23:12.728Z" },
267
+ { url = "https://files.pythonhosted.org/packages/66/98/316fdc00f8c0939e6fe50461dd343c162d3ad51d1286eb25b7db54361d50/tomli-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a525685c2f97da40762b8695eb7aa0af4c8344ca1905c73e4e29cb04d34607dc", size = 162775, upload-time = "2026-10-07T12:23:13.941Z" },
268
+ { url = "https://files.pythonhosted.org/packages/c5/22/7b10fa5bb01c9539f53f69b619361b19350acc73657772ea7ac70ba309a8/tomli-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9dbb18c1cfb2f6517942fc9314437f66aa06d94436ffb1f06102ef3572f35276", size = 161406, upload-time = "2026-10-07T12:23:15.215Z" },
269
+ { url = "https://files.pythonhosted.org/packages/9c/e7/1a069d86dfd20f1f84f71c63faed9f83c1d890bc06c27d82dc7d888fb573/tomli-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:752e8b1aa6a4367ef8bf6a1a1e005540f7ed055ba36d7193796812ca5404eb52", size = 273855, upload-time = "2026-10-07T12:23:16.471Z" },
270
+ { url = "https://files.pythonhosted.org/packages/ae/83/d1ef43d1687d092ab9c235455c76e6e709483b346b056f086095c7c263a5/tomli-2.5.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47300f9bf791808f77d82747691c4bb09cb14bdf3060cca99b42cdc4361d5a7", size = 284910, upload-time = "2026-10-07T12:23:18.166Z" },
271
+ { url = "https://files.pythonhosted.org/packages/cc/05/f4d9cf7de61822ece0c3873f30d291e324911c71a378b8bfe5ced13fd9f5/tomli-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:19b0dd8749f4ea2f112c5fcfb3c5248390c899d7e2e173f1d91abee1fa0ff391", size = 277723, upload-time = "2026-10-07T12:23:19.355Z" },
272
+ { url = "https://files.pythonhosted.org/packages/42/28/78262493141fa543151cf005760c3cb01d09fc28a11f993c05109902cb8c/tomli-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:57b1c3b01fab802e2899bc3d168dca320e14165e2fd9fd584760fb4ca5826859", size = 285115, upload-time = "2026-10-07T12:23:20.698Z" },
273
+ { url = "https://files.pythonhosted.org/packages/1a/b9/e1dab9a30bcb677b5cc5cee810609cfd64f24306a3055767dd3fda00b1e0/tomli-2.5.0-cp315-cp315-win32.whl", hash = "sha256:667e521b37a6c5ccaa044202c235b530f90177ffe2cd4a64ecc213c7dd535feb", size = 103475, upload-time = "2026-10-07T12:23:21.941Z" },
274
+ { url = "https://files.pythonhosted.org/packages/4c/bd/31a3790c11d6ea95fcf5e6022ac0f8d0543c9b61120b730fc481bd43d3b4/tomli-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:d747252933c8a65ef6bd8da0fbb7ce28a90eb6119d8cd00772cd528aa07b68d5", size = 114589, upload-time = "2026-10-07T12:23:23.098Z" },
275
+ { url = "https://files.pythonhosted.org/packages/47/a2/4f6310fa699364f0e3af7ee3af88dddd9af066d33e716a0265bbe2b3ea84/tomli-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:75dbcde8751b0a960aa3de173aa5e894d590755c6d7758b7e774c06f1dc3cbdd", size = 111493, upload-time = "2026-10-07T12:23:24.233Z" },
276
+ { url = "https://files.pythonhosted.org/packages/68/14/00853f0b396d8971107ae1921bb5b322fdee1650d2f16bf06c20adb532e5/tomli-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2419c2a189551987b59d80e63ec355671283336f41c6b9b89462df679c7d0c57", size = 171380, upload-time = "2026-10-07T12:23:25.512Z" },
277
+ { url = "https://files.pythonhosted.org/packages/89/ad/fa6949321dadee46b27363974fb197b94c911c3b0f7a5fd26d7dc18fc2a0/tomli-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dc598040da8d42cf20f0be588ed7004f46db12a0ac6c32e03a59dccedaaadcd", size = 170553, upload-time = "2026-10-07T12:23:26.855Z" },
278
+ { url = "https://files.pythonhosted.org/packages/53/aa/3056c919eb3e084df3752b2cf5f865dcc04af0b27dba2f66d7b28af4633a/tomli-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49096930c8d886c9bbdab62d2d0d17ce823ddeea522309a190b36245d5b49e01", size = 294428, upload-time = "2026-10-07T12:23:28.132Z" },
279
+ { url = "https://files.pythonhosted.org/packages/96/b2/faeeb5d8769ea3832021d73e892c8391eae7b4b4f8b55a789127bd8b18a9/tomli-2.5.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8ade5023067f99fe72b88accd30d0ea05a158e9e32a11f124e731ea9695313f", size = 304909, upload-time = "2026-10-07T12:23:29.381Z" },
280
+ { url = "https://files.pythonhosted.org/packages/f6/52/f094c09e73fb654b621716d019acb5d29bdfd1be01df80c281d552bda48d/tomli-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b69564772b5c8f22ea5f498dff08cfa825045b4d4c4400529000bdf818aa3b2a", size = 293220, upload-time = "2026-10-07T12:23:30.608Z" },
281
+ { url = "https://files.pythonhosted.org/packages/86/f5/0c30541078ca4b505ce3bd76ed931facbfec524dd018535d691d1af0a6d2/tomli-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8ff3a2ca028c7eee0c777f9a092038d0a594a9fa04e215f929a22c329e2cb142", size = 305705, upload-time = "2026-10-07T12:23:32.181Z" },
282
+ { url = "https://files.pythonhosted.org/packages/05/74/590e7d19d6a118fc5cc5704ff358e21d95b8573f6b9443b1519f29ca8825/tomli-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:62fc1bc8eb03e3a9cadfca713d65614ed8e09d974a283295ffe3a831976b4dc5", size = 108432, upload-time = "2026-10-07T12:23:33.496Z" },
283
+ { url = "https://files.pythonhosted.org/packages/1c/b8/63a75cfb27a17c38550e44025d3a6e7be64516fd8608a3b75703bf37d81b/tomli-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f3fcbc57b1791fa6cbe5d8434179d51de12be1a4811469529f47f6e7487a2571", size = 117281, upload-time = "2026-10-07T12:23:34.648Z" },
284
+ { url = "https://files.pythonhosted.org/packages/72/01/e8c1debb2173973372934c68fc8e46170ab60ef23ed4592dff4dec6e8993/tomli-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d2ba24db8a9376921b5e87b4762b9adb0f3f1deaea68f2b8b0bb2c11efb9c3e7", size = 115069, upload-time = "2026-10-07T12:23:35.77Z" },
285
+ { url = "https://files.pythonhosted.org/packages/60/3f/3e3f8fd0919249b0200c80fbc4f9a1e70be19f9883da71dfb7f8b9ab8aca/tomli-2.5.0-py3-none-any.whl", hash = "sha256:32a7b79ac57a2e83670ce329ccf675798bc5a2094783a63676866b70503f2e2b", size = 14765, upload-time = "2026-10-07T12:23:36.875Z" },
286
+ ]
287
+
288
+ [[package]]
289
+ name = "typing-extensions"
290
+ version = "4.16.0"
291
+ source = { registry = "https://pypi.org/simple" }
292
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
293
+ wheels = [
294
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
295
+ ]