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.
- crypticorn/auth/client/api/admin_api.py +6 -26
- crypticorn/auth/client/api/auth_api.py +36 -148
- crypticorn/auth/client/api/service_api.py +3 -16
- crypticorn/auth/client/api/user_api.py +33 -136
- crypticorn/auth/client/api/wallet_api.py +18 -76
- crypticorn/auth/client/api_client.py +0 -5
- crypticorn/auth/client/models/add_wallet_request.py +1 -1
- crypticorn/auth/client/models/authorize_user_request.py +1 -1
- crypticorn/auth/client/models/create_api_key_request.py +1 -1
- crypticorn/auth/client/models/create_user_request.py +1 -1
- crypticorn/auth/client/models/get_api_keys200_response_inner.py +1 -1
- crypticorn/auth/client/models/list_wallets200_response_balances_inner_sale_round.py +1 -1
- crypticorn/auth/client/models/list_wallets200_response_balances_inner_wallet.py +1 -1
- crypticorn/auth/client/models/list_wallets200_response_balances_inner_wallet_vesting_wallets_inner.py +1 -1
- crypticorn/auth/client/models/list_wallets200_response_data_inner.py +1 -1
- crypticorn/auth/client/models/logout_default_response.py +1 -1
- crypticorn/auth/client/models/oauth_callback200_response_user.py +1 -1
- crypticorn/auth/client/models/refresh_token_info200_response_user_session.py +1 -1
- crypticorn/auth/client/models/rotate_tokens200_response.py +1 -1
- crypticorn/auth/client/models/token_info200_response.py +1 -1
- crypticorn/auth/client/models/update_user_request.py +1 -1
- crypticorn/auth/client/models/user_by_username200_response.py +1 -1
- crypticorn/auth/client/models/verify200_response.py +1 -1
- crypticorn/auth/client/models/verify_email200_response_auth.py +1 -1
- crypticorn/auth/client/models/verify_email200_response_auth_auth.py +1 -1
- crypticorn/auth/client/models/whoami200_response.py +1 -1
- crypticorn/cli/init.py +1 -1
- crypticorn/cli/templates/.env.docker.temp +3 -0
- crypticorn/cli/templates/.env.example.temp +4 -0
- crypticorn/cli/templates/Dockerfile +5 -2
- crypticorn/client.py +0 -1
- crypticorn/common/auth.py +31 -4
- crypticorn/common/decorators.py +1 -2
- crypticorn/common/enums.py +0 -2
- crypticorn/common/errors.py +10 -0
- crypticorn/common/metrics.py +1 -1
- crypticorn/common/middleware.py +0 -1
- crypticorn/common/pagination.py +116 -24
- crypticorn/common/router/admin_router.py +1 -11
- crypticorn/common/router/status_router.py +33 -2
- crypticorn/common/scopes.py +2 -2
- crypticorn/common/utils.py +1 -2
- crypticorn/dex/__init__.py +6 -0
- crypticorn/dex/client/__init__.py +49 -0
- crypticorn/dex/client/api/__init__.py +6 -0
- crypticorn/dex/client/api/admin_api.py +2983 -0
- crypticorn/dex/client/api/signals_api.py +1794 -0
- crypticorn/dex/client/api/status_api.py +889 -0
- crypticorn/dex/client/api_client.py +753 -0
- crypticorn/dex/client/api_response.py +20 -0
- crypticorn/dex/client/configuration.py +620 -0
- crypticorn/dex/client/exceptions.py +220 -0
- crypticorn/dex/client/models/__init__.py +30 -0
- crypticorn/dex/client/models/api_error_identifier.py +121 -0
- crypticorn/dex/client/models/api_error_level.py +37 -0
- crypticorn/dex/client/models/api_error_type.py +37 -0
- crypticorn/dex/client/models/exception_detail.py +117 -0
- crypticorn/dex/client/models/log_level.py +38 -0
- crypticorn/dex/client/models/paginated_response_signal_with_token.py +134 -0
- crypticorn/dex/client/models/risk.py +86 -0
- crypticorn/dex/client/models/signal_overview_stats.py +156 -0
- crypticorn/dex/client/models/signal_volume.py +84 -0
- crypticorn/dex/client/models/signal_with_token.py +163 -0
- crypticorn/dex/client/models/token_data.py +127 -0
- crypticorn/dex/client/models/token_detail.py +116 -0
- crypticorn/dex/client/py.typed +0 -0
- crypticorn/dex/client/rest.py +217 -0
- crypticorn/dex/main.py +1 -0
- crypticorn/hive/client/api/admin_api.py +18 -75
- crypticorn/hive/client/api/data_api.py +6 -28
- crypticorn/hive/client/api/models_api.py +22 -88
- crypticorn/hive/client/api/status_api.py +6 -27
- crypticorn/hive/client/api_client.py +0 -5
- crypticorn/hive/client/models/coin_info.py +1 -1
- crypticorn/hive/client/models/exception_detail.py +1 -1
- crypticorn/hive/client/models/target_info.py +1 -1
- crypticorn/hive/utils.py +2 -2
- crypticorn/klines/client/api/admin_api.py +18 -75
- crypticorn/klines/client/api/change_in_timeframe_api.py +3 -16
- crypticorn/klines/client/api/funding_rates_api.py +3 -16
- crypticorn/klines/client/api/ohlcv_data_api.py +3 -16
- crypticorn/klines/client/api/status_api.py +6 -27
- crypticorn/klines/client/api/symbols_api.py +3 -16
- crypticorn/klines/client/api/udf_api.py +18 -74
- crypticorn/klines/client/api_client.py +0 -5
- crypticorn/klines/client/models/exception_detail.py +1 -1
- crypticorn/klines/client/models/ohlcv.py +1 -1
- crypticorn/klines/client/models/symbol_group.py +1 -1
- crypticorn/klines/client/models/udf_config.py +1 -1
- crypticorn/metrics/client/api/admin_api.py +18 -75
- crypticorn/metrics/client/api/exchanges_api.py +12 -52
- crypticorn/metrics/client/api/indicators_api.py +6 -28
- crypticorn/metrics/client/api/logs_api.py +3 -16
- crypticorn/metrics/client/api/marketcap_api.py +12 -52
- crypticorn/metrics/client/api/markets_api.py +3 -16
- crypticorn/metrics/client/api/quote_currencies_api.py +3 -16
- crypticorn/metrics/client/api/status_api.py +6 -27
- crypticorn/metrics/client/api/tokens_api.py +6 -26
- crypticorn/metrics/client/api_client.py +0 -5
- crypticorn/metrics/client/models/exception_detail.py +1 -1
- crypticorn/metrics/client/models/exchange_mapping.py +1 -1
- crypticorn/metrics/client/models/marketcap_ranking.py +1 -1
- crypticorn/metrics/client/models/marketcap_symbol_ranking.py +1 -1
- crypticorn/metrics/client/models/ohlcv.py +1 -1
- crypticorn/pay/client/api/admin_api.py +21 -87
- crypticorn/pay/client/api/now_payments_api.py +15 -64
- crypticorn/pay/client/api/payments_api.py +6 -28
- crypticorn/pay/client/api/products_api.py +12 -52
- crypticorn/pay/client/api/status_api.py +6 -27
- crypticorn/pay/client/api_client.py +0 -5
- crypticorn/pay/client/models/exception_detail.py +1 -1
- crypticorn/pay/client/models/now_create_invoice_req.py +1 -1
- crypticorn/pay/client/models/now_create_invoice_res.py +1 -1
- crypticorn/pay/client/models/product.py +1 -1
- crypticorn/pay/client/models/product_create.py +1 -1
- crypticorn/pay/client/models/product_update.py +1 -1
- crypticorn/trade/client/__init__.py +15 -0
- crypticorn/trade/client/api/admin_api.py +43 -107
- crypticorn/trade/client/api/api_keys_api.py +207 -184
- crypticorn/trade/client/api/bots_api.py +6557 -240
- crypticorn/trade/client/api/exchanges_api.py +9 -36
- crypticorn/trade/client/api/notifications_api.py +18 -72
- crypticorn/trade/client/api/orders_api.py +220 -115
- crypticorn/trade/client/api/status_api.py +6 -24
- crypticorn/trade/client/api/strategies_api.py +15 -60
- crypticorn/trade/client/api/trading_actions_api.py +431 -112
- crypticorn/trade/client/models/__init__.py +15 -0
- crypticorn/trade/client/models/bot.py +7 -18
- crypticorn/trade/client/models/bot_create.py +17 -1
- crypticorn/trade/client/models/bot_update.py +17 -1
- crypticorn/trade/client/models/exchange_key_create.py +17 -1
- crypticorn/trade/client/models/exchange_key_update.py +17 -1
- crypticorn/trade/client/models/notification.py +17 -1
- crypticorn/trade/client/models/notification_create.py +17 -1
- crypticorn/trade/client/models/notification_update.py +17 -1
- crypticorn/trade/client/models/orders_count.py +88 -0
- crypticorn/trade/client/models/paginated_response_futures_trading_action.py +134 -0
- crypticorn/trade/client/models/paginated_response_order.py +134 -0
- crypticorn/trade/client/models/paginated_response_union_futures_trading_action_spot_trading_action.py +141 -0
- crypticorn/trade/client/models/paginated_response_union_futures_trading_action_spot_trading_action_data_inner.py +165 -0
- crypticorn/trade/client/models/pn_l.py +95 -0
- crypticorn/trade/client/models/spot_trading_action.py +207 -0
- crypticorn/trade/client/models/strategy.py +17 -1
- crypticorn/trade/client/models/strategy_create.py +17 -1
- crypticorn/trade/client/models/strategy_update.py +17 -1
- {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/METADATA +3 -3
- {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/RECORD +151 -116
- {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/WHEEL +0 -0
- {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/entry_points.txt +0 -0
- {crypticorn-2.17.0rc4.dist-info → crypticorn-2.17.0rc6.dist-info}/licenses/LICENSE +0 -0
- {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
|
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
|
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
|
25
|
+
from typing import Set
|
26
26
|
from typing_extensions import Self
|
27
27
|
|
28
28
|
|
@@ -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
|
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
|
25
|
+
from typing import Set
|
26
26
|
from typing_extensions import Self
|
27
27
|
|
28
28
|
|
@@ -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
|
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
|
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
|
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
|
25
|
+
from typing import Set
|
26
26
|
from typing_extensions import Self
|
27
27
|
|
28
28
|
|
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(
|
36
|
+
click.secho("File already exists, use --force / -f to overwrite", fg="red")
|
37
37
|
return False
|
38
38
|
return True
|
39
39
|
|
@@ -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
|
-
|
22
|
+
# and in the docker build:
|
23
|
+
# build-args: |
|
24
|
+
# CACHEBUST=${{ github.run_id }} # invalidates dependency cache on every workflow run
|
23
25
|
|
24
|
-
|
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
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
|
-
|
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
|
crypticorn/common/decorators.py
CHANGED
@@ -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
|
4
|
+
from pydantic import BaseModel, create_model
|
6
5
|
from pydantic.fields import FieldInfo
|
7
6
|
|
8
7
|
|
crypticorn/common/enums.py
CHANGED
crypticorn/common/errors.py
CHANGED
@@ -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,
|
crypticorn/common/metrics.py
CHANGED
crypticorn/common/middleware.py
CHANGED
@@ -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
|
crypticorn/common/pagination.py
CHANGED
@@ -9,7 +9,20 @@ T = TypeVar("T")
|
|
9
9
|
|
10
10
|
class PaginatedResponse(BaseModel, Generic[T]):
|
11
11
|
"""Pydantic model for paginated response
|
12
|
-
>>>
|
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
|
-
|
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
|
-
>>>
|
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
|
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
|
-
|
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
|
-
>>>
|
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
|
-
"
|
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
|
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
|
-
|
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
|
-
>>>
|
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[
|
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(
|
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
|
148
|
-
|
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
|
-
>>>
|
231
|
+
>>> params: Annotated[PageSortFilterParams[Order], Query()],
|
153
232
|
>>> ) -> PaginatedResponse[Order]:
|
154
233
|
>>> ...
|
155
234
|
"""
|
156
235
|
|
157
|
-
|
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
|
161
|
-
|
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
|
-
>>>
|
251
|
+
>>> params: Annotated[HeavyPageSortFilterParams[Order], Query()],
|
166
252
|
>>> ) -> PaginatedResponse[Order]:
|
167
253
|
>>> ...
|
168
254
|
"""
|
169
255
|
|
170
|
-
|
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
|
193
|
-
raise ValueError(
|
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
|
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)
|