crypticorn 2.13.0__py3-none-any.whl → 2.13.2__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.
- crypticorn/auth/client/models/create_user_request.py +4 -16
- crypticorn/auth/main.py +8 -0
- crypticorn/client.py +24 -21
- crypticorn/common/auth.py +44 -12
- crypticorn/common/errors.py +78 -75
- crypticorn/hive/client/configuration.py +2 -2
- crypticorn/hive/client/models/api_error_identifier.py +2 -2
- crypticorn/hive/main.py +9 -5
- crypticorn/klines/client/models/api_error_identifier.py +1 -1
- crypticorn/klines/main.py +7 -0
- crypticorn/metrics/client/models/api_error_identifier.py +1 -1
- crypticorn/metrics/main.py +8 -3
- crypticorn/pay/client/api/products_api.py +228 -1
- crypticorn/pay/client/configuration.py +2 -2
- crypticorn/pay/client/models/api_error_identifier.py +7 -3
- crypticorn/pay/main.py +7 -0
- crypticorn/trade/client/__init__.py +1 -0
- crypticorn/trade/client/api/strategies_api.py +296 -26
- crypticorn/trade/client/api/trading_actions_api.py +4 -4
- crypticorn/trade/client/models/__init__.py +1 -0
- crypticorn/trade/client/models/api_error_identifier.py +6 -2
- crypticorn/trade/client/models/futures_trading_action.py +23 -37
- crypticorn/trade/client/models/futures_trading_action_create.py +28 -42
- crypticorn/trade/client/models/order.py +40 -34
- crypticorn/trade/client/models/spot_trading_action_create.py +10 -25
- crypticorn/trade/client/models/strategy_exchange_info.py +3 -3
- crypticorn/trade/client/models/tpsl.py +7 -16
- crypticorn/trade/client/models/tpsl_create.py +103 -0
- crypticorn/trade/main.py +8 -2
- {crypticorn-2.13.0.dist-info → crypticorn-2.13.2.dist-info}/METADATA +30 -3
- {crypticorn-2.13.0.dist-info → crypticorn-2.13.2.dist-info}/RECORD +35 -34
- {crypticorn-2.13.0.dist-info → crypticorn-2.13.2.dist-info}/WHEEL +0 -0
- {crypticorn-2.13.0.dist-info → crypticorn-2.13.2.dist-info}/entry_points.txt +0 -0
- {crypticorn-2.13.0.dist-info → crypticorn-2.13.2.dist-info}/licenses/LICENSE +0 -0
- {crypticorn-2.13.0.dist-info → crypticorn-2.13.2.dist-info}/top_level.txt +0 -0
@@ -17,7 +17,7 @@ import pprint
|
|
17
17
|
import re # noqa: F401
|
18
18
|
import json
|
19
19
|
|
20
|
-
from pydantic import BaseModel, ConfigDict, Field, StrictStr
|
20
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictStr
|
21
21
|
from typing import Any, ClassVar, Dict, List, Optional
|
22
22
|
from typing_extensions import Annotated
|
23
23
|
from typing import Optional, Set
|
@@ -29,12 +29,10 @@ class CreateUserRequest(BaseModel):
|
|
29
29
|
CreateUserRequest
|
30
30
|
""" # noqa: E501
|
31
31
|
|
32
|
-
email:
|
32
|
+
email: StrictStr
|
33
33
|
password: Annotated[str, Field(min_length=8, strict=True)]
|
34
|
-
username: Optional[
|
35
|
-
|
36
|
-
] = None
|
37
|
-
name: Optional[Annotated[str, Field(min_length=1, strict=True)]] = None
|
34
|
+
username: Optional[StrictStr] = None
|
35
|
+
name: Optional[StrictStr] = None
|
38
36
|
picture: Optional[StrictStr] = None
|
39
37
|
__properties: ClassVar[List[str]] = [
|
40
38
|
"email",
|
@@ -44,16 +42,6 @@ class CreateUserRequest(BaseModel):
|
|
44
42
|
"picture",
|
45
43
|
]
|
46
44
|
|
47
|
-
@field_validator("username")
|
48
|
-
def username_validate_regular_expression(cls, value):
|
49
|
-
"""Validates the regular expression"""
|
50
|
-
if value is None:
|
51
|
-
return value
|
52
|
-
|
53
|
-
if not re.match(r"^[a-zA-Z0-9_]+$", value):
|
54
|
-
raise ValueError(r"must validate the regular expression /^[a-zA-Z0-9_]+$/")
|
55
|
-
return value
|
56
|
-
|
57
45
|
model_config = ConfigDict(
|
58
46
|
populate_by_name=True,
|
59
47
|
validate_assignment=True,
|
crypticorn/auth/main.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
1
3
|
from crypticorn.auth import (
|
2
4
|
ApiClient,
|
3
5
|
Configuration,
|
@@ -8,6 +10,9 @@ from crypticorn.auth import (
|
|
8
10
|
AuthApi,
|
9
11
|
)
|
10
12
|
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from aiohttp import ClientSession
|
15
|
+
|
11
16
|
|
12
17
|
class AuthClient:
|
13
18
|
"""
|
@@ -19,9 +24,12 @@ class AuthClient:
|
|
19
24
|
def __init__(
|
20
25
|
self,
|
21
26
|
config: Configuration,
|
27
|
+
http_client: Optional[ClientSession] = None,
|
22
28
|
):
|
23
29
|
self.config = config
|
24
30
|
self.base_client = ApiClient(configuration=self.config)
|
31
|
+
if http_client is not None:
|
32
|
+
self.base_client.rest_client.pool_manager = http_client
|
25
33
|
# Instantiate all the endpoint clients
|
26
34
|
self.admin = AdminApi(self.base_client)
|
27
35
|
self.service = ServiceApi(self.base_client)
|
crypticorn/client.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
from typing import TypeVar
|
1
|
+
from typing import TypeVar, Optional
|
2
|
+
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
2
3
|
from crypticorn.hive import HiveClient
|
3
4
|
from crypticorn.klines import KlinesClient
|
4
5
|
from crypticorn.pay import PayClient
|
@@ -6,6 +7,7 @@ from crypticorn.trade import TradeClient
|
|
6
7
|
from crypticorn.metrics import MetricsClient
|
7
8
|
from crypticorn.auth import AuthClient
|
8
9
|
from crypticorn.common import BaseUrl, ApiVersion, Service, apikey_header as aph
|
10
|
+
from importlib.metadata import version
|
9
11
|
|
10
12
|
ConfigT = TypeVar("ConfigT")
|
11
13
|
SubClient = TypeVar("SubClient")
|
@@ -14,14 +16,13 @@ SubClient = TypeVar("SubClient")
|
|
14
16
|
class ApiClient:
|
15
17
|
"""
|
16
18
|
The official Python client for interacting with the Crypticorn API.
|
17
|
-
|
18
19
|
It is consisting of multiple microservices covering the whole stack of the Crypticorn project.
|
19
20
|
"""
|
20
21
|
|
21
22
|
def __init__(
|
22
23
|
self,
|
23
|
-
api_key: str = None,
|
24
|
-
jwt: str = None,
|
24
|
+
api_key: Optional[str] = None,
|
25
|
+
jwt: Optional[str] = None,
|
25
26
|
base_url: BaseUrl = BaseUrl.PROD,
|
26
27
|
):
|
27
28
|
self.base_url = base_url
|
@@ -30,6 +31,15 @@ class ApiClient:
|
|
30
31
|
"""The API key to use for authentication (recommended)."""
|
31
32
|
self.jwt = jwt
|
32
33
|
"""The JWT to use for authentication (not recommended)."""
|
34
|
+
self.version = version("crypticorn")
|
35
|
+
"""The version of the client."""
|
36
|
+
|
37
|
+
# self._http_client = ClientSession(
|
38
|
+
# timeout=ClientTimeout(total=30.0),
|
39
|
+
# connector=TCPConnector(limit=100, limit_per_host=20),
|
40
|
+
# headers={"User-Agent": f"crypticorn/python/{self.version}"},
|
41
|
+
# )
|
42
|
+
self._http_client = None # temporary fix for the issue with the event loop
|
33
43
|
|
34
44
|
self._service_classes: dict[Service, type[SubClient]] = {
|
35
45
|
Service.HIVE: HiveClient,
|
@@ -39,9 +49,10 @@ class ApiClient:
|
|
39
49
|
Service.METRICS: MetricsClient,
|
40
50
|
Service.AUTH: AuthClient,
|
41
51
|
}
|
42
|
-
|
43
52
|
self._services: dict[Service, SubClient] = {
|
44
|
-
service: client_class(
|
53
|
+
service: client_class(
|
54
|
+
self._get_default_config(service), http_client=self._http_client
|
55
|
+
)
|
45
56
|
for service, client_class in self._service_classes.items()
|
46
57
|
}
|
47
58
|
|
@@ -88,17 +99,13 @@ class ApiClient:
|
|
88
99
|
return self._services[Service.AUTH]
|
89
100
|
|
90
101
|
async def close(self):
|
91
|
-
"""Close all client sessions."""
|
92
102
|
for service in self._services.values():
|
93
103
|
if hasattr(service.base_client, "close"):
|
94
104
|
await service.base_client.close()
|
95
105
|
|
96
|
-
def _get_default_config(
|
97
|
-
|
98
|
-
|
99
|
-
"""
|
100
|
-
Get the default configuration for a given service.
|
101
|
-
"""
|
106
|
+
def _get_default_config(self, service, version=None):
|
107
|
+
if version is None:
|
108
|
+
version = ApiVersion.V1
|
102
109
|
config_class = self._service_classes[service].config_class
|
103
110
|
return config_class(
|
104
111
|
host=f"{self.base_url}/{version}/{service}",
|
@@ -106,11 +113,7 @@ class ApiClient:
|
|
106
113
|
api_key={aph.scheme_name: self.api_key} if self.api_key else None,
|
107
114
|
)
|
108
115
|
|
109
|
-
def configure(
|
110
|
-
self,
|
111
|
-
config: ConfigT,
|
112
|
-
service: Service,
|
113
|
-
):
|
116
|
+
def configure(self, config: ConfigT, service: Service) -> None:
|
114
117
|
"""
|
115
118
|
Update a sub-client's configuration by overriding with the values set in the new config.
|
116
119
|
Useful for testing a specific service against a local server instead of the default proxy.
|
@@ -125,13 +128,13 @@ class ApiClient:
|
|
125
128
|
assert Service.validate(service), f"Invalid service: {service}"
|
126
129
|
client = self._services[service]
|
127
130
|
new_config = client.config
|
128
|
-
|
129
131
|
for attr in vars(config):
|
130
132
|
new_value = getattr(config, attr)
|
131
133
|
if new_value:
|
132
134
|
setattr(new_config, attr, new_value)
|
133
|
-
|
134
|
-
|
135
|
+
self._services[service] = type(client)(
|
136
|
+
new_config, http_client=self._http_client
|
137
|
+
)
|
135
138
|
|
136
139
|
async def __aenter__(self):
|
137
140
|
return self
|
crypticorn/common/auth.py
CHANGED
@@ -70,7 +70,7 @@ class AuthHandler:
|
|
70
70
|
self, api_scopes: list[Scope], user_scopes: list[Scope]
|
71
71
|
) -> bool:
|
72
72
|
"""
|
73
|
-
Checks if the
|
73
|
+
Checks if the required scopes are a subset of the user scopes.
|
74
74
|
"""
|
75
75
|
if not set(api_scopes).issubset(user_scopes):
|
76
76
|
raise HTTPException(
|
@@ -137,10 +137,21 @@ class AuthHandler:
|
|
137
137
|
sec: SecurityScopes = SecurityScopes(),
|
138
138
|
) -> Verify200Response:
|
139
139
|
"""
|
140
|
-
Verifies the API key and checks
|
140
|
+
Verifies the API key and checks the scopes.
|
141
141
|
Use this function if you only want to allow access via the API key.
|
142
|
+
This function is used for HTTP connections.
|
142
143
|
"""
|
143
|
-
|
144
|
+
try:
|
145
|
+
return await self.combined_auth(bearer=None, api_key=api_key, sec=sec)
|
146
|
+
except HTTPException as e:
|
147
|
+
if e.detail.get("code") == ApiError.NO_CREDENTIALS.identifier:
|
148
|
+
raise HTTPException(
|
149
|
+
content=ExceptionContent(
|
150
|
+
error=ApiError.NO_API_KEY,
|
151
|
+
message="No credentials provided. API key is required",
|
152
|
+
),
|
153
|
+
)
|
154
|
+
raise e
|
144
155
|
|
145
156
|
async def bearer_auth(
|
146
157
|
self,
|
@@ -151,10 +162,21 @@ class AuthHandler:
|
|
151
162
|
sec: SecurityScopes = SecurityScopes(),
|
152
163
|
) -> Verify200Response:
|
153
164
|
"""
|
154
|
-
Verifies the bearer token and checks
|
165
|
+
Verifies the bearer token and checks the scopes.
|
155
166
|
Use this function if you only want to allow access via the bearer token.
|
167
|
+
This function is used for HTTP connections.
|
156
168
|
"""
|
157
|
-
|
169
|
+
try:
|
170
|
+
return await self.combined_auth(bearer=bearer, api_key=None, sec=sec)
|
171
|
+
except HTTPException as e:
|
172
|
+
if e.detail.get("code") == ApiError.NO_CREDENTIALS.identifier:
|
173
|
+
raise HTTPException(
|
174
|
+
content=ExceptionContent(
|
175
|
+
error=ApiError.NO_BEARER,
|
176
|
+
message="No credentials provided. Bearer token is required",
|
177
|
+
),
|
178
|
+
)
|
179
|
+
raise e
|
158
180
|
|
159
181
|
async def combined_auth(
|
160
182
|
self,
|
@@ -165,9 +187,10 @@ class AuthHandler:
|
|
165
187
|
sec: SecurityScopes = SecurityScopes(),
|
166
188
|
) -> Verify200Response:
|
167
189
|
"""
|
168
|
-
Verifies the bearer token and/or API key and checks
|
190
|
+
Verifies the bearer token and/or API key and checks the scopes.
|
169
191
|
Returns early on the first successful verification, otherwise tries all available tokens.
|
170
192
|
Use this function if you want to allow access via either the bearer token or the API key.
|
193
|
+
This function is used for HTTP connections.
|
171
194
|
"""
|
172
195
|
tokens = [bearer, api_key]
|
173
196
|
|
@@ -197,7 +220,7 @@ class AuthHandler:
|
|
197
220
|
raise HTTPException(
|
198
221
|
content=ExceptionContent(
|
199
222
|
error=ApiError.NO_CREDENTIALS,
|
200
|
-
message="No credentials provided",
|
223
|
+
message="No credentials provided. Either API key or bearer token is required.",
|
201
224
|
),
|
202
225
|
)
|
203
226
|
|
@@ -207,10 +230,14 @@ class AuthHandler:
|
|
207
230
|
sec: SecurityScopes = SecurityScopes(),
|
208
231
|
) -> Verify200Response:
|
209
232
|
"""
|
210
|
-
Verifies the API key and checks
|
233
|
+
Verifies the API key and checks the scopes.
|
211
234
|
Use this function if you only want to allow access via the API key.
|
235
|
+
This function is used for WebSocket connections.
|
212
236
|
"""
|
213
|
-
|
237
|
+
try:
|
238
|
+
return await self.api_key_auth(api_key=api_key, sec=sec)
|
239
|
+
except HTTPException as e:
|
240
|
+
raise WebSocketException.from_http_exception(e)
|
214
241
|
|
215
242
|
async def ws_bearer_auth(
|
216
243
|
self,
|
@@ -218,10 +245,14 @@ class AuthHandler:
|
|
218
245
|
sec: SecurityScopes = SecurityScopes(),
|
219
246
|
) -> Verify200Response:
|
220
247
|
"""
|
221
|
-
Verifies the bearer token and checks
|
248
|
+
Verifies the bearer token and checks the scopes.
|
222
249
|
Use this function if you only want to allow access via the bearer token.
|
250
|
+
This function is used for WebSocket connections.
|
223
251
|
"""
|
224
|
-
|
252
|
+
try:
|
253
|
+
return await self.bearer_auth(bearer=bearer, sec=sec)
|
254
|
+
except HTTPException as e:
|
255
|
+
raise WebSocketException.from_http_exception(e)
|
225
256
|
|
226
257
|
async def ws_combined_auth(
|
227
258
|
self,
|
@@ -230,8 +261,9 @@ class AuthHandler:
|
|
230
261
|
sec: SecurityScopes = SecurityScopes(),
|
231
262
|
) -> Verify200Response:
|
232
263
|
"""
|
233
|
-
Verifies the bearer token and/or API key and checks
|
264
|
+
Verifies the bearer token and/or API key and checks the scopes.
|
234
265
|
Use this function if you want to allow access via either the bearer token or the API key.
|
266
|
+
This function is used for WebSocket connections.
|
235
267
|
"""
|
236
268
|
credentials = (
|
237
269
|
HTTPAuthorizationCredentials(scheme="Bearer", credentials=bearer)
|