salestax-python 0.1.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.
- salestax_python-0.1.0/.gitignore +54 -0
- salestax_python-0.1.0/CHANGELOG.md +21 -0
- salestax_python-0.1.0/LICENSE +21 -0
- salestax_python-0.1.0/PKG-INFO +158 -0
- salestax_python-0.1.0/README.md +120 -0
- salestax_python-0.1.0/examples/async_usage.py +20 -0
- salestax_python-0.1.0/examples/basic_calculation.py +13 -0
- salestax_python-0.1.0/examples/batch_calculation.py +17 -0
- salestax_python-0.1.0/examples/error_handling.py +18 -0
- salestax_python-0.1.0/pyproject.toml +111 -0
- salestax_python-0.1.0/salestax/__init__.py +61 -0
- salestax_python-0.1.0/salestax/_config.py +53 -0
- salestax_python-0.1.0/salestax/_transport/__init__.py +18 -0
- salestax_python-0.1.0/salestax/_transport/http_client.py +287 -0
- salestax_python-0.1.0/salestax/_transport/httpx_transport.py +54 -0
- salestax_python-0.1.0/salestax/_transport/retry.py +49 -0
- salestax_python-0.1.0/salestax/_transport/types.py +91 -0
- salestax_python-0.1.0/salestax/_transport/urllib_transport.py +55 -0
- salestax_python-0.1.0/salestax/_transport/user_agent.py +15 -0
- salestax_python-0.1.0/salestax/_version.py +3 -0
- salestax_python-0.1.0/salestax/client.py +215 -0
- salestax_python-0.1.0/salestax/errors.py +202 -0
- salestax_python-0.1.0/salestax/models.py +80 -0
- salestax_python-0.1.0/salestax/py.typed +1 -0
- salestax_python-0.1.0/salestax/resources/__init__.py +5 -0
- salestax_python-0.1.0/salestax/resources/jurisdictions.py +27 -0
- salestax_python-0.1.0/salestax/resources/rates.py +25 -0
- salestax_python-0.1.0/salestax/resources/tax.py +146 -0
- salestax_python-0.1.0/salestax/utils.py +22 -0
- salestax_python-0.1.0/tests/__init__.py +0 -0
- salestax_python-0.1.0/tests/conftest.py +100 -0
- salestax_python-0.1.0/tests/integration/__init__.py +0 -0
- salestax_python-0.1.0/tests/integration/test_live.py +39 -0
- salestax_python-0.1.0/tests/unit/__init__.py +0 -0
- salestax_python-0.1.0/tests/unit/test_async.py +117 -0
- salestax_python-0.1.0/tests/unit/test_client.py +30 -0
- salestax_python-0.1.0/tests/unit/test_client_extras.py +151 -0
- salestax_python-0.1.0/tests/unit/test_errors.py +50 -0
- salestax_python-0.1.0/tests/unit/test_hooks.py +52 -0
- salestax_python-0.1.0/tests/unit/test_httpx_transport.py +79 -0
- salestax_python-0.1.0/tests/unit/test_retry.py +44 -0
- salestax_python-0.1.0/tests/unit/test_tax.py +72 -0
- salestax_python-0.1.0/tests/unit/test_transport.py +110 -0
- salestax_python-0.1.0/tests/unit/test_urllib_transport.py +142 -0
- salestax_python-0.1.0/tests/unit/test_utils.py +58 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -- Python bytecode / caches --
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
# -- Distribution / packaging --
|
|
8
|
+
build/
|
|
9
|
+
dist/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
*.egg
|
|
12
|
+
MANIFEST
|
|
13
|
+
|
|
14
|
+
# -- Virtual environments --
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
ENV/
|
|
19
|
+
.python-version
|
|
20
|
+
|
|
21
|
+
# -- Test / coverage artifacts --
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
.coverage.*
|
|
25
|
+
coverage.xml
|
|
26
|
+
htmlcov/
|
|
27
|
+
.tox/
|
|
28
|
+
.nox/
|
|
29
|
+
|
|
30
|
+
# -- Type checker / linter caches --
|
|
31
|
+
.mypy_cache/
|
|
32
|
+
.dmypy.json
|
|
33
|
+
dmypy.json
|
|
34
|
+
.ruff_cache/
|
|
35
|
+
|
|
36
|
+
# -- Build backends --
|
|
37
|
+
.hatch/
|
|
38
|
+
|
|
39
|
+
# -- Editors / IDEs --
|
|
40
|
+
.idea/
|
|
41
|
+
.vscode/
|
|
42
|
+
*.swp
|
|
43
|
+
*.swo
|
|
44
|
+
*~
|
|
45
|
+
|
|
46
|
+
# -- OS noise --
|
|
47
|
+
.DS_Store
|
|
48
|
+
Thumbs.db
|
|
49
|
+
desktop.ini
|
|
50
|
+
|
|
51
|
+
# -- Secrets / local config (never commit) --
|
|
52
|
+
.env
|
|
53
|
+
.env.*
|
|
54
|
+
!.env.example
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-09-22
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Initial release.
|
|
14
|
+
- `SalesTaxClient` (sync) with zero runtime dependencies.
|
|
15
|
+
- `AsyncSalesTaxClient` (async, requires `[async]` extra).
|
|
16
|
+
- Resources: `client.tax`, `client.rates`, `client.jurisdictions`.
|
|
17
|
+
- Automatic retries with exponential backoff, jitter, and `Retry-After` support.
|
|
18
|
+
- Full error hierarchy: `RateLimitError`, `ValidationError`, `TimeoutError`, and more.
|
|
19
|
+
- Lifecycle hooks: `on_request`, `on_response`, `on_retry`.
|
|
20
|
+
- Batch chunking via `calculate_batch_chunked()`.
|
|
21
|
+
- PEP 561 `py.typed` marker; `mypy --strict` clean.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 InstaBlick-USA
|
|
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,158 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: salestax-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Sales Tax Calculator API — real-time sales tax across 70+ countries, 51 US jurisdictions, and batch processing.
|
|
5
|
+
Project-URL: Homepage, https://salestaxcalculatorapi.com
|
|
6
|
+
Project-URL: Documentation, https://salestaxcalculatorapi.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/InstaBlick-USA/salestax-python
|
|
8
|
+
Project-URL: Issues, https://github.com/InstaBlick-USA/salestax-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/InstaBlick-USA/salestax-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: InstaBlick USA <dev@salestaxcalculatorapi.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: api-client,async,checkout,ecommerce,gst,python,sales-tax,tax-api,vat
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Provides-Extra: async
|
|
27
|
+
Requires-Dist: httpx>=0.27; extra == 'async'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy>=1.9; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
36
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# salestax-python
|
|
40
|
+
|
|
41
|
+
[](https://pypi.org/project/salestax-python/)
|
|
42
|
+
[](https://pypi.org/project/salestax-python/)
|
|
43
|
+
[](https://github.com/InstaBlick-USA/salestax-python/actions)
|
|
44
|
+
[](./LICENSE)
|
|
45
|
+
|
|
46
|
+
Official Python SDK for the **[Sales Tax Calculator API](https://salestaxcalculatorapi.com)** —
|
|
47
|
+
real-time sales tax for 70+ countries, 51 US jurisdictions, and 13 Canadian provinces.
|
|
48
|
+
Batch up to 100 transactions. Zero runtime dependencies (sync). Full type hints.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install salestax-python
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Async support:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install salestax-python[async]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quickstart
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from salestax import SalesTaxClient
|
|
66
|
+
|
|
67
|
+
with SalesTaxClient() as client: # reads SALESTAX_API_KEY
|
|
68
|
+
tax = client.tax.calculate(zip_code="90210", amount=100)
|
|
69
|
+
|
|
70
|
+
print(tax["taxAmount"]) # 9.75
|
|
71
|
+
print(tax["totalAmount"]) # 109.75
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Get your API key → **[salestaxcalculatorapi.com](https://salestaxcalculatorapi.com)**
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
- **Zero runtime dependencies** for the sync client — stdlib `urllib` only.
|
|
79
|
+
- **Optional async client** via `httpx` (`pip install salestax-python[async]`).
|
|
80
|
+
- **Automatic retries** with exponential backoff + jitter, honors `Retry-After`.
|
|
81
|
+
- **Typed errors** — `RateLimitError`, `ValidationError`, `TimeoutError`, and more.
|
|
82
|
+
No string parsing. Every error carries `code`, `request_id`, and (where applicable) `param`.
|
|
83
|
+
- **Lifecycle hooks** — `on_request`, `on_response`, `on_retry` for logging and telemetry.
|
|
84
|
+
- **Batch chunking** — `calculate_batch_chunked()` splits >100 automatically.
|
|
85
|
+
- **Fully typed** — ships `py.typed`, `mypy --strict` clean.
|
|
86
|
+
|
|
87
|
+
## Async
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import asyncio
|
|
91
|
+
from salestax import AsyncSalesTaxClient
|
|
92
|
+
|
|
93
|
+
async def main():
|
|
94
|
+
async with AsyncSalesTaxClient() as client:
|
|
95
|
+
tax = await client.tax.calculate(zip_code="90210", amount=100)
|
|
96
|
+
print(tax["taxAmount"])
|
|
97
|
+
|
|
98
|
+
asyncio.run(main())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Batch
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
with SalesTaxClient() as client:
|
|
105
|
+
result = client.tax.calculate_batch_chunked(
|
|
106
|
+
transactions, # any length
|
|
107
|
+
idempotency_key="order-12345",
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Error Handling
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from salestax import RateLimitError, ValidationError
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
client.tax.calculate(zip_code="90210", amount=100)
|
|
118
|
+
except RateLimitError as err:
|
|
119
|
+
# err.retry_after_ms — back off
|
|
120
|
+
...
|
|
121
|
+
except ValidationError as err:
|
|
122
|
+
# err.param — which field failed
|
|
123
|
+
...
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from salestax import SalesTaxClient, RetryPolicy
|
|
130
|
+
|
|
131
|
+
client = SalesTaxClient(
|
|
132
|
+
api_key="sk_live_...",
|
|
133
|
+
timeout_ms=20_000,
|
|
134
|
+
chunk_batch=True,
|
|
135
|
+
retry=RetryPolicy(max_retries=4),
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Hooks
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
client = SalesTaxClient(hooks={
|
|
143
|
+
"on_retry": lambda info: print(f"retry {info['attempt']} in {info['delay_ms']}ms"),
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Also Available For
|
|
148
|
+
|
|
149
|
+
- [salestax-node](https://github.com/InstaBlick-USA/salestax-node) — Node.js / TypeScript
|
|
150
|
+
- [salestax-ruby](https://github.com/InstaBlick-USA/salestax-ruby) — Ruby
|
|
151
|
+
|
|
152
|
+
## Documentation
|
|
153
|
+
|
|
154
|
+
Full API reference → **[salestaxcalculatorapi.com/docs](https://salestaxcalculatorapi.com/docs)**
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT © [InstaBlick USA](https://github.com/InstaBlick-USA)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# salestax-python
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/salestax-python/)
|
|
4
|
+
[](https://pypi.org/project/salestax-python/)
|
|
5
|
+
[](https://github.com/InstaBlick-USA/salestax-python/actions)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
Official Python SDK for the **[Sales Tax Calculator API](https://salestaxcalculatorapi.com)** —
|
|
9
|
+
real-time sales tax for 70+ countries, 51 US jurisdictions, and 13 Canadian provinces.
|
|
10
|
+
Batch up to 100 transactions. Zero runtime dependencies (sync). Full type hints.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install salestax-python
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Async support:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install salestax-python[async]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from salestax import SalesTaxClient
|
|
28
|
+
|
|
29
|
+
with SalesTaxClient() as client: # reads SALESTAX_API_KEY
|
|
30
|
+
tax = client.tax.calculate(zip_code="90210", amount=100)
|
|
31
|
+
|
|
32
|
+
print(tax["taxAmount"]) # 9.75
|
|
33
|
+
print(tax["totalAmount"]) # 109.75
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Get your API key → **[salestaxcalculatorapi.com](https://salestaxcalculatorapi.com)**
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **Zero runtime dependencies** for the sync client — stdlib `urllib` only.
|
|
41
|
+
- **Optional async client** via `httpx` (`pip install salestax-python[async]`).
|
|
42
|
+
- **Automatic retries** with exponential backoff + jitter, honors `Retry-After`.
|
|
43
|
+
- **Typed errors** — `RateLimitError`, `ValidationError`, `TimeoutError`, and more.
|
|
44
|
+
No string parsing. Every error carries `code`, `request_id`, and (where applicable) `param`.
|
|
45
|
+
- **Lifecycle hooks** — `on_request`, `on_response`, `on_retry` for logging and telemetry.
|
|
46
|
+
- **Batch chunking** — `calculate_batch_chunked()` splits >100 automatically.
|
|
47
|
+
- **Fully typed** — ships `py.typed`, `mypy --strict` clean.
|
|
48
|
+
|
|
49
|
+
## Async
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import asyncio
|
|
53
|
+
from salestax import AsyncSalesTaxClient
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
async with AsyncSalesTaxClient() as client:
|
|
57
|
+
tax = await client.tax.calculate(zip_code="90210", amount=100)
|
|
58
|
+
print(tax["taxAmount"])
|
|
59
|
+
|
|
60
|
+
asyncio.run(main())
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Batch
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
with SalesTaxClient() as client:
|
|
67
|
+
result = client.tax.calculate_batch_chunked(
|
|
68
|
+
transactions, # any length
|
|
69
|
+
idempotency_key="order-12345",
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Error Handling
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from salestax import RateLimitError, ValidationError
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
client.tax.calculate(zip_code="90210", amount=100)
|
|
80
|
+
except RateLimitError as err:
|
|
81
|
+
# err.retry_after_ms — back off
|
|
82
|
+
...
|
|
83
|
+
except ValidationError as err:
|
|
84
|
+
# err.param — which field failed
|
|
85
|
+
...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Configuration
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from salestax import SalesTaxClient, RetryPolicy
|
|
92
|
+
|
|
93
|
+
client = SalesTaxClient(
|
|
94
|
+
api_key="sk_live_...",
|
|
95
|
+
timeout_ms=20_000,
|
|
96
|
+
chunk_batch=True,
|
|
97
|
+
retry=RetryPolicy(max_retries=4),
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Hooks
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
client = SalesTaxClient(hooks={
|
|
105
|
+
"on_retry": lambda info: print(f"retry {info['attempt']} in {info['delay_ms']}ms"),
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Also Available For
|
|
110
|
+
|
|
111
|
+
- [salestax-node](https://github.com/InstaBlick-USA/salestax-node) — Node.js / TypeScript
|
|
112
|
+
- [salestax-ruby](https://github.com/InstaBlick-USA/salestax-ruby) — Ruby
|
|
113
|
+
|
|
114
|
+
## Documentation
|
|
115
|
+
|
|
116
|
+
Full API reference → **[salestaxcalculatorapi.com/docs](https://salestaxcalculatorapi.com/docs)**
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT © [InstaBlick USA](https://github.com/InstaBlick-USA)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Async client example. Requires: pip install salestax-python[async]"""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from salestax import AsyncSalesTaxClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def main() -> None:
|
|
9
|
+
async with AsyncSalesTaxClient() as client:
|
|
10
|
+
results = await asyncio.gather(
|
|
11
|
+
client.tax.calculate(zip_code="90210", amount=100),
|
|
12
|
+
client.tax.calculate(zip_code="10001", amount=200),
|
|
13
|
+
client.rates.get("60601"),
|
|
14
|
+
)
|
|
15
|
+
for r in results:
|
|
16
|
+
print(r)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Minimal single-calculation example.
|
|
2
|
+
|
|
3
|
+
Run:
|
|
4
|
+
SALESTAX_API_KEY=sk_live_... python examples/basic_calculation.py
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from salestax import SalesTaxClient
|
|
8
|
+
|
|
9
|
+
with SalesTaxClient() as client:
|
|
10
|
+
tax = client.tax.calculate(zip_code="90210", amount=100)
|
|
11
|
+
|
|
12
|
+
print(f"Tax: ${tax['taxAmount']:.2f}")
|
|
13
|
+
print(f"Total: ${tax['totalAmount']:.2f}")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Batch example with automatic chunking for >100 transactions."""
|
|
2
|
+
|
|
3
|
+
from salestax import SalesTaxClient
|
|
4
|
+
|
|
5
|
+
transactions = [
|
|
6
|
+
{"zipCode": "90210", "amount": 100},
|
|
7
|
+
{"zipCode": "10001", "amount": 250},
|
|
8
|
+
{"zipCode": "60601", "amount": 75.50},
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
with SalesTaxClient() as client:
|
|
12
|
+
result = client.tax.calculate_batch(
|
|
13
|
+
transactions, idempotency_key="order-2026-0922-001"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
for i, calc in enumerate(result["results"]):
|
|
17
|
+
print(f"#{i}: tax={calc['taxAmount']:.2f} total={calc['totalAmount']:.2f}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Catch and branch on specific error types."""
|
|
2
|
+
|
|
3
|
+
from salestax import (
|
|
4
|
+
RateLimitError,
|
|
5
|
+
SalesTaxClient,
|
|
6
|
+
TimeoutError,
|
|
7
|
+
ValidationError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
with SalesTaxClient() as client:
|
|
11
|
+
try:
|
|
12
|
+
client.tax.calculate(zip_code="90210", amount=100)
|
|
13
|
+
except RateLimitError as err:
|
|
14
|
+
print(f"Rate limited. Retry in {err.retry_after_ms}ms. req={err.request_id}")
|
|
15
|
+
except ValidationError as err:
|
|
16
|
+
print(f"Bad input on `{err.param}`: {err.message}")
|
|
17
|
+
except TimeoutError:
|
|
18
|
+
print("Request timed out — safe to retry.")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "salestax-python"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for Sales Tax Calculator API — real-time sales tax across 70+ countries, 51 US jurisdictions, and batch processing."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "InstaBlick USA", email = "dev@salestaxcalculatorapi.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"sales-tax", "tax-api", "ecommerce", "checkout",
|
|
15
|
+
"python", "api-client", "vat", "gst", "async"
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://salestaxcalculatorapi.com"
|
|
33
|
+
Documentation = "https://salestaxcalculatorapi.com/docs"
|
|
34
|
+
Repository = "https://github.com/InstaBlick-USA/salestax-python"
|
|
35
|
+
Issues = "https://github.com/InstaBlick-USA/salestax-python/issues"
|
|
36
|
+
Changelog = "https://github.com/InstaBlick-USA/salestax-python/blob/main/CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
async = ["httpx>=0.27"]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=8.0",
|
|
42
|
+
"pytest-asyncio>=0.23",
|
|
43
|
+
"pytest-cov>=5.0",
|
|
44
|
+
"respx>=0.21",
|
|
45
|
+
"mypy>=1.9",
|
|
46
|
+
"ruff>=0.4",
|
|
47
|
+
"build>=1.2",
|
|
48
|
+
"twine>=5.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["salestax"]
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.sdist]
|
|
55
|
+
include = [
|
|
56
|
+
"salestax",
|
|
57
|
+
"tests",
|
|
58
|
+
"examples",
|
|
59
|
+
"README.md",
|
|
60
|
+
"CHANGELOG.md",
|
|
61
|
+
"LICENSE",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 100
|
|
66
|
+
target-version = "py39"
|
|
67
|
+
src = ["salestax", "tests"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = ["E", "F", "I", "N", "UP", "B", "C4", "SIM", "RUF"]
|
|
71
|
+
ignore = ["E501"]
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint.isort]
|
|
74
|
+
known-first-party = ["salestax"]
|
|
75
|
+
|
|
76
|
+
[tool.mypy]
|
|
77
|
+
python_version = "3.10"
|
|
78
|
+
strict = true
|
|
79
|
+
warn_unused_ignores = true
|
|
80
|
+
warn_redundant_casts = true
|
|
81
|
+
disallow_untyped_defs = true
|
|
82
|
+
disallow_incomplete_defs = true
|
|
83
|
+
no_implicit_optional = true
|
|
84
|
+
show_error_codes = true
|
|
85
|
+
files = ["salestax"]
|
|
86
|
+
|
|
87
|
+
[[tool.mypy.overrides]]
|
|
88
|
+
module = ["httpx.*"]
|
|
89
|
+
ignore_missing_imports = true
|
|
90
|
+
|
|
91
|
+
[tool.pytest.ini_options]
|
|
92
|
+
testpaths = ["tests"]
|
|
93
|
+
addopts = "-ra --strict-markers"
|
|
94
|
+
asyncio_mode = "auto"
|
|
95
|
+
markers = [
|
|
96
|
+
"integration: marks tests that hit the live API (deselect with '-m \"not integration\"')",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
[tool.coverage.run]
|
|
100
|
+
source = ["salestax"]
|
|
101
|
+
branch = true
|
|
102
|
+
omit = ["salestax/_version.py"]
|
|
103
|
+
|
|
104
|
+
[tool.coverage.report]
|
|
105
|
+
fail_under = 90
|
|
106
|
+
show_missing = true
|
|
107
|
+
exclude_lines = [
|
|
108
|
+
"pragma: no cover",
|
|
109
|
+
"if TYPE_CHECKING:",
|
|
110
|
+
"raise NotImplementedError",
|
|
111
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Official Python SDK for the Sales Tax Calculator API.
|
|
2
|
+
|
|
3
|
+
Real-time sales tax for 70+ countries, 51 US jurisdictions, and 13 Canadian
|
|
4
|
+
provinces. Batch up to 100 transactions per call.
|
|
5
|
+
|
|
6
|
+
Docs: https://salestaxcalculatorapi.com/docs
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from ._config import ClientOptions, RetryPolicy
|
|
12
|
+
from ._version import __version__
|
|
13
|
+
from .client import AsyncSalesTaxClient, SalesTaxClient
|
|
14
|
+
from .errors import (
|
|
15
|
+
ApiError,
|
|
16
|
+
AuthenticationError,
|
|
17
|
+
ConflictError,
|
|
18
|
+
ConnectionError,
|
|
19
|
+
NotFoundError,
|
|
20
|
+
PermissionError,
|
|
21
|
+
RateLimitError,
|
|
22
|
+
SalesTaxError,
|
|
23
|
+
ServerError,
|
|
24
|
+
TimeoutError,
|
|
25
|
+
ValidationError,
|
|
26
|
+
)
|
|
27
|
+
from .models import (
|
|
28
|
+
BatchResult,
|
|
29
|
+
CalculateTaxParams,
|
|
30
|
+
Jurisdiction,
|
|
31
|
+
JurisdictionQuery,
|
|
32
|
+
TaxBreakdownEntry,
|
|
33
|
+
TaxCalculation,
|
|
34
|
+
TaxRate,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"ApiError",
|
|
39
|
+
"AsyncSalesTaxClient",
|
|
40
|
+
"AuthenticationError",
|
|
41
|
+
"BatchResult",
|
|
42
|
+
"CalculateTaxParams",
|
|
43
|
+
"ClientOptions",
|
|
44
|
+
"ConflictError",
|
|
45
|
+
"ConnectionError",
|
|
46
|
+
"Jurisdiction",
|
|
47
|
+
"JurisdictionQuery",
|
|
48
|
+
"NotFoundError",
|
|
49
|
+
"PermissionError",
|
|
50
|
+
"RateLimitError",
|
|
51
|
+
"RetryPolicy",
|
|
52
|
+
"SalesTaxClient",
|
|
53
|
+
"SalesTaxError",
|
|
54
|
+
"ServerError",
|
|
55
|
+
"TaxBreakdownEntry",
|
|
56
|
+
"TaxCalculation",
|
|
57
|
+
"TaxRate",
|
|
58
|
+
"TimeoutError",
|
|
59
|
+
"ValidationError",
|
|
60
|
+
"__version__",
|
|
61
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Configuration and defaults for the SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass, field, replace
|
|
7
|
+
|
|
8
|
+
DEFAULT_BASE_URL = "https://api.salestaxcalculatorapi.com/v1"
|
|
9
|
+
DEFAULT_TIMEOUT_MS = 30_000
|
|
10
|
+
MAX_BATCH_SIZE = 100
|
|
11
|
+
ENV_API_KEY = "SALESTAX_API_KEY"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class RetryPolicy:
|
|
16
|
+
"""Retry behavior for transient failures.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
max_retries: Number of retries *after* the initial attempt.
|
|
20
|
+
initial_delay_ms: Base delay for the first retry.
|
|
21
|
+
max_delay_ms: Ceiling for any single delay.
|
|
22
|
+
backoff_factor: Exponential multiplier applied per attempt.
|
|
23
|
+
jitter: Apply full jitter to each computed delay.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
max_retries: int = 2
|
|
27
|
+
initial_delay_ms: int = 250
|
|
28
|
+
max_delay_ms: int = 8_000
|
|
29
|
+
backoff_factor: float = 2.0
|
|
30
|
+
jitter: bool = True
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ClientOptions:
|
|
35
|
+
"""Immutable options container. Prefer passing keyword args to the client."""
|
|
36
|
+
|
|
37
|
+
api_key: str | None = None
|
|
38
|
+
base_url: str = DEFAULT_BASE_URL
|
|
39
|
+
timeout_ms: int = DEFAULT_TIMEOUT_MS
|
|
40
|
+
retry: RetryPolicy = field(default_factory=RetryPolicy)
|
|
41
|
+
default_headers: dict[str, str] = field(default_factory=dict)
|
|
42
|
+
chunk_batch: bool = False
|
|
43
|
+
|
|
44
|
+
def resolved_api_key(self) -> str:
|
|
45
|
+
key = self.api_key or os.environ.get(ENV_API_KEY)
|
|
46
|
+
if not key:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Missing API key. Pass api_key=... or set the {ENV_API_KEY} environment variable."
|
|
49
|
+
)
|
|
50
|
+
return key
|
|
51
|
+
|
|
52
|
+
def with_overrides(self, **kwargs: object) -> ClientOptions:
|
|
53
|
+
return replace(self, **kwargs) # type: ignore[arg-type]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .http_client import AsyncHttpClient, HttpClient
|
|
2
|
+
from .retry import compute_delay_ms, parse_retry_after
|
|
3
|
+
from .types import AsyncTransport, Hooks, RequestOptions, SyncTransport
|
|
4
|
+
from .urllib_transport import UrllibTransport
|
|
5
|
+
from .user_agent import build_user_agent
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AsyncHttpClient",
|
|
9
|
+
"AsyncTransport",
|
|
10
|
+
"Hooks",
|
|
11
|
+
"HttpClient",
|
|
12
|
+
"RequestOptions",
|
|
13
|
+
"SyncTransport",
|
|
14
|
+
"UrllibTransport",
|
|
15
|
+
"build_user_agent",
|
|
16
|
+
"compute_delay_ms",
|
|
17
|
+
"parse_retry_after",
|
|
18
|
+
]
|