salestax-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ """``client.tax`` resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import Any, Union
7
+
8
+ from .._config import MAX_BATCH_SIZE
9
+ from ..errors import ValidationError
10
+ from ..models import BatchResult, CalculateTaxParams, TaxCalculation
11
+ from ..utils import chunk
12
+
13
+ _TransactionLike = Union[CalculateTaxParams, dict[str, Any]]
14
+
15
+
16
+ def _amount(value: Any) -> float:
17
+ if not isinstance(value, (int, float)) or isinstance(value, bool):
18
+ raise ValidationError(
19
+ "INVALID_AMOUNT",
20
+ "amount must be a non-negative number",
21
+ status_code=400,
22
+ param="amount",
23
+ )
24
+ amount = float(value)
25
+ if amount < 0:
26
+ raise ValidationError(
27
+ "INVALID_AMOUNT",
28
+ "amount must be a non-negative number",
29
+ status_code=400,
30
+ param="amount",
31
+ )
32
+ return amount
33
+
34
+
35
+ class TaxResource:
36
+ """Tax calculation endpoints. Do not instantiate directly."""
37
+
38
+ def __init__(self, http: Any, *, chunk_batch: bool = False) -> None:
39
+ self._http = http
40
+ self._chunk_batch = chunk_batch
41
+
42
+ def calculate(
43
+ self,
44
+ *,
45
+ zip_code: str,
46
+ amount: float,
47
+ state: str | None = None,
48
+ country: str | None = None,
49
+ city: str | None = None,
50
+ idempotency_key: str | None = None,
51
+ retryable: bool = True,
52
+ ) -> TaxCalculation:
53
+ """Calculate tax for a single transaction.
54
+
55
+ Raises:
56
+ ValidationError: If ``zip_code`` or ``amount`` is invalid.
57
+ ApiError: If the API returns a non-2xx response.
58
+ """
59
+ if not zip_code:
60
+ raise ValidationError(
61
+ "MISSING_PARAM", "zip_code is required", status_code=400, param="zip_code"
62
+ )
63
+ _amount(amount)
64
+
65
+ payload: dict[str, Any] = {"zipCode": zip_code, "amount": amount}
66
+ if state is not None:
67
+ payload["state"] = state
68
+ if country is not None:
69
+ payload["country"] = country
70
+ if city is not None:
71
+ payload["city"] = city
72
+
73
+ return self._http.send("POST", "/calculate", body=payload) # type: ignore[no-any-return]
74
+
75
+ def calculate_batch(
76
+ self,
77
+ transactions: Sequence[_TransactionLike],
78
+ *,
79
+ idempotency_key: str | None = None,
80
+ retryable: bool = True,
81
+ ) -> BatchResult:
82
+ """Calculate tax for up to 100 transactions in one call.
83
+
84
+ Raises:
85
+ ValidationError: If the batch is empty, exceeds 100 items without
86
+ ``chunk_batch=True``, or contains an invalid amount.
87
+ """
88
+ self._validate_batch(transactions)
89
+ for t in transactions:
90
+ _amount(t.get("amount"))
91
+ payload = {"transactions": list(transactions)}
92
+ return self._http.send( # type: ignore[no-any-return]
93
+ "POST",
94
+ "/calculate/batch",
95
+ body=payload,
96
+ options=_request_options(idempotency_key, retryable),
97
+ )
98
+
99
+ def calculate_batch_chunked(
100
+ self,
101
+ transactions: Sequence[_TransactionLike],
102
+ *,
103
+ idempotency_key: str | None = None,
104
+ retryable: bool = True,
105
+ ) -> BatchResult:
106
+ """Calculate tax for an arbitrary number of transactions.
107
+
108
+ Automatically splits the input into batches of ``MAX_BATCH_SIZE`` and
109
+ concatenates results in the original order.
110
+ """
111
+ if not transactions:
112
+ raise ValidationError(
113
+ "EMPTY_BATCH", "Batch must contain at least one transaction", status_code=400
114
+ )
115
+ for t in transactions:
116
+ _amount(t.get("amount"))
117
+
118
+ results: list[TaxCalculation] = []
119
+ for batch in chunk(list(transactions), MAX_BATCH_SIZE):
120
+ res = self._http.send(
121
+ "POST",
122
+ "/calculate/batch",
123
+ body={"transactions": batch},
124
+ options=_request_options(idempotency_key, retryable),
125
+ )
126
+ results.extend(res.get("results", []))
127
+ return {"results": results, "count": len(results)}
128
+
129
+ def _validate_batch(self, transactions: Sequence[_TransactionLike]) -> None:
130
+ if not transactions:
131
+ raise ValidationError(
132
+ "EMPTY_BATCH", "Batch must contain at least one transaction", status_code=400
133
+ )
134
+ if len(transactions) > MAX_BATCH_SIZE and not self._chunk_batch:
135
+ raise ValidationError(
136
+ "BATCH_LIMIT_EXCEEDED",
137
+ f"Batch is limited to {MAX_BATCH_SIZE} transactions. "
138
+ "Use calculate_batch_chunked() or enable chunk_batch=True.",
139
+ status_code=400,
140
+ )
141
+
142
+
143
+ def _request_options(idempotency_key: str | None, retryable: bool) -> Any:
144
+ from .._transport.types import RequestOptions
145
+
146
+ return RequestOptions(idempotency_key=idempotency_key, retryable=retryable)
salestax/utils.py ADDED
@@ -0,0 +1,22 @@
1
+ """Small internal helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import TypeVar
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ def chunk(items: Sequence[T], size: int) -> list[list[T]]:
12
+ """Split ``items`` into lists of at most ``size`` elements."""
13
+ if size <= 0:
14
+ raise ValueError("size must be positive")
15
+ return [list(items[i : i + size]) for i in range(0, len(items), size)]
16
+
17
+
18
+ def redact(value: str, keep: int = 4) -> str:
19
+ """Redact all but the last ``keep`` characters. Used in log output."""
20
+ if len(value) <= keep:
21
+ return "*" * len(value)
22
+ return "*" * (len(value) - keep) + value[-keep:]
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/salestax-python)](https://pypi.org/project/salestax-python/)
42
+ [![Python versions](https://img.shields.io/pypi/pyversions/salestax-python)](https://pypi.org/project/salestax-python/)
43
+ [![CI](https://github.com/InstaBlick-USA/salestax-python/actions/workflows/ci.yml/badge.svg)](https://github.com/InstaBlick-USA/salestax-python/actions)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./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,23 @@
1
+ salestax/__init__.py,sha256=rpr-zj2hpvKdXa9Oz5kMtcfmBArwkED1kAz8jLeN6yw,1320
2
+ salestax/_config.py,sha256=Y2OqwHEUQVpclhMUKiXmtGHR-qYTxejwgniebkXfLYU,1659
3
+ salestax/_version.py,sha256=VmPUojNETb-e_3dWOA7kZeosNlKu8t67NvIMY5AV5lU,73
4
+ salestax/client.py,sha256=sPAnXbqJSC59QiZo_8D3FgfXpAz59WQZp72RFpvCpX4,7524
5
+ salestax/errors.py,sha256=ZQw7jmnZSFiysXiUyP1akdOHf0jCZvBKmRXEV2AOvAA,5307
6
+ salestax/models.py,sha256=n6vfoWGyvue595tzpdlcbSlATIIhQBpl-2FyqwXrxOQ,1703
7
+ salestax/py.typed,sha256=S2JdbeRy7souiuGCm5NHAx_wf4AZZewwlWSTyuYq89o,44
8
+ salestax/utils.py,sha256=pzJdO_fhOlJwVG8t2zc-pCCpQaqp8RhpVYxuQIuadMA,655
9
+ salestax/_transport/__init__.py,sha256=dWzJhvFxSNMs5-z7fnzYbfO_IcHpy_f44S0qp5GjYbc,497
10
+ salestax/_transport/http_client.py,sha256=KgVpvi3s2o1LEZcNevkPYdjlp5YSXMqhLKgiStnE-ao,9399
11
+ salestax/_transport/httpx_transport.py,sha256=E_AUrIiYmaQcmuQKBfn3eii4i2mi4tuN-NjNZyVR8VY,1605
12
+ salestax/_transport/retry.py,sha256=46mhtRnRoSqRSBL5-R0PQnkIBJAdsIhOJXLIhhpYuEU,1435
13
+ salestax/_transport/types.py,sha256=5T4TMAjJf2nME79V_THufa8CqCsba9X5nezWiJqw9lc,2336
14
+ salestax/_transport/urllib_transport.py,sha256=wom8qIrYIUSV33lD0OBuKowsAcMdcNwMsgCZ8WuZG2M,2028
15
+ salestax/_transport/user_agent.py,sha256=wOE5_SMA5kk3KmLUXq3Pe2sUr08ZfbFjoE5gKipLSmI,444
16
+ salestax/resources/__init__.py,sha256=DD4u3HhflZkywAHWGAF0BDMooc-t55NuclLsIMuha6g,180
17
+ salestax/resources/jurisdictions.py,sha256=nenRJp15ds_6DBd8lIsV40U_CJqviYRMWzTWgmSAt9Y,830
18
+ salestax/resources/rates.py,sha256=jiWztqFmAklITTetGt7p3eaOdmp0FVX4pZSO3nhWluo,780
19
+ salestax/resources/tax.py,sha256=sVYSG0Y-w1aNY39f-C7c8ttPuDhe1TKaFlXEuBGXDJM,4925
20
+ salestax_python-0.1.0.dist-info/METADATA,sha256=EvACOB77pM9sKAEN5AhHR9ny6HrAf7jUB7SzFOLzQ70,5207
21
+ salestax_python-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
22
+ salestax_python-0.1.0.dist-info/licenses/LICENSE,sha256=KEd6gVA2mih1LR627--d2IqpDOnsqkR9aMn5DGg_PK4,1070
23
+ salestax_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.