crypticorn 2.5.0rc5__py3-none-any.whl → 2.5.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/main.py +2 -0
- crypticorn/client.py +60 -69
- crypticorn/common/__init__.py +3 -1
- crypticorn/common/auth.py +38 -20
- crypticorn/common/enums.py +4 -31
- crypticorn/common/errors.py +72 -17
- crypticorn/common/exceptions.py +29 -15
- crypticorn/common/mixins.py +37 -0
- crypticorn/common/status_router.py +42 -0
- crypticorn/common/urls.py +2 -1
- crypticorn/common/utils.py +9 -14
- crypticorn/hive/main.py +2 -0
- crypticorn/klines/client/__init__.py +10 -43
- crypticorn/klines/client/api/__init__.py +1 -1
- crypticorn/klines/client/api/change_in_timeframe_api.py +16 -31
- crypticorn/klines/client/api/funding_rates_api.py +8 -22
- crypticorn/klines/client/api/ohlcv_data_api.py +17 -38
- crypticorn/klines/client/api/{health_check_api.py → status_api.py} +18 -23
- crypticorn/klines/client/api/symbols_api.py +18 -34
- crypticorn/klines/client/api/udf_api.py +48 -59
- crypticorn/klines/client/api_client.py +1 -1
- crypticorn/klines/client/configuration.py +1 -1
- crypticorn/klines/client/exceptions.py +1 -1
- crypticorn/klines/client/models/__init__.py +9 -42
- crypticorn/klines/client/models/{change_in_timeframe_response.py → change_in_timeframe.py} +5 -5
- crypticorn/klines/client/models/{error_response.py → exception_detail.py} +25 -20
- crypticorn/klines/client/models/{funding_rate_response.py → funding_rate.py} +5 -5
- crypticorn/klines/client/models/{ohlcv_response.py → ohlcv_history.py} +14 -14
- crypticorn/klines/client/models/resolution.py +1 -1
- crypticorn/klines/client/models/{exchange.py → search_symbol.py} +17 -13
- crypticorn/klines/client/models/sort_direction.py +1 -1
- crypticorn/klines/client/models/{symbol_group_response.py → symbol_group.py} +5 -5
- crypticorn/klines/client/models/{symbol_info_response.py → symbol_info.py} +5 -5
- crypticorn/klines/client/models/symbol_type.py +1 -1
- crypticorn/klines/client/models/timeframe.py +1 -1
- crypticorn/klines/client/models/{udf_config_response.py → udf_config.py} +7 -19
- crypticorn/klines/client/rest.py +1 -1
- crypticorn/klines/main.py +40 -23
- crypticorn/metrics/client/__init__.py +3 -22
- crypticorn/metrics/client/api/__init__.py +1 -1
- crypticorn/metrics/client/api/exchanges_api.py +53 -97
- crypticorn/metrics/client/api/indicators_api.py +26 -52
- crypticorn/metrics/client/api/logs_api.py +8 -23
- crypticorn/metrics/client/api/marketcap_api.py +36 -88
- crypticorn/metrics/client/api/markets_api.py +26 -55
- crypticorn/metrics/client/api/{health_check_api.py → status_api.py} +18 -23
- crypticorn/metrics/client/api/tokens_api.py +7 -21
- crypticorn/metrics/client/api_client.py +1 -1
- crypticorn/metrics/client/configuration.py +1 -1
- crypticorn/metrics/client/exceptions.py +1 -1
- crypticorn/metrics/client/models/__init__.py +2 -21
- crypticorn/metrics/client/models/{error_response.py → exception_detail.py} +25 -20
- crypticorn/metrics/client/models/severity.py +1 -1
- crypticorn/metrics/client/models/time_interval.py +1 -1
- crypticorn/metrics/client/models/trading_status.py +1 -1
- crypticorn/metrics/client/rest.py +1 -1
- crypticorn/metrics/main.py +51 -43
- crypticorn/pay/main.py +2 -0
- crypticorn/trade/client/__init__.py +1 -5
- crypticorn/trade/client/api/exchanges_api.py +6 -6
- crypticorn/trade/client/api/trading_actions_api.py +16 -15
- crypticorn/trade/client/models/__init__.py +1 -5
- crypticorn/trade/client/models/action_model.py +1 -2
- crypticorn/trade/client/models/bot_model.py +3 -7
- crypticorn/trade/client/models/exchange_key_model.py +2 -11
- crypticorn/trade/client/models/execution_ids.py +1 -1
- crypticorn/trade/client/models/futures_trading_action.py +1 -2
- crypticorn/trade/client/models/notification_model.py +3 -12
- crypticorn/trade/client/models/order_model.py +7 -21
- crypticorn/trade/client/models/spot_trading_action.py +230 -0
- crypticorn/trade/client/models/strategy_exchange_info.py +2 -3
- crypticorn/trade/client/models/strategy_model_input.py +1 -2
- crypticorn/trade/client/models/strategy_model_output.py +1 -2
- crypticorn/trade/client/models/tpsl.py +3 -1
- crypticorn/trade/main.py +2 -0
- {crypticorn-2.5.0rc5.dist-info → crypticorn-2.5.2.dist-info}/METADATA +7 -5
- {crypticorn-2.5.0rc5.dist-info → crypticorn-2.5.2.dist-info}/RECORD +81 -141
- {crypticorn-2.5.0rc5.dist-info → crypticorn-2.5.2.dist-info}/WHEEL +1 -1
- crypticorn/common/sorter.py +0 -40
- crypticorn/klines/client/models/base_response_health_check_response.py +0 -114
- crypticorn/klines/client/models/base_response_list_change_in_timeframe_response.py +0 -123
- crypticorn/klines/client/models/base_response_list_funding_rate_response.py +0 -118
- crypticorn/klines/client/models/base_response_list_str.py +0 -106
- crypticorn/klines/client/models/base_response_ohlcv_response.py +0 -114
- crypticorn/klines/client/models/health_check_response.py +0 -91
- crypticorn/klines/client/models/history_error_response.py +0 -89
- crypticorn/klines/client/models/history_no_data_response.py +0 -99
- crypticorn/klines/client/models/history_success_response.py +0 -99
- crypticorn/klines/client/models/http_validation_error.py +0 -99
- crypticorn/klines/client/models/market.py +0 -35
- crypticorn/klines/client/models/market_type.py +0 -35
- crypticorn/klines/client/models/response_get_history_udf_history_get.py +0 -198
- crypticorn/klines/client/models/response_get_udf_history.py +0 -198
- crypticorn/klines/client/models/search_symbol_response.py +0 -104
- crypticorn/klines/client/models/validation_error.py +0 -105
- crypticorn/klines/client/models/validation_error_loc_inner.py +0 -159
- crypticorn/metrics/client/models/base_response_dict.py +0 -106
- crypticorn/metrics/client/models/base_response_health_check_response.py +0 -114
- crypticorn/metrics/client/models/base_response_list_dict.py +0 -106
- crypticorn/metrics/client/models/base_response_list_exchange_mapping.py +0 -118
- crypticorn/metrics/client/models/base_response_list_str.py +0 -106
- crypticorn/metrics/client/models/exchange_mapping.py +0 -134
- crypticorn/metrics/client/models/health_check_response.py +0 -91
- crypticorn/metrics/client/models/http_validation_error.py +0 -99
- crypticorn/metrics/client/models/market.py +0 -35
- crypticorn/metrics/client/models/market_type.py +0 -35
- crypticorn/metrics/client/models/validation_error.py +0 -105
- crypticorn/metrics/client/models/validation_error_loc_inner.py +0 -159
- crypticorn/pay/client/models/api_status_res.py +0 -83
- crypticorn/pay/client/models/body_create_now_invoice.py +0 -98
- crypticorn/pay/client/models/body_create_product.py +0 -98
- crypticorn/pay/client/models/body_get_products.py +0 -87
- crypticorn/pay/client/models/body_handle_now_webhook.py +0 -98
- crypticorn/pay/client/models/body_update_product.py +0 -98
- crypticorn/pay/client/models/combined_payment_history.py +0 -101
- crypticorn/pay/client/models/create_invoice_req.py +0 -188
- crypticorn/pay/client/models/create_invoice_res.py +0 -188
- crypticorn/pay/client/models/currency.py +0 -165
- crypticorn/pay/client/models/estimate_price_req.py +0 -91
- crypticorn/pay/client/models/estimate_price_res.py +0 -102
- crypticorn/pay/client/models/get_currencies_res.py +0 -99
- crypticorn/pay/client/models/get_payment_status_res.py +0 -222
- crypticorn/pay/client/models/get_payments_list_res.py +0 -109
- crypticorn/pay/client/models/min_amount_req.py +0 -124
- crypticorn/pay/client/models/min_amount_res.py +0 -105
- crypticorn/pay/client/models/now_fee_structure.py +0 -104
- crypticorn/pay/client/models/now_payment_model.py +0 -124
- crypticorn/pay/client/models/now_payment_status.py +0 -42
- crypticorn/pay/client/models/now_webhook_payload.py +0 -181
- crypticorn/pay/client/models/partial_product_update_model.py +0 -150
- crypticorn/pay/client/models/product.py +0 -87
- crypticorn/pay/client/models/product_model.py +0 -128
- crypticorn/pay/client/models/product_subs_model.py +0 -108
- crypticorn/pay/client/models/product_update_model.py +0 -150
- crypticorn/pay/client/models/unified_payment_model.py +0 -112
- crypticorn/trade/client/models/api_error_identifier.py +0 -104
- crypticorn/trade/client/models/api_error_level.py +0 -37
- crypticorn/trade/client/models/api_error_type.py +0 -37
- crypticorn/trade/client/models/api_key_model.py +0 -156
- crypticorn/trade/client/models/exchange.py +0 -35
- crypticorn/trade/client/models/market_type.py +0 -35
- /crypticorn/common/{pydantic.py → decorators.py} +0 -0
- {crypticorn-2.5.0rc5.dist-info → crypticorn-2.5.2.dist-info}/entry_points.txt +0 -0
- {crypticorn-2.5.0rc5.dist-info → crypticorn-2.5.2.dist-info}/top_level.txt +0 -0
crypticorn/auth/main.py
CHANGED
crypticorn/client.py
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
from typing import
|
2
|
-
|
3
|
-
from crypticorn.
|
4
|
-
from crypticorn.
|
5
|
-
from crypticorn.
|
6
|
-
from crypticorn.
|
7
|
-
from crypticorn.
|
8
|
-
from crypticorn.auth import AuthClient, Configuration as AuthConfig
|
1
|
+
from typing import TypeVar
|
2
|
+
from crypticorn.hive import HiveClient
|
3
|
+
from crypticorn.klines import KlinesClient
|
4
|
+
from crypticorn.pay import PayClient
|
5
|
+
from crypticorn.trade import TradeClient
|
6
|
+
from crypticorn.metrics import MetricsClient
|
7
|
+
from crypticorn.auth import AuthClient
|
9
8
|
from crypticorn.common import BaseUrl, ApiVersion, Service, apikey_header as aph
|
10
|
-
|
9
|
+
|
10
|
+
ConfigT = TypeVar("ConfigT")
|
11
|
+
SubClient = TypeVar("SubClient")
|
11
12
|
|
12
13
|
|
13
14
|
class ApiClient:
|
@@ -30,35 +31,49 @@ class ApiClient:
|
|
30
31
|
self.jwt = jwt
|
31
32
|
"""The JWT to use for authentication."""
|
32
33
|
|
33
|
-
self.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
34
|
+
self.service_classes: dict[Service, type[SubClient]] = {
|
35
|
+
Service.HIVE: HiveClient,
|
36
|
+
Service.TRADE: TradeClient,
|
37
|
+
Service.KLINES: KlinesClient,
|
38
|
+
Service.PAY: PayClient,
|
39
|
+
Service.METRICS: MetricsClient,
|
40
|
+
Service.AUTH: AuthClient,
|
41
|
+
}
|
42
|
+
|
43
|
+
self.services: dict[Service, SubClient] = {
|
44
|
+
service: client_class(self._get_default_config(service))
|
45
|
+
for service, client_class in self.service_classes.items()
|
46
|
+
}
|
47
|
+
|
48
|
+
@property
|
49
|
+
def hive(self) -> HiveClient:
|
50
|
+
return self.services[Service.HIVE]
|
51
|
+
|
52
|
+
@property
|
53
|
+
def trade(self) -> TradeClient:
|
54
|
+
return self.services[Service.TRADE]
|
55
|
+
|
56
|
+
@property
|
57
|
+
def klines(self) -> KlinesClient:
|
58
|
+
return self.services[Service.KLINES]
|
59
|
+
|
60
|
+
@property
|
61
|
+
def metrics(self) -> MetricsClient:
|
62
|
+
return self.services[Service.METRICS]
|
63
|
+
|
64
|
+
@property
|
65
|
+
def pay(self) -> PayClient:
|
66
|
+
return self.services[Service.PAY]
|
67
|
+
|
68
|
+
@property
|
69
|
+
def auth(self) -> AuthClient:
|
70
|
+
return self.services[Service.AUTH]
|
47
71
|
|
48
72
|
async def close(self):
|
49
73
|
"""Close all client sessions."""
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
self.klines.base_client,
|
54
|
-
self.pay.base_client,
|
55
|
-
self.metrics.base_client,
|
56
|
-
self.auth.base_client,
|
57
|
-
]
|
58
|
-
|
59
|
-
for client in clients:
|
60
|
-
if hasattr(client, "close"):
|
61
|
-
await client.close()
|
74
|
+
for service in self.services.values():
|
75
|
+
if hasattr(service.base_client, "close"):
|
76
|
+
await service.base_client.close()
|
62
77
|
|
63
78
|
def _get_default_config(
|
64
79
|
self, service: Service, version: ApiVersion = ApiVersion.V1
|
@@ -66,63 +81,39 @@ class ApiClient:
|
|
66
81
|
"""
|
67
82
|
Get the default configuration for a given service.
|
68
83
|
"""
|
69
|
-
config_class =
|
70
|
-
Service.HIVE: HiveConfig,
|
71
|
-
Service.TRADE: TradeConfig,
|
72
|
-
Service.KLINES: KlinesConfig,
|
73
|
-
Service.PAY: PayConfig,
|
74
|
-
Service.METRICS: MetricsConfig,
|
75
|
-
Service.AUTH: AuthConfig,
|
76
|
-
}[service]
|
84
|
+
config_class = self.service_classes[service].config_class
|
77
85
|
return config_class(
|
78
86
|
host=f"{self.base_url}/{version}/{service}",
|
79
87
|
access_token=self.jwt,
|
80
88
|
api_key={aph.scheme_name: self.api_key} if self.api_key else None,
|
81
|
-
# not necessary
|
82
|
-
# api_key_prefix=(
|
83
|
-
# {aph.scheme_name: aph.model.name} if self.api_key else None
|
84
|
-
# ),
|
85
89
|
)
|
86
90
|
|
87
91
|
def configure(
|
88
92
|
self,
|
89
|
-
config:
|
90
|
-
|
91
|
-
],
|
92
|
-
sub_client: any,
|
93
|
+
config: ConfigT,
|
94
|
+
service: Service,
|
93
95
|
):
|
94
96
|
"""
|
95
97
|
Update a sub-client's configuration by overriding with the values set in the new config.
|
96
98
|
Useful for testing a specific service against a local server instead of the default proxy.
|
97
99
|
|
98
100
|
:param config: The new configuration to use for the sub-client.
|
99
|
-
:param
|
101
|
+
:param service: The service to configure.
|
100
102
|
|
101
103
|
Example:
|
102
|
-
This will override the host for the Hive client to connect to http://localhost:8000 instead of the default proxy:
|
103
104
|
>>> async with ApiClient(base_url=BaseUrl.DEV, jwt=jwt) as client:
|
104
|
-
>>> client.configure(config=HiveConfig(host="http://localhost:8000"),
|
105
|
+
>>> client.configure(config=HiveConfig(host="http://localhost:8000"), client=client.hive)
|
105
106
|
"""
|
106
|
-
|
107
|
+
assert Service.validate(service), f"Invalid service: {service}"
|
108
|
+
client = self.services[service]
|
109
|
+
new_config = client.config
|
110
|
+
|
107
111
|
for attr in vars(config):
|
108
112
|
new_value = getattr(config, attr)
|
109
113
|
if new_value:
|
110
114
|
setattr(new_config, attr, new_value)
|
111
115
|
|
112
|
-
|
113
|
-
self.hive = HiveClient(new_config)
|
114
|
-
elif sub_client == self.trade:
|
115
|
-
self.trade = TradeClient(new_config)
|
116
|
-
elif sub_client == self.klines:
|
117
|
-
self.klines = KlinesClient(new_config)
|
118
|
-
elif sub_client == self.pay:
|
119
|
-
self.pay = PayClient(new_config)
|
120
|
-
elif sub_client == self.metrics:
|
121
|
-
self.metrics = MetricsClient(new_config)
|
122
|
-
elif sub_client == self.auth:
|
123
|
-
self.auth = AuthClient(new_config)
|
124
|
-
else:
|
125
|
-
raise ValueError(f"Unknown sub-client: {sub_client}")
|
116
|
+
self.services[service] = type(client)(new_config)
|
126
117
|
|
127
118
|
async def __aenter__(self):
|
128
119
|
return self
|
crypticorn/common/__init__.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
from crypticorn.common.errors import *
|
2
2
|
from crypticorn.common.scopes import *
|
3
3
|
from crypticorn.common.urls import *
|
4
|
-
from crypticorn.common.
|
4
|
+
from crypticorn.common.decorators import *
|
5
|
+
from crypticorn.common.mixins import *
|
5
6
|
from crypticorn.common.auth import *
|
6
7
|
from crypticorn.common.enums import *
|
7
8
|
from crypticorn.common.utils import *
|
8
9
|
from crypticorn.common.exceptions import *
|
10
|
+
from crypticorn.common.status_router import router as status_router
|
crypticorn/common/auth.py
CHANGED
@@ -2,14 +2,10 @@ import json
|
|
2
2
|
|
3
3
|
from crypticorn.auth import Verify200Response, AuthClient, Configuration
|
4
4
|
from crypticorn.auth.client.exceptions import ApiException
|
5
|
-
from crypticorn.common import
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
Scope,
|
10
|
-
Service,
|
11
|
-
)
|
12
|
-
from fastapi import Depends, HTTPException, Query, status, WebSocketException
|
5
|
+
from crypticorn.common.scopes import Scope
|
6
|
+
from crypticorn.common.exceptions import ApiError, HTTPException, ExceptionContent
|
7
|
+
from crypticorn.common.urls import BaseUrl, Service, ApiVersion
|
8
|
+
from fastapi import Depends, Query, status, WebSocketException
|
13
9
|
from fastapi.security import (
|
14
10
|
HTTPAuthorizationCredentials,
|
15
11
|
SecurityScopes,
|
@@ -48,11 +44,6 @@ class AuthHandler:
|
|
48
44
|
self.url = f"{base_url}/{ApiVersion.V1}/{Service.AUTH}"
|
49
45
|
self.client = AuthClient(Configuration(host=self.url))
|
50
46
|
|
51
|
-
self.no_credentials_exception = HTTPException(
|
52
|
-
status_code=status.HTTP_401_UNAUTHORIZED,
|
53
|
-
detail=ApiError.NO_CREDENTIALS.identifier,
|
54
|
-
)
|
55
|
-
|
56
47
|
async def _verify_api_key(self, api_key: str) -> Verify200Response:
|
57
48
|
"""
|
58
49
|
Verifies the API key.
|
@@ -76,8 +67,10 @@ class AuthHandler:
|
|
76
67
|
"""
|
77
68
|
if not set(api_scopes).issubset(user_scopes):
|
78
69
|
raise HTTPException(
|
79
|
-
|
80
|
-
|
70
|
+
content=ExceptionContent(
|
71
|
+
error=ApiError.INSUFFICIENT_SCOPES,
|
72
|
+
message="Insufficient scopes to access this resource",
|
73
|
+
),
|
81
74
|
)
|
82
75
|
|
83
76
|
async def _extract_message(self, e: ApiException) -> str:
|
@@ -100,16 +93,33 @@ class AuthHandler:
|
|
100
93
|
Handles exceptions and returns a HTTPException with the appropriate status code and detail.
|
101
94
|
"""
|
102
95
|
if isinstance(e, ApiException):
|
96
|
+
# handle the TRPC Zod errors from auth-service
|
97
|
+
# Unfortunately, we cannot share the error messages defined in python/crypticorn/common/errors.py with the typescript client
|
98
|
+
message = await self._extract_message(e)
|
99
|
+
if message == "Invalid API key":
|
100
|
+
error = ApiError.INVALID_API_KEY
|
101
|
+
elif message == "API key expired":
|
102
|
+
error = ApiError.EXPIRED_API_KEY
|
103
|
+
elif message == "jwt expired":
|
104
|
+
error = ApiError.EXPIRED_BEARER
|
105
|
+
else:
|
106
|
+
error = (
|
107
|
+
ApiError.INVALID_BEARER
|
108
|
+
) # jwt malformed, jwt not active (https://www.npmjs.com/package/jsonwebtoken#errors--codes)
|
103
109
|
return HTTPException(
|
104
|
-
|
105
|
-
|
110
|
+
content=ExceptionContent(
|
111
|
+
error=error,
|
112
|
+
message=message,
|
113
|
+
),
|
106
114
|
)
|
107
115
|
elif isinstance(e, HTTPException):
|
108
116
|
return e
|
109
117
|
else:
|
110
118
|
return HTTPException(
|
111
|
-
|
112
|
-
|
119
|
+
content=ExceptionContent(
|
120
|
+
error=ApiError.UNKNOWN_ERROR,
|
121
|
+
message=str(e),
|
122
|
+
),
|
113
123
|
)
|
114
124
|
|
115
125
|
async def api_key_auth(
|
@@ -172,7 +182,15 @@ class AuthHandler:
|
|
172
182
|
last_error = await self._handle_exception(e)
|
173
183
|
continue
|
174
184
|
|
175
|
-
|
185
|
+
if last_error:
|
186
|
+
raise last_error
|
187
|
+
else:
|
188
|
+
raise HTTPException(
|
189
|
+
content=ExceptionContent(
|
190
|
+
error=ApiError.NO_CREDENTIALS,
|
191
|
+
message="No credentials provided",
|
192
|
+
),
|
193
|
+
)
|
176
194
|
|
177
195
|
async def ws_api_key_auth(
|
178
196
|
self,
|
crypticorn/common/enums.py
CHANGED
@@ -1,42 +1,15 @@
|
|
1
1
|
from enum import StrEnum
|
2
|
+
from crypticorn.common.mixins import ValidateEnumMixin, ExcludeEnumMixin
|
2
3
|
|
3
4
|
|
4
|
-
class ValidateEnumMixin:
|
5
|
-
"""
|
6
|
-
Mixin for validating enum values manually.
|
7
|
-
|
8
|
-
⚠️ Note:
|
9
|
-
This does NOT enforce validation automatically on enum creation.
|
10
|
-
It's up to the developer to call `Class.validate(value)` where needed.
|
11
|
-
|
12
|
-
Usage:
|
13
|
-
>>> class Color(ValidateEnumMixin, StrEnum):
|
14
|
-
>>> RED = "red"
|
15
|
-
>>> GREEN = "green"
|
16
|
-
|
17
|
-
>>> Color.validate("red") # True
|
18
|
-
>>> Color.validate("yellow") # False
|
19
|
-
|
20
|
-
Order of inheritance matters — the mixin must come first.
|
21
|
-
"""
|
22
|
-
|
23
|
-
@classmethod
|
24
|
-
def validate(cls, value) -> bool:
|
25
|
-
try:
|
26
|
-
cls(value)
|
27
|
-
return True
|
28
|
-
except ValueError:
|
29
|
-
return False
|
30
|
-
|
31
|
-
|
32
|
-
class Exchange(ValidateEnumMixin, StrEnum):
|
5
|
+
class Exchange(ValidateEnumMixin, ExcludeEnumMixin, StrEnum):
|
33
6
|
"""Supported exchanges for trading"""
|
34
7
|
|
35
8
|
KUCOIN = "kucoin"
|
36
9
|
BINGX = "bingx"
|
37
10
|
|
38
11
|
|
39
|
-
class InternalExchange(ValidateEnumMixin, StrEnum):
|
12
|
+
class InternalExchange(ValidateEnumMixin, ExcludeEnumMixin, StrEnum):
|
40
13
|
"""All exchanges we are using, including public (Exchange)"""
|
41
14
|
|
42
15
|
KUCOIN = "kucoin"
|
@@ -47,7 +20,7 @@ class InternalExchange(ValidateEnumMixin, StrEnum):
|
|
47
20
|
BITGET = "bitget"
|
48
21
|
|
49
22
|
|
50
|
-
class MarketType(ValidateEnumMixin, StrEnum):
|
23
|
+
class MarketType(ValidateEnumMixin, ExcludeEnumMixin, StrEnum):
|
51
24
|
"""
|
52
25
|
Market types
|
53
26
|
"""
|
crypticorn/common/errors.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from enum import Enum, EnumMeta, StrEnum
|
2
2
|
import logging
|
3
3
|
from fastapi import status
|
4
|
+
from crypticorn.common.mixins import ExcludeEnumMixin
|
4
5
|
|
5
6
|
logger = logging.getLogger(__name__)
|
6
7
|
|
@@ -18,7 +19,7 @@ class Fallback(EnumMeta):
|
|
18
19
|
return cls.UNKNOWN_ERROR
|
19
20
|
|
20
21
|
|
21
|
-
class ApiErrorType(StrEnum):
|
22
|
+
class ApiErrorType(ExcludeEnumMixin, StrEnum):
|
22
23
|
"""Type of API error"""
|
23
24
|
|
24
25
|
USER_ERROR = "user error"
|
@@ -31,15 +32,17 @@ class ApiErrorType(StrEnum):
|
|
31
32
|
"""error that does not need to be handled or does not affect the program or is a placeholder."""
|
32
33
|
|
33
34
|
|
34
|
-
class ApiErrorIdentifier(StrEnum):
|
35
|
+
class ApiErrorIdentifier(ExcludeEnumMixin, StrEnum):
|
35
36
|
"""API error identifiers"""
|
36
37
|
|
37
38
|
ALLOCATION_BELOW_EXPOSURE = "allocation_below_current_exposure"
|
38
39
|
ALLOCATION_BELOW_MINIMUM = "allocation_below_min_amount"
|
40
|
+
ALPHANUMERIC_CHARACTERS_ONLY = "alphanumeric_characters_only"
|
39
41
|
BLACK_SWAN = "black_swan"
|
40
42
|
BOT_ALREADY_DELETED = "bot_already_deleted"
|
41
43
|
BOT_DISABLED = "bot_disabled"
|
42
44
|
BOT_STOPPING_COMPLETED = "bot_stopping_completed"
|
45
|
+
BOT_STOPPING_STARTED = "bot_stopping_started"
|
43
46
|
CLIENT_ORDER_ID_REPEATED = "client_order_id_already_exists"
|
44
47
|
CONTENT_TYPE_ERROR = "invalid_content_type"
|
45
48
|
DELETE_BOT_ERROR = "delete_bot_error"
|
@@ -56,6 +59,9 @@ class ApiErrorIdentifier(StrEnum):
|
|
56
59
|
EXCHANGE_SYSTEM_CONFIG_ERROR = "exchange_system_configuration_error"
|
57
60
|
EXCHANGE_SYSTEM_ERROR = "exchange_internal_system_error"
|
58
61
|
EXCHANGE_USER_FROZEN = "exchange_user_account_is_frozen"
|
62
|
+
EXPIRED_API_KEY = "api_key_expired"
|
63
|
+
EXPIRED_BEARER = "bearer_token_expired"
|
64
|
+
FORBIDDEN = "forbidden"
|
59
65
|
HEDGE_MODE_NOT_ACTIVE = "hedge_mode_not_active"
|
60
66
|
HTTP_ERROR = "http_request_error"
|
61
67
|
INSUFFICIENT_BALANCE = "insufficient_balance"
|
@@ -68,13 +74,15 @@ class ApiErrorIdentifier(StrEnum):
|
|
68
74
|
INVALID_EXCHANGE_KEY = "invalid_exchange_key"
|
69
75
|
INVALID_MARGIN_MODE = "invalid_margin_mode"
|
70
76
|
INVALID_PARAMETER = "invalid_parameter_provided"
|
71
|
-
JWT_EXPIRED = "jwt_expired"
|
72
77
|
LEVERAGE_EXCEEDED = "leverage_limit_exceeded"
|
73
78
|
LIQUIDATION_PRICE_VIOLATION = "order_violates_liquidation_price_constraints"
|
74
79
|
NO_CREDENTIALS = "no_credentials"
|
75
80
|
NOW_API_DOWN = "now_api_down"
|
76
|
-
OBJECT_NOT_FOUND = "object_not_found"
|
77
81
|
OBJECT_ALREADY_EXISTS = "object_already_exists"
|
82
|
+
OBJECT_CREATED = "object_created"
|
83
|
+
OBJECT_DELETED = "object_deleted"
|
84
|
+
OBJECT_NOT_FOUND = "object_not_found"
|
85
|
+
OBJECT_UPDATED = "object_updated"
|
78
86
|
ORDER_ALREADY_FILLED = "order_is_already_filled"
|
79
87
|
ORDER_IN_PROCESS = "order_is_being_processed"
|
80
88
|
ORDER_LIMIT_EXCEEDED = "order_quantity_limit_exceeded"
|
@@ -90,6 +98,7 @@ class ApiErrorIdentifier(StrEnum):
|
|
90
98
|
RISK_LIMIT_EXCEEDED = "risk_limit_exceeded"
|
91
99
|
RPC_TIMEOUT = "rpc_timeout"
|
92
100
|
SETTLEMENT_IN_PROGRESS = "system_settlement_in_process"
|
101
|
+
STRATEGY_ALREADY_EXISTS = "strategy_already_exists"
|
93
102
|
STRATEGY_DISABLED = "strategy_disabled"
|
94
103
|
STRATEGY_LEVERAGE_MISMATCH = "strategy_leverage_mismatch"
|
95
104
|
STRATEGY_NOT_SUPPORTING_EXCHANGE = "strategy_not_supporting_exchange"
|
@@ -108,7 +117,7 @@ class ApiErrorIdentifier(StrEnum):
|
|
108
117
|
return ApiError[self.value]
|
109
118
|
|
110
119
|
|
111
|
-
class ApiErrorLevel(StrEnum):
|
120
|
+
class ApiErrorLevel(ExcludeEnumMixin, StrEnum):
|
112
121
|
"""API error levels"""
|
113
122
|
|
114
123
|
ERROR = "error"
|
@@ -117,7 +126,7 @@ class ApiErrorLevel(StrEnum):
|
|
117
126
|
WARNING = "warning"
|
118
127
|
|
119
128
|
|
120
|
-
class ApiError(Enum, metaclass=Fallback):
|
129
|
+
class ApiError(ExcludeEnumMixin, Enum, metaclass=Fallback):
|
121
130
|
"""API error codes. Fallback to UNKNOWN_ERROR for error codes not yet published to PyPI."""
|
122
131
|
|
123
132
|
ALLOCATION_BELOW_EXPOSURE = (
|
@@ -130,6 +139,11 @@ class ApiError(Enum, metaclass=Fallback):
|
|
130
139
|
ApiErrorType.USER_ERROR,
|
131
140
|
ApiErrorLevel.ERROR,
|
132
141
|
)
|
142
|
+
ALPHANUMERIC_CHARACTERS_ONLY = (
|
143
|
+
ApiErrorIdentifier.ALPHANUMERIC_CHARACTERS_ONLY,
|
144
|
+
ApiErrorType.USER_ERROR,
|
145
|
+
ApiErrorLevel.ERROR,
|
146
|
+
)
|
133
147
|
BLACK_SWAN = (
|
134
148
|
ApiErrorIdentifier.BLACK_SWAN,
|
135
149
|
ApiErrorType.USER_ERROR,
|
@@ -150,6 +164,11 @@ class ApiError(Enum, metaclass=Fallback):
|
|
150
164
|
ApiErrorType.NO_ERROR,
|
151
165
|
ApiErrorLevel.INFO,
|
152
166
|
)
|
167
|
+
BOT_STOPPING_STARTED = (
|
168
|
+
ApiErrorIdentifier.BOT_STOPPING_STARTED,
|
169
|
+
ApiErrorType.NO_ERROR,
|
170
|
+
ApiErrorLevel.INFO,
|
171
|
+
)
|
153
172
|
CLIENT_ORDER_ID_REPEATED = (
|
154
173
|
ApiErrorIdentifier.CLIENT_ORDER_ID_REPEATED,
|
155
174
|
ApiErrorType.SERVER_ERROR,
|
@@ -230,6 +249,21 @@ class ApiError(Enum, metaclass=Fallback):
|
|
230
249
|
ApiErrorType.USER_ERROR,
|
231
250
|
ApiErrorLevel.ERROR,
|
232
251
|
)
|
252
|
+
EXPIRED_API_KEY = (
|
253
|
+
ApiErrorIdentifier.EXPIRED_API_KEY,
|
254
|
+
ApiErrorType.USER_ERROR,
|
255
|
+
ApiErrorLevel.ERROR,
|
256
|
+
)
|
257
|
+
EXPIRED_BEARER = (
|
258
|
+
ApiErrorIdentifier.EXPIRED_BEARER,
|
259
|
+
ApiErrorType.USER_ERROR,
|
260
|
+
ApiErrorLevel.ERROR,
|
261
|
+
)
|
262
|
+
FORBIDDEN = (
|
263
|
+
ApiErrorIdentifier.FORBIDDEN,
|
264
|
+
ApiErrorType.USER_ERROR,
|
265
|
+
ApiErrorLevel.ERROR,
|
266
|
+
)
|
233
267
|
HEDGE_MODE_NOT_ACTIVE = (
|
234
268
|
ApiErrorIdentifier.HEDGE_MODE_NOT_ACTIVE,
|
235
269
|
ApiErrorType.USER_ERROR,
|
@@ -290,11 +324,6 @@ class ApiError(Enum, metaclass=Fallback):
|
|
290
324
|
ApiErrorType.SERVER_ERROR,
|
291
325
|
ApiErrorLevel.ERROR,
|
292
326
|
)
|
293
|
-
JWT_EXPIRED = (
|
294
|
-
ApiErrorIdentifier.JWT_EXPIRED,
|
295
|
-
ApiErrorType.SERVER_ERROR,
|
296
|
-
ApiErrorLevel.ERROR,
|
297
|
-
)
|
298
327
|
LEVERAGE_EXCEEDED = (
|
299
328
|
ApiErrorIdentifier.LEVERAGE_EXCEEDED,
|
300
329
|
ApiErrorType.SERVER_ERROR,
|
@@ -315,15 +344,30 @@ class ApiError(Enum, metaclass=Fallback):
|
|
315
344
|
ApiErrorType.SERVER_ERROR,
|
316
345
|
ApiErrorLevel.ERROR,
|
317
346
|
)
|
347
|
+
OBJECT_ALREADY_EXISTS = (
|
348
|
+
ApiErrorIdentifier.OBJECT_ALREADY_EXISTS,
|
349
|
+
ApiErrorType.SERVER_ERROR,
|
350
|
+
ApiErrorLevel.ERROR,
|
351
|
+
)
|
352
|
+
OBJECT_CREATED = (
|
353
|
+
ApiErrorIdentifier.OBJECT_CREATED,
|
354
|
+
ApiErrorType.SERVER_ERROR,
|
355
|
+
ApiErrorLevel.INFO,
|
356
|
+
)
|
357
|
+
OBJECT_DELETED = (
|
358
|
+
ApiErrorIdentifier.OBJECT_DELETED,
|
359
|
+
ApiErrorType.SERVER_ERROR,
|
360
|
+
ApiErrorLevel.INFO,
|
361
|
+
)
|
318
362
|
OBJECT_NOT_FOUND = (
|
319
363
|
ApiErrorIdentifier.OBJECT_NOT_FOUND,
|
320
364
|
ApiErrorType.SERVER_ERROR,
|
321
365
|
ApiErrorLevel.ERROR,
|
322
366
|
)
|
323
|
-
|
324
|
-
ApiErrorIdentifier.
|
367
|
+
OBJECT_UPDATED = (
|
368
|
+
ApiErrorIdentifier.OBJECT_UPDATED,
|
325
369
|
ApiErrorType.SERVER_ERROR,
|
326
|
-
ApiErrorLevel.
|
370
|
+
ApiErrorLevel.INFO,
|
327
371
|
)
|
328
372
|
ORDER_ALREADY_FILLED = (
|
329
373
|
ApiErrorIdentifier.ORDER_ALREADY_FILLED,
|
@@ -400,6 +444,11 @@ class ApiError(Enum, metaclass=Fallback):
|
|
400
444
|
ApiErrorType.EXCHANGE_ERROR,
|
401
445
|
ApiErrorLevel.ERROR,
|
402
446
|
)
|
447
|
+
STRATEGY_ALREADY_EXISTS = (
|
448
|
+
ApiErrorIdentifier.STRATEGY_ALREADY_EXISTS,
|
449
|
+
ApiErrorType.USER_ERROR,
|
450
|
+
ApiErrorLevel.ERROR,
|
451
|
+
)
|
403
452
|
STRATEGY_DISABLED = (
|
404
453
|
ApiErrorIdentifier.STRATEGY_DISABLED,
|
405
454
|
ApiErrorType.USER_ERROR,
|
@@ -476,18 +525,18 @@ class ApiError(Enum, metaclass=Fallback):
|
|
476
525
|
class HttpStatusMapper:
|
477
526
|
"""Map API errors to HTTP status codes."""
|
478
527
|
|
479
|
-
# TODO: decide if we need all of these mappings, since most errors are not exposed to the client via HTTP
|
480
|
-
# in case we remove some, update the pytest length check
|
481
528
|
_mapping = {
|
482
529
|
# Authentication/Authorization
|
483
|
-
ApiError.
|
530
|
+
ApiError.EXPIRED_BEARER: status.HTTP_401_UNAUTHORIZED,
|
484
531
|
ApiError.INVALID_BEARER: status.HTTP_401_UNAUTHORIZED,
|
532
|
+
ApiError.EXPIRED_API_KEY: status.HTTP_401_UNAUTHORIZED,
|
485
533
|
ApiError.INVALID_API_KEY: status.HTTP_401_UNAUTHORIZED,
|
486
534
|
ApiError.NO_CREDENTIALS: status.HTTP_401_UNAUTHORIZED,
|
487
535
|
ApiError.INSUFFICIENT_SCOPES: status.HTTP_403_FORBIDDEN,
|
488
536
|
ApiError.EXCHANGE_PERMISSION_DENIED: status.HTTP_403_FORBIDDEN,
|
489
537
|
ApiError.EXCHANGE_USER_FROZEN: status.HTTP_403_FORBIDDEN,
|
490
538
|
ApiError.TRADING_LOCKED: status.HTTP_403_FORBIDDEN,
|
539
|
+
ApiError.FORBIDDEN: status.HTTP_403_FORBIDDEN,
|
491
540
|
# Not Found
|
492
541
|
ApiError.URL_NOT_FOUND: status.HTTP_404_NOT_FOUND,
|
493
542
|
ApiError.OBJECT_NOT_FOUND: status.HTTP_404_NOT_FOUND,
|
@@ -499,6 +548,7 @@ class HttpStatusMapper:
|
|
499
548
|
ApiError.OBJECT_ALREADY_EXISTS: status.HTTP_409_CONFLICT,
|
500
549
|
ApiError.EXCHANGE_KEY_ALREADY_EXISTS: status.HTTP_409_CONFLICT,
|
501
550
|
ApiError.BOT_ALREADY_DELETED: status.HTTP_409_CONFLICT,
|
551
|
+
ApiError.STRATEGY_ALREADY_EXISTS: status.HTTP_409_CONFLICT,
|
502
552
|
# Invalid Content
|
503
553
|
ApiError.CONTENT_TYPE_ERROR: status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
|
504
554
|
ApiError.INVALID_DATA_REQUEST: status.HTTP_422_UNPROCESSABLE_ENTITY,
|
@@ -519,6 +569,7 @@ class HttpStatusMapper:
|
|
519
569
|
ApiError.POSITION_SUSPENDED: status.HTTP_503_SERVICE_UNAVAILABLE,
|
520
570
|
ApiError.TRADING_SUSPENDED: status.HTTP_503_SERVICE_UNAVAILABLE,
|
521
571
|
# Bad Requests (400) - Invalid parameters or states
|
572
|
+
ApiError.ALPHANUMERIC_CHARACTERS_ONLY: status.HTTP_400_BAD_REQUEST,
|
522
573
|
ApiError.ALLOCATION_BELOW_EXPOSURE: status.HTTP_400_BAD_REQUEST,
|
523
574
|
ApiError.ALLOCATION_BELOW_MINIMUM: status.HTTP_400_BAD_REQUEST,
|
524
575
|
ApiError.BLACK_SWAN: status.HTTP_400_BAD_REQUEST,
|
@@ -555,6 +606,10 @@ class HttpStatusMapper:
|
|
555
606
|
# Success cases
|
556
607
|
ApiError.SUCCESS: status.HTTP_200_OK,
|
557
608
|
ApiError.BOT_STOPPING_COMPLETED: status.HTTP_200_OK,
|
609
|
+
ApiError.BOT_STOPPING_STARTED: status.HTTP_200_OK,
|
610
|
+
ApiError.OBJECT_CREATED: status.HTTP_201_CREATED,
|
611
|
+
ApiError.OBJECT_UPDATED: status.HTTP_200_OK,
|
612
|
+
ApiError.OBJECT_DELETED: status.HTTP_204_NO_CONTENT,
|
558
613
|
}
|
559
614
|
|
560
615
|
@classmethod
|