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.
Files changed (32) hide show
  1. crypticorn/auth/client/models/authorize_user_request.py +10 -4
  2. crypticorn/auth/client/models/create_api_key_request.py +19 -19
  3. crypticorn/auth/client/models/get_api_keys200_response_inner.py +18 -10
  4. crypticorn/auth/client/models/refresh_token_info200_response_user_session.py +11 -7
  5. crypticorn/auth/client/models/verify200_response.py +15 -9
  6. crypticorn/auth/client/models/verify_email200_response_auth_auth.py +15 -9
  7. crypticorn/client.py +17 -10
  8. crypticorn/common/__init__.py +2 -1
  9. crypticorn/common/auth.py +1 -0
  10. crypticorn/common/errors.py +63 -16
  11. crypticorn/common/pydantic.py +14 -8
  12. crypticorn/common/scopes.py +3 -3
  13. crypticorn/common/sorter.py +8 -6
  14. crypticorn/common/urls.py +2 -0
  15. crypticorn/klines/client/configuration.py +1 -3
  16. crypticorn/metrics/__init__.py +3 -1
  17. crypticorn/metrics/main.py +6 -5
  18. crypticorn/pay/client/__init__.py +3 -0
  19. crypticorn/pay/client/api/now_payments_api.py +3 -3
  20. crypticorn/pay/client/api/payments_api.py +47 -19
  21. crypticorn/pay/client/api/products_api.py +72 -63
  22. crypticorn/pay/client/models/__init__.py +3 -0
  23. crypticorn/pay/client/models/now_webhook_payload.py +1 -1
  24. crypticorn/pay/client/models/partial_product_update_model.py +150 -0
  25. crypticorn/pay/client/models/product_update_model.py +150 -0
  26. crypticorn/trade/client/api/api_keys_api.py +50 -50
  27. crypticorn/trade/client/api/bots_api.py +3 -3
  28. crypticorn/trade/client/api/futures_trading_panel_api.py +15 -15
  29. {crypticorn-2.2.1.dist-info → crypticorn-2.2.3.dist-info}/METADATA +1 -1
  30. {crypticorn-2.2.1.dist-info → crypticorn-2.2.3.dist-info}/RECORD +32 -30
  31. {crypticorn-2.2.1.dist-info → crypticorn-2.2.3.dist-info}/WHEEL +0 -0
  32. {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
- password: Annotated[str, Field(min_length=1, strict=True)]
34
- admin: Optional[StrictBool] = None
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 pydantic import (
21
- BaseModel,
22
- ConfigDict,
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
- expires_in: Optional[Union[StrictFloat, StrictInt]] = Field(
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
- __properties: ClassVar[List[str]] = ["name", "scopes", "expiresIn"]
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:api_keys",
59
- "write:trade:api_keys",
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:api_keys', 'write:trade:api_keys', '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')"
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
- "expiresIn": obj.get("expiresIn"),
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] = None
37
- created_at: datetime
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:api_keys",
59
- "write:trade:api_keys",
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:api_keys', 'write:trade:api_keys', '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')"
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] = None
37
- user_agent: Optional[StrictStr] = None
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]] = None
35
- nbf: Optional[Union[StrictFloat, StrictInt]] = None
36
- iat: Optional[Union[StrictFloat, StrictInt]] = None
37
- jti: Optional[StrictStr] = None
38
- scopes: Optional[List[StrictStr]] = None
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]] = None
35
- nbf: Optional[Union[StrictFloat, StrictInt]] = None
36
- iat: Optional[Union[StrictFloat, StrictInt]] = None
37
- jti: Optional[StrictStr] = None
38
- scopes: Optional[List[StrictStr]] = None
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
- '''The base URL the client will use to connect to the API.'''
25
+ """The base URL the client will use to connect to the API."""
24
26
  self.api_key = api_key
25
- '''The API key to use for authentication.'''
27
+ """The API key to use for authentication."""
26
28
  self.jwt = jwt
27
- '''The JWT to use for authentication.'''
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("The auth module does only accept JWT to be used to authenticate. If you use this module, you need to provide a JWT.")
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(self, service: Service, version: ApiVersion = ApiVersion.V1):
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=({aph.scheme_name: aph.model.name} if self.api_key else None),
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
-
@@ -1,4 +1,5 @@
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.auth import *
4
+ from crypticorn.common.pydantic import *
5
+ from crypticorn.common.auth import *
crypticorn/common/auth.py CHANGED
@@ -31,6 +31,7 @@ apikey_header = APIKeyHeader(
31
31
  description="The API key to use for authentication.",
32
32
  )
33
33
 
34
+
34
35
  # Auth Handler
35
36
  class AuthHandler:
36
37
  """
@@ -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
- API_KEY_ALREADY_EXISTS = "api_key_already_exists"
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
- API_KEY_ALREADY_EXISTS = (
94
- ApiErrorIdentifier.API_KEY_ALREADY_EXISTS,
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
- EXCHANGE_API_KEY_IN_USE = (
149
- ApiErrorIdentifier.EXCHANGE_API_KEY_IN_USE,
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.SERVER_ERROR,
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))}"
@@ -6,8 +6,9 @@ from pydantic.fields import FieldInfo
6
6
 
7
7
 
8
8
  def partial_model(model: Type[BaseModel]) -> Type[BaseModel]:
9
- '''Marks all fields of a model as optional. Useful for updating models.
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
- def make_field_optional(field: FieldInfo, default: Any = None) -> Tuple[Any, FieldInfo]:
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
- f'Partial{model.__name__}',
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
+ )
@@ -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
- READ_TRADE_APIKEYS = "read:trade:api_keys"
24
- WRITE_TRADE_APIKEYS = "write:trade:api_keys"
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
- # Read projections
43
+ # Scopes that can be purchased - these actually exist in the jwt token
44
44
  READ_PREDICTIONS = "read:predictions"
@@ -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 ApiErrorIdentifier(str, Enum):")
7
+ class_start = file_content.find(f"class {class_name}(str, Enum):")
8
8
  if class_start == -1:
9
- return "Could not find ApiErrorIdentifier class"
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 ApiErrorIdentifier(str, Enum):\n\n"
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
- pyperclip.copy(sorted_content)
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
@@ -1,11 +1,13 @@
1
1
  from enum import StrEnum
2
2
 
3
+
3
4
  class ApiEnv(StrEnum):
4
5
  PROD = "prod"
5
6
  DEV = "dev"
6
7
  LOCAL = "local"
7
8
  DOCKER = "docker"
8
9
 
10
+
9
11
  class BaseUrl(StrEnum):
10
12
  PROD = "https://api.crypticorn.com"
11
13
  DEV = "https://api.crypticorn.dev"
@@ -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