crypticorn 2.7.5__py3-none-any.whl → 2.8.0rc2__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
@@ -3,6 +3,10 @@
3
3
  .. include:: ../CHANGELOG.md
4
4
  """
5
5
 
6
+ from crypticorn.common.logging import configure_logging
7
+ configure_logging("crypticorn")
8
+
6
9
  from crypticorn.client import ApiClient
7
10
 
8
11
  __all__ = ["ApiClient"]
12
+
@@ -78,10 +78,12 @@ class CreateApiKeyRequest(BaseModel):
78
78
  "read:metrics:tokens",
79
79
  "read:metrics:markets",
80
80
  "read:sentiment",
81
+ "read:admin",
82
+ "write:admin",
81
83
  ]
82
84
  ):
83
85
  raise ValueError(
84
- "each list item must be one of ('read:predictions', '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:metrics:marketcap', 'read:metrics:indicators', 'read:metrics:exchanges', 'read:metrics:tokens', 'read:metrics:markets', 'read:sentiment')"
86
+ "each list item must be one of ('read:predictions', '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:metrics:marketcap', 'read:metrics:indicators', 'read:metrics:exchanges', 'read:metrics:tokens', 'read:metrics:markets', 'read:sentiment', 'read:admin', 'write:admin')"
85
87
  )
86
88
  return value
87
89
 
@@ -86,10 +86,12 @@ class GetApiKeys200ResponseInner(BaseModel):
86
86
  "read:metrics:tokens",
87
87
  "read:metrics:markets",
88
88
  "read:sentiment",
89
+ "read:admin",
90
+ "write:admin",
89
91
  ]
90
92
  ):
91
93
  raise ValueError(
92
- "each list item must be one of ('read:predictions', '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:metrics:marketcap', 'read:metrics:indicators', 'read:metrics:exchanges', 'read:metrics:tokens', 'read:metrics:markets', 'read:sentiment')"
94
+ "each list item must be one of ('read:predictions', '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:metrics:marketcap', 'read:metrics:indicators', 'read:metrics:exchanges', 'read:metrics:tokens', 'read:metrics:markets', 'read:sentiment', 'read:admin', 'write:admin')"
93
95
  )
94
96
  return value
95
97
 
@@ -8,4 +8,8 @@ from crypticorn.common.enums import *
8
8
  from crypticorn.common.utils import *
9
9
  from crypticorn.common.exceptions import *
10
10
  from crypticorn.common.pagination import *
11
- from crypticorn.common.status_router import router as status_router
11
+ from crypticorn.common.logging import *
12
+ from crypticorn.common.ansi_colors import *
13
+ from crypticorn.common.middleware import *
14
+ from crypticorn.common.router.status_router import router as status_router
15
+ from crypticorn.common.router.admin_router import router as admin_router
@@ -0,0 +1,36 @@
1
+ from enum import StrEnum
2
+ from typing import TYPE_CHECKING
3
+
4
+ class AnsiColors(StrEnum):
5
+ # Regular Text Colors
6
+ BLACK = "\033[30m" # black
7
+ RED = "\033[31m" # red
8
+ GREEN = "\033[32m" # green
9
+ YELLOW = "\033[33m" # yellow
10
+ BLUE = "\033[34m" # blue
11
+ MAGENTA = "\033[35m" # magenta
12
+ CYAN = "\033[36m" # cyan
13
+ WHITE = "\033[37m" # white
14
+
15
+ # Bright Text Colors
16
+ BLACK_BRIGHT = "\033[90m" # black_bright
17
+ RED_BRIGHT = "\033[91m" # red_bright
18
+ GREEN_BRIGHT = "\033[92m" # green_bright
19
+ YELLOW_BRIGHT = "\033[93m" # yellow_bright
20
+ BLUE_BRIGHT = "\033[94m" # blue_bright
21
+ MAGENTA_BRIGHT = "\033[95m" # magenta_bright
22
+ CYAN_BRIGHT = "\033[96m" # cyan_bright
23
+ WHITE_BRIGHT = "\033[97m" # white_bright
24
+
25
+ # Bold Text Colors
26
+ BLACK_BOLD = "\033[1;30m" # black_bold
27
+ RED_BOLD = "\033[1;31m" # red_bold
28
+ GREEN_BOLD = "\033[1;32m" # green_bold
29
+ YELLOW_BOLD = "\033[1;33m" # yellow_bold
30
+ BLUE_BOLD = "\033[1;34m" # blue_bold
31
+ MAGENTA_BOLD = "\033[1;35m" # magenta_bold
32
+ CYAN_BOLD = "\033[1;36m" # cyan_bold
33
+ WHITE_BOLD = "\033[1;37m" # white_bold
34
+
35
+ # Reset Color
36
+ RESET = "\033[0m"
crypticorn/common/auth.py CHANGED
@@ -75,7 +75,9 @@ class AuthHandler:
75
75
  raise HTTPException(
76
76
  content=ExceptionContent(
77
77
  error=ApiError.INSUFFICIENT_SCOPES,
78
- message="Insufficient scopes to access this resource",
78
+ message="Insufficient scopes to access this resource (required: "
79
+ + ", ".join(api_scopes)
80
+ + ")",
79
81
  ),
80
82
  )
81
83
 
@@ -9,6 +9,7 @@ import logging
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
+
12
13
  class ExceptionType(StrEnum):
13
14
  HTTP = "http"
14
15
  WEBSOCKET = "websocket"
@@ -91,34 +92,44 @@ class WebSocketException(HTTPException):
91
92
  )
92
93
 
93
94
 
94
- async def general_handler(request: Request, exc: Exception):
95
+ async def general_handler(request: Request, exc: Exception) -> JSONResponse:
95
96
  """This is the default exception handler for all exceptions."""
96
- body = ExceptionContent(message=str(exc), error=ApiError.UNKNOWN_ERROR).enrich()
97
- logger.error(f"Unknown error: {body.detail}")
98
- return JSONResponse(
99
- status_code=body.status_code, content=HTTPException(content=body).detail
97
+ body = ExceptionContent(message=str(exc), error=ApiError.UNKNOWN_ERROR)
98
+ res = JSONResponse(
99
+ status_code=body.enrich().status_code,
100
+ content=HTTPException(content=body).detail,
100
101
  )
102
+ logger.error(f"Response validation error: {res}")
103
+ return res
101
104
 
102
105
 
103
- async def request_validation_handler(request: Request, exc: RequestValidationError):
106
+ async def request_validation_handler(
107
+ request: Request, exc: RequestValidationError
108
+ ) -> JSONResponse:
104
109
  """This is the exception handler for all request validation errors."""
105
- body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_REQUEST).enrich()
106
- logger.error(f"Request validation error: {body.detail}")
107
- return JSONResponse(
108
- status_code=body.status_code, content=HTTPException(content=body).detail
110
+ body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_REQUEST)
111
+ res = JSONResponse(
112
+ status_code=body.enrich().status_code,
113
+ content=HTTPException(content=body).detail,
109
114
  )
115
+ logger.error(f"Response validation error: {res}")
116
+ return res
110
117
 
111
118
 
112
- async def response_validation_handler(request: Request, exc: ResponseValidationError):
119
+ async def response_validation_handler(
120
+ request: Request, exc: ResponseValidationError
121
+ ) -> JSONResponse:
113
122
  """This is the exception handler for all response validation errors."""
114
- body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_RESPONSE).enrich()
115
- logger.error(f"Response validation error: {body.detail}")
116
- return JSONResponse(
117
- status_code=body.status_code, content=HTTPException(content=body).detail
123
+ body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_RESPONSE)
124
+ res = JSONResponse(
125
+ status_code=body.enrich().status_code,
126
+ content=HTTPException(content=body).detail,
118
127
  )
128
+ logger.error(f"Response validation error: {res}")
129
+ return res
119
130
 
120
131
 
121
- async def http_handler(request: Request, exc: HTTPException):
132
+ async def http_handler(request: Request, exc: HTTPException) -> JSONResponse:
122
133
  """This is the exception handler for HTTPExceptions. It unwraps the HTTPException and returns the detail in a flat JSON response."""
123
134
  logger.error(f"HTTP error: {exc.detail}")
124
135
  return JSONResponse(status_code=exc.status_code, content=exc.detail)
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+ # shared_logger.py
3
+ import logging
4
+ import sys
5
+ from contextvars import ContextVar
6
+ from contextlib import asynccontextmanager
7
+ import json
8
+ from pydantic import BaseModel
9
+ from enum import StrEnum
10
+ from crypticorn.common.mixins import ValidateEnumMixin
11
+ from crypticorn.common.ansi_colors import AnsiColors as C
12
+ from datetime import datetime
13
+
14
+
15
+ class LogLevel(ValidateEnumMixin, StrEnum):
16
+ DEBUG = "DEBUG"
17
+ INFO = "INFO"
18
+ WARNING = "WARNING"
19
+ ERROR = "ERROR"
20
+ CRITICAL = "CRITICAL"
21
+
22
+ @classmethod
23
+ def get_color(cls, level: str) -> str:
24
+ if level == cls.DEBUG:
25
+ return C.GREEN_BRIGHT
26
+ elif level == cls.INFO:
27
+ return C.BLUE_BRIGHT
28
+ elif level == cls.WARNING:
29
+ return C.YELLOW_BRIGHT
30
+ elif level == cls.ERROR:
31
+ return C.RED_BRIGHT
32
+ elif level == cls.CRITICAL:
33
+ return C.RED_BOLD
34
+ else:
35
+ return C.RESET
36
+
37
+ @staticmethod
38
+ def get_level(level: "LogLevel") -> int:
39
+ return logging._nameToLevel.get(level, logging.INFO)
40
+
41
+ @staticmethod
42
+ def get_name(level: int) -> "LogLevel":
43
+ return LogLevel(logging._levelToName.get(level, "INFO"))
44
+
45
+
46
+ _LOGFORMAT = (
47
+ f"{C.CYAN_BOLD}%(asctime)s{C.RESET} - "
48
+ f"{C.GREEN_BOLD}%(name)s{C.RESET} - "
49
+ f"%(levelcolor)s%(levelname)s{C.RESET} - "
50
+ f"%(message)s"
51
+ )
52
+ _PLAIN_LOGFORMAT = "%(asctime)s - " "%(name)s - " "%(levelname)s - " "%(message)s"
53
+ _DATEFMT = "%Y-%m-%d %H:%M:%S.%f:"
54
+
55
+
56
+ class CustomFormatter(logging.Formatter):
57
+ def __init__(self, *args, **kwargs):
58
+ super().__init__(*args, **kwargs)
59
+
60
+ def format(self, record):
61
+ color = LogLevel.get_color(record.levelname)
62
+ record.levelcolor = color
63
+ return super().format(record)
64
+
65
+ def formatTime(self, record, datefmt=_DATEFMT):
66
+ dt = datetime.fromtimestamp(record.created)
67
+ s = dt.strftime(datefmt)
68
+ return s[:-3] # Trim last 3 digits to get milliseconds
69
+
70
+
71
+ def configure_logging(
72
+ name: str,
73
+ fmt: str = _LOGFORMAT,
74
+ datefmt: str = _DATEFMT,
75
+ stdout_level: int = logging.INFO,
76
+ file_level: int = logging.INFO,
77
+ log_file: str = None,
78
+ filters: list[logging.Filter] = [],
79
+ ) -> None:
80
+ """Configures the logging for the application.
81
+ Run this function as early as possible in the application (for example using the `lifespan` parameter in FastAPI).
82
+ Then use can use the default `logging.getLogger(__name__)` method to get the logger.
83
+ """
84
+ logger = logging.getLogger(name)
85
+
86
+ if logger.handlers: # clear existing handlers to avoid duplicates
87
+ logger.handlers.clear()
88
+
89
+ logger.setLevel(min(stdout_level, file_level)) # set to most verbose level
90
+
91
+ # Configure stdout handler
92
+ stdout_handler = logging.StreamHandler(sys.stdout)
93
+ stdout_handler.setLevel(stdout_level)
94
+ stdout_handler.setFormatter(CustomFormatter(fmt=fmt, datefmt=datefmt))
95
+ for filter in filters:
96
+ stdout_handler.addFilter(filter)
97
+ logger.addHandler(stdout_handler)
98
+
99
+ # Configure file handler
100
+ if log_file:
101
+ os.makedirs(os.path.dirname(log_file), exist_ok=True)
102
+ file_handler = logging.RotatingFileHandler(
103
+ log_file, maxBytes=10 * 1024 * 1024, backupCount=5
104
+ )
105
+ file_handler.setLevel(file_level)
106
+ file_handler.setFormatter(CustomFormatter(fmt=fmt, datefmt=datefmt))
107
+ for filter in filters:
108
+ file_handler.addFilter(filter)
109
+ logger.addHandler(file_handler)
110
+
111
+
112
+ def disable_logging():
113
+ """Disable logging for the crypticorn logger.
114
+ """
115
+ logger = logging.getLogger("crypticorn")
116
+ logger.disabled = True
@@ -0,0 +1,27 @@
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from crypticorn.common.logging import configure_logging
4
+ import logging
5
+
6
+
7
+ def add_cors_middleware(app: "FastAPI"):
8
+ app.add_middleware(
9
+ CORSMiddleware,
10
+ allow_origins=[
11
+ "http://localhost:5173", # vite dev server
12
+ "http://localhost:4173", # vite preview server
13
+ ],
14
+ allow_origin_regex="^https:\/\/([a-zA-Z0-9-]+\.)*crypticorn\.(dev|com)\/?$", # matches (multiple or no) subdomains of crypticorn.dev and crypticorn.com
15
+ allow_credentials=True,
16
+ allow_methods=["*"],
17
+ allow_headers=["*"],
18
+ )
19
+
20
+ async def default_lifespan(app: FastAPI):
21
+ """Default lifespan for the applications.
22
+ This is used to configure the logging for the application.
23
+ To override this, pass a different lifespan to the FastAPI constructor or call this lifespan within a custom lifespan.
24
+ """
25
+ configure_logging(__name__) # for the consuming app
26
+ logger = logging.getLogger(__name__)
27
+ yield
@@ -0,0 +1,100 @@
1
+ """
2
+ This module contains the admin router for the API.
3
+ It provides endpoints for monitoring the server and getting information about the environment.
4
+ ONLY ALLOW ACCESS TO THIS ROUTER WITH ADMIN SCOPES.
5
+ >>> app.include_router(admin_router, dependencies=[Security(auth_handler.combined_auth, scopes=[Scope.WRITE_LOGS])])
6
+ """
7
+
8
+ import os
9
+ import pkg_resources
10
+ import threading
11
+ import time
12
+ import psutil
13
+ from fastapi import APIRouter, Query
14
+ from typing import Literal, Union
15
+ from crypticorn.common.logging import LogLevel
16
+ import logging
17
+
18
+ router = APIRouter(tags=["Admin"], prefix="/admin")
19
+
20
+ START_TIME = time.time()
21
+
22
+
23
+ @router.get("/log-level", status_code=200, operation_id="getLogLevel")
24
+ async def get_logging_level() -> LogLevel:
25
+ """
26
+ Get the log level of the server logger.
27
+ """
28
+ return LogLevel.get_name(logging.getLogger().level)
29
+
30
+
31
+ @router.get("/uptime", operation_id="getUptime", status_code=200)
32
+ def get_uptime(type: Literal["seconds", "human"] = "seconds") -> Union[int, str]:
33
+ """Return the server uptime in seconds or human-readable form."""
34
+ uptime_seconds = int(time.time() - START_TIME)
35
+ if type == "seconds":
36
+ return uptime_seconds
37
+ elif type == "human":
38
+ return time.strftime("%H:%M:%S", time.gmtime(uptime_seconds))
39
+
40
+
41
+ @router.get("/memory", operation_id="getMemoryUsage", status_code=200)
42
+ def get_memory_usage() -> int:
43
+ """
44
+ Resident Set Size (RSS) in MB — the actual memory used by the process in RAM.
45
+ Represents the physical memory footprint. Important for monitoring real usage.
46
+ """
47
+ process = psutil.Process(os.getpid())
48
+ mem_info = process.memory_info()
49
+ return round(mem_info.rss / (1024 * 1024), 2)
50
+
51
+
52
+ @router.get("/threads", operation_id="getThreads", status_code=200)
53
+ def get_threads() -> dict:
54
+ """Return count and names of active threads."""
55
+ threads = threading.enumerate()
56
+ return {
57
+ "count": len(threads),
58
+ "threads": [t.name for t in threads],
59
+ }
60
+
61
+
62
+ @router.get("/limits", operation_id="getContainerLimits", status_code=200)
63
+ def get_container_limits() -> dict:
64
+ """Return container resource limits from cgroup."""
65
+ limits = {}
66
+ try:
67
+ with open("/sys/fs/cgroup/memory/memory.limit_in_bytes") as f:
68
+ limits["memory_limit_MB"] = int(f.read().strip()) / 1024 / 1024
69
+ except Exception:
70
+ limits["memory_limit_MB"] = "N/A"
71
+
72
+ try:
73
+ with open("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") as f1, open(
74
+ "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
75
+ ) as f2:
76
+ quota = int(f1.read().strip())
77
+ period = int(f2.read().strip())
78
+ limits["cpu_limit_cores"] = quota / period if quota > 0 else "N/A"
79
+ except Exception:
80
+ limits["cpu_limit_cores"] = "N/A"
81
+
82
+ return limits
83
+
84
+
85
+ @router.get("/dependencies", operation_id="getDependencies", status_code=200)
86
+ def list_installed_packages(
87
+ include: list[str] = Query(
88
+ default=None,
89
+ description="List of dependencies to include in the response. If not provided, all installed packages will be returned.",
90
+ )
91
+ ) -> list:
92
+ """Return a list of installed packages and versions."""
93
+ return sorted(
94
+ [
95
+ {dist.project_name: dist.version}
96
+ for dist in pkg_resources.working_set
97
+ if include is None or dist.project_name in include
98
+ ],
99
+ key=lambda x: next(iter(x)),
100
+ )
@@ -0,0 +1,24 @@
1
+ from datetime import datetime
2
+ from typing import Literal
3
+ from fastapi import APIRouter
4
+
5
+ router = APIRouter(tags=["Status"], prefix="")
6
+
7
+
8
+ @router.get("/", operation_id="ping")
9
+ async def ping() -> str:
10
+ """
11
+ Returns 'OK' if the API is running.
12
+ """
13
+ return "OK"
14
+
15
+
16
+ @router.get("/time", operation_id="getTime")
17
+ async def time(type: Literal["iso", "unix"] = "iso") -> str:
18
+ """
19
+ Returns the current time in either ISO or Unix timestamp (seconds) format.
20
+ """
21
+ if type == "iso":
22
+ return datetime.now().isoformat()
23
+ elif type == "unix":
24
+ return str(int(datetime.now().timestamp()))
@@ -8,6 +8,9 @@ class Scope(StrEnum):
8
8
 
9
9
  # If you update anything here, also update the scopes in the auth-service repository
10
10
 
11
+ WRITE_ADMIN = "write:admin"
12
+ READ_ADMIN = "read:admin"
13
+
11
14
  # Scopes that can be purchased - these actually exist in the jwt token
12
15
  READ_PREDICTIONS = "read:predictions"
13
16
 
@@ -55,6 +58,8 @@ class Scope(StrEnum):
55
58
  return [
56
59
  cls.WRITE_TRADE_STRATEGIES,
57
60
  cls.WRITE_PAY_PRODUCTS,
61
+ cls.WRITE_ADMIN,
62
+ cls.READ_ADMIN,
58
63
  ]
59
64
 
60
65
  @classmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crypticorn
3
- Version: 2.7.5
3
+ Version: 2.8.0rc2
4
4
  Summary: Maximise Your Crypto Trading Profits with Machine Learning
5
5
  Author-email: Crypticorn <timon@crypticorn.com>
6
6
  License: MIT
@@ -16,6 +16,7 @@ Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  Requires-Dist: fastapi<1.0.0,>=0.115.0
18
18
  Requires-Dist: click<9.0.0,>=8.0.0
19
+ Requires-Dist: psutil<8.0.0,>=7.0.0
19
20
  Requires-Dist: urllib3<3.0.0,>=1.25.3
20
21
  Requires-Dist: python_dateutil<3.0.0,>=2.8.2
21
22
  Requires-Dist: aiohttp<4.0.0,>=3.8.4
@@ -1,4 +1,4 @@
1
- crypticorn/__init__.py,sha256=TL41V09dmtbd2ee07wmOuG9KJJpyvLMPJi5DEd9bDyU,129
1
+ crypticorn/__init__.py,sha256=Fi5xI_NnK1cVteLdFY5SJ-mzYkYz3af-WOUauZkfTa0,219
2
2
  crypticorn/client.py,sha256=jMdF4mZuqB8YMZZOwJkTcvpHluGqw6Q-taSF6vQS5WM,3912
3
3
  crypticorn/auth/__init__.py,sha256=JAl1tBLK9pYLr_-YKaj581c-c94PWLoqnatTIVAVvMM,81
4
4
  crypticorn/auth/main.py,sha256=j8eRGN2gUWyeOCnWnUPe3mCAfaidGnOMnZRiSQy-yzM,720
@@ -22,9 +22,9 @@ crypticorn/auth/client/models/authorize_user200_response.py,sha256=fstTaffARzCe7
22
22
  crypticorn/auth/client/models/authorize_user200_response_auth.py,sha256=OQzmKNICdr7tHrlhEDIxagDZx7XuW8VO2U3OMsWIJOA,3208
23
23
  crypticorn/auth/client/models/authorize_user_request.py,sha256=o_1Ms6npD_1dJQ6aYshEk9JPV7JIkmAmuRawDDPUoTc,3205
24
24
  crypticorn/auth/client/models/create_api_key200_response.py,sha256=UGjZwITv2EdS7bbt55D0w2an6_uO4u6gYr-GihL9m80,2459
25
- crypticorn/auth/client/models/create_api_key_request.py,sha256=V98HWwlRT2AYo96D7W5L4P-MFrOVi62zvHsFY3CVl8M,5479
25
+ crypticorn/auth/client/models/create_api_key_request.py,sha256=VaxptajDgoPPIfkkF1qh_wU3a7aTSc5wcjqvDcR5mnQ,5577
26
26
  crypticorn/auth/client/models/create_user_request.py,sha256=nq4t2R4ldqSsJUoVhLw9YocdUwatH4gQ-jdFz3jaqrg,3481
27
- crypticorn/auth/client/models/get_api_keys200_response_inner.py,sha256=G5YxZyrm06NrhfdsS-JA2P97doso2jFscElmGR7teXs,5867
27
+ crypticorn/auth/client/models/get_api_keys200_response_inner.py,sha256=DiNnVej8GasK09PorQCk4jS_LWzwCxtjHEVH42cOA4U,5965
28
28
  crypticorn/auth/client/models/list_wallets200_response.py,sha256=G2LQiThhtynkuDGFvIs-BowR38ut7cRbIeCzK4wMnPw,4732
29
29
  crypticorn/auth/client/models/list_wallets200_response_balances_inner.py,sha256=acTJa8B5U8j3_wS1TUYXnprptqF_T4dlV-wVaQ5ZXig,4023
30
30
  crypticorn/auth/client/models/list_wallets200_response_balances_inner_sale_round.py,sha256=KrCHFXXfc6U61nDFsq26gRtQQnc2_tSjL2fwQOnBfzI,3603
@@ -63,18 +63,22 @@ 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=42ajAXlz0LDBb1AFyX8xvwpp1MB_YrvqutFDkLthUQM,464
67
- crypticorn/common/auth.py,sha256=60SRXlW72VJO8rGzCiemWmzGu8tXDqWr0wt9EM6p8aI,8631
66
+ crypticorn/common/__init__.py,sha256=29n5tUr9-yjjJ8sjkbMrwq7pDKZ2Z1qz1bR1msGJDkA,671
67
+ crypticorn/common/ansi_colors.py,sha256=7nRsc5TPRwKI_7yVHD2WrhcOhHUcysktAIEPw_Zl5qk,1173
68
+ crypticorn/common/auth.py,sha256=GIb9MikQsSxqz-K5rDIOroP5mFoTgQqwByTGO5JqcdM,8713
68
69
  crypticorn/common/decorators.py,sha256=pmnGYCIrLv59wZkDbvPyK9NJmgPJWW74LXTdIWSjOkY,1063
69
70
  crypticorn/common/enums.py,sha256=RitDVqlG_HTe6tHT6bWusZNFCeYk1eQvJVH-7x3_Zlg,668
70
71
  crypticorn/common/errors.py,sha256=8jxZ2lLn_NoFKKq6n2JwKPsR0dA2vkGnbXDfEK6ndH0,27851
71
- crypticorn/common/exceptions.py,sha256=FOxScGTnAoiBkpC5lccQ6_b1jIPcWxawZvR_H2KBCNY,5953
72
+ crypticorn/common/exceptions.py,sha256=BuRLQIg2_pwsaQAhPKP3lY9q2GNp0SlRdXlvp1O2USo,6088
73
+ crypticorn/common/logging.py,sha256=aI1RfWMkNDTGov-qiUmvjRk0b9otBwZggWJ5cX5Zmdw,3651
74
+ crypticorn/common/middleware.py,sha256=mh1lNaN3X_UKH7PflewhlVEc1AKTqhB-tf7r1gLzV3E,1036
72
75
  crypticorn/common/mixins.py,sha256=LKPcNTR8uREeDGWTlWozNx7rS1mYdQVx1RllLhxIAsE,1640
73
76
  crypticorn/common/pagination.py,sha256=c07jrMNrBaNTmgx4sppdP7ND4RNT7NBqBXWvofazIlE,2251
74
- crypticorn/common/scopes.py,sha256=gbxrzME18ASQS18IHg96TvFZxh5-O8ffD2caGpfs0lc,2333
75
- crypticorn/common/status_router.py,sha256=s7LY3aNQPhtDUgNWHRszfCQMl0Uh13li_jR8jeeolnw,1139
77
+ crypticorn/common/scopes.py,sha256=ofJ5FDf30wab572XvDzAXVKBIUWa3shScAmzNrJsWqQ,2453
76
78
  crypticorn/common/urls.py,sha256=3Gf1NU1XQYcOTjcdztG3bDAE98FVbgTK2QXzUe7tFVQ,878
77
79
  crypticorn/common/utils.py,sha256=Kz2-I96MKIGKM18PHQ77VbKHLMGUvZG_jjj7xpQed8k,2138
80
+ crypticorn/common/router/admin_router.py,sha256=HUhFr5HVOM0JmVgAlLZbQMuSjNfcqULhBqJlNcMJTW4,3376
81
+ crypticorn/common/router/status_router.py,sha256=922dHfpVM5auOTuNswIhDRmk_1bmU5hH83bxoT70uss,616
78
82
  crypticorn/hive/__init__.py,sha256=hRfTlEzEql4msytdUC_04vfaHzVKG5CGZle1M-9QFgY,81
79
83
  crypticorn/hive/main.py,sha256=4oQ2RybZMbe0kRxVJrVAABsN5kUTCMExQFJDSnAzBUY,2428
80
84
  crypticorn/hive/utils.py,sha256=dxQ_OszrnTsslO5hDefMmgfj6yRwRPr8sr17fGizWIw,2066
@@ -224,8 +228,8 @@ crypticorn/trade/client/models/strategy_model_input.py,sha256=ala19jARyfA5ysys5D
224
228
  crypticorn/trade/client/models/strategy_model_output.py,sha256=2o2lhbgUSTznowpMLEHF1Ex9TG9oRmzlCIb-gXqo7_s,5643
225
229
  crypticorn/trade/client/models/tpsl.py,sha256=C2KgTIZs-a8W4msdaXgBKJcwtA-o5wR4rBauRP-iQxU,4317
226
230
  crypticorn/trade/client/models/trading_action_type.py,sha256=pGq_TFLMPfYFizYP-xKgEC1ZF4U3lGdJYoGa_ZH2x-Q,769
227
- crypticorn-2.7.5.dist-info/METADATA,sha256=5E_LCIIfd_O0t6Q8HBqaVLQVSvq_xgvVmMiCgPW_AZU,6607
228
- crypticorn-2.7.5.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
229
- crypticorn-2.7.5.dist-info/entry_points.txt,sha256=d_xHsGvUTebPveVUK0SrpDFQ5ZRSjlI7lNCc11sn2PM,59
230
- crypticorn-2.7.5.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
231
- crypticorn-2.7.5.dist-info/RECORD,,
231
+ crypticorn-2.8.0rc2.dist-info/METADATA,sha256=BiEarCgxuOZOuT9onAHw8ciilAQZ03IriGI21WKjYyY,6646
232
+ crypticorn-2.8.0rc2.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
233
+ crypticorn-2.8.0rc2.dist-info/entry_points.txt,sha256=d_xHsGvUTebPveVUK0SrpDFQ5ZRSjlI7lNCc11sn2PM,59
234
+ crypticorn-2.8.0rc2.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
235
+ crypticorn-2.8.0rc2.dist-info/RECORD,,
@@ -1,44 +0,0 @@
1
- from datetime import datetime
2
- from typing import Literal
3
- from fastapi import APIRouter
4
-
5
- router = APIRouter(tags=["Status"], prefix="")
6
-
7
-
8
- @router.get("/", operation_id="ping")
9
- async def ping() -> str:
10
- """
11
- Returns 'OK' if the API is running.
12
- """
13
- return "OK"
14
-
15
-
16
- @router.get("/time", operation_id="getTime")
17
- async def time(type: Literal["iso", "unix"] = "iso") -> str:
18
- """
19
- Returns the current time in the specified format.
20
- """
21
- if type == "iso":
22
- return datetime.now().isoformat()
23
- else:
24
- return str(int(datetime.now().timestamp()))
25
-
26
-
27
- @router.get("/config", operation_id="getConfig")
28
- async def config() -> dict:
29
- """
30
- Returns the version of the crypticorn library and the environment.
31
- """
32
- import importlib.metadata
33
- import os
34
- from dotenv import load_dotenv
35
-
36
- load_dotenv()
37
- try:
38
- crypticorn_version = importlib.metadata.version("crypticorn")
39
- except importlib.metadata.PackageNotFoundError:
40
- crypticorn_version = "not installed"
41
- return {
42
- "crypticorn": f"v{crypticorn_version}",
43
- "environment": os.getenv("API_ENV", "not set"),
44
- }