oneshot-python 0.26.0__tar.gz → 0.27.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.
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/PKG-INFO +1 -1
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/oneshot/_errors.py +6 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/oneshot/client.py +9 -3
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/pyproject.toml +1 -1
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_payment_rejection.py +22 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/uv.lock +1 -1
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/.gitignore +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/README.md +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/oneshot/__init__.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/oneshot/_types.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/oneshot/physical_mail.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/oneshot/x402.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/__init__.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_access_token.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_balance.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_browser.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_budgets.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_charge_amount.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_compute.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_domains.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_email_payload.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_emergency_error.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_linkedin.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_max_cost_header.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_phones_pending.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_physical_mail.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_poll_backoff.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_request_id.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_tag_receipt_value.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_top_up_credits.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_wait_false.py +0 -0
- {oneshot_python-0.26.0 → oneshot_python-0.27.0}/tests/test_x402.py +0 -0
|
@@ -33,12 +33,18 @@ class PaymentError(OneShotError):
|
|
|
33
33
|
expected: dict | None = None,
|
|
34
34
|
received: dict | None = None,
|
|
35
35
|
quote_id: str | None = None,
|
|
36
|
+
payment_attempt_id: str | None = None,
|
|
37
|
+
stage: str | None = None,
|
|
38
|
+
diagnostics: dict | None = None,
|
|
36
39
|
) -> None:
|
|
37
40
|
super().__init__(message)
|
|
38
41
|
self.reason = reason
|
|
39
42
|
self.expected = expected or {}
|
|
40
43
|
self.received = received or {}
|
|
41
44
|
self.quote_id = quote_id
|
|
45
|
+
self.payment_attempt_id = payment_attempt_id
|
|
46
|
+
self.stage = stage
|
|
47
|
+
self.diagnostics = diagnostics
|
|
42
48
|
|
|
43
49
|
|
|
44
50
|
class JobError(OneShotError):
|
|
@@ -56,7 +56,7 @@ try:
|
|
|
56
56
|
|
|
57
57
|
SDK_VERSION = _pkg_version("oneshot-python")
|
|
58
58
|
except Exception: # pragma: no cover - editable/source runs without dist metadata
|
|
59
|
-
SDK_VERSION = "0.
|
|
59
|
+
SDK_VERSION = "0.27.0"
|
|
60
60
|
|
|
61
61
|
# ---------------------------------------------------------------------------
|
|
62
62
|
# Environment configuration
|
|
@@ -206,13 +206,13 @@ def _parse_payment_rejection(resp: Any) -> Optional[PaymentError]:
|
|
|
206
206
|
payment is refused. Returns None for any other response so the caller can
|
|
207
207
|
fall back to ``ToolError``.
|
|
208
208
|
"""
|
|
209
|
-
if resp.status_code
|
|
209
|
+
if resp.status_code not in (402, 500):
|
|
210
210
|
return None
|
|
211
211
|
try:
|
|
212
212
|
data = resp.json()
|
|
213
213
|
except Exception: # noqa: BLE001 - non-JSON body
|
|
214
214
|
return None
|
|
215
|
-
if not isinstance(data, dict) or data.get("error")
|
|
215
|
+
if not isinstance(data, dict) or data.get("error") not in ("payment_verification_failed", "payment_processing_error"):
|
|
216
216
|
return None
|
|
217
217
|
|
|
218
218
|
reason = data.get("reason") or "unknown"
|
|
@@ -230,6 +230,9 @@ def _parse_payment_rejection(resp: Any) -> Optional[PaymentError]:
|
|
|
230
230
|
if data.get("message"):
|
|
231
231
|
message += f" ({data['message']})"
|
|
232
232
|
|
|
233
|
+
if data.get("payment_attempt_id"):
|
|
234
|
+
message += f" [payment attempt {data['payment_attempt_id']}]"
|
|
235
|
+
|
|
233
236
|
return PaymentError(
|
|
234
237
|
message,
|
|
235
238
|
reason,
|
|
@@ -241,6 +244,9 @@ def _parse_payment_rejection(resp: Any) -> Optional[PaymentError]:
|
|
|
241
244
|
},
|
|
242
245
|
received={"amount": received.get("amount")},
|
|
243
246
|
quote_id=data.get("quote_id"),
|
|
247
|
+
payment_attempt_id=data.get("payment_attempt_id"),
|
|
248
|
+
stage=data.get("stage"),
|
|
249
|
+
diagnostics=data.get("diagnostics"),
|
|
244
250
|
)
|
|
245
251
|
|
|
246
252
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "oneshot-python"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.27.0"
|
|
4
4
|
description = "Core Python SDK for the OneShot API — HTTP client with x402 payment handling — 43 tools"
|
|
5
5
|
readme = {text = "Core Python SDK for the OneShot API", content-type = "text/plain"}
|
|
6
6
|
license = "MIT"
|
|
@@ -86,3 +86,25 @@ class TestParsePaymentRejection:
|
|
|
86
86
|
err = _parse_payment_rejection(_Resp(402, REJECTION))
|
|
87
87
|
assert isinstance(err, OneShotError)
|
|
88
88
|
assert isinstance(err, Exported)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_diagnostics_and_attempt_correlation():
|
|
92
|
+
diagnostics = {"status": "complete", "findings": [{"code": "unknown"}]}
|
|
93
|
+
error = _parse_payment_rejection(_Resp(402, {
|
|
94
|
+
**REJECTION, "payment_attempt_id": "attempt-123", "stage": "verification", "diagnostics": diagnostics,
|
|
95
|
+
}))
|
|
96
|
+
assert error.payment_attempt_id == "attempt-123"
|
|
97
|
+
assert "attempt-123" in str(error)
|
|
98
|
+
assert error.stage == "verification"
|
|
99
|
+
assert error.diagnostics == diagnostics
|
|
100
|
+
assert PaymentError("old", "unknown").diagnostics is None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_transport_and_settlement_correlation():
|
|
104
|
+
error = _parse_payment_rejection(_Resp(500, {
|
|
105
|
+
"error": "payment_processing_error", "payment_attempt_id": "transport-123", "stage": "verification_transport",
|
|
106
|
+
}))
|
|
107
|
+
assert error.payment_attempt_id == "transport-123"
|
|
108
|
+
assert error.stage == "verification_transport"
|
|
109
|
+
error = _parse_payment_rejection(_Resp(402, {**REJECTION, "stage": "settlement"}))
|
|
110
|
+
assert error.stage == "settlement"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|