stx-python 0.6.0rc1__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.
stx/enums.py ADDED
@@ -0,0 +1,13 @@
1
+ """Region and environment names accepted by the clients."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class Region(str, Enum):
7
+ US = "us"
8
+ ONTARIO = "ontario"
9
+
10
+
11
+ class Environment(str, Enum):
12
+ PRODUCTION = "production"
13
+ DEMO = "demo"
stx/exceptions.py ADDED
@@ -0,0 +1,156 @@
1
+ """Exceptions raised by the SDK.
2
+
3
+ REST errors arrive as ``{"error": "<message>"}`` with an HTTP status. The
4
+ status picks the exception class; the message is kept verbatim on
5
+ ``.message`` (and ``str(exc)``), and the status on ``.status_code``.
6
+
7
+ ======== =================================== =========================
8
+ Status Meaning Exception
9
+ ======== =================================== =========================
10
+ 400 Bad parameters STXValidationException
11
+ 401 Missing or invalid signature STXAuthenticationException
12
+ 403 Read-only key or account state STXForbiddenException
13
+ 404 Not found, or not yours STXNotFoundException
14
+ 422 Rejected by the exchange STXRejectedException
15
+ 429 Rate limited STXRateLimitException
16
+ 5xx Server error STXServerException
17
+ ======== =================================== =========================
18
+
19
+ Network failures (connection refused, timeouts, TLS) raise
20
+ ``STXTransportException``. Socket-level problems (a join the server
21
+ refused, a channel reply with ``status: error``) raise
22
+ ``STXChannelException``.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from typing import Any, Optional
28
+
29
+
30
+ class STXException(Exception):
31
+ """Base for every error the SDK raises."""
32
+
33
+ def __init__(
34
+ self,
35
+ message: str,
36
+ *,
37
+ status_code: Optional[int] = None,
38
+ body: Any = None,
39
+ method: Optional[str] = None,
40
+ path: Optional[str] = None,
41
+ ) -> None:
42
+ super().__init__(message)
43
+ self.message = message
44
+ self.status_code = status_code
45
+ self.body = body
46
+ self.method = method
47
+ self.path = path
48
+
49
+ def __str__(self) -> str:
50
+ return self.message
51
+
52
+
53
+ class STXConfigException(STXException):
54
+ """The client is misconfigured: unknown region, missing key, bad PEM."""
55
+
56
+
57
+ class STXAPIException(STXException):
58
+ """The API answered with an error status. Base for the classes below."""
59
+
60
+
61
+ class STXValidationException(STXAPIException):
62
+ """400: a parameter or body field was rejected, e.g. ``price is required``."""
63
+
64
+
65
+ class STXAuthenticationException(STXAPIException):
66
+ """401: the signature, key id or timestamp was not accepted.
67
+
68
+ A machine clock more than 30 seconds off also lands here.
69
+ """
70
+
71
+
72
+ class STXForbiddenException(STXAPIException):
73
+ """403: the key is read-only, or the account may not do this."""
74
+
75
+
76
+ class STXNotFoundException(STXAPIException):
77
+ """404: the resource does not exist, or belongs to another account."""
78
+
79
+
80
+ class STXRejectedException(STXAPIException):
81
+ """422: the exchange refused the request, e.g. insufficient funds or a
82
+ closed market."""
83
+
84
+
85
+ class STXRateLimitException(STXAPIException):
86
+ """429: too many requests. ``retry_after`` is seconds, when sent."""
87
+
88
+ def __init__(self, message: str, *, retry_after: Optional[float] = None, **kwargs: Any):
89
+ super().__init__(message, **kwargs)
90
+ self.retry_after = retry_after
91
+
92
+
93
+ class STXServerException(STXAPIException):
94
+ """5xx: the server failed. Retried by the default policy."""
95
+
96
+
97
+ class STXTransportException(STXException):
98
+ """The request never got an answer: connection, DNS, TLS or timeout."""
99
+
100
+
101
+ class STXChannelException(STXException):
102
+ """A channel join or channel message was refused by the server.
103
+
104
+ ``reply`` holds the server's ``response`` object, for example
105
+ ``{"reason": "unauthorized"}``.
106
+ """
107
+
108
+ def __init__(self, message: str, *, topic: str = "", reply: Any = None) -> None:
109
+ super().__init__(message)
110
+ self.topic = topic
111
+ self.reply = reply
112
+
113
+
114
+ _STATUS_MAP = {
115
+ 400: STXValidationException,
116
+ 401: STXAuthenticationException,
117
+ 403: STXForbiddenException,
118
+ 404: STXNotFoundException,
119
+ 422: STXRejectedException,
120
+ 429: STXRateLimitException,
121
+ }
122
+
123
+
124
+ def error_for_status(
125
+ status_code: int,
126
+ body: Any,
127
+ *,
128
+ method: str,
129
+ path: str,
130
+ retry_after: Optional[str] = None,
131
+ ) -> STXAPIException:
132
+ """Build the exception for one non-2xx response."""
133
+ # Message: the API's ``error`` string when the body has one, else the
134
+ # body text itself, else the status line.
135
+ if isinstance(body, dict) and isinstance(body.get("error"), str):
136
+ message = body["error"]
137
+ elif isinstance(body, str) and body.strip():
138
+ message = body.strip()[:500]
139
+ else:
140
+ message = f"HTTP {status_code}"
141
+ kwargs = {"status_code": status_code, "body": body, "method": method, "path": path}
142
+ if status_code == 429:
143
+ return STXRateLimitException(message, retry_after=_parse_retry_after(retry_after), **kwargs)
144
+ cls = _STATUS_MAP.get(status_code)
145
+ if cls is None:
146
+ cls = STXServerException if status_code >= 500 else STXAPIException
147
+ return cls(message, **kwargs)
148
+
149
+
150
+ def _parse_retry_after(value: Optional[str]) -> Optional[float]:
151
+ if value is None:
152
+ return None
153
+ try:
154
+ return max(0.0, float(value))
155
+ except ValueError:
156
+ return None