crypticorn 2.2.1__py3-none-any.whl → 2.2.3__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/authorize_user_request.py +10 -4
- crypticorn/auth/client/models/create_api_key_request.py +19 -19
- crypticorn/auth/client/models/get_api_keys200_response_inner.py +18 -10
- crypticorn/auth/client/models/refresh_token_info200_response_user_session.py +11 -7
- crypticorn/auth/client/models/verify200_response.py +15 -9
- crypticorn/auth/client/models/verify_email200_response_auth_auth.py +15 -9
- crypticorn/client.py +17 -10
- crypticorn/common/__init__.py +2 -1
- crypticorn/common/auth.py +1 -0
- crypticorn/common/errors.py +63 -16
- crypticorn/common/pydantic.py +14 -8
- crypticorn/common/scopes.py +3 -3
- crypticorn/common/sorter.py +8 -6
- crypticorn/common/urls.py +2 -0
- crypticorn/klines/client/configuration.py +1 -3
- crypticorn/metrics/__init__.py +3 -1
- crypticorn/metrics/main.py +6 -5
- crypticorn/pay/client/__init__.py +3 -0
- crypticorn/pay/client/api/now_payments_api.py +3 -3
- crypticorn/pay/client/api/payments_api.py +47 -19
- crypticorn/pay/client/api/products_api.py +72 -63
- crypticorn/pay/client/models/__init__.py +3 -0
- crypticorn/pay/client/models/now_webhook_payload.py +1 -1
- crypticorn/pay/client/models/partial_product_update_model.py +150 -0
- crypticorn/pay/client/models/product_update_model.py +150 -0
- crypticorn/trade/client/api/api_keys_api.py +50 -50
- crypticorn/trade/client/api/bots_api.py +3 -3
- crypticorn/trade/client/api/futures_trading_panel_api.py +15 -15
- {crypticorn-2.2.1.dist-info → crypticorn-2.2.3.dist-info}/METADATA +1 -1
- {crypticorn-2.2.1.dist-info → crypticorn-2.2.3.dist-info}/RECORD +32 -30
- {crypticorn-2.2.1.dist-info → crypticorn-2.2.3.dist-info}/WHEEL +0 -0
- {crypticorn-2.2.1.dist-info → crypticorn-2.2.3.dist-info}/top_level.txt +0 -0
@@ -29,11 +29,17 @@ class AuthorizeUserRequest(BaseModel):
|
|
29
29
|
AuthorizeUserRequest
|
30
30
|
""" # noqa: E501
|
31
31
|
|
32
|
-
email: Annotated[str, Field(min_length=1, strict=True)]
|
33
|
-
|
34
|
-
|
32
|
+
email: Annotated[str, Field(min_length=1, strict=True)] = Field(
|
33
|
+
description="Email of the user"
|
34
|
+
)
|
35
|
+
password: Annotated[str, Field(min_length=1, strict=True)] = Field(
|
36
|
+
description="Password of the user"
|
37
|
+
)
|
38
|
+
admin: Optional[StrictBool] = Field(
|
39
|
+
default=None, description="Whether the user is an admin"
|
40
|
+
)
|
35
41
|
captcha_token: Annotated[str, Field(min_length=1, strict=True)] = Field(
|
36
|
-
alias="captchaToken"
|
42
|
+
description="Captcha token of the authorization request", alias="captchaToken"
|
37
43
|
)
|
38
44
|
__properties: ClassVar[List[str]] = ["email", "password", "admin", "captchaToken"]
|
39
45
|
|
@@ -17,16 +17,9 @@ import pprint
|
|
17
17
|
import re # noqa: F401
|
18
18
|
import json
|
19
19
|
|
20
|
-
from
|
21
|
-
|
22
|
-
|
23
|
-
Field,
|
24
|
-
StrictFloat,
|
25
|
-
StrictInt,
|
26
|
-
StrictStr,
|
27
|
-
field_validator,
|
28
|
-
)
|
29
|
-
from typing import Any, ClassVar, Dict, List, Optional, Union
|
20
|
+
from datetime import datetime
|
21
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
|
22
|
+
from typing import Any, ClassVar, Dict, List, Optional
|
30
23
|
from typing_extensions import Annotated
|
31
24
|
from typing import Optional, Set
|
32
25
|
from typing_extensions import Self
|
@@ -37,12 +30,18 @@ class CreateApiKeyRequest(BaseModel):
|
|
37
30
|
CreateApiKeyRequest
|
38
31
|
""" # noqa: E501
|
39
32
|
|
40
|
-
name: StrictStr
|
41
|
-
scopes: Annotated[List[StrictStr], Field(min_length=1)]
|
42
|
-
|
43
|
-
default=None, alias="expiresIn"
|
33
|
+
name: StrictStr = Field(description="Name of the API key")
|
34
|
+
scopes: Annotated[List[StrictStr], Field(min_length=1)] = Field(
|
35
|
+
description="Scopes of the API key"
|
44
36
|
)
|
45
|
-
|
37
|
+
expires_at: Optional[datetime] = Field(
|
38
|
+
default=None, description="Expiration time of the API key as a date"
|
39
|
+
)
|
40
|
+
ip_whitelist: Optional[List[StrictStr]] = Field(
|
41
|
+
default=None,
|
42
|
+
description="IP addresses that can access the API key. If empty, the API key will be accessible from any IP address.",
|
43
|
+
)
|
44
|
+
__properties: ClassVar[List[str]] = ["name", "scopes", "expires_at", "ip_whitelist"]
|
46
45
|
|
47
46
|
@field_validator("scopes")
|
48
47
|
def scopes_validate_enum(cls, value):
|
@@ -55,8 +54,8 @@ class CreateApiKeyRequest(BaseModel):
|
|
55
54
|
"write:hive:model",
|
56
55
|
"read:trade:bots",
|
57
56
|
"write:trade:bots",
|
58
|
-
"read:trade:
|
59
|
-
"write:trade:
|
57
|
+
"read:trade:exchangekeys",
|
58
|
+
"write:trade:exchangekeys",
|
60
59
|
"read:trade:orders",
|
61
60
|
"read:trade:actions",
|
62
61
|
"write:trade:actions",
|
@@ -76,7 +75,7 @@ class CreateApiKeyRequest(BaseModel):
|
|
76
75
|
]
|
77
76
|
):
|
78
77
|
raise ValueError(
|
79
|
-
"each list item must be one of ('read:hive:model', 'read:hive:data', 'write:hive:model', 'read:trade:bots', 'write:trade:bots', 'read:trade:
|
78
|
+
"each list item must be one of ('read:hive:model', 'read:hive:data', 'write:hive:model', 'read:trade:bots', 'write:trade:bots', 'read:trade:exchangekeys', 'write:trade:exchangekeys', 'read:trade:orders', 'read:trade:actions', 'write:trade:actions', 'read:trade:exchanges', 'read:trade:futures', 'write:trade:futures', 'read:trade:notifications', 'write:trade:notifications', 'read:trade:strategies', 'write:trade:strategies', 'read:pay:payments', 'read:pay:products', 'write:pay:products', 'read:pay:now', 'write:pay:now', 'read:predictions')"
|
80
79
|
)
|
81
80
|
return value
|
82
81
|
|
@@ -132,7 +131,8 @@ class CreateApiKeyRequest(BaseModel):
|
|
132
131
|
{
|
133
132
|
"name": obj.get("name"),
|
134
133
|
"scopes": obj.get("scopes"),
|
135
|
-
"
|
134
|
+
"expires_at": obj.get("expires_at"),
|
135
|
+
"ip_whitelist": obj.get("ip_whitelist"),
|
136
136
|
}
|
137
137
|
)
|
138
138
|
return _obj
|
@@ -18,7 +18,7 @@ import re # noqa: F401
|
|
18
18
|
import json
|
19
19
|
|
20
20
|
from datetime import datetime
|
21
|
-
from pydantic import BaseModel, ConfigDict, StrictStr, field_validator
|
21
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
|
22
22
|
from typing import Any, ClassVar, Dict, List, Optional
|
23
23
|
from typing import Optional, Set
|
24
24
|
from typing_extensions import Self
|
@@ -29,12 +29,18 @@ class GetApiKeys200ResponseInner(BaseModel):
|
|
29
29
|
GetApiKeys200ResponseInner
|
30
30
|
""" # noqa: E501
|
31
31
|
|
32
|
-
id: StrictStr
|
33
|
-
user_id: StrictStr
|
34
|
-
scopes: List[StrictStr]
|
35
|
-
name: StrictStr
|
36
|
-
expires_at: Optional[datetime] =
|
37
|
-
|
32
|
+
id: StrictStr = Field(description="ID of the API key")
|
33
|
+
user_id: StrictStr = Field(description="User ID of the API key")
|
34
|
+
scopes: List[StrictStr] = Field(description="Scopes of the API key")
|
35
|
+
name: StrictStr = Field(description="Name of the API key")
|
36
|
+
expires_at: Optional[datetime] = Field(
|
37
|
+
default=None, description="Expiration time of the API key as a date"
|
38
|
+
)
|
39
|
+
created_at: datetime = Field(description="Creation time of the API key as a date")
|
40
|
+
ip_whitelist: Optional[List[StrictStr]] = Field(
|
41
|
+
default=None,
|
42
|
+
description="IP addresses that can access the API key. If empty, the API key will be accessible from any IP address.",
|
43
|
+
)
|
38
44
|
__properties: ClassVar[List[str]] = [
|
39
45
|
"id",
|
40
46
|
"user_id",
|
@@ -42,6 +48,7 @@ class GetApiKeys200ResponseInner(BaseModel):
|
|
42
48
|
"name",
|
43
49
|
"expires_at",
|
44
50
|
"created_at",
|
51
|
+
"ip_whitelist",
|
45
52
|
]
|
46
53
|
|
47
54
|
@field_validator("scopes")
|
@@ -55,8 +62,8 @@ class GetApiKeys200ResponseInner(BaseModel):
|
|
55
62
|
"write:hive:model",
|
56
63
|
"read:trade:bots",
|
57
64
|
"write:trade:bots",
|
58
|
-
"read:trade:
|
59
|
-
"write:trade:
|
65
|
+
"read:trade:exchangekeys",
|
66
|
+
"write:trade:exchangekeys",
|
60
67
|
"read:trade:orders",
|
61
68
|
"read:trade:actions",
|
62
69
|
"write:trade:actions",
|
@@ -76,7 +83,7 @@ class GetApiKeys200ResponseInner(BaseModel):
|
|
76
83
|
]
|
77
84
|
):
|
78
85
|
raise ValueError(
|
79
|
-
"each list item must be one of ('read:hive:model', 'read:hive:data', 'write:hive:model', 'read:trade:bots', 'write:trade:bots', 'read:trade:
|
86
|
+
"each list item must be one of ('read:hive:model', 'read:hive:data', 'write:hive:model', 'read:trade:bots', 'write:trade:bots', 'read:trade:exchangekeys', 'write:trade:exchangekeys', 'read:trade:orders', 'read:trade:actions', 'write:trade:actions', 'read:trade:exchanges', 'read:trade:futures', 'write:trade:futures', 'read:trade:notifications', 'write:trade:notifications', 'read:trade:strategies', 'write:trade:strategies', 'read:pay:payments', 'read:pay:products', 'write:pay:products', 'read:pay:now', 'write:pay:now', 'read:predictions')"
|
80
87
|
)
|
81
88
|
return value
|
82
89
|
|
@@ -136,6 +143,7 @@ class GetApiKeys200ResponseInner(BaseModel):
|
|
136
143
|
"name": obj.get("name"),
|
137
144
|
"expires_at": obj.get("expires_at"),
|
138
145
|
"created_at": obj.get("created_at"),
|
146
|
+
"ip_whitelist": obj.get("ip_whitelist"),
|
139
147
|
}
|
140
148
|
)
|
141
149
|
return _obj
|
@@ -18,7 +18,7 @@ import re # noqa: F401
|
|
18
18
|
import json
|
19
19
|
|
20
20
|
from datetime import datetime
|
21
|
-
from pydantic import BaseModel, ConfigDict, StrictStr
|
21
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictStr
|
22
22
|
from typing import Any, ClassVar, Dict, List, Optional
|
23
23
|
from typing import Optional, Set
|
24
24
|
from typing_extensions import Self
|
@@ -29,12 +29,16 @@ class RefreshTokenInfo200ResponseUserSession(BaseModel):
|
|
29
29
|
RefreshTokenInfo200ResponseUserSession
|
30
30
|
""" # noqa: E501
|
31
31
|
|
32
|
-
id: StrictStr
|
33
|
-
user_id: StrictStr
|
34
|
-
token: StrictStr
|
35
|
-
expires_at: datetime
|
36
|
-
client_ip: Optional[StrictStr] =
|
37
|
-
|
32
|
+
id: StrictStr = Field(description="ID of the user session")
|
33
|
+
user_id: StrictStr = Field(description="User ID of the user session")
|
34
|
+
token: StrictStr = Field(description="Token of the user session")
|
35
|
+
expires_at: datetime = Field(description="Expiration time of the user session")
|
36
|
+
client_ip: Optional[StrictStr] = Field(
|
37
|
+
default=None, description="Client IP address of the user session"
|
38
|
+
)
|
39
|
+
user_agent: Optional[StrictStr] = Field(
|
40
|
+
default=None, description="User agent of the user session"
|
41
|
+
)
|
38
42
|
__properties: ClassVar[List[str]] = [
|
39
43
|
"id",
|
40
44
|
"user_id",
|
@@ -17,7 +17,7 @@ import pprint
|
|
17
17
|
import re # noqa: F401
|
18
18
|
import json
|
19
19
|
|
20
|
-
from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt, StrictStr
|
20
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr
|
21
21
|
from typing import Any, ClassVar, Dict, List, Optional, Union
|
22
22
|
from typing import Optional, Set
|
23
23
|
from typing_extensions import Self
|
@@ -28,14 +28,20 @@ class Verify200Response(BaseModel):
|
|
28
28
|
Verify200Response
|
29
29
|
""" # noqa: E501
|
30
30
|
|
31
|
-
iss: Optional[StrictStr] = None
|
32
|
-
sub: Optional[StrictStr] = None
|
33
|
-
aud: Optional[StrictStr] = None
|
34
|
-
exp: Optional[Union[StrictFloat, StrictInt]] =
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
iss: Optional[StrictStr] = Field(default=None, description="Issuer")
|
32
|
+
sub: Optional[StrictStr] = Field(default=None, description="Subject")
|
33
|
+
aud: Optional[StrictStr] = Field(default=None, description="Audience")
|
34
|
+
exp: Optional[Union[StrictFloat, StrictInt]] = Field(
|
35
|
+
default=None, description="Expiration time"
|
36
|
+
)
|
37
|
+
nbf: Optional[Union[StrictFloat, StrictInt]] = Field(
|
38
|
+
default=None, description="Not valid before time"
|
39
|
+
)
|
40
|
+
iat: Optional[Union[StrictFloat, StrictInt]] = Field(
|
41
|
+
default=None, description="Issued at time"
|
42
|
+
)
|
43
|
+
jti: Optional[StrictStr] = Field(default=None, description="JWT ID")
|
44
|
+
scopes: Optional[List[StrictStr]] = Field(default=None, description="Scopes")
|
39
45
|
__properties: ClassVar[List[str]] = [
|
40
46
|
"iss",
|
41
47
|
"sub",
|
@@ -17,7 +17,7 @@ import pprint
|
|
17
17
|
import re # noqa: F401
|
18
18
|
import json
|
19
19
|
|
20
|
-
from pydantic import BaseModel, ConfigDict, StrictFloat, StrictInt, StrictStr
|
20
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr
|
21
21
|
from typing import Any, ClassVar, Dict, List, Optional, Union
|
22
22
|
from typing import Optional, Set
|
23
23
|
from typing_extensions import Self
|
@@ -28,14 +28,20 @@ class VerifyEmail200ResponseAuthAuth(BaseModel):
|
|
28
28
|
VerifyEmail200ResponseAuthAuth
|
29
29
|
""" # noqa: E501
|
30
30
|
|
31
|
-
iss: Optional[StrictStr] = None
|
32
|
-
sub: Optional[StrictStr] = None
|
33
|
-
aud: Optional[StrictStr] = None
|
34
|
-
exp: Optional[Union[StrictFloat, StrictInt]] =
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
iss: Optional[StrictStr] = Field(default=None, description="Issuer")
|
32
|
+
sub: Optional[StrictStr] = Field(default=None, description="Subject")
|
33
|
+
aud: Optional[StrictStr] = Field(default=None, description="Audience")
|
34
|
+
exp: Optional[Union[StrictFloat, StrictInt]] = Field(
|
35
|
+
default=None, description="Expiration time"
|
36
|
+
)
|
37
|
+
nbf: Optional[Union[StrictFloat, StrictInt]] = Field(
|
38
|
+
default=None, description="Not valid before time"
|
39
|
+
)
|
40
|
+
iat: Optional[Union[StrictFloat, StrictInt]] = Field(
|
41
|
+
default=None, description="Issued at time"
|
42
|
+
)
|
43
|
+
jti: Optional[StrictStr] = Field(default=None, description="JWT ID")
|
44
|
+
scopes: Optional[List[StrictStr]] = Field(default=None, description="Scopes")
|
39
45
|
__properties: ClassVar[List[str]] = [
|
40
46
|
"iss",
|
41
47
|
"sub",
|
crypticorn/client.py
CHANGED
@@ -6,6 +6,8 @@ from crypticorn.metrics import MetricsClient
|
|
6
6
|
from crypticorn.auth import AuthClient
|
7
7
|
from crypticorn.common import BaseUrl, ApiVersion, Service, apikey_header as aph
|
8
8
|
import warnings
|
9
|
+
|
10
|
+
|
9
11
|
class ApiClient:
|
10
12
|
"""
|
11
13
|
The official client for interacting with the Crypticorn API.
|
@@ -20,25 +22,27 @@ class ApiClient:
|
|
20
22
|
base_url: BaseUrl = BaseUrl.PROD,
|
21
23
|
):
|
22
24
|
self.base_url = base_url
|
23
|
-
|
25
|
+
"""The base URL the client will use to connect to the API."""
|
24
26
|
self.api_key = api_key
|
25
|
-
|
27
|
+
"""The API key to use for authentication."""
|
26
28
|
self.jwt = jwt
|
27
|
-
|
29
|
+
"""The JWT to use for authentication."""
|
28
30
|
|
29
31
|
self.hive = HiveClient(self._get_default_config(Service.HIVE))
|
30
32
|
self.trade = TradeClient(self._get_default_config(Service.TRADE))
|
31
33
|
self.klines = KlinesClient(self._get_default_config(Service.KLINES))
|
32
34
|
self.pay = PayClient(self._get_default_config(Service.PAY))
|
33
35
|
self.metrics = MetricsClient(self._get_default_config(Service.METRICS))
|
34
|
-
self.auth = AuthClient(self._get_default_config(Service.AUTH))
|
36
|
+
self.auth = AuthClient(self._get_default_config(Service.AUTH))
|
35
37
|
|
36
38
|
def __new__(cls, *args, **kwargs):
|
37
39
|
if kwargs.get("api_key") and not kwargs.get("jwt"):
|
38
40
|
# auth-service does not allow api_key
|
39
|
-
warnings.warn(
|
41
|
+
warnings.warn(
|
42
|
+
"The auth module does only accept JWT to be used to authenticate. If you use this module, you need to provide a JWT."
|
43
|
+
)
|
40
44
|
return super().__new__(cls)
|
41
|
-
|
45
|
+
|
42
46
|
async def close(self):
|
43
47
|
"""Close all client sessions."""
|
44
48
|
clients = [
|
@@ -54,7 +58,9 @@ class ApiClient:
|
|
54
58
|
if hasattr(client, "close"):
|
55
59
|
await client.close()
|
56
60
|
|
57
|
-
def _get_default_config(
|
61
|
+
def _get_default_config(
|
62
|
+
self, service: Service, version: ApiVersion = ApiVersion.V1
|
63
|
+
):
|
58
64
|
"""
|
59
65
|
Get the default configuration for a given service.
|
60
66
|
"""
|
@@ -62,9 +68,11 @@ class ApiClient:
|
|
62
68
|
host=f"{self.base_url}/{version}/{service}",
|
63
69
|
access_token=self.jwt,
|
64
70
|
api_key={aph.scheme_name: self.api_key} if self.api_key else None,
|
65
|
-
api_key_prefix=(
|
71
|
+
api_key_prefix=(
|
72
|
+
{aph.scheme_name: aph.model.name} if self.api_key else None
|
73
|
+
),
|
66
74
|
)
|
67
|
-
|
75
|
+
|
68
76
|
def configure(self, config: Configuration, sub_client: any):
|
69
77
|
"""
|
70
78
|
Update a sub-client's configuration by overriding with the values set in the new config.
|
@@ -104,4 +112,3 @@ class ApiClient:
|
|
104
112
|
|
105
113
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
106
114
|
await self.close()
|
107
|
-
|
crypticorn/common/__init__.py
CHANGED
crypticorn/common/auth.py
CHANGED
crypticorn/common/errors.py
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
from enum import Enum
|
1
|
+
from enum import Enum, EnumMeta
|
2
|
+
import logging
|
3
|
+
|
4
|
+
logger = logging.getLogger(__name__)
|
5
|
+
|
6
|
+
|
7
|
+
class Fallback(EnumMeta):
|
8
|
+
def __getattr__(cls, name):
|
9
|
+
"""Fallback to UNKNOWN_ERROR for error codes not yet published to PyPI."""
|
10
|
+
logger.warning(
|
11
|
+
f"Unknown error code '{name}' - update crypticorn package or check for typos"
|
12
|
+
)
|
13
|
+
return cls.UNKNOWN_ERROR
|
2
14
|
|
3
15
|
|
4
16
|
class ApiErrorType(str, Enum):
|
@@ -17,7 +29,8 @@ class ApiErrorType(str, Enum):
|
|
17
29
|
class ApiErrorIdentifier(str, Enum):
|
18
30
|
"""API error identifiers"""
|
19
31
|
|
20
|
-
|
32
|
+
ALLOCATION_BELOW_EXPOSURE = "allocation_below_current_exposure"
|
33
|
+
ALLOCATION_BELOW_MINIMUM = "allocation_below_min_amount"
|
21
34
|
BLACK_SWAN = "black_swan"
|
22
35
|
BOT_ALREADY_DELETED = "bot_already_deleted"
|
23
36
|
BOT_DISABLED = "bot_disabled"
|
@@ -25,18 +38,19 @@ class ApiErrorIdentifier(str, Enum):
|
|
25
38
|
CLIENT_ORDER_ID_REPEATED = "client_order_id_already_exists"
|
26
39
|
CONTENT_TYPE_ERROR = "invalid_content_type"
|
27
40
|
DELETE_BOT_ERROR = "delete_bot_error"
|
28
|
-
EXCHANGE_API_KEY_IN_USE = "exchange_api_key_in_use"
|
29
41
|
EXCHANGE_INVALID_SIGNATURE = "exchange_invalid_signature"
|
30
42
|
EXCHANGE_INVALID_TIMESTAMP = "exchange_invalid_timestamp"
|
31
43
|
EXCHANGE_IP_RESTRICTED = "exchange_ip_address_is_not_authorized"
|
44
|
+
EXCHANGE_KEY_ALREADY_EXISTS = "exchange_key_already_exists"
|
45
|
+
EXCHANGE_KEY_IN_USE = "exchange_key_in_use"
|
32
46
|
EXCHANGE_MAINTENANCE = "exchange_system_under_maintenance"
|
33
47
|
EXCHANGE_RATE_LIMIT = "exchange_rate_limit_exceeded"
|
48
|
+
EXCHANGE_PERMISSION_DENIED = "insufficient_permissions_spot_and_futures_required"
|
34
49
|
EXCHANGE_SERVICE_UNAVAILABLE = "exchange_service_temporarily_unavailable"
|
35
50
|
EXCHANGE_SYSTEM_BUSY = "exchange_system_is_busy"
|
36
51
|
EXCHANGE_SYSTEM_CONFIG_ERROR = "exchange_system_configuration_error"
|
37
52
|
EXCHANGE_SYSTEM_ERROR = "exchange_internal_system_error"
|
38
53
|
EXCHANGE_USER_FROZEN = "exchange_user_account_is_frozen"
|
39
|
-
EXCHANGE_PERMISSION_DENIED = "insufficient_permissions_spot_and_futures_required"
|
40
54
|
HEDGE_MODE_NOT_ACTIVE = "hedge_mode_not_active"
|
41
55
|
HTTP_ERROR = "http_request_error"
|
42
56
|
INSUFFICIENT_BALANCE = "insufficient_balance"
|
@@ -51,6 +65,7 @@ class ApiErrorIdentifier(str, Enum):
|
|
51
65
|
LEVERAGE_EXCEEDED = "leverage_limit_exceeded"
|
52
66
|
LIQUIDATION_PRICE_VIOLATION = "order_violates_liquidation_price_constraints"
|
53
67
|
NO_CREDENTIALS = "no_credentials"
|
68
|
+
NOW_API_DOWN = "now_api_down"
|
54
69
|
OBJECT_NOT_FOUND = "object_not_found"
|
55
70
|
ORDER_ALREADY_FILLED = "order_is_already_filled"
|
56
71
|
ORDER_IN_PROCESS = "order_is_being_processed"
|
@@ -68,11 +83,13 @@ class ApiErrorIdentifier(str, Enum):
|
|
68
83
|
RPC_TIMEOUT = "rpc_timeout"
|
69
84
|
SETTLEMENT_IN_PROGRESS = "system_settlement_in_process"
|
70
85
|
STRATEGY_DISABLED = "strategy_disabled"
|
86
|
+
STRATEGY_LEVERAGE_MISMATCH = "strategy_leverage_mismatch"
|
87
|
+
STRATEGY_NOT_SUPPORTING_EXCHANGE = "strategy_not_supporting_exchange"
|
71
88
|
SUCCESS = "success"
|
72
89
|
SYMBOL_NOT_FOUND = "symbol_does_not_exist"
|
73
|
-
TRADING_LOCKED = "trading_has_been_locked"
|
74
90
|
TRADING_ACTION_EXPIRED = "trading_action_expired"
|
75
91
|
TRADING_ACTION_SKIPPED = "trading_action_skipped"
|
92
|
+
TRADING_LOCKED = "trading_has_been_locked"
|
76
93
|
TRADING_SUSPENDED = "trading_is_suspended"
|
77
94
|
UNKNOWN_ERROR = "unknown_error_occurred"
|
78
95
|
URL_NOT_FOUND = "requested_resource_not_found"
|
@@ -87,11 +104,16 @@ class ApiErrorLevel(str, Enum):
|
|
87
104
|
WARNING = "warning"
|
88
105
|
|
89
106
|
|
90
|
-
class ApiError(Enum):
|
107
|
+
class ApiError(Enum, metaclass=Fallback):
|
91
108
|
"""API error codes"""
|
92
109
|
|
93
|
-
|
94
|
-
ApiErrorIdentifier.
|
110
|
+
ALLOCATION_BELOW_EXPOSURE = (
|
111
|
+
ApiErrorIdentifier.ALLOCATION_BELOW_EXPOSURE,
|
112
|
+
ApiErrorType.USER_ERROR,
|
113
|
+
ApiErrorLevel.ERROR,
|
114
|
+
)
|
115
|
+
ALLOCATION_BELOW_MINIMUM = (
|
116
|
+
ApiErrorIdentifier.ALLOCATION_BELOW_MINIMUM,
|
95
117
|
ApiErrorType.USER_ERROR,
|
96
118
|
ApiErrorLevel.ERROR,
|
97
119
|
)
|
@@ -145,8 +167,13 @@ class ApiError(Enum):
|
|
145
167
|
ApiErrorType.SERVER_ERROR,
|
146
168
|
ApiErrorLevel.ERROR,
|
147
169
|
)
|
148
|
-
|
149
|
-
ApiErrorIdentifier.
|
170
|
+
EXCHANGE_KEY_ALREADY_EXISTS = (
|
171
|
+
ApiErrorIdentifier.EXCHANGE_KEY_ALREADY_EXISTS,
|
172
|
+
ApiErrorType.USER_ERROR,
|
173
|
+
ApiErrorLevel.ERROR,
|
174
|
+
)
|
175
|
+
EXCHANGE_KEY_IN_USE = (
|
176
|
+
ApiErrorIdentifier.EXCHANGE_KEY_IN_USE,
|
150
177
|
ApiErrorType.SERVER_ERROR,
|
151
178
|
ApiErrorLevel.ERROR,
|
152
179
|
)
|
@@ -155,16 +182,16 @@ class ApiError(Enum):
|
|
155
182
|
ApiErrorType.EXCHANGE_ERROR,
|
156
183
|
ApiErrorLevel.ERROR,
|
157
184
|
)
|
158
|
-
EXCHANGE_PERMISSION_DENIED = (
|
159
|
-
ApiErrorIdentifier.EXCHANGE_PERMISSION_DENIED,
|
160
|
-
ApiErrorType.USER_ERROR,
|
161
|
-
ApiErrorLevel.ERROR,
|
162
|
-
)
|
163
185
|
EXCHANGE_RATE_LIMIT = (
|
164
186
|
ApiErrorIdentifier.EXCHANGE_RATE_LIMIT,
|
165
187
|
ApiErrorType.EXCHANGE_ERROR,
|
166
188
|
ApiErrorLevel.ERROR,
|
167
189
|
)
|
190
|
+
EXCHANGE_PERMISSION_DENIED = (
|
191
|
+
ApiErrorIdentifier.EXCHANGE_PERMISSION_DENIED,
|
192
|
+
ApiErrorType.USER_ERROR,
|
193
|
+
ApiErrorLevel.ERROR,
|
194
|
+
)
|
168
195
|
EXCHANGE_SERVICE_UNAVAILABLE = (
|
169
196
|
ApiErrorIdentifier.EXCHANGE_SERVICE_UNAVAILABLE,
|
170
197
|
ApiErrorType.EXCHANGE_ERROR,
|
@@ -260,6 +287,11 @@ class ApiError(Enum):
|
|
260
287
|
ApiErrorType.USER_ERROR,
|
261
288
|
ApiErrorLevel.ERROR,
|
262
289
|
)
|
290
|
+
NOW_API_DOWN = (
|
291
|
+
ApiErrorIdentifier.NOW_API_DOWN,
|
292
|
+
ApiErrorType.SERVER_ERROR,
|
293
|
+
ApiErrorLevel.ERROR,
|
294
|
+
)
|
263
295
|
OBJECT_NOT_FOUND = (
|
264
296
|
ApiErrorIdentifier.OBJECT_NOT_FOUND,
|
265
297
|
ApiErrorType.SERVER_ERROR,
|
@@ -342,7 +374,17 @@ class ApiError(Enum):
|
|
342
374
|
)
|
343
375
|
STRATEGY_DISABLED = (
|
344
376
|
ApiErrorIdentifier.STRATEGY_DISABLED,
|
345
|
-
ApiErrorType.
|
377
|
+
ApiErrorType.USER_ERROR,
|
378
|
+
ApiErrorLevel.ERROR,
|
379
|
+
)
|
380
|
+
STRATEGY_LEVERAGE_MISMATCH = (
|
381
|
+
ApiErrorIdentifier.STRATEGY_LEVERAGE_MISMATCH,
|
382
|
+
ApiErrorType.USER_ERROR,
|
383
|
+
ApiErrorLevel.ERROR,
|
384
|
+
)
|
385
|
+
STRATEGY_NOT_SUPPORTING_EXCHANGE = (
|
386
|
+
ApiErrorIdentifier.STRATEGY_NOT_SUPPORTING_EXCHANGE,
|
387
|
+
ApiErrorType.USER_ERROR,
|
346
388
|
ApiErrorLevel.ERROR,
|
347
389
|
)
|
348
390
|
SUCCESS = (ApiErrorIdentifier.SUCCESS, ApiErrorType.NO_ERROR, ApiErrorLevel.SUCCESS)
|
@@ -393,3 +435,8 @@ class ApiError(Enum):
|
|
393
435
|
@property
|
394
436
|
def level(self) -> ApiErrorLevel:
|
395
437
|
return self.value[2]
|
438
|
+
|
439
|
+
|
440
|
+
assert len(list(ApiErrorIdentifier)) == len(
|
441
|
+
list(ApiError)
|
442
|
+
), f"{len(list(ApiErrorIdentifier))} != {len(list(ApiError))}"
|
crypticorn/common/pydantic.py
CHANGED
@@ -6,8 +6,9 @@ from pydantic.fields import FieldInfo
|
|
6
6
|
|
7
7
|
|
8
8
|
def partial_model(model: Type[BaseModel]) -> Type[BaseModel]:
|
9
|
-
|
10
|
-
|
9
|
+
"""Marks all fields of a model as optional. Useful for updating models.
|
10
|
+
Inherits all fields, docstrings, and the model name.
|
11
|
+
|
11
12
|
>>> @partial_model
|
12
13
|
>>> class Model(BaseModel):
|
13
14
|
>>> i: int
|
@@ -15,18 +16,23 @@ def partial_model(model: Type[BaseModel]) -> Type[BaseModel]:
|
|
15
16
|
>>> s: str
|
16
17
|
|
17
18
|
>>> Model(i=1)
|
18
|
-
|
19
|
-
|
19
|
+
"""
|
20
|
+
|
21
|
+
def make_field_optional(
|
22
|
+
field: FieldInfo, default: Any = None
|
23
|
+
) -> Tuple[Any, FieldInfo]:
|
20
24
|
new = deepcopy(field)
|
21
25
|
new.default = default
|
22
26
|
new.annotation = Optional[field.annotation] # type: ignore
|
23
|
-
return new.annotation, new
|
27
|
+
return new.annotation, new
|
28
|
+
|
24
29
|
return create_model(
|
25
|
-
|
30
|
+
model.__name__,
|
26
31
|
__base__=model,
|
27
32
|
__module__=model.__module__,
|
33
|
+
__doc__=model.__doc__,
|
28
34
|
**{
|
29
35
|
field_name: make_field_optional(field_info)
|
30
36
|
for field_name, field_info in model.model_fields.items()
|
31
|
-
}
|
32
|
-
)
|
37
|
+
},
|
38
|
+
)
|
crypticorn/common/scopes.py
CHANGED
@@ -20,8 +20,8 @@ class Scope(StrEnum):
|
|
20
20
|
# Trade scopes
|
21
21
|
READ_TRADE_BOTS = "read:trade:bots"
|
22
22
|
WRITE_TRADE_BOTS = "write:trade:bots"
|
23
|
-
|
24
|
-
|
23
|
+
READ_TRADE_EXCHANGEKEYS = "read:trade:exchangekeys"
|
24
|
+
WRITE_TRADE_EXCHANGEKEYS = "write:trade:exchangekeys"
|
25
25
|
READ_TRADE_ORDERS = "read:trade:orders"
|
26
26
|
READ_TRADE_ACTIONS = "read:trade:actions"
|
27
27
|
WRITE_TRADE_ACTIONS = "write:trade:actions"
|
@@ -40,5 +40,5 @@ class Scope(StrEnum):
|
|
40
40
|
READ_PAY_NOW = "read:pay:now"
|
41
41
|
WRITE_PAY_NOW = "write:pay:now"
|
42
42
|
|
43
|
-
#
|
43
|
+
# Scopes that can be purchased - these actually exist in the jwt token
|
44
44
|
READ_PREDICTIONS = "read:predictions"
|
crypticorn/common/sorter.py
CHANGED
@@ -2,11 +2,11 @@ import re
|
|
2
2
|
import pyperclip
|
3
3
|
|
4
4
|
|
5
|
-
def sort_api_errors(file_content):
|
5
|
+
def sort_api_errors(file_content, class_name="ApiErrorIdentifier"):
|
6
6
|
# Find the start of the ApiError class definition
|
7
|
-
class_start = file_content.find("class
|
7
|
+
class_start = file_content.find(f"class {class_name}(str, Enum):")
|
8
8
|
if class_start == -1:
|
9
|
-
return "Could not find
|
9
|
+
return f"Could not find {class_name} class"
|
10
10
|
|
11
11
|
# Find all enum definitions
|
12
12
|
enum_pattern = r' ([A-Z_]+)\s*=\s*"([a-z_]+)"'
|
@@ -23,7 +23,7 @@ def sort_api_errors(file_content):
|
|
23
23
|
sorted_entries = sorted(enum_entries, key=lambda x: x[0])
|
24
24
|
|
25
25
|
# Reconstruct the class content
|
26
|
-
class_header = "class
|
26
|
+
class_header = f"class {class_name}(str, Enum):\n\n"
|
27
27
|
sorted_content = class_header + "\n ".join(entry[1] for entry in sorted_entries)
|
28
28
|
|
29
29
|
return sorted_content
|
@@ -34,5 +34,7 @@ if __name__ == "__main__":
|
|
34
34
|
with open("python/crypticorn/common/errors.py", "r") as f:
|
35
35
|
content = f.read()
|
36
36
|
|
37
|
-
sorted_content = sort_api_errors(content)
|
38
|
-
|
37
|
+
sorted_content = sort_api_errors(content, "ApiErrorIdentifier")
|
38
|
+
print(sorted_content)
|
39
|
+
sorted_content = sort_api_errors(content, "ApiError")
|
40
|
+
print(sorted_content)
|
crypticorn/common/urls.py
CHANGED
@@ -190,9 +190,7 @@ class Configuration:
|
|
190
190
|
debug: Optional[bool] = None,
|
191
191
|
) -> None:
|
192
192
|
"""Constructor"""
|
193
|
-
self._base_path =
|
194
|
-
"http://localhost/v1/klines" if host is None else host
|
195
|
-
)
|
193
|
+
self._base_path = "http://localhost/v1/klines" if host is None else host
|
196
194
|
"""Default Base url
|
197
195
|
"""
|
198
196
|
self.server_index = 0 if server_index is None and host is None else server_index
|