txapi-sdk 1.0.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.
- txapi/__init__.py +75 -0
- txapi/_client.py +364 -0
- txapi/_core.py +302 -0
- txapi/_endpoints.py +813 -0
- txapi/_errors.py +190 -0
- txapi/_generated.py +10599 -0
- txapi/_pagination.py +140 -0
- txapi/_protocols.py +41 -0
- txapi/_ratelimit.py +81 -0
- txapi/_version.py +7 -0
- txapi/live.py +632 -0
- txapi/py.typed +0 -0
- txapi/types.py +532 -0
- txapi/webhooks.py +137 -0
- txapi_sdk-1.0.0.dist-info/METADATA +537 -0
- txapi_sdk-1.0.0.dist-info/RECORD +18 -0
- txapi_sdk-1.0.0.dist-info/WHEEL +4 -0
- txapi_sdk-1.0.0.dist-info/licenses/LICENSE +21 -0
txapi/_errors.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Exception hierarchy of the txApi SDK (SPEC §4).
|
|
2
|
+
|
|
3
|
+
Every exception raised by the SDK derives from :class:`TxApiError`.
|
|
4
|
+
None of them ever contains the API key: messages come from the API's
|
|
5
|
+
``error`` text, the response body or the HTTP reason phrase.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from ._ratelimit import RateLimit
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"APIConnectionError",
|
|
18
|
+
"APITimeoutError",
|
|
19
|
+
"AuthenticationError",
|
|
20
|
+
"BadRequestError",
|
|
21
|
+
"CommandError",
|
|
22
|
+
"ConflictError",
|
|
23
|
+
"NotFoundError",
|
|
24
|
+
"PermissionDeniedError",
|
|
25
|
+
"RateLimitError",
|
|
26
|
+
"ServerError",
|
|
27
|
+
"TxAdminRejectedError",
|
|
28
|
+
"TxAdminUnavailableError",
|
|
29
|
+
"TxApiError",
|
|
30
|
+
"ValidationError",
|
|
31
|
+
"WebhookVerificationError",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TxApiError(Exception):
|
|
36
|
+
"""Base class of every error raised by the SDK.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
message: Human-readable message (the API's ``error`` text when available).
|
|
40
|
+
status: HTTP status code, ``None`` for errors without a response.
|
|
41
|
+
body: Parsed response body (``dict``/``list``/``str``), ``None`` without a response.
|
|
42
|
+
headers: Response headers, ``None`` without a response.
|
|
43
|
+
rate_limit: Rate-limit state of the failed response (SPEC §6), if sent.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
message: str
|
|
47
|
+
status: int | None
|
|
48
|
+
body: Any
|
|
49
|
+
headers: httpx.Headers | None
|
|
50
|
+
rate_limit: RateLimit | None
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
message: str,
|
|
55
|
+
*,
|
|
56
|
+
status: int | None = None,
|
|
57
|
+
body: Any = None,
|
|
58
|
+
headers: httpx.Headers | None = None,
|
|
59
|
+
rate_limit: RateLimit | None = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
super().__init__(message)
|
|
62
|
+
self.message = message
|
|
63
|
+
self.status = status
|
|
64
|
+
self.body = body
|
|
65
|
+
self.headers = headers
|
|
66
|
+
self.rate_limit = rate_limit
|
|
67
|
+
|
|
68
|
+
def __str__(self) -> str:
|
|
69
|
+
if self.status is not None:
|
|
70
|
+
return f"{self.status}: {self.message}"
|
|
71
|
+
return self.message
|
|
72
|
+
|
|
73
|
+
def __repr__(self) -> str:
|
|
74
|
+
return f"{type(self).__name__}(status={self.status!r}, message={self.message!r})"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class BadRequestError(TxApiError):
|
|
78
|
+
"""HTTP 400, 405 or 415 — the request was invalid."""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AuthenticationError(TxApiError):
|
|
82
|
+
"""HTTP 401 — key missing, invalid, revoked or expired."""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class PermissionDeniedError(TxApiError):
|
|
86
|
+
"""HTTP 403 — plan feature or key scope missing, IP not allowed, server disabled,
|
|
87
|
+
or the txAdmin account lacks a permission."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class NotFoundError(TxApiError):
|
|
91
|
+
"""HTTP 404 — unknown player, action, admin or server."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ConflictError(TxApiError):
|
|
95
|
+
"""HTTP 409 — e.g. no txAdmin server configured."""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TxAdminRejectedError(TxApiError):
|
|
99
|
+
"""HTTP 422 — txAdmin refused the action (e.g. the player is offline)."""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RateLimitError(TxApiError):
|
|
103
|
+
"""HTTP 429.
|
|
104
|
+
|
|
105
|
+
Attributes:
|
|
106
|
+
retry_after: Seconds from ``Retry-After``; ``None`` when the header is missing.
|
|
107
|
+
quota_exhausted: ``True`` when ``Retry-After`` is missing — the monthly quota
|
|
108
|
+
is used up (it resets on the 1st of the month, 00:00 UTC). Retrying will not help.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
retry_after: float | None
|
|
112
|
+
quota_exhausted: bool
|
|
113
|
+
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
message: str,
|
|
117
|
+
*,
|
|
118
|
+
retry_after: float | None = None,
|
|
119
|
+
status: int | None = 429,
|
|
120
|
+
body: Any = None,
|
|
121
|
+
headers: httpx.Headers | None = None,
|
|
122
|
+
rate_limit: RateLimit | None = None,
|
|
123
|
+
) -> None:
|
|
124
|
+
super().__init__(message, status=status, body=body, headers=headers, rate_limit=rate_limit)
|
|
125
|
+
self.retry_after = retry_after
|
|
126
|
+
self.quota_exhausted = retry_after is None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ServerError(TxApiError):
|
|
130
|
+
"""HTTP 5xx (other than 502)."""
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TxAdminUnavailableError(ServerError):
|
|
134
|
+
"""HTTP 502 — txAdmin is unreachable or answered garbage."""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class APIConnectionError(TxApiError):
|
|
138
|
+
"""Transport problem: DNS, refused connection, reset, TLS … (spec name: ``ConnectionError``)."""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class APITimeoutError(APIConnectionError):
|
|
142
|
+
"""The request (or a live command) timed out (spec name: ``TimeoutError``)."""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ValidationError(TxApiError, ValueError):
|
|
146
|
+
"""Client-side check failed; no request was sent (SPEC §2)."""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class WebhookVerificationError(TxApiError):
|
|
150
|
+
"""A webhook delivery failed signature verification (SPEC §8).
|
|
151
|
+
|
|
152
|
+
Attributes:
|
|
153
|
+
reason: Machine-friendly reason, e.g. ``"signature_mismatch"``.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
reason: str
|
|
157
|
+
|
|
158
|
+
def __init__(self, message: str, *, reason: str) -> None:
|
|
159
|
+
super().__init__(message)
|
|
160
|
+
self.reason = reason
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class CommandError(TxApiError):
|
|
164
|
+
"""The live console answered a :meth:`~txapi.live.ConsoleStream.send_command`
|
|
165
|
+
with an ``error`` frame (empty/too long command, rate limit, quota, txAdmin down)."""
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
_STATUS_ERRORS: dict[int, type[TxApiError]] = {
|
|
169
|
+
400: BadRequestError,
|
|
170
|
+
405: BadRequestError,
|
|
171
|
+
415: BadRequestError,
|
|
172
|
+
401: AuthenticationError,
|
|
173
|
+
403: PermissionDeniedError,
|
|
174
|
+
404: NotFoundError,
|
|
175
|
+
409: ConflictError,
|
|
176
|
+
422: TxAdminRejectedError,
|
|
177
|
+
502: TxAdminUnavailableError,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def error_class_for_status(status: int) -> type[TxApiError]:
|
|
182
|
+
"""Exception class for an HTTP error status (SPEC §4)."""
|
|
183
|
+
if status == 429:
|
|
184
|
+
return RateLimitError
|
|
185
|
+
known = _STATUS_ERRORS.get(status)
|
|
186
|
+
if known is not None:
|
|
187
|
+
return known
|
|
188
|
+
if status >= 500:
|
|
189
|
+
return ServerError
|
|
190
|
+
return TxApiError
|