crypticorn 2.17.0rc4__py3-none-any.whl → 2.17.0rc6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. crypticorn/auth/client/api/admin_api.py +6 -26
  2. crypticorn/auth/client/api/auth_api.py +36 -148
  3. crypticorn/auth/client/api/service_api.py +3 -16
  4. crypticorn/auth/client/api/user_api.py +33 -136
  5. crypticorn/auth/client/api/wallet_api.py +18 -76
  6. crypticorn/auth/client/api_client.py +0 -5
  7. crypticorn/auth/client/models/add_wallet_request.py +1 -1
  8. crypticorn/auth/client/models/authorize_user_request.py +1 -1
  9. crypticorn/auth/client/models/create_api_key_request.py +1 -1
  10. crypticorn/auth/client/models/create_user_request.py +1 -1
  11. crypticorn/auth/client/models/get_api_keys200_response_inner.py +1 -1
  12. crypticorn/auth/client/models/list_wallets200_response_balances_inner_sale_round.py +1 -1
  13. crypticorn/auth/client/models/list_wallets200_response_balances_inner_wallet.py +1 -1
  14. crypticorn/auth/client/models/list_wallets200_response_balances_inner_wallet_vesting_wallets_inner.py +1 -1
  15. crypticorn/auth/client/models/list_wallets200_response_data_inner.py +1 -1
  16. crypticorn/auth/client/models/logout_default_response.py +1 -1
  17. crypticorn/auth/client/models/oauth_callback200_response_user.py +1 -1
  18. crypticorn/auth/client/models/refresh_token_info200_response_user_session.py +1 -1
  19. crypticorn/auth/client/models/rotate_tokens200_response.py +1 -1
  20. crypticorn/auth/client/models/token_info200_response.py +1 -1
  21. crypticorn/auth/client/models/update_user_request.py +1 -1
  22. crypticorn/auth/client/models/user_by_username200_response.py +1 -1
  23. crypticorn/auth/client/models/verify200_response.py +1 -1
  24. crypticorn/auth/client/models/verify_email200_response_auth.py +1 -1
  25. crypticorn/auth/client/models/verify_email200_response_auth_auth.py +1 -1
  26. crypticorn/auth/client/models/whoami200_response.py +1 -1
  27. crypticorn/cli/init.py +1 -1
  28. crypticorn/cli/templates/.env.docker.temp +3 -0
  29. crypticorn/cli/templates/.env.example.temp +4 -0
  30. crypticorn/cli/templates/Dockerfile +5 -2
  31. crypticorn/client.py +0 -1
  32. crypticorn/common/auth.py +31 -4
  33. crypticorn/common/decorators.py +1 -2
  34. crypticorn/common/enums.py +0 -2
  35. crypticorn/common/errors.py +10 -0
  36. crypticorn/common/metrics.py +1 -1
  37. crypticorn/common/middleware.py +0 -1
  38. crypticorn/common/pagination.py +116 -24
  39. crypticorn/common/router/admin_router.py +1 -11
  40. crypticorn/common/router/status_router.py +33 -2
  41. crypticorn/common/scopes.py +2 -2
  42. crypticorn/common/utils.py +1 -2
  43. crypticorn/dex/__init__.py +6 -0
  44. crypticorn/dex/client/__init__.py +49 -0
  45. crypticorn/dex/client/api/__init__.py +6 -0
  46. crypticorn/dex/client/api/admin_api.py +2983 -0
  47. crypticorn/dex/client/api/signals_api.py +1794 -0
  48. crypticorn/dex/client/api/status_api.py +889 -0
  49. crypticorn/dex/client/api_client.py +753 -0
  50. crypticorn/dex/client/api_response.py +20 -0
  51. crypticorn/dex/client/configuration.py +620 -0
  52. crypticorn/dex/client/exceptions.py +220 -0
  53. crypticorn/dex/client/models/__init__.py +30 -0
  54. crypticorn/dex/client/models/api_error_identifier.py +121 -0
  55. crypticorn/dex/client/models/api_error_level.py +37 -0
  56. crypticorn/dex/client/models/api_error_type.py +37 -0
  57. crypticorn/dex/client/models/exception_detail.py +117 -0
  58. crypticorn/dex/client/models/log_level.py +38 -0
  59. crypticorn/dex/client/models/paginated_response_signal_with_token.py +134 -0
  60. crypticorn/dex/client/models/risk.py +86 -0
  61. crypticorn/dex/client/models/signal_overview_stats.py +156 -0
  62. crypticorn/dex/client/models/signal_volume.py +84 -0
  63. crypticorn/dex/client/models/signal_with_token.py +163 -0
  64. crypticorn/dex/client/models/token_data.py +127 -0
  65. crypticorn/dex/client/models/token_detail.py +116 -0
  66. crypticorn/dex/client/py.typed +0 -0
  67. crypticorn/dex/client/rest.py +217 -0
  68. crypticorn/dex/main.py +1 -0
  69. crypticorn/hive/client/api/admin_api.py +18 -75
  70. crypticorn/hive/client/api/data_api.py +6 -28
  71. crypticorn/hive/client/api/models_api.py +22 -88
  72. crypticorn/hive/client/api/status_api.py +6 -27
  73. crypticorn/hive/client/api_client.py +0 -5
  74. crypticorn/hive/client/models/coin_info.py +1 -1
  75. crypticorn/hive/client/models/exception_detail.py +1 -1
  76. crypticorn/hive/client/models/target_info.py +1 -1
  77. crypticorn/hive/utils.py +2 -2
  78. crypticorn/klines/client/api/admin_api.py +18 -75
  79. crypticorn/klines/client/api/change_in_timeframe_api.py +3 -16
  80. crypticorn/klines/client/api/funding_rates_api.py +3 -16
  81. crypticorn/klines/client/api/ohlcv_data_api.py +3 -16
  82. crypticorn/klines/client/api/status_api.py +6 -27
  83. crypticorn/klines/client/api/symbols_api.py +3 -16
  84. crypticorn/klines/client/api/udf_api.py +18 -74
  85. crypticorn/klines/client/api_client.py +0 -5
  86. crypticorn/klines/client/models/exception_detail.py +1 -1
  87. crypticorn/klines/client/models/ohlcv.py +1 -1
  88. crypticorn/klines/client/models/symbol_group.py +1 -1
  89. crypticorn/klines/client/models/udf_config.py +1 -1
  90. crypticorn/metrics/client/api/admin_api.py +18 -75
  91. crypticorn/metrics/client/api/exchanges_api.py +12 -52
  92. crypticorn/metrics/client/api/indicators_api.py +6 -28
  93. crypticorn/metrics/client/api/logs_api.py +3 -16
  94. crypticorn/metrics/client/api/marketcap_api.py +12 -52
  95. crypticorn/metrics/client/api/markets_api.py +3 -16
  96. crypticorn/metrics/client/api/quote_currencies_api.py +3 -16
  97. crypticorn/metrics/client/api/status_api.py +6 -27
  98. crypticorn/metrics/client/api/tokens_api.py +6 -26
  99. crypticorn/metrics/client/api_client.py +0 -5
  100. crypticorn/metrics/client/models/exception_detail.py +1 -1
  101. crypticorn/metrics/client/models/exchange_mapping.py +1 -1
  102. crypticorn/metrics/client/models/marketcap_ranking.py +1 -1
  103. crypticorn/metrics/client/models/marketcap_symbol_ranking.py +1 -1
  104. crypticorn/metrics/client/models/ohlcv.py +1 -1
  105. crypticorn/pay/client/api/admin_api.py +21 -87
  106. crypticorn/pay/client/api/now_payments_api.py +15 -64
  107. crypticorn/pay/client/api/payments_api.py +6 -28
  108. crypticorn/pay/client/api/products_api.py +12 -52
  109. crypticorn/pay/client/api/status_api.py +6 -27
  110. crypticorn/pay/client/api_client.py +0 -5
  111. crypticorn/pay/client/models/exception_detail.py +1 -1
  112. crypticorn/pay/client/models/now_create_invoice_req.py +1 -1
  113. crypticorn/pay/client/models/now_create_invoice_res.py +1 -1
  114. crypticorn/pay/client/models/product.py +1 -1
  115. crypticorn/pay/client/models/product_create.py +1 -1
  116. crypticorn/pay/client/models/product_update.py +1 -1
  117. crypticorn/trade/client/__init__.py +15 -0
  118. crypticorn/trade/client/api/admin_api.py +43 -107
  119. crypticorn/trade/client/api/api_keys_api.py +207 -184
  120. crypticorn/trade/client/api/bots_api.py +6557 -240
  121. crypticorn/trade/client/api/exchanges_api.py +9 -36
  122. crypticorn/trade/client/api/notifications_api.py +18 -72
  123. crypticorn/trade/client/api/orders_api.py +220 -115
  124. crypticorn/trade/client/api/status_api.py +6 -24
  125. crypticorn/trade/client/api/strategies_api.py +15 -60
  126. crypticorn/trade/client/api/trading_actions_api.py +431 -112
  127. crypticorn/trade/client/models/__init__.py +15 -0
  128. crypticorn/trade/client/models/bot.py +7 -18
  129. crypticorn/trade/client/models/bot_create.py +17 -1
  130. crypticorn/trade/client/models/bot_update.py +17 -1
  131. crypticorn/trade/client/models/exchange_key_create.py +17 -1
  132. crypticorn/trade/client/models/exchange_key_update.py +17 -1
  133. crypticorn/trade/client/models/notification.py +17 -1
  134. crypticorn/trade/client/models/notification_create.py +17 -1
  135. crypticorn/trade/client/models/notification_update.py +17 -1
  136. crypticorn/trade/client/models/orders_count.py +88 -0
  137. crypticorn/trade/client/models/paginated_response_futures_trading_action.py +134 -0
  138. crypticorn/trade/client/models/paginated_response_order.py +134 -0
  139. crypticorn/trade/client/models/paginated_response_union_futures_trading_action_spot_trading_action.py +141 -0
  140. crypticorn/trade/client/models/paginated_response_union_futures_trading_action_spot_trading_action_data_inner.py +165 -0
  141. crypticorn/trade/client/models/pn_l.py +95 -0
  142. crypticorn/trade/client/models/spot_trading_action.py +207 -0
  143. crypticorn/trade/client/models/strategy.py +17 -1
  144. crypticorn/trade/client/models/strategy_create.py +17 -1
  145. crypticorn/trade/client/models/strategy_update.py +17 -1
  146. {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/METADATA +3 -3
  147. {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/RECORD +151 -116
  148. {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/WHEEL +0 -0
  149. {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/entry_points.txt +0 -0
  150. {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/licenses/LICENSE +0 -0
  151. {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,7 @@ import json
20
20
  from datetime import datetime
21
21
  from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator
22
22
  from typing import Any, ClassVar, Dict, List, Optional
23
- from typing import Optional, Set
23
+ from typing import Set
24
24
  from typing_extensions import Self
25
25
 
26
26
 
@@ -20,7 +20,7 @@ import json
20
20
  from datetime import datetime
21
21
  from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr
22
22
  from typing import Any, ClassVar, Dict, List, Optional, Union
23
- from typing import Optional, Set
23
+ from typing import Set
24
24
  from typing_extensions import Self
25
25
 
26
26
 
@@ -22,7 +22,7 @@ from typing import Any, ClassVar, Dict, List, Optional, Union
22
22
  from crypticorn.auth.client.models.list_wallets200_response_balances_inner_wallet_vesting_wallets_inner import (
23
23
  ListWallets200ResponseBalancesInnerWalletVestingWalletsInner,
24
24
  )
25
- from typing import Optional, Set
25
+ from typing import Set
26
26
  from typing_extensions import Self
27
27
 
28
28
 
@@ -27,7 +27,7 @@ from pydantic import (
27
27
  StrictStr,
28
28
  )
29
29
  from typing import Any, ClassVar, Dict, List, Optional, Union
30
- from typing import Optional, Set
30
+ from typing import Set
31
31
  from typing_extensions import Self
32
32
 
33
33
 
@@ -20,7 +20,7 @@ import json
20
20
  from datetime import datetime
21
21
  from pydantic import BaseModel, ConfigDict, StrictStr
22
22
  from typing import Any, ClassVar, Dict, List, Optional
23
- from typing import Optional, Set
23
+ from typing import Set
24
24
  from typing_extensions import Self
25
25
 
26
26
 
@@ -22,7 +22,7 @@ from typing import Any, ClassVar, Dict, List, Optional
22
22
  from crypticorn.auth.client.models.logout_default_response_issues_inner import (
23
23
  LogoutDefaultResponseIssuesInner,
24
24
  )
25
- from typing import Optional, Set
25
+ from typing import Set
26
26
  from typing_extensions import Self
27
27
 
28
28
 
@@ -19,7 +19,7 @@ import json
19
19
 
20
20
  from pydantic import BaseModel, ConfigDict, StrictStr
21
21
  from typing import Any, ClassVar, Dict, List, Optional
22
- from typing import Optional, Set
22
+ from typing import Set
23
23
  from typing_extensions import Self
24
24
 
25
25
 
@@ -20,7 +20,7 @@ import json
20
20
  from datetime import datetime
21
21
  from pydantic import BaseModel, ConfigDict, Field, StrictStr
22
22
  from typing import Any, ClassVar, Dict, List, Optional
23
- from typing import Optional, Set
23
+ from typing import Set
24
24
  from typing_extensions import Self
25
25
 
26
26
 
@@ -22,7 +22,7 @@ from typing import Any, ClassVar, Dict, List, Optional, Union
22
22
  from crypticorn.auth.client.models.verify_email200_response_auth_auth import (
23
23
  VerifyEmail200ResponseAuthAuth,
24
24
  )
25
- from typing import Optional, Set
25
+ from typing import Set
26
26
  from typing_extensions import Self
27
27
 
28
28
 
@@ -22,7 +22,7 @@ from typing import Any, ClassVar, Dict, List, Optional
22
22
  from crypticorn.auth.client.models.verify_email200_response_auth_auth import (
23
23
  VerifyEmail200ResponseAuthAuth,
24
24
  )
25
- from typing import Optional, Set
25
+ from typing import Set
26
26
  from typing_extensions import Self
27
27
 
28
28
 
@@ -19,7 +19,7 @@ import json
19
19
 
20
20
  from pydantic import BaseModel, ConfigDict, StrictStr
21
21
  from typing import Any, ClassVar, Dict, List, Optional
22
- from typing import Optional, Set
22
+ from typing import Set
23
23
  from typing_extensions import Self
24
24
 
25
25
 
@@ -19,7 +19,7 @@ import json
19
19
 
20
20
  from pydantic import BaseModel, ConfigDict, Field, StrictStr
21
21
  from typing import Any, ClassVar, Dict, List, Optional
22
- from typing import Optional, Set
22
+ from typing import Set
23
23
  from typing_extensions import Self
24
24
 
25
25
 
@@ -27,7 +27,7 @@ from pydantic import (
27
27
  StrictStr,
28
28
  )
29
29
  from typing import Any, ClassVar, Dict, List, Optional, Union
30
- from typing import Optional, Set
30
+ from typing import Set
31
31
  from typing_extensions import Self
32
32
 
33
33
 
@@ -22,7 +22,7 @@ from typing import Any, ClassVar, Dict, List, Optional
22
22
  from crypticorn.auth.client.models.verify_email200_response_auth_auth import (
23
23
  VerifyEmail200ResponseAuthAuth,
24
24
  )
25
- from typing import Optional, Set
25
+ from typing import Set
26
26
  from typing_extensions import Self
27
27
 
28
28
 
@@ -27,7 +27,7 @@ from pydantic import (
27
27
  StrictStr,
28
28
  )
29
29
  from typing import Any, ClassVar, Dict, List, Optional, Union
30
- from typing import Optional, Set
30
+ from typing import Set
31
31
  from typing_extensions import Self
32
32
 
33
33
 
@@ -19,7 +19,7 @@ import json
19
19
 
20
20
  from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr
21
21
  from typing import Any, ClassVar, Dict, List, Optional
22
- from typing import Optional, Set
22
+ from typing import Set
23
23
  from typing_extensions import Self
24
24
 
25
25
 
crypticorn/cli/init.py CHANGED
@@ -33,7 +33,7 @@ def copy_template(template_name: str, target_path: Path):
33
33
 
34
34
  def check_file_exists(path: Path, force: bool):
35
35
  if path.exists() and not force:
36
- click.secho(f"File already exists, use --force / -f to overwrite", fg="red")
36
+ click.secho("File already exists, use --force / -f to overwrite", fg="red")
37
37
  return False
38
38
  return True
39
39
 
@@ -0,0 +1,3 @@
1
+ # Specifies which API environment to use in docker builds: "0" if the API_ENV should be used, "1" for the internal host.
2
+ # Any database connections would have to be overridden as well.
3
+ IS_DOCKER=1
@@ -0,0 +1,4 @@
1
+ # Specifies which API environment to use: "local", "dev", "prod", or "docker".
2
+ # When running inside Docker, this defaults to "docker" (internal network host),
3
+ # unless IS_DOCKER is set to 0 in .env.docker.
4
+ API_ENV=local
@@ -19,14 +19,17 @@ WORKDIR /code
19
19
  # and to the workflow:
20
20
  # env:
21
21
  # API_ENV: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
22
- ARG API_ENV
22
+ # and in the docker build:
23
+ # build-args: |
24
+ # CACHEBUST=${{ github.run_id }} # invalidates dependency cache on every workflow run
23
25
 
24
- RUN pip install --no-cache-dir -r requirements.txt
26
+ ARG API_ENV
25
27
 
26
28
  RUN if [ "$API_ENV" != "prod" ]; then \
27
29
  pip install --pre crypticorn; \
28
30
  fi
29
31
 
32
+ RUN pip install --no-cache-dir -r requirements.txt
30
33
 
31
34
  COPY ./src /code/src
32
35
 
crypticorn/client.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from typing import TypeVar, Optional
2
- import asyncio
3
2
  import warnings
4
3
  from aiohttp import ClientSession, ClientTimeout, TCPConnector
5
4
  from crypticorn.hive import HiveClient
crypticorn/common/auth.py CHANGED
@@ -7,7 +7,6 @@ from crypticorn.common.exceptions import (
7
7
  ApiError,
8
8
  HTTPException,
9
9
  ExceptionContent,
10
- WebSocketException,
11
10
  )
12
11
  from crypticorn.common.urls import BaseUrl, Service, ApiVersion
13
12
  from fastapi import Depends, Query
@@ -16,9 +15,11 @@ from fastapi.security import (
16
15
  SecurityScopes,
17
16
  HTTPBearer,
18
17
  APIKeyHeader,
18
+ HTTPBasic,
19
19
  )
20
20
  from typing_extensions import Annotated
21
21
  from typing import Union
22
+ from fastapi.security import HTTPBasicCredentials
22
23
 
23
24
  # Auth Schemes
24
25
  http_bearer = HTTPBearer(
@@ -33,6 +34,12 @@ apikey_header = APIKeyHeader(
33
34
  description="The API key to use for authentication.",
34
35
  )
35
36
 
37
+ basic_auth = HTTPBasic(
38
+ scheme_name="Basic",
39
+ auto_error=False,
40
+ description="The username and password to use for authentication. Only used in /admin/metrics",
41
+ )
42
+
36
43
 
37
44
  # Auth Handler
38
45
  class AuthHandler:
@@ -151,6 +158,7 @@ class AuthHandler:
151
158
  error=ApiError.NO_API_KEY,
152
159
  message="No credentials provided. API key is required",
153
160
  ),
161
+ headers={"WWW-Authenticate": "X-API-Key"},
154
162
  )
155
163
  raise e
156
164
 
@@ -176,6 +184,7 @@ class AuthHandler:
176
184
  error=ApiError.NO_BEARER,
177
185
  message="No credentials provided. Bearer token is required",
178
186
  ),
187
+ headers={"WWW-Authenticate": "Bearer"},
179
188
  )
180
189
  raise e
181
190
 
@@ -223,6 +232,7 @@ class AuthHandler:
223
232
  error=ApiError.NO_CREDENTIALS,
224
233
  message="No credentials provided. Either API key or bearer token is required.",
225
234
  ),
235
+ headers={"WWW-Authenticate": "Bearer, X-API-Key"},
226
236
  )
227
237
 
228
238
  async def ws_api_key_auth(
@@ -266,6 +276,23 @@ class AuthHandler:
266
276
  if bearer
267
277
  else None
268
278
  )
269
- return await self.combined_auth(
270
- bearer=credentials, api_key=api_key, sec=sec
271
- )
279
+ return await self.combined_auth(bearer=credentials, api_key=api_key, sec=sec)
280
+
281
+ async def basic_auth(
282
+ self,
283
+ credentials: Annotated[HTTPBasicCredentials, Depends(basic_auth)],
284
+ ):
285
+ """
286
+ Verifies the basic authentication credentials. This authentication method should just be used for special cases like /admin/metrics, where JWT and API key authentication are not desired or not possible.
287
+ """
288
+ try:
289
+ await self.client.login.verify_basic_auth(credentials.username, credentials.password)
290
+ except ApiException as e:
291
+ raise HTTPException(
292
+ content=ExceptionContent(
293
+ error=ApiError.INVALID_BASIC_AUTH,
294
+ message="Invalid basic authentication credentials",
295
+ ),
296
+ headers={"WWW-Authenticate": "Basic"},
297
+ )
298
+ return credentials.username
@@ -1,8 +1,7 @@
1
- from datetime import datetime
2
1
  from typing import Optional, Type, Any, Tuple
3
2
  from copy import deepcopy
4
3
 
5
- from pydantic import BaseModel, create_model, field_validator, BeforeValidator
4
+ from pydantic import BaseModel, create_model
6
5
  from pydantic.fields import FieldInfo
7
6
 
8
7
 
@@ -5,9 +5,7 @@ try:
5
5
  except ImportError:
6
6
  from strenum import StrEnum
7
7
 
8
- from enum import Enum
9
8
  import warnings
10
- import typing_extensions
11
9
  from crypticorn.common.mixins import ValidateEnumMixin
12
10
  from crypticorn.common.warnings import CrypticornDeprecatedSince215
13
11
 
@@ -70,6 +70,7 @@ class ApiErrorIdentifier(StrEnum):
70
70
  INSUFFICIENT_MARGIN = "insufficient_margin"
71
71
  INSUFFICIENT_SCOPES = "insufficient_scopes"
72
72
  INVALID_API_KEY = "invalid_api_key"
73
+ INVALID_BASIC_AUTH = "invalid_basic_auth"
73
74
  INVALID_BEARER = "invalid_bearer"
74
75
  INVALID_DATA_REQUEST = "invalid_data"
75
76
  INVALID_DATA_RESPONSE = "invalid_data_response"
@@ -302,6 +303,11 @@ class ApiError(Enum, metaclass=ApiErrorFallback):
302
303
  ApiErrorType.USER_ERROR,
303
304
  ApiErrorLevel.ERROR,
304
305
  )
306
+ INVALID_BASIC_AUTH = (
307
+ ApiErrorIdentifier.INVALID_BASIC_AUTH,
308
+ ApiErrorType.USER_ERROR,
309
+ ApiErrorLevel.ERROR,
310
+ )
305
311
  INVALID_BEARER = (
306
312
  ApiErrorIdentifier.INVALID_BEARER,
307
313
  ApiErrorType.USER_ERROR,
@@ -569,6 +575,10 @@ class StatusCodeMapper:
569
575
  status.HTTP_401_UNAUTHORIZED,
570
576
  status.WS_1008_POLICY_VIOLATION,
571
577
  ),
578
+ ApiError.INVALID_BASIC_AUTH: (
579
+ status.HTTP_401_UNAUTHORIZED,
580
+ status.WS_1008_POLICY_VIOLATION,
581
+ ),
572
582
  ApiError.INVALID_BEARER: (
573
583
  status.HTTP_401_UNAUTHORIZED,
574
584
  status.WS_1008_POLICY_VIOLATION,
@@ -1,5 +1,5 @@
1
1
  # metrics/registry.py
2
- from prometheus_client import Counter, Gauge, Histogram, Summary, CollectorRegistry
2
+ from prometheus_client import Counter, Histogram, CollectorRegistry
3
3
 
4
4
  registry = CollectorRegistry()
5
5
 
@@ -2,7 +2,6 @@ import time
2
2
  from fastapi import FastAPI
3
3
  from fastapi.middleware.cors import CORSMiddleware
4
4
  from starlette.middleware.base import BaseHTTPMiddleware
5
- from starlette.requests import Request
6
5
  from crypticorn.common.logging import configure_logging
7
6
  from contextlib import asynccontextmanager
8
7
  from typing_extensions import deprecated
@@ -9,7 +9,20 @@ T = TypeVar("T")
9
9
 
10
10
  class PaginatedResponse(BaseModel, Generic[T]):
11
11
  """Pydantic model for paginated response
12
- >>> PaginatedResponse[ItemModel](data=items, total=total_items, page=1, size=10, prev=None, next=2)
12
+ >>> @router.get("", operation_id="getOrders")
13
+ >>> async def get_orders(
14
+ >>> params: Annotated[PaginationParams, Query()],
15
+ >>> ) -> PaginatedResponse[Order]:
16
+ >>> ...
17
+ >>> return PaginatedResponse[Order](
18
+ data=orders,
19
+ total=count,
20
+ page=params.page,
21
+ page_size=params.page_size,
22
+ prev=PaginatedResponse.get_prev_page(params.page),
23
+ next=PaginatedResponse.get_next_page(count, params.page_size, params.page),
24
+ last=PaginatedResponse.get_last_page(count, params.page_size),
25
+ )
13
26
  """
14
27
 
15
28
  data: list[T]
@@ -26,14 +39,14 @@ class PaginatedResponse(BaseModel, Generic[T]):
26
39
  if page < math.ceil(total / page_size):
27
40
  return page + 1
28
41
  return None
29
-
42
+
30
43
  @staticmethod
31
44
  def get_prev_page(page: int) -> Optional[int]:
32
45
  """Get the previous page number"""
33
46
  if page > 1:
34
47
  return page - 1
35
48
  return None
36
-
49
+
37
50
  @staticmethod
38
51
  def get_last_page(total: int, page_size: int) -> int:
39
52
  """Get the last page number"""
@@ -42,16 +55,16 @@ class PaginatedResponse(BaseModel, Generic[T]):
42
55
 
43
56
  class PaginationParams(BaseModel, Generic[T]):
44
57
  """Standard pagination parameters for usage in API endpoints. Check the [fastapi docs](https://fastapi.tiangolo.com/tutorial/query-param-models/?h=qu#query-parameters-with-a-pydantic-model) for usage examples.
45
- Can only be used isolated, not in combination with other parameters. If you need to combine with other parameters, use `FilterComboParams` or refer to this [workaround](https://github.com/fastapi/fastapi/discussions/13448#discussioncomment-12440374).
58
+ You should inherit from this class when adding additional parameters. You should use this class in combination with `PaginatedResponse` to return the paginated response.
46
59
  Usage:
47
60
  >>> @router.get("", operation_id="getOrders")
48
61
  >>> async def get_orders(
49
- >>> paginate: Annotated[PaginationParams[Order], Query()],
62
+ >>> params: Annotated[PaginationParams[Order], Query()],
50
63
  >>> ) -> PaginatedResponse[Order]:
51
64
  >>> ...
52
65
 
53
66
  The default size is 10 items per page and there is a `HeavyPaginationParams` class with 100 items per page. You can override this default:
54
- >>> class LightPaginationParams(PaginationParams[T]):
67
+ >>> class LightPaginationParams(PaginationParams):
55
68
  >>> page_size: int = Field(default=5, description="The number of items per page")
56
69
  """
57
70
 
@@ -71,11 +84,11 @@ class HeavyPaginationParams(PaginationParams[T]):
71
84
 
72
85
  class SortParams(BaseModel, Generic[T]):
73
86
  """Standard sort parameters for usage in API endpoints. Check the [fastapi docs](https://fastapi.tiangolo.com/tutorial/query-param-models/?h=qu#query-parameters-with-a-pydantic-model) for usage examples.
74
- Can only be used isolated, not in combination with other parameters. If you need to combine with other parameters, use `FilterComboParams` or refer to this [workaround](https://github.com/fastapi/fastapi/discussions/13448#discussioncomment-12440374).
87
+ You should inherit from this class when adding additional parameters.
75
88
  Usage:
76
89
  >>> @router.get("", operation_id="getOrders")
77
90
  >>> async def get_orders(
78
- >>> sort: Annotated[SortParams[Order], Query()],
91
+ >>> params: Annotated[SortParams[Order], Query()],
79
92
  >>> ) -> PaginatedResponse[Order]:
80
93
  >>> ...
81
94
  """
@@ -91,7 +104,7 @@ class SortParams(BaseModel, Generic[T]):
91
104
  args: tuple = self.__pydantic_generic_metadata__.get("args")
92
105
  if not args or not issubclass(args[0], BaseModel):
93
106
  raise TypeError(
94
- "PaginationParams must be used with a Pydantic BaseModel as a generic parameter"
107
+ "SortParams must be used with a Pydantic BaseModel as a generic parameter"
95
108
  )
96
109
  if self.sort_by:
97
110
  # check if the sort field is valid
@@ -104,24 +117,31 @@ class SortParams(BaseModel, Generic[T]):
104
117
  raise ValueError(
105
118
  f"Invalid order: '{self.sort_order}' — must be one of: ['asc', 'desc']"
106
119
  )
107
- if self.sort_order and self.sort_by is None or self.sort_by and self.sort_order is None:
120
+ if (
121
+ self.sort_order
122
+ and self.sort_by is None
123
+ or self.sort_by
124
+ and self.sort_order is None
125
+ ):
108
126
  raise ValueError("sort_order and sort_by must be provided together")
109
127
  return self
110
128
 
111
129
 
112
130
  class FilterParams(BaseModel, Generic[T]):
113
131
  """Standard filter parameters for usage in API endpoints. Check the [fastapi docs](https://fastapi.tiangolo.com/tutorial/query-param-models/?h=qu#query-parameters-with-a-pydantic-model) for usage examples.
114
- Can only be used isolated, not in combination with other parameters. If you need to combine with other parameters, use `FilterComboParams` or refer to this [workaround](https://github.com/fastapi/fastapi/discussions/13448#discussioncomment-12440374).
132
+ You should inherit from this class when adding additional parameters.
115
133
  Usage:
116
134
  >>> @router.get("", operation_id="getOrders")
117
135
  >>> async def get_orders(
118
- >>> filter: Annotated[FilterParams[Order], Query()],
136
+ >>> params: Annotated[FilterParams[Order], Query()],
119
137
  >>> ) -> PaginatedResponse[Order]:
120
138
  >>> ...
121
139
  """
122
140
 
123
141
  filter_by: Optional[str] = Field(None, description="The field to filter by")
124
- filter_value: Optional[Any] = Field(None, description="The value to filter with")
142
+ filter_value: Optional[str] = Field(None, description="The value to filter with")
143
+ # currently openapi-gen does not support typing.Any in combo with None, so we use str
144
+ # this is fine since the input is a string anyways from the request and the correct type is enforced by the model validator from the filter_by field
125
145
 
126
146
  @model_validator(mode="after")
127
147
  def validate_filter(self):
@@ -140,34 +160,104 @@ class FilterParams(BaseModel, Generic[T]):
140
160
  raise ValueError(
141
161
  f"Invalid field: '{self.filter_by}'. Must be one of: {list(model.model_fields)}"
142
162
  )
143
- self.filter_value = _enforce_field_type(model, self.filter_by, self.filter_value)
163
+ self.filter_value = _enforce_field_type(
164
+ model, self.filter_by, self.filter_value
165
+ )
166
+ return self
167
+
168
+
169
+ class SortFilterParams(SortParams[T], FilterParams[T]):
170
+ """Combines sort and filter parameters. Just a convenience class for when you need to combine sort and filter parameters.
171
+ You should inherit from this class when adding additional parameters.
172
+ Usage:
173
+ >>> @router.get("", operation_id="getOrders")
174
+ >>> async def get_orders(
175
+ >>> params: Annotated[SortFilterParams[Order], Query()],
176
+ >>> ) -> PaginatedResponse[Order]:
177
+ >>> ...
178
+ """
179
+
180
+ @model_validator(mode="after")
181
+ def validate_sort_filter(self):
182
+ self.validate_sort()
183
+ self.validate_filter()
184
+ return self
185
+
186
+
187
+ class PageFilterParams(PaginationParams[T], FilterParams[T]):
188
+ """Combines pagination and filter parameters. Just a convenience class for when you need to combine pagination and filter parameters.
189
+ You should inherit from this class when adding additional parameters.
190
+ Usage:
191
+ >>> @router.get("", operation_id="getOrders")
192
+ >>> async def get_orders(
193
+ >>> params: Annotated[PageFilterParams[Order], Query()],
194
+ >>> ) -> PaginatedResponse[Order]:
195
+ >>> ...
196
+ """
197
+
198
+ @model_validator(mode="after")
199
+ def validate_page_filter(self):
200
+ self.validate_filter()
201
+ return self
202
+
203
+
204
+ class PageSortParams(PaginationParams[T], SortParams[T]):
205
+ """Combines pagination and sort parameters. Just a convenience class for when you need to combine pagination and sort parameters.
206
+ You should inherit from this class when adding additional parameters.
207
+ Usage:
208
+ >>> @router.get("", operation_id="getOrders")
209
+ >>> async def get_orders(
210
+ >>> params: Annotated[PageSortParams[Order], Query()],
211
+ >>> ) -> PaginatedResponse[Order]:
212
+ >>> ...
213
+ """
214
+
215
+ @model_validator(mode="after")
216
+ def validate_page_sort(self):
217
+ self.validate_sort()
144
218
  return self
145
219
 
146
220
 
147
- class FilterComboParams(PaginationParams[T], SortParams[T], FilterParams[T], ):
148
- """Combines pagination, filter, and sort parameters.
221
+ class PageSortFilterParams(
222
+ PaginationParams[T],
223
+ SortParams[T],
224
+ FilterParams[T],
225
+ ):
226
+ """Combines pagination, filter, and sort parameters. Just a convenience class for when you need to combine pagination, filter, and sort parameters.
227
+ You should inherit from this class when adding additional parameters.
149
228
  Usage:
150
229
  >>> @router.get("", operation_id="getOrders")
151
230
  >>> async def get_orders(
152
- >>> filter_combo: Annotated[FilterComboParams[Order], Query()],
231
+ >>> params: Annotated[PageSortFilterParams[Order], Query()],
153
232
  >>> ) -> PaginatedResponse[Order]:
154
233
  >>> ...
155
234
  """
156
235
 
157
- pass
236
+ @model_validator(mode="after")
237
+ def validate_filter_combo(self):
238
+ self.validate_filter()
239
+ self.validate_sort()
240
+ return self
158
241
 
159
242
 
160
- class HeavyFilterComboParams(HeavyPaginationParams[T], FilterParams[T], SortParams[T]):
161
- """Combines pagination, filter, and sort parameters.
243
+ class HeavyPageSortFilterParams(
244
+ HeavyPaginationParams[T], FilterParams[T], SortParams[T]
245
+ ):
246
+ """Combines heavy pagination, filter, and sort parameters. Just a convenience class for when you need to combine heavy pagination, filter, and sort parameters.
247
+ You should inherit from this class when adding additional parameters.
162
248
  Usage:
163
249
  >>> @router.get("", operation_id="getOrders")
164
250
  >>> async def get_orders(
165
- >>> filter_combo: Annotated[HeavyFilterComboParams[Order], Query()],
251
+ >>> params: Annotated[HeavyPageSortFilterParams[Order], Query()],
166
252
  >>> ) -> PaginatedResponse[Order]:
167
253
  >>> ...
168
254
  """
169
255
 
170
- pass
256
+ @model_validator(mode="after")
257
+ def validate_heavy_page_sort_filter(self):
258
+ self.validate_filter()
259
+ self.validate_sort()
260
+ return self
171
261
 
172
262
 
173
263
  def _enforce_field_type(model: Type[BaseModel], field_name: str, value: Any) -> Any:
@@ -189,5 +279,7 @@ def _enforce_field_type(model: Type[BaseModel], field_name: str, value: Any) ->
189
279
 
190
280
  try:
191
281
  return expected_type(value)
192
- except Exception as e:
193
- raise ValueError(f"Expected {expected_type} for field {field_name}, got {type(value)}")
282
+ except Exception:
283
+ raise ValueError(
284
+ f"Expected {expected_type} for field {field_name}, got {type(value)}"
285
+ )
@@ -13,10 +13,8 @@ import psutil
13
13
  import re
14
14
  import logging
15
15
  from typing import Literal
16
- from fastapi import APIRouter, Query, Response
17
- from prometheus_client import generate_latest, CONTENT_TYPE_LATEST
16
+ from fastapi import APIRouter, Query
18
17
  from crypticorn.common.logging import LogLevel
19
- from crypticorn.common.metrics import registry
20
18
 
21
19
  router = APIRouter(tags=["Admin"], prefix="/admin")
22
20
 
@@ -106,11 +104,3 @@ def list_installed_packages(
106
104
  or any(re.match(pattern, dist.metadata["Name"]) for pattern in include)
107
105
  }
108
106
  return dict(sorted(packages.items()))
109
-
110
-
111
- @router.get("/metrics", operation_id="getMetrics")
112
- def metrics():
113
- """
114
- Get Prometheus metrics for the application. Returns plain text.
115
- """
116
- return Response(generate_latest(registry), media_type=CONTENT_TYPE_LATEST)