crypticorn 2.3.0__py3-none-any.whl → 2.4.0__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.
@@ -0,0 +1,3 @@
1
+ from crypticorn.cli.init import init_group
2
+
3
+ __all__ = ["init_group"]
@@ -0,0 +1,14 @@
1
+ # crypticorn/cli.py
2
+
3
+ import click
4
+ from crypticorn.cli import init_group
5
+
6
+ @click.group()
7
+ def cli():
8
+ """🧙 Crypticorn CLI — magic for our microservices."""
9
+ pass
10
+
11
+ cli.add_command(init_group, name="init")
12
+
13
+ if __name__ == "__main__":
14
+ cli()
crypticorn/cli/init.py ADDED
@@ -0,0 +1,86 @@
1
+ import click
2
+ from pathlib import Path
3
+ import subprocess
4
+ import importlib.resources as pkg_resources
5
+ import crypticorn.cli.templates as templates
6
+
7
+ def get_git_root() -> Path:
8
+ '''Get the root directory of the git repository.'''
9
+ try:
10
+ return Path(subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip())
11
+ except Exception:
12
+ return Path.cwd()
13
+
14
+ def copy_template(template_name: str, target_path: Path):
15
+ '''Copy a template file to the target path.'''
16
+ with pkg_resources.files(templates).joinpath(template_name).open("r") as template_file:
17
+ content = template_file.read()
18
+
19
+ target_path.parent.mkdir(parents=True, exist_ok=True)
20
+ with target_path.open("w") as f:
21
+ f.write(content)
22
+
23
+ click.secho(f"✅ Created: {target_path}", fg="green")
24
+
25
+ @click.group()
26
+ def init_group():
27
+ """Initialize files like CI configs, linters, etc."""
28
+ pass
29
+
30
+ @init_group.command("ruff")
31
+ @click.option('-f', '--force', is_flag=True, help='Force overwrite the ruff.yml')
32
+ def init_ruff(force):
33
+ """Add .github/workflows/ruff.yml"""
34
+ root = get_git_root()
35
+ target = root / ".github/workflows/ruff.yml"
36
+ if target.exists() and not force:
37
+ click.secho("File already exists, use --force / -f to overwrite", fg="red")
38
+ return
39
+ copy_template("ruff.yml", target)
40
+
41
+ @init_group.command("docker")
42
+ @click.option('-o', '--output', type=click.Path(), help='Custom output path for the Dockerfile')
43
+ @click.option('-f', '--force', is_flag=True, help='Force overwrite the Dockerfile')
44
+ def init_docker(output, force):
45
+ """Add Dockerfile"""
46
+ root = get_git_root()
47
+ target = Path(output) if output else root / "Dockerfile"
48
+ if target.exists() and not force:
49
+ click.secho("File already exists, use --force / -f to overwrite", fg="red")
50
+ return
51
+ copy_template("Dockerfile", target)
52
+ click.secho("Make sure to update the Dockerfile", fg="yellow")
53
+
54
+ @init_group.command("auth")
55
+ @click.option('-o', '--output', type=click.Path(), help='Custom output path for the auth handler')
56
+ @click.option('-f', '--force', is_flag=True, help='Force overwrite the auth handler')
57
+ def init_auth(output, force):
58
+ """Add auth.py with auth handler. Everything you need to start using the auth service."""
59
+ root = get_git_root()
60
+ if Path(output).is_dir():
61
+ click.secho("Output path is a directory, please provide a file path", fg="red")
62
+ return
63
+ target = Path(output) if output else root / "src/auth.py"
64
+ if target.exists() and not force:
65
+ click.secho("File already exists, use --force / -f to overwrite", fg="red")
66
+ return
67
+ copy_template("auth.py", target)
68
+ click.secho('''
69
+ Make sure to update the .env file with:
70
+ IS_DOCKER=0
71
+ API_ENV=local
72
+ and the docker-compose.yml file with:
73
+ environment:
74
+ - IS_DOCKER=1
75
+ ''', fg="yellow")
76
+
77
+ @init_group.command("dependabot")
78
+ @click.option('-f', '--force', is_flag=True, help='Force overwrite the dependabot.yml')
79
+ def init_dependabot(force):
80
+ """Add dependabot.yml"""
81
+ root = get_git_root()
82
+ target = root / ".github/dependabot.yml"
83
+ if target.exists() and not force:
84
+ click.secho("File already exists, use --force / -f to overwrite", fg="red")
85
+ return
86
+ copy_template("dependabot.yml", target)
File without changes
@@ -0,0 +1,34 @@
1
+ from crypticorn.common import (
2
+ AuthHandler as AuthHandler,
3
+ Scope as Scope,
4
+ Verify200Response as Verify200Response,
5
+ BaseUrl as BaseUrl,
6
+ ApiEnv as ApiEnv,
7
+ )
8
+ from fastapi import Security as Security
9
+ import os
10
+ import dotenv
11
+ import logging
12
+
13
+ dotenv.load_dotenv()
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ DOCKER_ENV = os.getenv("IS_DOCKER")
18
+ API_ENV = os.getenv("API_ENV")
19
+
20
+ if not DOCKER_ENV:
21
+ raise ValueError("IS_DOCKER is not set. Please set it to '0' in .env and '1' in the docker-compose.yml file.")
22
+
23
+ if not API_ENV:
24
+ raise ValueError("API_ENV is not set. Please set it to 'prod', 'dev' or 'local' in .env (of type ApiEnv).")
25
+
26
+ if DOCKER_ENV == "0":
27
+ logger.info(f"Using {API_ENV} environment")
28
+ base_url = BaseUrl.from_env(ApiEnv(API_ENV))
29
+ else:
30
+ base_url = BaseUrl.DOCKER
31
+ logger.info("Using docker environment")
32
+
33
+ auth_handler = AuthHandler(base_url=base_url)
34
+ logger.info(f"Auth URL: {auth_handler.client.config.host}")
@@ -1,16 +1,38 @@
1
1
  from enum import StrEnum
2
2
 
3
+ class ValidateEnumMixin:
4
+ """
5
+ Mixin for validating enum values manually.
3
6
 
4
- class Exchange(StrEnum):
5
- """Supported exchanges for trading"""
7
+ ⚠️ Note:
8
+ This does NOT enforce validation automatically on enum creation.
9
+ It's up to the developer to call `Class.validate(value)` where needed.
10
+
11
+ Usage:
12
+ >>> class Color(ValidateEnumMixin, StrEnum):
13
+ >>> RED = "red"
14
+ >>> GREEN = "green"
6
15
 
16
+ >>> Color.validate("red") # True
17
+ >>> Color.validate("yellow") # False
18
+
19
+ Order of inheritance matters — the mixin must come first.
20
+ """
21
+ @classmethod
22
+ def validate(cls, value) -> bool:
23
+ try:
24
+ cls(value)
25
+ return True
26
+ except ValueError:
27
+ return False
28
+
29
+ class Exchange(ValidateEnumMixin, StrEnum):
30
+ """Supported exchanges for trading"""
7
31
  KUCOIN = "kucoin"
8
32
  BINGX = "bingx"
9
33
 
10
-
11
- class InternalExchange(StrEnum):
34
+ class InternalExchange(ValidateEnumMixin, StrEnum):
12
35
  """All exchanges we are using, including public (Exchange)"""
13
-
14
36
  KUCOIN = "kucoin"
15
37
  BINGX = "bingx"
16
38
  BINANCE = "binance"
@@ -18,11 +40,9 @@ class InternalExchange(StrEnum):
18
40
  HYPERLIQUID = "hyperliquid"
19
41
  BITGET = "bitget"
20
42
 
21
-
22
- class MarketType(StrEnum):
43
+ class MarketType(ValidateEnumMixin, StrEnum):
23
44
  """
24
45
  Market types
25
46
  """
26
-
27
47
  SPOT = "spot"
28
48
  FUTURES = "futures"
@@ -1,7 +1,17 @@
1
- from enum import StrEnum
1
+ from enum import StrEnum, EnumMeta
2
+ import logging
2
3
 
4
+ logger = logging.getLogger('uvicorn')
3
5
 
4
- class Scope(StrEnum):
6
+ class Fallback(EnumMeta):
7
+ """Fallback to no scope for unknown scopes."""
8
+ def __getattr__(cls, name):
9
+ logger.warning(
10
+ f"Unknown scope '{name}' - falling back to no scope - update crypticorn package or check for typos"
11
+ )
12
+ return None
13
+
14
+ class Scope(StrEnum, metaclass=Fallback):
5
15
  """
6
16
  The permission scopes for the API.
7
17
  """
@@ -39,7 +39,6 @@ from crypticorn.trade.client.exceptions import ApiAttributeError
39
39
  from crypticorn.trade.client.exceptions import ApiException
40
40
 
41
41
  # import models into sdk package
42
- from crypticorn.trade.client.models.api_key_model import APIKeyModel
43
42
  from crypticorn.trade.client.models.action_model import ActionModel
44
43
  from crypticorn.trade.client.models.api_error_identifier import ApiErrorIdentifier
45
44
  from crypticorn.trade.client.models.api_error_level import ApiErrorLevel
@@ -47,6 +46,7 @@ from crypticorn.trade.client.models.api_error_type import ApiErrorType
47
46
  from crypticorn.trade.client.models.bot_model import BotModel
48
47
  from crypticorn.trade.client.models.bot_status import BotStatus
49
48
  from crypticorn.trade.client.models.exchange import Exchange
49
+ from crypticorn.trade.client.models.exchange_key_model import ExchangeKeyModel
50
50
  from crypticorn.trade.client.models.execution_ids import ExecutionIds
51
51
  from crypticorn.trade.client.models.futures_balance import FuturesBalance
52
52
  from crypticorn.trade.client.models.futures_trading_action import FuturesTradingAction
@@ -18,7 +18,7 @@ from typing_extensions import Annotated
18
18
 
19
19
  from pydantic import StrictInt, StrictStr
20
20
  from typing import Any, List, Optional
21
- from crypticorn.trade.client.models.api_key_model import APIKeyModel
21
+ from crypticorn.trade.client.models.exchange_key_model import ExchangeKeyModel
22
22
 
23
23
  from crypticorn.trade.client.api_client import ApiClient, RequestSerialized
24
24
  from crypticorn.trade.client.api_response import ApiResponse
@@ -40,7 +40,7 @@ class APIKeysApi:
40
40
  @validate_call
41
41
  async def create_exchange_key(
42
42
  self,
43
- api_key_model: APIKeyModel,
43
+ exchange_key_model: ExchangeKeyModel,
44
44
  _request_timeout: Union[
45
45
  None,
46
46
  Annotated[StrictFloat, Field(gt=0)],
@@ -56,8 +56,8 @@ class APIKeysApi:
56
56
  """Post Exchange Key
57
57
 
58
58
 
59
- :param api_key_model: (required)
60
- :type api_key_model: APIKeyModel
59
+ :param exchange_key_model: (required)
60
+ :type exchange_key_model: ExchangeKeyModel
61
61
  :param _request_timeout: timeout setting for this request. If one
62
62
  number provided, it will be total request
63
63
  timeout. It can also be a pair (tuple) of
@@ -81,7 +81,7 @@ class APIKeysApi:
81
81
  """ # noqa: E501
82
82
 
83
83
  _param = self._create_exchange_key_serialize(
84
- api_key_model=api_key_model,
84
+ exchange_key_model=exchange_key_model,
85
85
  _request_auth=_request_auth,
86
86
  _content_type=_content_type,
87
87
  _headers=_headers,
@@ -104,7 +104,7 @@ class APIKeysApi:
104
104
  @validate_call
105
105
  async def create_exchange_key_with_http_info(
106
106
  self,
107
- api_key_model: APIKeyModel,
107
+ exchange_key_model: ExchangeKeyModel,
108
108
  _request_timeout: Union[
109
109
  None,
110
110
  Annotated[StrictFloat, Field(gt=0)],
@@ -120,8 +120,8 @@ class APIKeysApi:
120
120
  """Post Exchange Key
121
121
 
122
122
 
123
- :param api_key_model: (required)
124
- :type api_key_model: APIKeyModel
123
+ :param exchange_key_model: (required)
124
+ :type exchange_key_model: ExchangeKeyModel
125
125
  :param _request_timeout: timeout setting for this request. If one
126
126
  number provided, it will be total request
127
127
  timeout. It can also be a pair (tuple) of
@@ -145,7 +145,7 @@ class APIKeysApi:
145
145
  """ # noqa: E501
146
146
 
147
147
  _param = self._create_exchange_key_serialize(
148
- api_key_model=api_key_model,
148
+ exchange_key_model=exchange_key_model,
149
149
  _request_auth=_request_auth,
150
150
  _content_type=_content_type,
151
151
  _headers=_headers,
@@ -168,7 +168,7 @@ class APIKeysApi:
168
168
  @validate_call
169
169
  async def create_exchange_key_without_preload_content(
170
170
  self,
171
- api_key_model: APIKeyModel,
171
+ exchange_key_model: ExchangeKeyModel,
172
172
  _request_timeout: Union[
173
173
  None,
174
174
  Annotated[StrictFloat, Field(gt=0)],
@@ -184,8 +184,8 @@ class APIKeysApi:
184
184
  """Post Exchange Key
185
185
 
186
186
 
187
- :param api_key_model: (required)
188
- :type api_key_model: APIKeyModel
187
+ :param exchange_key_model: (required)
188
+ :type exchange_key_model: ExchangeKeyModel
189
189
  :param _request_timeout: timeout setting for this request. If one
190
190
  number provided, it will be total request
191
191
  timeout. It can also be a pair (tuple) of
@@ -209,7 +209,7 @@ class APIKeysApi:
209
209
  """ # noqa: E501
210
210
 
211
211
  _param = self._create_exchange_key_serialize(
212
- api_key_model=api_key_model,
212
+ exchange_key_model=exchange_key_model,
213
213
  _request_auth=_request_auth,
214
214
  _content_type=_content_type,
215
215
  _headers=_headers,
@@ -227,7 +227,7 @@ class APIKeysApi:
227
227
 
228
228
  def _create_exchange_key_serialize(
229
229
  self,
230
- api_key_model,
230
+ exchange_key_model,
231
231
  _request_auth,
232
232
  _content_type,
233
233
  _headers,
@@ -252,8 +252,8 @@ class APIKeysApi:
252
252
  # process the header parameters
253
253
  # process the form parameters
254
254
  # process the body parameter
255
- if api_key_model is not None:
256
- _body_params = api_key_model
255
+ if exchange_key_model is not None:
256
+ _body_params = exchange_key_model
257
257
 
258
258
  # set the HTTP header `Accept`
259
259
  if "Accept" not in _header_params:
@@ -546,7 +546,7 @@ class APIKeysApi:
546
546
  _content_type: Optional[StrictStr] = None,
547
547
  _headers: Optional[Dict[StrictStr, Any]] = None,
548
548
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
549
- ) -> APIKeyModel:
549
+ ) -> ExchangeKeyModel:
550
550
  """Get Exchange Key By Id
551
551
 
552
552
 
@@ -583,7 +583,7 @@ class APIKeysApi:
583
583
  )
584
584
 
585
585
  _response_types_map: Dict[str, Optional[str]] = {
586
- "200": "APIKeyModel",
586
+ "200": "ExchangeKeyModel",
587
587
  "422": "HTTPValidationError",
588
588
  }
589
589
  response_data = await self.api_client.call_api(
@@ -610,7 +610,7 @@ class APIKeysApi:
610
610
  _content_type: Optional[StrictStr] = None,
611
611
  _headers: Optional[Dict[StrictStr, Any]] = None,
612
612
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
613
- ) -> ApiResponse[APIKeyModel]:
613
+ ) -> ApiResponse[ExchangeKeyModel]:
614
614
  """Get Exchange Key By Id
615
615
 
616
616
 
@@ -647,7 +647,7 @@ class APIKeysApi:
647
647
  )
648
648
 
649
649
  _response_types_map: Dict[str, Optional[str]] = {
650
- "200": "APIKeyModel",
650
+ "200": "ExchangeKeyModel",
651
651
  "422": "HTTPValidationError",
652
652
  }
653
653
  response_data = await self.api_client.call_api(
@@ -711,7 +711,7 @@ class APIKeysApi:
711
711
  )
712
712
 
713
713
  _response_types_map: Dict[str, Optional[str]] = {
714
- "200": "APIKeyModel",
714
+ "200": "ExchangeKeyModel",
715
715
  "422": "HTTPValidationError",
716
716
  }
717
717
  response_data = await self.api_client.call_api(
@@ -789,7 +789,7 @@ class APIKeysApi:
789
789
  _content_type: Optional[StrictStr] = None,
790
790
  _headers: Optional[Dict[StrictStr, Any]] = None,
791
791
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
792
- ) -> List[APIKeyModel]:
792
+ ) -> List[ExchangeKeyModel]:
793
793
  """Get Exchange Keys
794
794
 
795
795
 
@@ -829,7 +829,7 @@ class APIKeysApi:
829
829
  )
830
830
 
831
831
  _response_types_map: Dict[str, Optional[str]] = {
832
- "200": "List[APIKeyModel]",
832
+ "200": "List[ExchangeKeyModel]",
833
833
  "422": "HTTPValidationError",
834
834
  }
835
835
  response_data = await self.api_client.call_api(
@@ -857,7 +857,7 @@ class APIKeysApi:
857
857
  _content_type: Optional[StrictStr] = None,
858
858
  _headers: Optional[Dict[StrictStr, Any]] = None,
859
859
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
860
- ) -> ApiResponse[List[APIKeyModel]]:
860
+ ) -> ApiResponse[List[ExchangeKeyModel]]:
861
861
  """Get Exchange Keys
862
862
 
863
863
 
@@ -897,7 +897,7 @@ class APIKeysApi:
897
897
  )
898
898
 
899
899
  _response_types_map: Dict[str, Optional[str]] = {
900
- "200": "List[APIKeyModel]",
900
+ "200": "List[ExchangeKeyModel]",
901
901
  "422": "HTTPValidationError",
902
902
  }
903
903
  response_data = await self.api_client.call_api(
@@ -965,7 +965,7 @@ class APIKeysApi:
965
965
  )
966
966
 
967
967
  _response_types_map: Dict[str, Optional[str]] = {
968
- "200": "List[APIKeyModel]",
968
+ "200": "List[ExchangeKeyModel]",
969
969
  "422": "HTTPValidationError",
970
970
  }
971
971
  response_data = await self.api_client.call_api(
@@ -1038,7 +1038,7 @@ class APIKeysApi:
1038
1038
  async def update_exchange_key(
1039
1039
  self,
1040
1040
  id: StrictStr,
1041
- api_key_model: APIKeyModel,
1041
+ exchange_key_model: ExchangeKeyModel,
1042
1042
  _request_timeout: Union[
1043
1043
  None,
1044
1044
  Annotated[StrictFloat, Field(gt=0)],
@@ -1056,8 +1056,8 @@ class APIKeysApi:
1056
1056
 
1057
1057
  :param id: (required)
1058
1058
  :type id: str
1059
- :param api_key_model: (required)
1060
- :type api_key_model: APIKeyModel
1059
+ :param exchange_key_model: (required)
1060
+ :type exchange_key_model: ExchangeKeyModel
1061
1061
  :param _request_timeout: timeout setting for this request. If one
1062
1062
  number provided, it will be total request
1063
1063
  timeout. It can also be a pair (tuple) of
@@ -1082,7 +1082,7 @@ class APIKeysApi:
1082
1082
 
1083
1083
  _param = self._update_exchange_key_serialize(
1084
1084
  id=id,
1085
- api_key_model=api_key_model,
1085
+ exchange_key_model=exchange_key_model,
1086
1086
  _request_auth=_request_auth,
1087
1087
  _content_type=_content_type,
1088
1088
  _headers=_headers,
@@ -1106,7 +1106,7 @@ class APIKeysApi:
1106
1106
  async def update_exchange_key_with_http_info(
1107
1107
  self,
1108
1108
  id: StrictStr,
1109
- api_key_model: APIKeyModel,
1109
+ exchange_key_model: ExchangeKeyModel,
1110
1110
  _request_timeout: Union[
1111
1111
  None,
1112
1112
  Annotated[StrictFloat, Field(gt=0)],
@@ -1124,8 +1124,8 @@ class APIKeysApi:
1124
1124
 
1125
1125
  :param id: (required)
1126
1126
  :type id: str
1127
- :param api_key_model: (required)
1128
- :type api_key_model: APIKeyModel
1127
+ :param exchange_key_model: (required)
1128
+ :type exchange_key_model: ExchangeKeyModel
1129
1129
  :param _request_timeout: timeout setting for this request. If one
1130
1130
  number provided, it will be total request
1131
1131
  timeout. It can also be a pair (tuple) of
@@ -1150,7 +1150,7 @@ class APIKeysApi:
1150
1150
 
1151
1151
  _param = self._update_exchange_key_serialize(
1152
1152
  id=id,
1153
- api_key_model=api_key_model,
1153
+ exchange_key_model=exchange_key_model,
1154
1154
  _request_auth=_request_auth,
1155
1155
  _content_type=_content_type,
1156
1156
  _headers=_headers,
@@ -1174,7 +1174,7 @@ class APIKeysApi:
1174
1174
  async def update_exchange_key_without_preload_content(
1175
1175
  self,
1176
1176
  id: StrictStr,
1177
- api_key_model: APIKeyModel,
1177
+ exchange_key_model: ExchangeKeyModel,
1178
1178
  _request_timeout: Union[
1179
1179
  None,
1180
1180
  Annotated[StrictFloat, Field(gt=0)],
@@ -1192,8 +1192,8 @@ class APIKeysApi:
1192
1192
 
1193
1193
  :param id: (required)
1194
1194
  :type id: str
1195
- :param api_key_model: (required)
1196
- :type api_key_model: APIKeyModel
1195
+ :param exchange_key_model: (required)
1196
+ :type exchange_key_model: ExchangeKeyModel
1197
1197
  :param _request_timeout: timeout setting for this request. If one
1198
1198
  number provided, it will be total request
1199
1199
  timeout. It can also be a pair (tuple) of
@@ -1218,7 +1218,7 @@ class APIKeysApi:
1218
1218
 
1219
1219
  _param = self._update_exchange_key_serialize(
1220
1220
  id=id,
1221
- api_key_model=api_key_model,
1221
+ exchange_key_model=exchange_key_model,
1222
1222
  _request_auth=_request_auth,
1223
1223
  _content_type=_content_type,
1224
1224
  _headers=_headers,
@@ -1237,7 +1237,7 @@ class APIKeysApi:
1237
1237
  def _update_exchange_key_serialize(
1238
1238
  self,
1239
1239
  id,
1240
- api_key_model,
1240
+ exchange_key_model,
1241
1241
  _request_auth,
1242
1242
  _content_type,
1243
1243
  _headers,
@@ -1264,8 +1264,8 @@ class APIKeysApi:
1264
1264
  # process the header parameters
1265
1265
  # process the form parameters
1266
1266
  # process the body parameter
1267
- if api_key_model is not None:
1268
- _body_params = api_key_model
1267
+ if exchange_key_model is not None:
1268
+ _body_params = exchange_key_model
1269
1269
 
1270
1270
  # set the HTTP header `Accept`
1271
1271
  if "Accept" not in _header_params:
@@ -14,7 +14,6 @@
14
14
 
15
15
 
16
16
  # import models into model package
17
- from crypticorn.trade.client.models.api_key_model import APIKeyModel
18
17
  from crypticorn.trade.client.models.action_model import ActionModel
19
18
  from crypticorn.trade.client.models.api_error_identifier import ApiErrorIdentifier
20
19
  from crypticorn.trade.client.models.api_error_level import ApiErrorLevel
@@ -22,6 +21,7 @@ from crypticorn.trade.client.models.api_error_type import ApiErrorType
22
21
  from crypticorn.trade.client.models.bot_model import BotModel
23
22
  from crypticorn.trade.client.models.bot_status import BotStatus
24
23
  from crypticorn.trade.client.models.exchange import Exchange
24
+ from crypticorn.trade.client.models.exchange_key_model import ExchangeKeyModel
25
25
  from crypticorn.trade.client.models.execution_ids import ExecutionIds
26
26
  from crypticorn.trade.client.models.futures_balance import FuturesBalance
27
27
  from crypticorn.trade.client.models.futures_trading_action import FuturesTradingAction
@@ -26,7 +26,8 @@ class ApiErrorIdentifier(str, Enum):
26
26
  """
27
27
  allowed enum values
28
28
  """
29
- API_KEY_ALREADY_EXISTS = "api_key_already_exists"
29
+ ALLOCATION_BELOW_CURRENT_EXPOSURE = "allocation_below_current_exposure"
30
+ ALLOCATION_BELOW_MIN_AMOUNT = "allocation_below_min_amount"
30
31
  BLACK_SWAN = "black_swan"
31
32
  BOT_ALREADY_DELETED = "bot_already_deleted"
32
33
  BOT_DISABLED = "bot_disabled"
@@ -34,12 +35,16 @@ class ApiErrorIdentifier(str, Enum):
34
35
  CLIENT_ORDER_ID_ALREADY_EXISTS = "client_order_id_already_exists"
35
36
  INVALID_CONTENT_TYPE = "invalid_content_type"
36
37
  DELETE_BOT_ERROR = "delete_bot_error"
37
- EXCHANGE_API_KEY_IN_USE = "exchange_api_key_in_use"
38
38
  EXCHANGE_INVALID_SIGNATURE = "exchange_invalid_signature"
39
39
  EXCHANGE_INVALID_TIMESTAMP = "exchange_invalid_timestamp"
40
40
  EXCHANGE_IP_ADDRESS_IS_NOT_AUTHORIZED = "exchange_ip_address_is_not_authorized"
41
+ EXCHANGE_KEY_ALREADY_EXISTS = "exchange_key_already_exists"
42
+ EXCHANGE_KEY_IN_USE = "exchange_key_in_use"
41
43
  EXCHANGE_SYSTEM_UNDER_MAINTENANCE = "exchange_system_under_maintenance"
42
44
  EXCHANGE_RATE_LIMIT_EXCEEDED = "exchange_rate_limit_exceeded"
45
+ INSUFFICIENT_PERMISSIONS_SPOT_AND_FUTURES_REQUIRED = (
46
+ "insufficient_permissions_spot_and_futures_required"
47
+ )
43
48
  EXCHANGE_SERVICE_TEMPORARILY_UNAVAILABLE = (
44
49
  "exchange_service_temporarily_unavailable"
45
50
  )
@@ -47,9 +52,6 @@ class ApiErrorIdentifier(str, Enum):
47
52
  EXCHANGE_SYSTEM_CONFIGURATION_ERROR = "exchange_system_configuration_error"
48
53
  EXCHANGE_INTERNAL_SYSTEM_ERROR = "exchange_internal_system_error"
49
54
  EXCHANGE_USER_ACCOUNT_IS_FROZEN = "exchange_user_account_is_frozen"
50
- INSUFFICIENT_PERMISSIONS_SPOT_AND_FUTURES_REQUIRED = (
51
- "insufficient_permissions_spot_and_futures_required"
52
- )
53
55
  HEDGE_MODE_NOT_ACTIVE = "hedge_mode_not_active"
54
56
  HTTP_REQUEST_ERROR = "http_request_error"
55
57
  INSUFFICIENT_BALANCE = "insufficient_balance"
@@ -66,6 +68,7 @@ class ApiErrorIdentifier(str, Enum):
66
68
  "order_violates_liquidation_price_constraints"
67
69
  )
68
70
  NO_CREDENTIALS = "no_credentials"
71
+ NOW_API_DOWN = "now_api_down"
69
72
  OBJECT_NOT_FOUND = "object_not_found"
70
73
  ORDER_IS_ALREADY_FILLED = "order_is_already_filled"
71
74
  ORDER_IS_BEING_PROCESSED = "order_is_being_processed"
@@ -83,11 +86,13 @@ class ApiErrorIdentifier(str, Enum):
83
86
  RPC_TIMEOUT = "rpc_timeout"
84
87
  SYSTEM_SETTLEMENT_IN_PROCESS = "system_settlement_in_process"
85
88
  STRATEGY_DISABLED = "strategy_disabled"
89
+ STRATEGY_LEVERAGE_MISMATCH = "strategy_leverage_mismatch"
90
+ STRATEGY_NOT_SUPPORTING_EXCHANGE = "strategy_not_supporting_exchange"
86
91
  SUCCESS = "success"
87
92
  SYMBOL_DOES_NOT_EXIST = "symbol_does_not_exist"
88
- TRADING_HAS_BEEN_LOCKED = "trading_has_been_locked"
89
93
  TRADING_ACTION_EXPIRED = "trading_action_expired"
90
94
  TRADING_ACTION_SKIPPED = "trading_action_skipped"
95
+ TRADING_HAS_BEEN_LOCKED = "trading_has_been_locked"
91
96
  TRADING_IS_SUSPENDED = "trading_is_suspended"
92
97
  UNKNOWN_ERROR_OCCURRED = "unknown_error_occurred"
93
98
  REQUESTED_RESOURCE_NOT_FOUND = "requested_resource_not_found"
@@ -20,7 +20,7 @@ from typing_extensions import Self
20
20
 
21
21
  class Exchange(str, Enum):
22
22
  """
23
- Supported exchanges
23
+ Supported exchanges for trading
24
24
  """
25
25
 
26
26
  """
@@ -0,0 +1,156 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ Trading API
5
+
6
+ No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
7
+
8
+ The version of the OpenAPI document: 0.1.0
9
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
10
+
11
+ Do not edit the class manually.
12
+ """ # noqa: E501
13
+
14
+
15
+ from __future__ import annotations
16
+ import pprint
17
+ import re # noqa: F401
18
+ import json
19
+
20
+ from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
21
+ from typing import Any, ClassVar, Dict, List, Optional
22
+ from typing import Optional, Set
23
+ from typing_extensions import Self
24
+
25
+
26
+ class ExchangeKeyModel(BaseModel):
27
+ """
28
+ ExchangeKeyModel
29
+ """ # noqa: E501
30
+
31
+ created_at: Optional[StrictInt] = None
32
+ updated_at: Optional[StrictInt] = None
33
+ id: Optional[StrictStr] = None
34
+ exchange: StrictStr = Field(description="Exchange name")
35
+ api_key: Optional[StrictStr] = None
36
+ secret: Optional[StrictStr] = None
37
+ passphrase: Optional[StrictStr] = None
38
+ label: StrictStr = Field(description="Label for the API key")
39
+ enabled: Optional[StrictBool] = None
40
+ user_id: Optional[StrictStr] = None
41
+ __properties: ClassVar[List[str]] = [
42
+ "created_at",
43
+ "updated_at",
44
+ "id",
45
+ "exchange",
46
+ "api_key",
47
+ "secret",
48
+ "passphrase",
49
+ "label",
50
+ "enabled",
51
+ "user_id",
52
+ ]
53
+
54
+ model_config = ConfigDict(
55
+ populate_by_name=True,
56
+ validate_assignment=True,
57
+ protected_namespaces=(),
58
+ )
59
+
60
+ def to_str(self) -> str:
61
+ """Returns the string representation of the model using alias"""
62
+ return pprint.pformat(self.model_dump(by_alias=True))
63
+
64
+ def to_json(self) -> str:
65
+ """Returns the JSON representation of the model using alias"""
66
+ # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
67
+ return json.dumps(self.to_dict())
68
+
69
+ @classmethod
70
+ def from_json(cls, json_str: str) -> Optional[Self]:
71
+ """Create an instance of ExchangeKeyModel from a JSON string"""
72
+ return cls.from_dict(json.loads(json_str))
73
+
74
+ def to_dict(self) -> Dict[str, Any]:
75
+ """Return the dictionary representation of the model using alias.
76
+
77
+ This has the following differences from calling pydantic's
78
+ `self.model_dump(by_alias=True)`:
79
+
80
+ * `None` is only added to the output dict for nullable fields that
81
+ were set at model initialization. Other fields with value `None`
82
+ are ignored.
83
+ """
84
+ excluded_fields: Set[str] = set([])
85
+
86
+ _dict = self.model_dump(
87
+ by_alias=True,
88
+ exclude=excluded_fields,
89
+ exclude_none=True,
90
+ )
91
+ # set to None if created_at (nullable) is None
92
+ # and model_fields_set contains the field
93
+ if self.created_at is None and "created_at" in self.model_fields_set:
94
+ _dict["created_at"] = None
95
+
96
+ # set to None if updated_at (nullable) is None
97
+ # and model_fields_set contains the field
98
+ if self.updated_at is None and "updated_at" in self.model_fields_set:
99
+ _dict["updated_at"] = None
100
+
101
+ # set to None if id (nullable) is None
102
+ # and model_fields_set contains the field
103
+ if self.id is None and "id" in self.model_fields_set:
104
+ _dict["id"] = None
105
+
106
+ # set to None if api_key (nullable) is None
107
+ # and model_fields_set contains the field
108
+ if self.api_key is None and "api_key" in self.model_fields_set:
109
+ _dict["api_key"] = None
110
+
111
+ # set to None if secret (nullable) is None
112
+ # and model_fields_set contains the field
113
+ if self.secret is None and "secret" in self.model_fields_set:
114
+ _dict["secret"] = None
115
+
116
+ # set to None if passphrase (nullable) is None
117
+ # and model_fields_set contains the field
118
+ if self.passphrase is None and "passphrase" in self.model_fields_set:
119
+ _dict["passphrase"] = None
120
+
121
+ # set to None if enabled (nullable) is None
122
+ # and model_fields_set contains the field
123
+ if self.enabled is None and "enabled" in self.model_fields_set:
124
+ _dict["enabled"] = None
125
+
126
+ # set to None if user_id (nullable) is None
127
+ # and model_fields_set contains the field
128
+ if self.user_id is None and "user_id" in self.model_fields_set:
129
+ _dict["user_id"] = None
130
+
131
+ return _dict
132
+
133
+ @classmethod
134
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
135
+ """Create an instance of ExchangeKeyModel from a dict"""
136
+ if obj is None:
137
+ return None
138
+
139
+ if not isinstance(obj, dict):
140
+ return cls.model_validate(obj)
141
+
142
+ _obj = cls.model_validate(
143
+ {
144
+ "created_at": obj.get("created_at"),
145
+ "updated_at": obj.get("updated_at"),
146
+ "id": obj.get("id"),
147
+ "exchange": obj.get("exchange"),
148
+ "api_key": obj.get("api_key"),
149
+ "secret": obj.get("secret"),
150
+ "passphrase": obj.get("passphrase"),
151
+ "label": obj.get("label"),
152
+ "enabled": obj.get("enabled"),
153
+ "user_id": obj.get("user_id"),
154
+ }
155
+ )
156
+ return _obj
@@ -20,7 +20,7 @@ from typing_extensions import Self
20
20
 
21
21
  class MarketType(str, Enum):
22
22
  """
23
- Type of market
23
+ Market types
24
24
  """
25
25
 
26
26
  """
crypticorn/trade/main.py CHANGED
@@ -33,4 +33,4 @@ class TradeClient:
33
33
  self.strategies = StrategiesApi(self.base_client)
34
34
  self.actions = TradingActionsApi(self.base_client)
35
35
  self.futures = FuturesTradingPanelApi(self.base_client)
36
- self.api_keys = APIKeysApi(self.base_client)
36
+ self.keys = APIKeysApi(self.base_client)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crypticorn
3
- Version: 2.3.0
3
+ Version: 2.4.0
4
4
  Summary: Maximise Your Crypto Trading Profits with AI Predictions
5
5
  Author-email: Crypticorn <timon@crypticorn.com>
6
6
  Project-URL: Homepage, https://crypticorn.com
@@ -14,6 +14,7 @@ Classifier: Typing :: Typed
14
14
  Requires-Python: >=3.10
15
15
  Description-Content-Type: text/markdown
16
16
  Requires-Dist: fastapi
17
+ Requires-Dist: click
17
18
  Requires-Dist: urllib3<3.0.0,>=1.25.3
18
19
  Requires-Dist: python_dateutil<3.0.0,>=2.8.2
19
20
  Requires-Dist: aiohttp<4.0.0,>=3.8.4
@@ -54,12 +54,17 @@ crypticorn/auth/client/models/verify_email_request.py,sha256=8MBfxPTLn5X6Z3vE2bl
54
54
  crypticorn/auth/client/models/verify_wallet_request.py,sha256=b0DAocvhKzPXPjM62DZqezlHxq3cNL7UVKl0d2judHQ,2691
55
55
  crypticorn/auth/client/models/wallet_verified200_response.py,sha256=QILnTLsCKdI-WdV_fsLBy1UH4ZZU-U-wWJ9ot8v08tI,2465
56
56
  crypticorn/auth/client/models/whoami200_response.py,sha256=uehdq5epgeOphhrIR3tbrseflxcLAzGyKF-VW-o5cY8,2974
57
+ crypticorn/cli/__init__.py,sha256=P5wOAU00ylb1uROkv6BmQwnKtSMGMB4HA0utVYxEhu8,68
58
+ crypticorn/cli/__main__.py,sha256=XujzQmkfLN6zDJ-AiUAUTn8Kq-G3HhQ0y3TaUvs9J1E,250
59
+ crypticorn/cli/init.py,sha256=n9ldSI5yNYlYTt4jSlOXVSiHBmb2QlE__MYcNkz6Cl0,3321
60
+ crypticorn/cli/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
+ crypticorn/cli/templates/auth.py,sha256=pr4O9ejxEz10QphEjXAtl0qfwLNKHYzLwvNKHs--WUQ,954
57
62
  crypticorn/common/__init__.py,sha256=r--xgeJUW4FGRabb1AyqlEFsySiSNfxO_Qi3uOpZ-xI,231
58
63
  crypticorn/common/auth.py,sha256=5aVNsl39ngsoFaR5xGuWERRccq4HibYbSp61Z4QOVew,7483
59
- crypticorn/common/enums.py,sha256=3A3stEQEHhyPfNMYAPFLKU5UMgERF6Ldgj8Br07G8Q8,484
64
+ crypticorn/common/enums.py,sha256=OQQT6uaounQzCGoYEM91yjhu8pU2eD5VaYiY8K89k3o,1235
60
65
  crypticorn/common/errors.py,sha256=HZvd4J2HAN7TvqlZxWtp29XV0wbkacWNZzgdVO-YWpw,14197
61
66
  crypticorn/common/pydantic.py,sha256=pmnGYCIrLv59wZkDbvPyK9NJmgPJWW74LXTdIWSjOkY,1063
62
- crypticorn/common/scopes.py,sha256=ghFa7aa7c91zo-BQWSc130EiBnAugO8ewBAJOKlbyCI,1489
67
+ crypticorn/common/scopes.py,sha256=QcmYUBiOO0kBJcNjxW-jXrURVF6b14wN-ayXhwOlyVY,1848
63
68
  crypticorn/common/sorter.py,sha256=Lx7hZMzsqrx2nqWOO0sFrSXSK1t2CqQJux70xU49Bz0,1282
64
69
  crypticorn/common/urls.py,sha256=X557WaODUqW2dECi-mOjTbmhkSpnp40fPXDdvlnBXfo,805
65
70
  crypticorn/hive/__init__.py,sha256=hRfTlEzEql4msytdUC_04vfaHzVKG5CGZle1M-9QFgY,81
@@ -219,8 +224,8 @@ crypticorn/pay/client/models/unified_payment_model.py,sha256=_Y9OtWZ-N7QDxsCte0U
219
224
  crypticorn/pay/client/models/validation_error.py,sha256=vzw1GCCSx0Tct-t8b4IfNQe4jaioantmwbwz2Cmi4AA,3226
220
225
  crypticorn/pay/client/models/validation_error_loc_inner.py,sha256=8Fp046J4qWnAIBATnrzS9qiKZKDY4C3UxSOJo9DTTcQ,5131
221
226
  crypticorn/trade/__init__.py,sha256=QzScH9n-ly3QSaBSpPP7EqYwhdzDqYCZJs0-AhEhrsY,84
222
- crypticorn/trade/main.py,sha256=UZQ9ZJlus5CHLZBOimF6Cr4vx9ocTL5SjRA69MZ73tQ,1044
223
- crypticorn/trade/client/__init__.py,sha256=Wa4Pn_QmZfZpScj3eM5hTh1gxcg9j9O13jImEySmQJ0,3480
227
+ crypticorn/trade/main.py,sha256=jn17PsnMp_xKuYic92u2ApytvT71au1cBse5TjgRQdM,1040
228
+ crypticorn/trade/client/__init__.py,sha256=Xj4_g-RPqLUqSJ7QTjWfrHvS5_hVtbEUauzom0XGBBs,3490
224
229
  crypticorn/trade/client/api_client.py,sha256=GZFlYDIxGpPWtK6CnTqZLN8kWSSSzcdziNY4kTB1hE4,26956
225
230
  crypticorn/trade/client/api_response.py,sha256=WhxwYDSMm6wPixp9CegO8dJzjFxDz3JF1yCq9s0ZqKE,639
226
231
  crypticorn/trade/client/configuration.py,sha256=Ih111_59QLT7CWHdE3L_5GRuw5ie0gx5mlu2Nyqu8Lc,18841
@@ -228,7 +233,7 @@ crypticorn/trade/client/exceptions.py,sha256=xSXlQpvCm5JkPI_N49jdTm72mSHMLOIDQ3g
228
233
  crypticorn/trade/client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
229
234
  crypticorn/trade/client/rest.py,sha256=o2sJyURJxO3XckjU_VuASM5AN0R5TsRcH1vtKCR97oI,6987
230
235
  crypticorn/trade/client/api/__init__.py,sha256=s6jcukWHyom8Bp9Vj0yrOL37-dNPNvdvMK_aXwpKZzc,668
231
- crypticorn/trade/client/api/api_keys_api.py,sha256=DYAPIg3Kpb3zyWV3gCPiqm4GC_2crHgsmh_3N9s2Fsc,51112
236
+ crypticorn/trade/client/api/api_keys_api.py,sha256=pMMXtKi8E7XKHUAqXpMoHHLWfN7YrO-5kdA4Lm5go0M,51412
232
237
  crypticorn/trade/client/api/bots_api.py,sha256=Ebd6741ucf4PGKZchmvxd0QFc_Nm4lj8wQeAbFuCvyg,41988
233
238
  crypticorn/trade/client/api/exchanges_api.py,sha256=PX8yeVZFDWxmsortW0HdphbNtYRYWaLv8nkE8VUmPgg,10166
234
239
  crypticorn/trade/client/api/futures_trading_panel_api.py,sha256=xOxjCqg2o0exmQofO4l65LVFTOLHC8YBbVNzcyyBou0,50581
@@ -237,21 +242,22 @@ crypticorn/trade/client/api/orders_api.py,sha256=zibuEr3448IefONOEcgti06nxNtJCvi
237
242
  crypticorn/trade/client/api/status_api.py,sha256=deaW23XWpUMqhFzxO9GqT0MI8ZlAIC_f4ewdPLExBMw,9934
238
243
  crypticorn/trade/client/api/strategies_api.py,sha256=asNtn8mZDTRu36PGGGHLRtevQ3NFs-6nQ1XRju2xSaU,42444
239
244
  crypticorn/trade/client/api/trading_actions_api.py,sha256=BfW61jUsOZxhzcYgLLF19hGeSHElarxUeK1Re0q_wpY,32402
240
- crypticorn/trade/client/models/__init__.py,sha256=qn31GdssVyCozLVuFZfK11ju9yDwY-Y3okMyn_ZmuRA,2234
245
+ crypticorn/trade/client/models/__init__.py,sha256=pYftF6baXUaReQDn54rRln4KljNnN4nbbJqedqW1XaU,2244
241
246
  crypticorn/trade/client/models/action_model.py,sha256=W-6IJl9KgeiBkZc95HH9kdvmmb-vzYtJ-HfKN0YzC_U,10131
242
- crypticorn/trade/client/models/api_error_identifier.py,sha256=X1NwUsJJyIYQuoUVaByZ3msoctJIr9w7dzZZPQ-2h-Y,4136
247
+ crypticorn/trade/client/models/api_error_identifier.py,sha256=kwCKzYwf61WSiscwNHjNFtsX8fzJDJoOT9vZYO2dLxk,4448
243
248
  crypticorn/trade/client/models/api_error_level.py,sha256=78zYTqbnUGvbjptf04e6-bpF8nN-YWqGxjGrdwNe4_4,799
244
249
  crypticorn/trade/client/models/api_error_type.py,sha256=ANXQ3lPxQ9Jyh_-Q4ljHFHt5uH6ljBHPzK7SDy7etek,840
245
250
  crypticorn/trade/client/models/api_key_model.py,sha256=CM6BeEc3ctmfLMnRht-_k_WDCjGWreOI7670h4KhhAM,5281
246
251
  crypticorn/trade/client/models/bot_model.py,sha256=t1VetjZs20-ekukHlwBaOwvMiDuZ3-hjQa1V6iaUYtE,5875
247
252
  crypticorn/trade/client/models/bot_status.py,sha256=p0oRXdqjqDwE1mcHjWjUwqqmWGIi1jTFWi8FgwArUOg,796
248
- crypticorn/trade/client/models/exchange.py,sha256=NF2pJ3978cGN7zWzgmRP4zMp1T8aK7f3TbV6kTMDcnE,748
253
+ crypticorn/trade/client/models/exchange.py,sha256=jRDYT8x47bfWa394pXBavpArL3i8IoyTZK7i5UX6dFg,760
254
+ crypticorn/trade/client/models/exchange_key_model.py,sha256=-ELnZDj2zp--GJcX3pegG92lLpFAm2MGrSE-AYmtneY,5301
249
255
  crypticorn/trade/client/models/execution_ids.py,sha256=7EO_v4LErOlLVbTCL412ST0GpstJOcn2A3zAA04VC6w,2876
250
256
  crypticorn/trade/client/models/futures_balance.py,sha256=JAUaPUkV0AlQZxrlOtpqkd6HOP4jkdpYZ6gyvGbAmG8,4181
251
257
  crypticorn/trade/client/models/futures_trading_action.py,sha256=aRXn0NeziJ_TuYvPsRWsBekWZiXvI7ZJoq-NXtoW_40,9498
252
258
  crypticorn/trade/client/models/http_validation_error.py,sha256=7rSmsYpBLuxIkCgl2pM-U3jCLeRSAX4s9s1CzsdE_1s,3118
253
259
  crypticorn/trade/client/models/margin_mode.py,sha256=Y02aSytOfvROKDZHrdD2xHOCDbW6tT9YL3fNkDyGIus,767
254
- crypticorn/trade/client/models/market_type.py,sha256=38cCEUR_8X0Fbxq6fa1-BagTfEuZ2gsO3kihYs7ngCA,747
260
+ crypticorn/trade/client/models/market_type.py,sha256=u4oa-sYI6AdnQp-JnNPCNKCkdC3ppzvndrdLscDuIf8,745
255
261
  crypticorn/trade/client/models/notification_model.py,sha256=pZc15SJRWPxGWjrKyEtJFLbBEPe-hBvQX9bOOjFeGQg,5010
256
262
  crypticorn/trade/client/models/order_model.py,sha256=uAP7WxfiNpqV4qdX0UZxjKf70WaelEAVDJlgVTonc3M,12682
257
263
  crypticorn/trade/client/models/order_status.py,sha256=cCS3aGJmqqVhoZ1JfLS-DeRj5qj-D82AI5OFblhAsik,842
@@ -263,7 +269,8 @@ crypticorn/trade/client/models/tpsl.py,sha256=QGPhcgadjxAgyzpRSwlZJg_CDLnKxdZgse
263
269
  crypticorn/trade/client/models/trading_action_type.py,sha256=jW0OsNz_ZNXlITxAfh979BH5U12oTXSr6qUVcKcGHhw,847
264
270
  crypticorn/trade/client/models/validation_error.py,sha256=uTkvsKrOAt-21UC0YPqCdRl_OMsuu7uhPtWuwRSYvv0,3228
265
271
  crypticorn/trade/client/models/validation_error_loc_inner.py,sha256=22ql-H829xTBgfxNQZsqd8fS3zQt9tLW1pj0iobo0jY,5131
266
- crypticorn-2.3.0.dist-info/METADATA,sha256=N1SnXJfCkIb03WUPAE9UC7ahB0OYFV06sF09kG4jYvU,5393
267
- crypticorn-2.3.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
268
- crypticorn-2.3.0.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
269
- crypticorn-2.3.0.dist-info/RECORD,,
272
+ crypticorn-2.4.0.dist-info/METADATA,sha256=1EE9L0vzwHHwCrV3kosjA7KTKXKF0qnHxORJ6x7YCqM,5414
273
+ crypticorn-2.4.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
274
+ crypticorn-2.4.0.dist-info/entry_points.txt,sha256=d_xHsGvUTebPveVUK0SrpDFQ5ZRSjlI7lNCc11sn2PM,59
275
+ crypticorn-2.4.0.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
276
+ crypticorn-2.4.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ crypticorn = crypticorn.cli.__main__:cli