crypticorn 2.8.1__py3-none-any.whl → 2.9.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.
crypticorn/__init__.py CHANGED
@@ -6,9 +6,13 @@ We adhere to [Semantic Versioning](https://semver.org/).
6
6
  You can find the full Changelog [below](#changelog).
7
7
  """
8
8
 
9
+ import warnings
10
+ import logging
9
11
  from crypticorn.common.logging import configure_logging
10
12
 
13
+ warnings.filterwarnings("default", "", DeprecationWarning)
11
14
  configure_logging()
15
+ logging.captureWarnings(True)
12
16
 
13
17
  from crypticorn.client import ApiClient
14
18
 
@@ -11,5 +11,7 @@ from crypticorn.common.pagination import *
11
11
  from crypticorn.common.logging import *
12
12
  from crypticorn.common.ansi_colors import *
13
13
  from crypticorn.common.middleware import *
14
+ from crypticorn.common.warnings import *
15
+ from crypticorn.common.openapi import *
14
16
  from crypticorn.common.router.status_router import router as status_router
15
17
  from crypticorn.common.router.admin_router import router as admin_router
@@ -1,8 +1,9 @@
1
1
  from enum import StrEnum
2
- from typing import TYPE_CHECKING
3
2
 
4
3
 
5
4
  class AnsiColors(StrEnum):
5
+ """Provides a collection of ANSI color codes for terminal text formatting, including regular, bright, and bold text colors. Useful for creating colorful and readable console output."""
6
+
6
7
  # Regular Text Colors
7
8
  BLACK = "\033[30m" # black
8
9
  RED = "\033[31m" # red
crypticorn/common/auth.py CHANGED
@@ -38,8 +38,8 @@ class AuthHandler:
38
38
  """
39
39
  Middleware for verifying API requests. Verifies the validity of the authentication token, scopes, etc.
40
40
 
41
- @param base_url: The base URL of the API.
42
- @param api_version: The version of the API.
41
+ :param base_url: The base URL of the API.
42
+ :param api_version: The version of the API.
43
43
  """
44
44
 
45
45
  def __init__(
@@ -1,3 +1,5 @@
1
+ """Defines common enumerations used throughout the codebase for type safety and consistency."""
2
+
1
3
  from enum import StrEnum
2
4
  from crypticorn.common.mixins import ValidateEnumMixin, ExcludeEnumMixin
3
5
 
@@ -1,10 +1,12 @@
1
+ """Comprehensive error handling system defining various API error types, HTTP exceptions, and error content structures."""
2
+
1
3
  from enum import Enum, StrEnum
2
4
  from fastapi import status
3
5
  from crypticorn.common.mixins import ExcludeEnumMixin, ApiErrorFallback
4
6
 
5
7
 
6
8
  class ApiErrorType(ExcludeEnumMixin, StrEnum):
7
- """Type of API error"""
9
+ """Type of the API error."""
8
10
 
9
11
  USER_ERROR = "user error"
10
12
  """user error by people using our services"""
@@ -17,7 +19,7 @@ class ApiErrorType(ExcludeEnumMixin, StrEnum):
17
19
 
18
20
 
19
21
  class ApiErrorIdentifier(ExcludeEnumMixin, StrEnum):
20
- """API error identifiers"""
22
+ """Unique identifier of the API error."""
21
23
 
22
24
  ALLOCATION_BELOW_EXPOSURE = "allocation_below_current_exposure"
23
25
  ALLOCATION_BELOW_MINIMUM = "allocation_below_min_amount"
@@ -103,7 +105,7 @@ class ApiErrorIdentifier(ExcludeEnumMixin, StrEnum):
103
105
 
104
106
 
105
107
  class ApiErrorLevel(ExcludeEnumMixin, StrEnum):
106
- """API error levels"""
108
+ """Level of the API error."""
107
109
 
108
110
  ERROR = "error"
109
111
  INFO = "info"
@@ -112,7 +114,8 @@ class ApiErrorLevel(ExcludeEnumMixin, StrEnum):
112
114
 
113
115
 
114
116
  class ApiError(ExcludeEnumMixin, Enum, metaclass=ApiErrorFallback):
115
- """API error codes. Fallback to UNKNOWN_ERROR for error codes not yet published to PyPI."""
117
+ # Fallback to UNKNOWN_ERROR for error codes not yet published to PyPI.
118
+ """Crypticorn API error enumeration."""
116
119
 
117
120
  ALLOCATION_BELOW_EXPOSURE = (
118
121
  ApiErrorIdentifier.ALLOCATION_BELOW_EXPOSURE,
@@ -518,7 +521,7 @@ class ApiError(ExcludeEnumMixin, Enum, metaclass=ApiErrorFallback):
518
521
 
519
522
 
520
523
  class StatusCodeMapper:
521
- """Map API errors to HTTP status codes."""
524
+ """Mapping of API errors to HTTP/Websocket status codes."""
522
525
 
523
526
  _mapping = {
524
527
  # Authentication/Authorization
@@ -1,4 +1,4 @@
1
- from typing import Optional, Dict, Any, Literal
1
+ from typing import Optional, Dict, Any
2
2
  from enum import StrEnum
3
3
  from pydantic import BaseModel, Field
4
4
  from fastapi import HTTPException as FastAPIHTTPException, Request, FastAPI
@@ -8,16 +8,18 @@ from crypticorn.common import ApiError, ApiErrorIdentifier, ApiErrorType, ApiErr
8
8
  import logging
9
9
  import json
10
10
 
11
- logger = logging.getLogger("crypticorn")
11
+ _logger = logging.getLogger("crypticorn")
12
12
 
13
13
 
14
- class ExceptionType(StrEnum):
14
+ class _ExceptionType(StrEnum):
15
+ """The protocol the exception is called from"""
16
+
15
17
  HTTP = "http"
16
18
  WEBSOCKET = "websocket"
17
19
 
18
20
 
19
21
  class ExceptionDetail(BaseModel):
20
- """This is the detail of the exception. It is used to enrich the exception with additional information by unwrapping the ApiError into its components."""
22
+ """Exception details returned to the client."""
21
23
 
22
24
  message: Optional[str] = Field(None, description="An additional error message")
23
25
  code: ApiErrorIdentifier = Field(..., description="The unique error code")
@@ -28,14 +30,14 @@ class ExceptionDetail(BaseModel):
28
30
 
29
31
 
30
32
  class ExceptionContent(BaseModel):
31
- """This is the detail of the exception. Pass an ApiError to the constructor and an optional human readable message."""
33
+ """Exception content used when raising an exception."""
32
34
 
33
35
  error: ApiError = Field(..., description="The unique error code")
34
36
  message: Optional[str] = Field(None, description="An additional error message")
35
37
  details: Any = Field(None, description="Additional details about the error")
36
38
 
37
39
  def enrich(
38
- self, _type: Optional[ExceptionType] = ExceptionType.HTTP
40
+ self, _type: Optional[_ExceptionType] = _ExceptionType.HTTP
39
41
  ) -> ExceptionDetail:
40
42
  return ExceptionDetail(
41
43
  message=self.message,
@@ -44,7 +46,7 @@ class ExceptionContent(BaseModel):
44
46
  level=self.error.level,
45
47
  status_code=(
46
48
  self.error.http_code
47
- if _type == ExceptionType.HTTP
49
+ if _type == _ExceptionType.HTTP
48
50
  else self.error.websocket_code
49
51
  ),
50
52
  details=self.details,
@@ -54,14 +56,14 @@ class ExceptionContent(BaseModel):
54
56
  class HTTPException(FastAPIHTTPException):
55
57
  """A custom HTTP exception wrapper around FastAPI's HTTPException.
56
58
  It allows for a more structured way to handle errors, with a message and an error code. The status code is being derived from the detail's error.
57
- The ApiError class is the source of truth for everything. If the error is not yet implemented, there are fallbacks to avoid errors while testing.
59
+ The ApiError class is the source of truth. If the error is not yet implemented, there are fallbacks in place.
58
60
  """
59
61
 
60
62
  def __init__(
61
63
  self,
62
64
  content: ExceptionContent,
63
65
  headers: Optional[Dict[str, str]] = None,
64
- _type: Optional[ExceptionType] = ExceptionType.HTTP,
66
+ _type: Optional[_ExceptionType] = _ExceptionType.HTTP,
65
67
  ):
66
68
  self.content = content
67
69
  self.headers = headers
@@ -82,11 +84,11 @@ class WebSocketException(HTTPException):
82
84
  def __init__(
83
85
  self, content: ExceptionContent, headers: Optional[Dict[str, str]] = None
84
86
  ):
85
- super().__init__(content, headers, _type=ExceptionType.WEBSOCKET)
87
+ super().__init__(content, headers, _type=_ExceptionType.WEBSOCKET)
86
88
 
87
89
  @classmethod
88
90
  def from_http_exception(cls, http_exception: HTTPException):
89
- """This is a helper method to convert an HTTPException to a WebSocketException."""
91
+ """Helper method to convert an HTTPException to a WebSocketException."""
90
92
  return WebSocketException(
91
93
  content=http_exception.content,
92
94
  headers=http_exception.headers,
@@ -94,46 +96,47 @@ class WebSocketException(HTTPException):
94
96
 
95
97
 
96
98
  async def general_handler(request: Request, exc: Exception) -> JSONResponse:
97
- """This is the default exception handler for all exceptions."""
99
+ """Default exception handler for all exceptions."""
98
100
  body = ExceptionContent(message=str(exc), error=ApiError.UNKNOWN_ERROR)
99
101
  res = JSONResponse(
100
102
  status_code=body.enrich().status_code,
101
103
  content=HTTPException(content=body).detail,
102
104
  )
103
- logger.error(f"Response validation error: {json.loads(res.__dict__.get('body'))}")
105
+ _logger.error(f"Response validation error: {json.loads(res.__dict__.get('body'))}")
104
106
  return res
105
107
 
106
108
 
107
109
  async def request_validation_handler(
108
110
  request: Request, exc: RequestValidationError
109
111
  ) -> JSONResponse:
110
- """This is the exception handler for all request validation errors."""
112
+ """Exception handler for all request validation errors."""
111
113
  body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_REQUEST)
112
114
  res = JSONResponse(
113
115
  status_code=body.enrich().status_code,
114
116
  content=HTTPException(content=body).detail,
115
117
  )
116
- logger.error(f"Response validation error: {json.loads(res.__dict__.get('body'))}")
118
+ _logger.error(f"Response validation error: {json.loads(res.__dict__.get('body'))}")
117
119
  return res
118
120
 
119
121
 
120
122
  async def response_validation_handler(
121
123
  request: Request, exc: ResponseValidationError
122
124
  ) -> JSONResponse:
123
- """This is the exception handler for all response validation errors."""
125
+ """Exception handler for all response validation errors."""
124
126
  body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_RESPONSE)
125
127
  res = JSONResponse(
126
128
  status_code=body.enrich().status_code,
127
129
  content=HTTPException(content=body).detail,
128
130
  )
129
- logger.error(f"Response validation error: {json.loads(res.__dict__.get('body'))}")
131
+ _logger.error(f"Response validation error: {json.loads(res.__dict__.get('body'))}")
130
132
  return res
131
133
 
132
134
 
133
135
  async def http_handler(request: Request, exc: HTTPException) -> JSONResponse:
134
- """This is the exception handler for HTTPExceptions. It unwraps the HTTPException and returns the detail in a flat JSON response."""
135
- logger.error(f"HTTP error: {exc.detail}")
136
- return JSONResponse(status_code=exc.status_code, content=exc.detail)
136
+ """Exception handler for HTTPExceptions. It unwraps the HTTPException and returns the detail in a flat JSON response."""
137
+ res = JSONResponse(status_code=exc.status_code, content=exc.detail)
138
+ _logger.error(f"HTTP error: {json.loads(res.__dict__.get('body'))}")
139
+ return res
137
140
 
138
141
 
139
142
  def register_exception_handlers(app: FastAPI):
@@ -1,12 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- # shared_logger.py
4
3
  import logging
5
4
  import sys
6
- from contextvars import ContextVar
7
- from contextlib import asynccontextmanager
8
- import json
9
- from pydantic import BaseModel
10
5
  from enum import StrEnum
11
6
  from crypticorn.common.mixins import ValidateEnumMixin
12
7
  from crypticorn.common.ansi_colors import AnsiColors as C
@@ -23,6 +18,7 @@ class LogLevel(ValidateEnumMixin, StrEnum):
23
18
 
24
19
  @classmethod
25
20
  def get_color(cls, level: str) -> str:
21
+ """Get the ansi color based on the log level."""
26
22
  if level == cls.DEBUG:
27
23
  return C.GREEN_BRIGHT
28
24
  elif level == cls.INFO:
@@ -38,10 +34,12 @@ class LogLevel(ValidateEnumMixin, StrEnum):
38
34
 
39
35
  @staticmethod
40
36
  def get_level(level: "LogLevel") -> int:
37
+ """Get the integer value from a log level name."""
41
38
  return logging._nameToLevel.get(level, logging.INFO)
42
39
 
43
40
  @staticmethod
44
41
  def get_name(level: int) -> "LogLevel":
42
+ """Get the level name from the integer value of a log level."""
45
43
  return LogLevel(logging._levelToName.get(level, "INFO"))
46
44
 
47
45
 
@@ -54,7 +52,7 @@ _LOGFORMAT = (
54
52
  _DATEFMT = "%Y-%m-%d %H:%M:%S.%f:"
55
53
 
56
54
 
57
- class CustomFormatter(logging.Formatter):
55
+ class _CustomFormatter(logging.Formatter):
58
56
  def __init__(self, *args, **kwargs):
59
57
  super().__init__(*args, **kwargs)
60
58
 
@@ -99,7 +97,7 @@ def configure_logging(
99
97
  # Configure stdout handler
100
98
  stdout_handler = logging.StreamHandler(sys.stdout)
101
99
  stdout_handler.setLevel(stdout_level)
102
- stdout_handler.setFormatter(CustomFormatter(fmt=fmt, datefmt=datefmt))
100
+ stdout_handler.setFormatter(_CustomFormatter(fmt=fmt, datefmt=datefmt))
103
101
  for filter in filters:
104
102
  stdout_handler.addFilter(filter)
105
103
  logger.addHandler(stdout_handler)
@@ -111,7 +109,7 @@ def configure_logging(
111
109
  log_file, maxBytes=10 * 1024 * 1024, backupCount=5
112
110
  )
113
111
  file_handler.setLevel(file_level)
114
- file_handler.setFormatter(CustomFormatter(fmt=fmt, datefmt=datefmt))
112
+ file_handler.setFormatter(_CustomFormatter(fmt=fmt, datefmt=datefmt))
115
113
  for filter in filters:
116
114
  file_handler.addFilter(filter)
117
115
  logger.addHandler(file_handler)
@@ -1,7 +1,7 @@
1
1
  from fastapi import FastAPI
2
2
  from fastapi.middleware.cors import CORSMiddleware
3
3
  from crypticorn.common.logging import configure_logging
4
- import logging
4
+ from contextlib import asynccontextmanager
5
5
 
6
6
 
7
7
  def add_cors_middleware(app: "FastAPI"):
@@ -18,6 +18,7 @@ def add_cors_middleware(app: "FastAPI"):
18
18
  )
19
19
 
20
20
 
21
+ @asynccontextmanager
21
22
  async def default_lifespan(app: FastAPI):
22
23
  """Default lifespan for the applications.
23
24
  This is used to configure the logging for the application.
@@ -1,7 +1,9 @@
1
1
  from enum import EnumMeta
2
2
  import logging
3
+ import warnings
4
+ from crypticorn.common.warnings import CrypticornDeprecatedSince28
3
5
 
4
- logger = logging.getLogger("uvicorn")
6
+ _logger = logging.getLogger("crypticorn")
5
7
 
6
8
 
7
9
  class ValidateEnumMixin:
@@ -35,7 +37,12 @@ class ValidateEnumMixin:
35
37
 
36
38
  # This Mixin will be removed in a future version. And has no effect from now on
37
39
  class ExcludeEnumMixin:
38
- """Mixin to exclude enum from OpenAPI schema. We use this to avoid duplicating enums when generating client code from the openapi spec."""
40
+ """(deprecated) Mixin to exclude enum from OpenAPI schema. We use this to avoid duplicating enums when generating client code from the openapi spec."""
41
+
42
+ warnings.warn(
43
+ "The `ExcludeEnumMixin` class is deprecated. Should be removed from enums inheriting this class.",
44
+ category=CrypticornDeprecatedSince28,
45
+ )
39
46
 
40
47
  @classmethod
41
48
  def __get_pydantic_json_schema__(cls, core_schema, handler):
@@ -51,7 +58,7 @@ class ApiErrorFallback(EnumMeta):
51
58
  # Let Pydantic/internal stuff pass silently ! fragile
52
59
  if name.startswith("__"):
53
60
  raise AttributeError(name)
54
- logger.warning(
61
+ _logger.warning(
55
62
  f"Unknown enum member '{name}' - update crypticorn package or check for typos"
56
63
  )
57
64
  return cls.UNKNOWN_ERROR
@@ -0,0 +1,11 @@
1
+ default_tags = [
2
+ {
3
+ "name": "Status",
4
+ "description": "These endpoints contain status operations.",
5
+ },
6
+ {
7
+ "name": "Admin",
8
+ "description": "These endpoints contain debugging and monitoring operations. They require admin scopes.",
9
+ }
10
+ ]
11
+
@@ -1,3 +1,5 @@
1
+ """Utilities for handling paginated API responses and cursor-based pagination."""
2
+
1
3
  from typing import Generic, Type, TypeVar, List, Optional, Literal
2
4
  from pydantic import BaseModel, Field, model_validator
3
5
 
@@ -10,8 +10,9 @@ import importlib.metadata
10
10
  import threading
11
11
  import time
12
12
  import psutil
13
+ import re
13
14
  from fastapi import APIRouter, Query
14
- from typing import Literal, Union
15
+ from typing import Literal
15
16
  from crypticorn.common.logging import LogLevel
16
17
  import logging
17
18
 
@@ -86,13 +87,19 @@ def get_container_limits() -> dict:
86
87
  def list_installed_packages(
87
88
  include: list[str] = Query(
88
89
  default=None,
89
- description="List of dependencies to include in the response. If not provided, all installed packages will be returned.",
90
+ description="List of regex patterns to match against package names. If not provided, all installed packages will be returned.",
90
91
  )
91
- ) -> list:
92
- """Return a list of installed packages and versions."""
92
+ ) -> dict[str, str]:
93
+ """Return a list of installed packages and versions.
94
+
95
+ The include parameter accepts regex patterns to match against package names.
96
+ For example:
97
+ - crypticorn.* will match all packages starting with 'crypticorn'
98
+ - .*tic.* will match all packages containing 'tic' in their name
99
+ """
93
100
  packages = {
94
101
  dist.metadata["Name"]: dist.version
95
102
  for dist in importlib.metadata.distributions()
96
- if include is None or dist.metadata["Name"] in include
103
+ if include is None or any(re.match(pattern, dist.metadata["Name"]) for pattern in include)
97
104
  }
98
105
  return dict(sorted(packages.items()))
@@ -1,3 +1,10 @@
1
+ """
2
+ This module contains the status router for the API.
3
+ It provides endpoints for checking the status of the API and get the server's time.
4
+ SHOULD ALLOW ACCESS TO THIS ROUTER WITHOUT.
5
+ >>> app.include_router(status_router)
6
+ """
7
+
1
8
  from datetime import datetime
2
9
  from typing import Literal
3
10
  from fastapi import APIRouter, Request
@@ -6,7 +13,7 @@ router = APIRouter(tags=["Status"], prefix="")
6
13
 
7
14
 
8
15
  @router.get("/", operation_id="ping")
9
- async def ping(request: Request) -> dict:
16
+ async def ping(request: Request) -> str:
10
17
  """
11
18
  Returns 'OK' if the API is running.
12
19
  """
@@ -54,7 +54,7 @@ class Scope(StrEnum):
54
54
 
55
55
  @classmethod
56
56
  def admin_scopes(cls) -> list["Scope"]:
57
- """Scopes that are only available to admins"""
57
+ """Scopes that are only available to admins."""
58
58
  return [
59
59
  cls.WRITE_TRADE_STRATEGIES,
60
60
  cls.WRITE_PAY_PRODUCTS,
@@ -64,14 +64,14 @@ class Scope(StrEnum):
64
64
 
65
65
  @classmethod
66
66
  def internal_scopes(cls) -> list["Scope"]:
67
- """Scopes that are only available to internal services"""
67
+ """Scopes that are only available to internal services."""
68
68
  return [
69
69
  cls.WRITE_TRADE_ACTIONS,
70
70
  ]
71
71
 
72
72
  @classmethod
73
73
  def purchaseable_scopes(cls) -> list["Scope"]:
74
- """Scopes that can be purchased"""
74
+ """Scopes that can be purchased."""
75
75
  return [
76
76
  cls.READ_PREDICTIONS,
77
77
  ]
crypticorn/common/urls.py CHANGED
@@ -3,6 +3,8 @@ from crypticorn.common.enums import ValidateEnumMixin
3
3
 
4
4
 
5
5
  class ApiEnv(StrEnum):
6
+ """The environment the API is being used with."""
7
+
6
8
  PROD = "prod"
7
9
  DEV = "dev"
8
10
  LOCAL = "local"
@@ -10,6 +12,8 @@ class ApiEnv(StrEnum):
10
12
 
11
13
 
12
14
  class BaseUrl(StrEnum):
15
+ """The base URL to connect to the API."""
16
+
13
17
  PROD = "https://api.crypticorn.com"
14
18
  DEV = "https://api.crypticorn.dev"
15
19
  LOCAL = "http://localhost"
@@ -17,6 +21,7 @@ class BaseUrl(StrEnum):
17
21
 
18
22
  @classmethod
19
23
  def from_env(cls, env: ApiEnv) -> "BaseUrl":
24
+ """Load the base URL from the API environment."""
20
25
  if env == ApiEnv.PROD:
21
26
  return cls.PROD
22
27
  elif env == ApiEnv.DEV:
@@ -28,10 +33,14 @@ class BaseUrl(StrEnum):
28
33
 
29
34
 
30
35
  class ApiVersion(StrEnum):
36
+ """Versions to use for the microservice APIs."""
37
+
31
38
  V1 = "v1"
32
39
 
33
40
 
34
41
  class Service(ValidateEnumMixin, StrEnum):
42
+ """The microservices available to connect to through the API"""
43
+
35
44
  HIVE = "hive"
36
45
  KLINES = "klines"
37
46
  PAY = "pay"
@@ -1,11 +1,14 @@
1
- from typing import Any, Union
1
+ """General utility functions and helper methods used across the codebase."""
2
+
3
+ from typing import Any
2
4
  from decimal import Decimal
3
5
  import string
4
6
  import random
5
- from fastapi import status
6
- from typing_extensions import deprecated
7
+ import typing_extensions
8
+ import warnings
7
9
 
8
- from crypticorn.common import ApiError, HTTPException, ExceptionContent
10
+ from crypticorn.common.exceptions import ApiError, HTTPException, ExceptionContent
11
+ from crypticorn.common.warnings import CrypticornDeprecatedSince25
9
12
 
10
13
 
11
14
  def throw_if_none(
@@ -33,7 +36,9 @@ def gen_random_id(length: int = 20) -> str:
33
36
  return "".join(random.choice(charset) for _ in range(length))
34
37
 
35
38
 
36
- @deprecated("Use math.isclose instead. Will be removed in a future version.")
39
+ @typing_extensions.deprecated(
40
+ "The `is_equal` method is deprecated; use `math.is_close` instead.", category=None
41
+ )
37
42
  def is_equal(
38
43
  a: float | Decimal,
39
44
  b: float | Decimal,
@@ -43,6 +48,10 @@ def is_equal(
43
48
  """
44
49
  Compare two Decimal numbers for approximate equality.
45
50
  """
51
+ warnings.warn(
52
+ "The `is_equal` method is deprecated; use `math.is_close` instead.",
53
+ category=CrypticornDeprecatedSince25,
54
+ )
46
55
  if not isinstance(a, Decimal):
47
56
  a = Decimal(str(a))
48
57
  if not isinstance(b, Decimal):
@@ -56,13 +65,12 @@ def is_equal(
56
65
 
57
66
  def optional_import(module_name: str, extra_name: str) -> Any:
58
67
  """
59
- Import a module optionally.
68
+ Tries to import a module. Raises `ImportError` if not found with a message to install the extra dependency.
60
69
  """
61
70
  try:
62
71
  return __import__(module_name)
63
72
  except ImportError as e:
64
- extra = f"[{extra_name}]"
65
73
  raise ImportError(
66
74
  f"Optional dependency '{module_name}' is required for this feature. "
67
- f"Install it with: pip install crypticorn{extra}"
75
+ f"Install it with: pip install crypticorn[{extra_name}]"
68
76
  ) from e
@@ -0,0 +1,63 @@
1
+ """Crypticorn-specific warnings."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class CrypticornDeprecationWarning(DeprecationWarning):
7
+ """A Crypticorn specific deprecation warning.
8
+
9
+ This warning is raised when using deprecated functionality in Crypticorn. It provides information on when the
10
+ deprecation was introduced and the expected version in which the corresponding functionality will be removed.
11
+
12
+ Attributes:
13
+ message: Description of the warning.
14
+ since: Crypticorn version in what the deprecation was introduced.
15
+ expected_removal: Crypticorn version in what the corresponding functionality expected to be removed.
16
+ """
17
+
18
+ message: str
19
+ since: tuple[int, int]
20
+ expected_removal: tuple[int, int]
21
+
22
+ def __init__(
23
+ self,
24
+ message: str,
25
+ *args: object,
26
+ since: tuple[int, int],
27
+ expected_removal: tuple[int, int] | None = None,
28
+ ) -> None:
29
+ super().__init__(message, *args)
30
+ self.message = message.rstrip(".")
31
+ self.since = since
32
+ self.expected_removal = (
33
+ expected_removal if expected_removal is not None else (since[0] + 1, 0)
34
+ )
35
+
36
+ def __str__(self) -> str:
37
+ message = (
38
+ f"{self.message}. Deprecated in Crypticorn v{self.since[0]}.{self.since[1]}"
39
+ f" to be removed in v{self.expected_removal[0]}.{self.expected_removal[1]}."
40
+ )
41
+ return message
42
+
43
+
44
+ class CrypticornDeprecatedSince25(CrypticornDeprecationWarning):
45
+ """A specific `CrypticornDeprecationWarning` subclass defining functionality deprecated since Crypticorn 2.5."""
46
+
47
+ def __init__(self, message: str, *args: object) -> None:
48
+ super().__init__(message, *args, since=(2, 5), expected_removal=(3, 0))
49
+
50
+
51
+ class CrypticornDeprecatedSince28(CrypticornDeprecationWarning):
52
+ """A specific `CrypticornDeprecationWarning` subclass defining functionality deprecated since Crypticorn 2.8."""
53
+
54
+ def __init__(self, message: str, *args: object) -> None:
55
+ super().__init__(message, *args, since=(2, 8), expected_removal=(3, 0))
56
+
57
+
58
+ class CrypticornExperimentalWarning(Warning):
59
+ """A Crypticorn specific experimental functionality warning.
60
+
61
+ This warning is raised when using experimental functionality in Crypticorn.
62
+ It is raised to warn users that the functionality may change or be removed in future versions of Crypticorn.
63
+ """
@@ -16,8 +16,8 @@ from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
16
16
  from typing import Any, Dict, List, Optional, Tuple, Union
17
17
  from typing_extensions import Annotated
18
18
 
19
- from pydantic import Field, StrictInt, StrictStr, field_validator
20
- from typing import Any, Dict, List, Optional
19
+ from pydantic import Field, StrictFloat, StrictInt, StrictStr, field_validator
20
+ from typing import Any, Dict, List, Optional, Union
21
21
  from typing_extensions import Annotated
22
22
  from crypticorn.metrics.client.models.log_level import LogLevel
23
23
 
@@ -248,7 +248,7 @@ class AdminApi:
248
248
  )
249
249
 
250
250
  # authentication setting
251
- _auth_settings: List[str] = []
251
+ _auth_settings: List[str] = ["APIKeyHeader", "HTTPBearer"]
252
252
 
253
253
  return self.api_client.param_serialize(
254
254
  method="GET",
@@ -509,7 +509,7 @@ class AdminApi:
509
509
  )
510
510
 
511
511
  # authentication setting
512
- _auth_settings: List[str] = []
512
+ _auth_settings: List[str] = ["APIKeyHeader", "HTTPBearer"]
513
513
 
514
514
  return self.api_client.param_serialize(
515
515
  method="GET",
@@ -541,9 +541,9 @@ class AdminApi:
541
541
  _headers: Optional[Dict[StrictStr, Any]] = None,
542
542
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
543
543
  ) -> LogLevel:
544
- """Get Logging Level
544
+ """(Deprecated) Get Logging Level
545
545
 
546
- Get the log level of the server logger.
546
+ Get the log level of the server logger. Will be removed in a future release.
547
547
 
548
548
  :param _request_timeout: timeout setting for this request. If one
549
549
  number provided, it will be total request
@@ -566,6 +566,7 @@ class AdminApi:
566
566
  :type _host_index: int, optional
567
567
  :return: Returns the result object.
568
568
  """ # noqa: E501
569
+ warnings.warn("GET /admin/log-level is deprecated.", DeprecationWarning)
569
570
 
570
571
  _param = self._get_log_level_serialize(
571
572
  _request_auth=_request_auth,
@@ -601,9 +602,9 @@ class AdminApi:
601
602
  _headers: Optional[Dict[StrictStr, Any]] = None,
602
603
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
603
604
  ) -> ApiResponse[LogLevel]:
604
- """Get Logging Level
605
+ """(Deprecated) Get Logging Level
605
606
 
606
- Get the log level of the server logger.
607
+ Get the log level of the server logger. Will be removed in a future release.
607
608
 
608
609
  :param _request_timeout: timeout setting for this request. If one
609
610
  number provided, it will be total request
@@ -626,6 +627,7 @@ class AdminApi:
626
627
  :type _host_index: int, optional
627
628
  :return: Returns the result object.
628
629
  """ # noqa: E501
630
+ warnings.warn("GET /admin/log-level is deprecated.", DeprecationWarning)
629
631
 
630
632
  _param = self._get_log_level_serialize(
631
633
  _request_auth=_request_auth,
@@ -661,9 +663,9 @@ class AdminApi:
661
663
  _headers: Optional[Dict[StrictStr, Any]] = None,
662
664
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
663
665
  ) -> RESTResponseType:
664
- """Get Logging Level
666
+ """(Deprecated) Get Logging Level
665
667
 
666
- Get the log level of the server logger.
668
+ Get the log level of the server logger. Will be removed in a future release.
667
669
 
668
670
  :param _request_timeout: timeout setting for this request. If one
669
671
  number provided, it will be total request
@@ -686,6 +688,7 @@ class AdminApi:
686
688
  :type _host_index: int, optional
687
689
  :return: Returns the result object.
688
690
  """ # noqa: E501
691
+ warnings.warn("GET /admin/log-level is deprecated.", DeprecationWarning)
689
692
 
690
693
  _param = self._get_log_level_serialize(
691
694
  _request_auth=_request_auth,
@@ -736,7 +739,7 @@ class AdminApi:
736
739
  )
737
740
 
738
741
  # authentication setting
739
- _auth_settings: List[str] = []
742
+ _auth_settings: List[str] = ["APIKeyHeader", "HTTPBearer"]
740
743
 
741
744
  return self.api_client.param_serialize(
742
745
  method="GET",
@@ -767,7 +770,7 @@ class AdminApi:
767
770
  _content_type: Optional[StrictStr] = None,
768
771
  _headers: Optional[Dict[StrictStr, Any]] = None,
769
772
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
770
- ) -> int:
773
+ ) -> float:
771
774
  """Get Memory Usage
772
775
 
773
776
  Resident Set Size (RSS) in MB — the actual memory used by the process in RAM. Represents the physical memory footprint. Important for monitoring real usage.
@@ -802,7 +805,7 @@ class AdminApi:
802
805
  )
803
806
 
804
807
  _response_types_map: Dict[str, Optional[str]] = {
805
- "200": "int",
808
+ "200": "float",
806
809
  }
807
810
  response_data = await self.api_client.call_api(
808
811
  *_param, _request_timeout=_request_timeout
@@ -827,7 +830,7 @@ class AdminApi:
827
830
  _content_type: Optional[StrictStr] = None,
828
831
  _headers: Optional[Dict[StrictStr, Any]] = None,
829
832
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
830
- ) -> ApiResponse[int]:
833
+ ) -> ApiResponse[float]:
831
834
  """Get Memory Usage
832
835
 
833
836
  Resident Set Size (RSS) in MB — the actual memory used by the process in RAM. Represents the physical memory footprint. Important for monitoring real usage.
@@ -862,7 +865,7 @@ class AdminApi:
862
865
  )
863
866
 
864
867
  _response_types_map: Dict[str, Optional[str]] = {
865
- "200": "int",
868
+ "200": "float",
866
869
  }
867
870
  response_data = await self.api_client.call_api(
868
871
  *_param, _request_timeout=_request_timeout
@@ -922,7 +925,7 @@ class AdminApi:
922
925
  )
923
926
 
924
927
  _response_types_map: Dict[str, Optional[str]] = {
925
- "200": "int",
928
+ "200": "float",
926
929
  }
927
930
  response_data = await self.api_client.call_api(
928
931
  *_param, _request_timeout=_request_timeout
@@ -963,7 +966,7 @@ class AdminApi:
963
966
  )
964
967
 
965
968
  # authentication setting
966
- _auth_settings: List[str] = []
969
+ _auth_settings: List[str] = ["APIKeyHeader", "HTTPBearer"]
967
970
 
968
971
  return self.api_client.param_serialize(
969
972
  method="GET",
@@ -1190,7 +1193,7 @@ class AdminApi:
1190
1193
  )
1191
1194
 
1192
1195
  # authentication setting
1193
- _auth_settings: List[str] = []
1196
+ _auth_settings: List[str] = ["APIKeyHeader", "HTTPBearer"]
1194
1197
 
1195
1198
  return self.api_client.param_serialize(
1196
1199
  method="GET",
@@ -1434,7 +1437,7 @@ class AdminApi:
1434
1437
  )
1435
1438
 
1436
1439
  # authentication setting
1437
- _auth_settings: List[str] = []
1440
+ _auth_settings: List[str] = ["APIKeyHeader", "HTTPBearer"]
1438
1441
 
1439
1442
  return self.api_client.param_serialize(
1440
1443
  method="GET",
@@ -17,7 +17,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
17
17
  from typing_extensions import Annotated
18
18
 
19
19
  from pydantic import StrictStr, field_validator
20
- from typing import Optional
20
+ from typing import Any, Dict, Optional
21
21
 
22
22
  from crypticorn.metrics.client.api_client import ApiClient, RequestSerialized
23
23
  from crypticorn.metrics.client.api_response import ApiResponse
@@ -294,7 +294,7 @@ class StatusApi:
294
294
  _content_type: Optional[StrictStr] = None,
295
295
  _headers: Optional[Dict[StrictStr, Any]] = None,
296
296
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
297
- ) -> str:
297
+ ) -> Dict[str, object]:
298
298
  """Ping
299
299
 
300
300
  Returns 'OK' if the API is running.
@@ -329,7 +329,7 @@ class StatusApi:
329
329
  )
330
330
 
331
331
  _response_types_map: Dict[str, Optional[str]] = {
332
- "200": "str",
332
+ "200": "Dict[str, object]",
333
333
  }
334
334
  response_data = await self.api_client.call_api(
335
335
  *_param, _request_timeout=_request_timeout
@@ -354,7 +354,7 @@ class StatusApi:
354
354
  _content_type: Optional[StrictStr] = None,
355
355
  _headers: Optional[Dict[StrictStr, Any]] = None,
356
356
  _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
357
- ) -> ApiResponse[str]:
357
+ ) -> ApiResponse[Dict[str, object]]:
358
358
  """Ping
359
359
 
360
360
  Returns 'OK' if the API is running.
@@ -389,7 +389,7 @@ class StatusApi:
389
389
  )
390
390
 
391
391
  _response_types_map: Dict[str, Optional[str]] = {
392
- "200": "str",
392
+ "200": "Dict[str, object]",
393
393
  }
394
394
  response_data = await self.api_client.call_api(
395
395
  *_param, _request_timeout=_request_timeout
@@ -449,7 +449,7 @@ class StatusApi:
449
449
  )
450
450
 
451
451
  _response_types_map: Dict[str, Optional[str]] = {
452
- "200": "str",
452
+ "200": "Dict[str, object]",
453
453
  }
454
454
  response_data = await self.api_client.call_api(
455
455
  *_param, _request_timeout=_request_timeout
@@ -215,7 +215,9 @@ class Configuration:
215
215
  debug: Optional[bool] = None,
216
216
  ) -> None:
217
217
  """Constructor"""
218
- self._base_path = "http://localhost/v1/metrics" if host is None else host
218
+ self._base_path = (
219
+ "https://api.crypticorn.dev/v1/metrics" if host is None else host
220
+ )
219
221
  """Default Base url
220
222
  """
221
223
  self.server_index = 0 if server_index is None and host is None else server_index
@@ -557,7 +559,7 @@ class Configuration:
557
559
  """
558
560
  return [
559
561
  {
560
- "url": "http://localhost/v1/metrics",
562
+ "url": "https://api.crypticorn.dev/v1/metrics",
561
563
  "description": "No description provided",
562
564
  }
563
565
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crypticorn
3
- Version: 2.8.1
3
+ Version: 2.9.0
4
4
  Summary: Maximise Your Crypto Trading Profits with Machine Learning
5
5
  Author-email: Crypticorn <timon@crypticorn.com>
6
6
  License: MIT
@@ -43,6 +43,7 @@ Requires-Dist: isort; extra == "dev"
43
43
  Requires-Dist: mypy; extra == "dev"
44
44
  Requires-Dist: openapi-generator-cli<8.0.0,>=7.12.0; extra == "dev"
45
45
  Requires-Dist: pdoc==15.0.3; extra == "dev"
46
+ Requires-Dist: python-semantic-release==9.21.0; extra == "dev"
46
47
  Provides-Extra: test
47
48
  Requires-Dist: pytest==8.3.5; extra == "test"
48
49
  Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
@@ -1,4 +1,4 @@
1
- crypticorn/__init__.py,sha256=IWYO6MWcpNmgkH05xEywxcIjcegWuSo5cpNIDTRcpC8,303
1
+ crypticorn/__init__.py,sha256=ctrwe5CQtYhnetHYPgSmC0CIHa4xbDsLZvpY38tfEow,423
2
2
  crypticorn/client.py,sha256=XcJhgMoNSFQZJU3AoYuvxRMh-HPBPBlugKMpGSHxbIE,4700
3
3
  crypticorn/auth/__init__.py,sha256=JAl1tBLK9pYLr_-YKaj581c-c94PWLoqnatTIVAVvMM,81
4
4
  crypticorn/auth/main.py,sha256=j8eRGN2gUWyeOCnWnUPe3mCAfaidGnOMnZRiSQy-yzM,720
@@ -63,22 +63,24 @@ crypticorn/cli/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
63
63
  crypticorn/cli/templates/auth.py,sha256=Q1TxlA7qzhjvrqp1xz1aV2vGnj3DKFNN-VSl3o0B-dI,983
64
64
  crypticorn/cli/templates/dependabot.yml,sha256=ct5ieB8KAV1KLzoYKUNm6dZ9wKG_P_JQHgRjZUfT54w,861
65
65
  crypticorn/cli/templates/ruff.yml,sha256=gWicFFTzC4nToSmRkIIGipos8CZ447YG0kebBCJhtJE,319
66
- crypticorn/common/__init__.py,sha256=29n5tUr9-yjjJ8sjkbMrwq7pDKZ2Z1qz1bR1msGJDkA,671
67
- crypticorn/common/ansi_colors.py,sha256=_ja-pxo0dUAT4WFZuYkOLliWA7LqTjvcew9dJUcgugU,1174
68
- crypticorn/common/auth.py,sha256=GIb9MikQsSxqz-K5rDIOroP5mFoTgQqwByTGO5JqcdM,8713
66
+ crypticorn/common/__init__.py,sha256=DXEuUU_kaLBSBcvpiFie_ROuK5XEZuTMIfsg-BZE0iE,752
67
+ crypticorn/common/ansi_colors.py,sha256=ts49UtfTy-c0uvlGwb3wE-jE_GbXvSBSfzwrDlNi0HE,1331
68
+ crypticorn/common/auth.py,sha256=5wgApDw5x7dI-IWU9tX_gih-gMozi7Y5Tgpen-A9fbo,8713
69
69
  crypticorn/common/decorators.py,sha256=pmnGYCIrLv59wZkDbvPyK9NJmgPJWW74LXTdIWSjOkY,1063
70
- crypticorn/common/enums.py,sha256=RitDVqlG_HTe6tHT6bWusZNFCeYk1eQvJVH-7x3_Zlg,668
71
- crypticorn/common/errors.py,sha256=8jxZ2lLn_NoFKKq6n2JwKPsR0dA2vkGnbXDfEK6ndH0,27851
72
- crypticorn/common/exceptions.py,sha256=9ftvKoMFP9rNvaYhCKDgbJXwTYrBlReMmFwL3RzZJYQ,6203
73
- crypticorn/common/logging.py,sha256=xF7j4-rGj6ALlz5JwbMi4oEeyfI6VKDh3GlR3kCe3hc,4295
74
- crypticorn/common/middleware.py,sha256=YF0_tTjQekZkb6ip3xsqy04JCI4S8011CiSL3helU2E,962
75
- crypticorn/common/mixins.py,sha256=o-VONtAS_nHH-OPCFXox6kdX_Xdn1g37uTtkLqij-6U,1722
76
- crypticorn/common/pagination.py,sha256=c07jrMNrBaNTmgx4sppdP7ND4RNT7NBqBXWvofazIlE,2251
77
- crypticorn/common/scopes.py,sha256=ofJ5FDf30wab572XvDzAXVKBIUWa3shScAmzNrJsWqQ,2453
78
- crypticorn/common/urls.py,sha256=3Gf1NU1XQYcOTjcdztG3bDAE98FVbgTK2QXzUe7tFVQ,878
79
- crypticorn/common/utils.py,sha256=Kz2-I96MKIGKM18PHQ77VbKHLMGUvZG_jjj7xpQed8k,2138
80
- crypticorn/common/router/admin_router.py,sha256=_Uvpkrg6daNiAdfr_2rtBlcCq8OvHy04Me12VRWdh4Y,3434
81
- crypticorn/common/router/status_router.py,sha256=_PPeYi8ps2Og-oJCnf9O7heaCYHq3oStF0_He0D_tZo,642
70
+ crypticorn/common/enums.py,sha256=8iPG1UFtU50HKytOrubYJsFJ_isMY5z69U2mezXA-NE,765
71
+ crypticorn/common/errors.py,sha256=E7H8DVUP4E4uu-ze9v7aJBrfV7ODgbFBXJeyVSXbdVo,28041
72
+ crypticorn/common/exceptions.py,sha256=iq_VFkZ4jr_7BeEjDwlmHyRYPLIYN98-MJGJrxe2OqM,6036
73
+ crypticorn/common/logging.py,sha256=bpWT3ip8CM5tf_jKECJjwrVVf5GYcRjfo-CPb47gISU,4346
74
+ crypticorn/common/middleware.py,sha256=O7XiXPimNYUhF9QTv6yFUTVlb91-SK-3CfTrWMNP6Ck,1011
75
+ crypticorn/common/mixins.py,sha256=NoZL1kXgSb4oVOGNWPqPwCcVZYjHzlsyL80bz6Xxlzg,2002
76
+ crypticorn/common/openapi.py,sha256=Hppim3bLIy_uDixuC3ZPGCsxwk9WU5R45ua8XqZFatg,321
77
+ crypticorn/common/pagination.py,sha256=BYMNB4JUW9eeiTw1q3CyHXaT_-hk_BrSXAOqvif08Ek,2334
78
+ crypticorn/common/scopes.py,sha256=TsSzMFHQAJ45CwhSrU3uRPHyHHjrCgPdJhAyjxY6b2Q,2456
79
+ crypticorn/common/urls.py,sha256=qJHxdkpwQR1iPLSPMYVoobnLNSsQoKVV5SsRng4dZYs,1161
80
+ crypticorn/common/utils.py,sha256=N4h-FtojufTuc4IBU7TeuSOygFvErpgZDrwl4epGmvw,2503
81
+ crypticorn/common/warnings.py,sha256=bIfMLbKaCntMV88rTnoWgV6SZ9FJvo_adXv3yFW1rqg,2395
82
+ crypticorn/common/router/admin_router.py,sha256=G-dtyvgLnsPpWSB4Gqkw2_m9w2bCMM-bs--sDWe63AY,3736
83
+ crypticorn/common/router/status_router.py,sha256=it6kfvx_Pn4Rv06fmViwhwr-m6f4WuSgcZwc6VTaMz4,868
82
84
  crypticorn/hive/__init__.py,sha256=hRfTlEzEql4msytdUC_04vfaHzVKG5CGZle1M-9QFgY,81
83
85
  crypticorn/hive/main.py,sha256=eoo5bTmbfS94zuYHDkD20orNANESYuTURHQNw3pIEvQ,2875
84
86
  crypticorn/hive/utils.py,sha256=5T2GYnIFazXgAdUlO03xWqcMWhWkM82cfWvwsO8geHE,2040
@@ -159,19 +161,19 @@ crypticorn/metrics/main.py,sha256=I4KZ-46ro6I6-EWf3p1BZV-8Iryorr8pRUZhv3yXzXI,35
159
161
  crypticorn/metrics/client/__init__.py,sha256=zp5tyfddEBfFrCqp9YEAwObC60lZ0GnqO3dvsAOt1zE,2530
160
162
  crypticorn/metrics/client/api_client.py,sha256=pGWJuO-mgxlUdhJGwkScf7CviGzjDrmUAiU0LXasQY4,26934
161
163
  crypticorn/metrics/client/api_response.py,sha256=WhxwYDSMm6wPixp9CegO8dJzjFxDz3JF1yCq9s0ZqKE,639
162
- crypticorn/metrics/client/configuration.py,sha256=wA1hBWEINMM_AlZg-DAv1AelmkjBERB5d2gA66aEwSM,19158
164
+ crypticorn/metrics/client/configuration.py,sha256=BQNgtkCNhI3uB8qCV54TMf85b3sFzQYvXGMbhar5KSM,19202
163
165
  crypticorn/metrics/client/exceptions.py,sha256=UegnYftFlQDXAQv8BmD20yRzTtWpjTHcuOymTBWmgeE,6421
164
166
  crypticorn/metrics/client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
165
167
  crypticorn/metrics/client/rest.py,sha256=pWeYnpTfTV7L5U6Kli3b7i8VrmqdG8sskqSnTHPIoQo,7025
166
168
  crypticorn/metrics/client/api/__init__.py,sha256=nNmEy9XBH8jQboMzedrzeGl8OVuDo_iylCaFw4Fgysg,649
167
- crypticorn/metrics/client/api/admin_api.py,sha256=8JGUN0w-bIcENKLJVgBvkVgAlDIRqilTTaF8ulUCPok,58540
169
+ crypticorn/metrics/client/api/admin_api.py,sha256=QAfVE4Ag2QAsu1imyK-ylrQDxQX4-Ue10H7pxAfGPb0,59131
168
170
  crypticorn/metrics/client/api/exchanges_api.py,sha256=BZiJH8hxxSnI9SXydgErM6gzvIR-t9vNXbh9fFotpQQ,40455
169
171
  crypticorn/metrics/client/api/indicators_api.py,sha256=gltFmv_EorYbeWMnp-N0QkgdVKrkvi1iOZUP_ewkXZ0,26748
170
172
  crypticorn/metrics/client/api/logs_api.py,sha256=lDOixn5hn3DWc6HjExWtKZfy7U4NfcSLsO1bNFrx4GE,13550
171
173
  crypticorn/metrics/client/api/marketcap_api.py,sha256=28lQlBJh5hdW7fULJl55bAJy_HWZWEdouds63YJIwAQ,51106
172
174
  crypticorn/metrics/client/api/markets_api.py,sha256=NbPtD5bQK_Nt73hlVd6cd1pAZ7HO1QQgNl_abNoN00s,14739
173
175
  crypticorn/metrics/client/api/quote_currencies_api.py,sha256=H4c3zOp5eTTUrRMlMH-H8aIIBpV4Ioj8c65UUt_BEuE,11259
174
- crypticorn/metrics/client/api/status_api.py,sha256=_Ou_EGmjPyv32G-S4QKfRemdpGG6FUsgOkbGDfYaFp0,19633
176
+ crypticorn/metrics/client/api/status_api.py,sha256=hxQf-22CurM-1TQygsYeYiL8CVhFe9cCPbDkX0zLvfg,19714
175
177
  crypticorn/metrics/client/api/tokens_api.py,sha256=x5a-YAeAgFJm-pN4K3-lOM-WPVYAxoBr-AYb-oxhysM,19522
176
178
  crypticorn/metrics/client/models/__init__.py,sha256=Voa1tj-CTpvzF6UmGJf0h0zFqG-7wFV8TSwH_lst0WY,1285
177
179
  crypticorn/metrics/client/models/api_error_identifier.py,sha256=HrL78MgQ0NbWv8CJhl6Zbp3QSIK2xO8lAZy5y6M8NCk,4948
@@ -255,8 +257,8 @@ crypticorn/trade/client/models/strategy_model_input.py,sha256=ala19jARyfA5ysys5D
255
257
  crypticorn/trade/client/models/strategy_model_output.py,sha256=2o2lhbgUSTznowpMLEHF1Ex9TG9oRmzlCIb-gXqo7_s,5643
256
258
  crypticorn/trade/client/models/tpsl.py,sha256=C2KgTIZs-a8W4msdaXgBKJcwtA-o5wR4rBauRP-iQxU,4317
257
259
  crypticorn/trade/client/models/trading_action_type.py,sha256=pGq_TFLMPfYFizYP-xKgEC1ZF4U3lGdJYoGa_ZH2x-Q,769
258
- crypticorn-2.8.1.dist-info/METADATA,sha256=03ryK64UR_9PrQWtB60H8y896n-YKmmb99gW2DO9gJU,8140
259
- crypticorn-2.8.1.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
260
- crypticorn-2.8.1.dist-info/entry_points.txt,sha256=d_xHsGvUTebPveVUK0SrpDFQ5ZRSjlI7lNCc11sn2PM,59
261
- crypticorn-2.8.1.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
262
- crypticorn-2.8.1.dist-info/RECORD,,
260
+ crypticorn-2.9.0.dist-info/METADATA,sha256=x3EAb8VtrmVvAz4cMxg_D1qM-sS5Jum2VwKTFF7pYcE,8203
261
+ crypticorn-2.9.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
262
+ crypticorn-2.9.0.dist-info/entry_points.txt,sha256=d_xHsGvUTebPveVUK0SrpDFQ5ZRSjlI7lNCc11sn2PM,59
263
+ crypticorn-2.9.0.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
264
+ crypticorn-2.9.0.dist-info/RECORD,,