From 591ff5d120cc865821dde7612e7b65e3e8ee2dbd Mon Sep 17 00:00:00 2001 From: Artem Date: Sun, 2 Nov 2025 16:20:25 +0100 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D1=83=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=B0=D0=BB=D0=B8=D0=B0=D1=81=D0=BE=D0=B2=20=D0=B2=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=D0=BB=D1=8F=D1=85=20CreateUserRequestDto=20?= =?UTF-8?q?=D0=B8=20ConfigData;=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20HTTPS=20=D0=B2=20Remn?= =?UTF-8?q?awaveSDK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- remnawave/__init__.py | 4 ++++ remnawave/models/users.py | 3 +++ remnawave/models/users_stats.py | 3 +++ remnawave/models/xray_config.py | 3 +++ 4 files changed, 13 insertions(+) diff --git a/remnawave/__init__.py b/remnawave/__init__.py index 95d38fc..67f6628 100644 --- a/remnawave/__init__.py +++ b/remnawave/__init__.py @@ -137,6 +137,10 @@ class RemnawaveSDK: if self.custom_headers: headers.update(self.custom_headers) + if "http://" in self.base_url: + headers["x-forwarded-proto"] = "https" + headers["x-forwarded-for"] = "127.0.0.1" + return headers def _prepare_url(self) -> str: diff --git a/remnawave/models/users.py b/remnawave/models/users.py index d05e879..4df4b4a 100644 --- a/remnawave/models/users.py +++ b/remnawave/models/users.py @@ -8,6 +8,7 @@ from pydantic import ( RootModel, StringConstraints, ) +from pydantic.alias_generators import to_camel from remnawave.enums import TrafficLimitStrategy, UserStatus @@ -133,6 +134,8 @@ class UserResponseDto(BaseModel): external_squad_uuid: UUID | None = Field(None, alias="externalSquadUuid") created_at: datetime = Field(alias="createdAt") updated_at: datetime = Field(alias="updatedAt") + + model_config = {"alias_generator": to_camel, "populate_by_name": True} class EmailUserResponseDto(RootModel[list[UserResponseDto]]): diff --git a/remnawave/models/users_stats.py b/remnawave/models/users_stats.py index 171a75d..cb906be 100644 --- a/remnawave/models/users_stats.py +++ b/remnawave/models/users_stats.py @@ -3,6 +3,7 @@ from typing import List from uuid import UUID from pydantic import BaseModel, Field +from pydantic.alias_generators import to_camel class UserUsageByRange(BaseModel): @@ -11,6 +12,8 @@ class UserUsageByRange(BaseModel): node_name: str = Field(alias="nodeName") total: int date: datetime.date + + model_config = {"alias_generator": to_camel, "populate_by_name": True} class UserUsageByRangeResponseDto(List[UserUsageByRange]): diff --git a/remnawave/models/xray_config.py b/remnawave/models/xray_config.py index 945db42..12ed8dd 100644 --- a/remnawave/models/xray_config.py +++ b/remnawave/models/xray_config.py @@ -1,10 +1,13 @@ from typing import Any, Dict from pydantic import BaseModel +from pydantic.alias_generators import to_camel class ConfigData(BaseModel): config: Any + + model_config = {"alias_generator": to_camel, "populate_by_name": True} class GetConfigResponseDto(ConfigData): From 91ac8ef33edfdeab69685ebf6db14bf0d5ae7dbd Mon Sep 17 00:00:00 2001 From: Artem Date: Wed, 12 Nov 2025 00:01:39 +0100 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8=20API=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D1=84=D0=B8=D0=BA=D0=BE=D0=BC=20=D1=83=D0=B7=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B8=20=D0=B2=D0=BD=D0=B5=D1=88=D0=BD=D0=B8=D0=BC=D0=B8=20?= =?UTF-8?q?=D0=BE=D1=82=D1=80=D1=8F=D0=B4=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- pyproject.toml | 4 +- remnawave/controllers/nodes.py | 10 ++ remnawave/enums/error_code.py | 153 +++++++++++++++++++++++++ remnawave/exceptions/__init__.py | 42 ++++--- remnawave/exceptions/general.py | 74 +++++++++++-- remnawave/exceptions/handler.py | 166 +++++++++++++++++++++++----- remnawave/models/__init__.py | 4 + remnawave/models/external_squads.py | 20 +++- remnawave/models/nodes.py | 7 +- remnawave/models/users.py | 3 - 11 files changed, 424 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 786f991..4dc0468 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ pip install git+https://github.com/remnawave/python-sdk.git@development | Contract Version | Remnawave Panel Version | | ---------------- | ----------------------- | -| 2.2.13 | >=2.2.0 | +| 2.2.6 | >=2.2.6 | +| 2.2.3 | >=2.2.13 | | 2.1.19 | >=2.1.19, <2.2.0 | | 2.1.18 | >=2.1.18 | | 2.1.17 | >=2.1.16, <=2.1.17 | diff --git a/pyproject.toml b/pyproject.toml index 5191236..e34b8a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "remnawave" -version = "2.2.3.post2" -description = "A Python SDK for interacting with the Remnawave API v2.2.3." +version = "2.2.6" +description = "A Python SDK for interacting with the Remnawave API v2.2.6." authors = [ {name = "Artem",email = "dev@forestsnet.com"} ] diff --git a/remnawave/controllers/nodes.py b/remnawave/controllers/nodes.py index d19889e..fb86663 100644 --- a/remnawave/controllers/nodes.py +++ b/remnawave/controllers/nodes.py @@ -18,6 +18,8 @@ from remnawave.models import ( UpdateNodeRequestDto, UpdateNodeResponseDto, RestartAllNodesRequestBodyDto, + ResetNodeTrafficRequestDto, + ResetNodeTrafficResponseDto ) from remnawave.rapid import BaseController, delete, get, patch, post @@ -100,4 +102,12 @@ class NodesController(BaseController): body: Annotated[ReorderNodeRequestDto, PydanticBody()], ) -> ReorderNodeResponseDto: """Reorder Nodes""" + ... + + @post("/nodes/actions/reset-traffic", response_class=ResetNodeTrafficResponseDto) + async def reset_traffic_all_nodes( + self, + body: Annotated[ResetNodeTrafficRequestDto, PydanticBody()], + ) -> ResetNodeTrafficResponseDto: + """Reset Traffic All Nodes""" ... \ No newline at end of file diff --git a/remnawave/enums/error_code.py b/remnawave/enums/error_code.py index e532d21..2cbcbb4 100644 --- a/remnawave/enums/error_code.py +++ b/remnawave/enums/error_code.py @@ -78,3 +78,156 @@ class ErrorCode(StrEnum): SUBSCRIPTION_SETTINGS_NOT_FOUND = "A071" GET_SUBSCRIPTION_SETTINGS_ERROR = "A072" UPDATE_SUBSCRIPTION_SETTINGS_ERROR = "A073" + CREATE_INBOUND_ERROR = "A074" + DELETE_INBOUND_ERROR = "A075" + GET_INBOUND_ERROR = "A076" + INBOUND_NOT_FOUND = "A077" + INBOUND_TAG_ALREADY_EXISTS = "A078" + CREATE_HOST_BULK_ACTION_ERROR = "A079" + DELETE_HOST_BULK_ACTION_ERROR = "A080" + UPDATE_HOST_BULK_ACTION_ERROR = "A081" + BULK_ACTION_NOT_FOUND = "A082" + GET_USERS_STATS_ERROR = "A083" + RESET_USERS_TRAFFIC_BULK_ERROR = "A084" + UPDATE_USERS_BULK_ERROR = "A085" + DELETE_USERS_BULK_ERROR = "A086" + GET_USERS_BULK_ERROR = "A087" + CREATE_TEMPLATE_ERROR = "A088" + TEMPLATE_NOT_FOUND = "A089" + UPDATE_TEMPLATE_ERROR = "A090" + DELETE_TEMPLATE_ERROR = "A091" + TEMPLATE_NAME_ALREADY_EXISTS = "A092" + GET_TEMPLATE_ERROR = "A093" + GET_ALL_TEMPLATES_ERROR = "A094" + GENERATE_CONFIG_ERROR = "A095" + INVALID_TEMPLATE_TYPE = "A096" + CREATE_EXTERNAL_SQUAD_ERROR = "A097" + EXTERNAL_SQUAD_NOT_FOUND = "A098" + UPDATE_EXTERNAL_SQUAD_ERROR = "A099" + DELETE_EXTERNAL_SQUAD_ERROR = "A100" + EXTERNAL_SQUAD_NAME_ALREADY_EXISTS = "A101" + ADD_USERS_TO_EXTERNAL_SQUAD_ERROR = "A102" + REMOVE_USERS_FROM_EXTERNAL_SQUAD_ERROR = "A103" + GET_EXTERNAL_SQUAD_ERROR = "A104" + GET_ALL_EXTERNAL_SQUADS_ERROR = "A105" + CREATE_INTERNAL_SQUAD_ERROR = "A106" + INTERNAL_SQUAD_NOT_FOUND = "A107" + UPDATE_INTERNAL_SQUAD_ERROR = "A108" + DELETE_INTERNAL_SQUAD_ERROR = "A109" + INTERNAL_SQUAD_NAME_ALREADY_EXISTS = "A110" + GET_INTERNAL_SQUAD_ERROR = "A111" + GET_ALL_INTERNAL_SQUADS_ERROR = "A112" + CREATE_WEBHOOK_ERROR = "A113" + WEBHOOK_NOT_FOUND = "A114" + UPDATE_WEBHOOK_ERROR = "A115" + DELETE_WEBHOOK_ERROR = "A116" + WEBHOOK_URL_ALREADY_EXISTS = "A117" + GET_WEBHOOK_ERROR = "A118" + GET_ALL_WEBHOOKS_ERROR = "A119" + WEBHOOK_DELIVERY_ERROR = "A120" + CREATE_PASSKEY_ERROR = "A121" + PASSKEY_NOT_FOUND = "A122" + DELETE_PASSKEY_ERROR = "A123" + GET_PASSKEY_ERROR = "A124" + GET_ALL_PASSKEYS_ERROR = "A125" + PASSKEY_ALREADY_EXISTS = "A126" + CREATE_SNIPPET_ERROR = "A127" + SNIPPET_NOT_FOUND = "A128" + UPDATE_SNIPPET_ERROR = "A129" + DELETE_SNIPPET_ERROR = "A130" + SNIPPET_NAME_ALREADY_EXISTS = "A131" + GET_SNIPPET_ERROR = "A132" + GET_ALL_SNIPPETS_ERROR = "A133" + HWID_RESET_ERROR = "A134" + HWID_NOT_FOUND = "A135" + GET_HWID_ERROR = "A136" + GET_ALL_HWIDS_ERROR = "A137" + DELETE_HWID_ERROR = "A138" + BANDWIDTH_STATS_ERROR = "A139" + GET_NODES_USAGE_STATS_ERROR = "A140" + SUBSCRIPTION_REQUEST_ERROR = "A141" + SUBSCRIPTION_REQUEST_NOT_FOUND = "A142" + GET_SUBSCRIPTION_REQUEST_ERROR = "A143" + GET_ALL_SUBSCRIPTION_REQUESTS_ERROR = "A144" + APPROVE_SUBSCRIPTION_REQUEST_ERROR = "A145" + REJECT_SUBSCRIPTION_REQUEST_ERROR = "A146" + CREATE_SUBSCRIPTION_REQUEST_HISTORY_ERROR = "A147" + GET_SUBSCRIPTION_REQUEST_HISTORY_ERROR = "A148" + KEYGEN_ERROR = "A149" + GENERATE_KEYS_ERROR = "A150" + INVALID_KEY_TYPE = "A151" + SYSTEM_STATS_ERROR = "A152" + SYSTEM_HEALTH_ERROR = "A153" + NODES_METRICS_ERROR = "A154" + X25519_KEYGEN_ERROR = "A155" + HAPP_CRYPTO_ERROR = "A156" + SRR_MATCHER_ERROR = "A157" + GET_REMNAWAVE_SETTINGS_ERROR = "A158" + UPDATE_REMNAWAVE_SETTINGS_ERROR = "A159" + OAUTH_ERROR = "A160" + PASSKEY_SETTINGS_ERROR = "A161" + TELEGRAM_AUTH_ERROR = "A162" + BRANDING_SETTINGS_ERROR = "A163" + CONFIG_PROFILE_ERROR = "A164" + CONFIG_PROFILE_NOT_FOUND = "A165" + CREATE_CONFIG_PROFILE_ERROR = "A166" + UPDATE_CONFIG_PROFILE_ERROR = "A167" + DELETE_CONFIG_PROFILE_ERROR = "A168" + GET_CONFIG_PROFILE_ERROR = "A169" + GET_ALL_CONFIG_PROFILES_ERROR = "A170" + XRAY_CONFIG_ERROR = "A171" + XRAY_CONFIG_VALIDATION_ERROR = "A172" + INFRA_BILLING_ERROR = "A173" + INFRA_BILLING_NOT_FOUND = "A174" + GET_INFRA_BILLING_ERROR = "A175" + UPDATE_INFRA_BILLING_ERROR = "A176" + CALCULATE_BILLING_ERROR = "A177" + BILLING_PERIOD_ERROR = "A178" + + # Добавляем новые коды из failed тестов + CREATE_SUBSCRIPTION_TEMPLATE_ERROR = "A179" + SUBSCRIPTION_TEMPLATE_NOT_FOUND = "A180" + UPDATE_SUBSCRIPTION_TEMPLATE_ERROR = "A181" + DELETE_SUBSCRIPTION_TEMPLATE_ERROR = "A182" + GET_SUBSCRIPTION_TEMPLATE_ERROR = "A183" + + # Валидационные ошибки + VALIDATION_ERROR = "V001" + INVALID_UUID_FORMAT = "V002" + INVALID_EMAIL_FORMAT = "V003" + INVALID_DATE_FORMAT = "V004" + REQUIRED_FIELD_MISSING = "V005" + FIELD_TOO_LONG = "V006" + FIELD_TOO_SHORT = "V007" + INVALID_ENUM_VALUE = "V008" + INVALID_REGEX_PATTERN = "V009" + NUMERIC_VALIDATION_ERROR = "V010" + + # Сетевые ошибки + NETWORK_ERROR = "N003" + TIMEOUT_ERROR = "N004" + CONNECTION_ERROR = "N005" + DNS_ERROR = "N006" + SSL_ERROR = "N007" + + # Ошибки аутентификации и авторизации + INVALID_TOKEN = "AUTH001" + TOKEN_EXPIRED = "AUTH002" + INVALID_CREDENTIALS = "AUTH003" + TWO_FACTOR_REQUIRED = "AUTH004" + ACCOUNT_LOCKED = "AUTH005" + PASSWORD_COMPLEXITY_ERROR = "AUTH006" + + # Ошибки бизнес-логики + TRAFFIC_LIMIT_EXCEEDED = "BL001" + USER_LIMIT_EXCEEDED = "BL002" + SUBSCRIPTION_EXPIRED = "BL003" + FEATURE_NOT_AVAILABLE = "BL004" + QUOTA_EXCEEDED = "BL005" + RESOURCE_LOCKED = "BL006" + + # Общие коды + UNKNOWN = "UNKNOWN" + NOT_IMPLEMENTED = "NOT_IMPLEMENTED" + MAINTENANCE_MODE = "MAINTENANCE" + RATE_LIMIT_EXCEEDED = "RATE_LIMIT" \ No newline at end of file diff --git a/remnawave/exceptions/__init__.py b/remnawave/exceptions/__init__.py index da87a70..39b4dbe 100644 --- a/remnawave/exceptions/__init__.py +++ b/remnawave/exceptions/__init__.py @@ -1,23 +1,39 @@ -from .handler import handle_api_error from .general import ( - ConflictError, - ApiErrorResponse, - BadRequestError, - NotFoundError, - ForbiddenError, - UnauthorizedError, - ServerError, ApiError, + ApiErrorResponse, + AuthenticationError, + BadRequestError, + BusinessLogicError, + ConflictError, + FeatureNotAvailableError, + ForbiddenError, + MaintenanceError, + NetworkError, + NotFoundError, + QuotaExceededError, + RateLimitError, + ServerError, + UnauthorizedError, + ValidationError, ) +from .handler import handle_api_error __all__ = [ - "handle_api_error", "ApiError", "ApiErrorResponse", - "NotFoundError", + "AuthenticationError", "BadRequestError", - "ForbiddenError", - "UnauthorizedError", + "BusinessLogicError", "ConflictError", + "FeatureNotAvailableError", + "ForbiddenError", + "MaintenanceError", + "NetworkError", + "NotFoundError", + "QuotaExceededError", + "RateLimitError", "ServerError", -] + "UnauthorizedError", + "ValidationError", + "handle_api_error", +] \ No newline at end of file diff --git a/remnawave/exceptions/general.py b/remnawave/exceptions/general.py index 8c7e63e..dadb01a 100644 --- a/remnawave/exceptions/general.py +++ b/remnawave/exceptions/general.py @@ -1,14 +1,13 @@ from datetime import datetime +from typing import Any, List, Optional from pydantic import AliasChoices, BaseModel, Field from remnawave.enums import ErrorCode -from typing import Any, List, Optional - - class ApiErrorResponse(BaseModel): + """Standard API error response model""" timestamp: Optional[datetime] = Field(None, description="Время возникновения ошибки") path: Optional[str] = Field(None, description="Путь запроса") message: str = Field(..., description="Сообщение об ошибке") @@ -23,6 +22,8 @@ class ApiErrorResponse(BaseModel): class ApiError(Exception): + """Base API error exception""" + def __init__(self, status_code: int, error: ApiErrorResponse): self.status_code = status_code self.error = error @@ -30,38 +31,93 @@ class ApiError(Exception): f"API Error {error.code}: {error.message} (HTTP {status_code})" ) + @property + def code(self) -> Optional[str]: + """Get error code""" + return self.error.code + + @property + def message(self) -> str: + """Get error message""" + return self.error.message + + @property + def timestamp(self) -> Optional[datetime]: + """Get error timestamp""" + return self.error.timestamp + + @property + def path(self) -> Optional[str]: + """Get request path""" + return self.error.path + class BadRequestError(ApiError): """Ошибки клиента (400)""" - pass class UnauthorizedError(ApiError): """Ошибка авторизации (401)""" - pass class ForbiddenError(ApiError): """Доступ запрещен (403)""" - pass class NotFoundError(ApiError): """Ресурс не найден (404)""" - pass class ConflictError(ApiError): """Конфликт (409)""" + pass + +class ValidationError(ApiError): + """Ошибка валидации данных (422)""" pass class ServerError(ApiError): - """Серверная ошибка (500)""" - + """Серверная ошибка (500+)""" pass + + +# Новые специализированные исключения +class NetworkError(ApiError): + """Сетевые ошибки""" + pass + + +class AuthenticationError(ApiError): + """Ошибки аутентификации""" + pass + + +class BusinessLogicError(ApiError): + """Ошибки бизнес-логики""" + pass + + +class RateLimitError(BadRequestError): + """Превышен лимит запросов""" + pass + + +class MaintenanceError(ServerError): + """Режим обслуживания""" + pass + + +class QuotaExceededError(BusinessLogicError): + """Превышена квота""" + pass + + +class FeatureNotAvailableError(BusinessLogicError): + """Функция недоступна""" + pass \ No newline at end of file diff --git a/remnawave/exceptions/handler.py b/remnawave/exceptions/handler.py index efeafd7..ddaa318 100644 --- a/remnawave/exceptions/handler.py +++ b/remnawave/exceptions/handler.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import Dict, Type import httpx @@ -12,11 +13,15 @@ from .general import ( NotFoundError, ServerError, UnauthorizedError, + ValidationError, + NetworkError, + AuthenticationError, + BusinessLogicError, ) -ERRORS: dict[str, dict] = { +ERRORS: Dict[str, Type[ApiError]] = { ErrorCode.INTERNAL_SERVER_ERROR: ServerError, - ErrorCode.LOGIN_ERROR: ServerError, + ErrorCode.LOGIN_ERROR: AuthenticationError, ErrorCode.UNAUTHORIZED: UnauthorizedError, ErrorCode.FORBIDDEN_ROLE_ERROR: ForbiddenError, ErrorCode.CREATE_API_TOKEN_ERROR: ServerError, @@ -33,9 +38,9 @@ ERRORS: dict[str, dict] = { ErrorCode.CREATE_MANY_INBOUNDS_ERROR: ServerError, ErrorCode.FIND_ALL_INBOUNDS_ERROR: ServerError, ErrorCode.CREATE_USER_ERROR: ServerError, - ErrorCode.USER_USERNAME_ALREADY_EXISTS: BadRequestError, - ErrorCode.USER_SHORT_UUID_ALREADY_EXISTS: BadRequestError, - ErrorCode.USER_SUBSCRIPTION_UUID_ALREADY_EXISTS: BadRequestError, + ErrorCode.USER_USERNAME_ALREADY_EXISTS: ConflictError, + ErrorCode.USER_SHORT_UUID_ALREADY_EXISTS: ConflictError, + ErrorCode.USER_SUBSCRIPTION_UUID_ALREADY_EXISTS: ConflictError, ErrorCode.CREATE_USER_WITH_INBOUNDS_ERROR: ServerError, ErrorCode.CANT_GET_CREATED_USER_WITH_INBOUNDS: ServerError, ErrorCode.GET_ALL_USERS_ERROR: ServerError, @@ -43,12 +48,12 @@ ERRORS: dict[str, dict] = { ErrorCode.GET_USER_BY_ERROR: ServerError, ErrorCode.REVOKE_USER_SUBSCRIPTION_ERROR: ServerError, ErrorCode.DISABLE_USER_ERROR: ServerError, - ErrorCode.USER_ALREADY_DISABLED: BadRequestError, - ErrorCode.USER_ALREADY_ENABLED: BadRequestError, + ErrorCode.USER_ALREADY_DISABLED: ConflictError, + ErrorCode.USER_ALREADY_ENABLED: ConflictError, ErrorCode.ENABLE_USER_ERROR: ServerError, ErrorCode.CREATE_NODE_ERROR: ServerError, - ErrorCode.NODE_NAME_ALREADY_EXISTS: BadRequestError, - ErrorCode.NODE_ADDRESS_ALREADY_EXISTS: BadRequestError, + ErrorCode.NODE_NAME_ALREADY_EXISTS: ConflictError, + ErrorCode.NODE_ADDRESS_ALREADY_EXISTS: ConflictError, ErrorCode.NODE_ERROR_WITH_MSG: ServerError, ErrorCode.NODE_ERROR_500_WITH_MSG: ServerError, ErrorCode.RESTART_NODE_ERROR: ServerError, @@ -61,7 +66,7 @@ ERRORS: dict[str, dict] = { ErrorCode.GET_ONE_NODE_ERROR: ServerError, ErrorCode.DELETE_NODE_ERROR: ServerError, ErrorCode.CREATE_HOST_ERROR: ServerError, - ErrorCode.HOST_REMARK_ALREADY_EXISTS: BadRequestError, + ErrorCode.HOST_REMARK_ALREADY_EXISTS: ConflictError, ErrorCode.HOST_NOT_FOUND: NotFoundError, ErrorCode.DELETE_HOST_ERROR: ServerError, ErrorCode.GET_USER_STATS_ERROR: ServerError, @@ -77,7 +82,7 @@ ERRORS: dict[str, dict] = { ErrorCode.GET_ALL_INBOUNDS_ERROR: ServerError, ErrorCode.BULK_DELETE_USERS_BY_STATUS_ERROR: ServerError, ErrorCode.UPDATE_INBOUND_ERROR: ServerError, - ErrorCode.CONFIG_VALIDATION_ERROR: ServerError, + ErrorCode.CONFIG_VALIDATION_ERROR: ValidationError, ErrorCode.USERS_NOT_FOUND: NotFoundError, ErrorCode.GET_USER_BY_UNIQUE_FIELDS_NOT_FOUND: NotFoundError, ErrorCode.UPDATE_EXCEEDED_TRAFFIC_USERS_ERROR: ServerError, @@ -85,15 +90,106 @@ ERRORS: dict[str, dict] = { ErrorCode.CREATE_ADMIN_ERROR: ServerError, ErrorCode.GET_AUTH_STATUS_ERROR: ServerError, ErrorCode.FORBIDDEN_ONE: ForbiddenError, + ErrorCode.FORBIDDEN_TWO: ForbiddenError, ErrorCode.DISABLE_NODE_ERROR: ServerError, ErrorCode.GET_ONE_HOST_ERROR: ServerError, ErrorCode.SUBSCRIPTION_SETTINGS_NOT_FOUND: NotFoundError, ErrorCode.GET_SUBSCRIPTION_SETTINGS_ERROR: ServerError, ErrorCode.UPDATE_SUBSCRIPTION_SETTINGS_ERROR: ServerError, + + ErrorCode.CREATE_SUBSCRIPTION_TEMPLATE_ERROR: ServerError, + ErrorCode.SUBSCRIPTION_TEMPLATE_NOT_FOUND: NotFoundError, + ErrorCode.UPDATE_SUBSCRIPTION_TEMPLATE_ERROR: ServerError, + ErrorCode.DELETE_SUBSCRIPTION_TEMPLATE_ERROR: ServerError, + ErrorCode.GET_SUBSCRIPTION_TEMPLATE_ERROR: ServerError, + + ErrorCode.CREATE_INBOUND_ERROR: ServerError, + ErrorCode.DELETE_INBOUND_ERROR: ServerError, + ErrorCode.GET_INBOUND_ERROR: ServerError, + ErrorCode.INBOUND_NOT_FOUND: NotFoundError, + ErrorCode.INBOUND_TAG_ALREADY_EXISTS: ConflictError, + + ErrorCode.CREATE_EXTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.EXTERNAL_SQUAD_NOT_FOUND: NotFoundError, + ErrorCode.UPDATE_EXTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.DELETE_EXTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.EXTERNAL_SQUAD_NAME_ALREADY_EXISTS: ConflictError, + ErrorCode.ADD_USERS_TO_EXTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.REMOVE_USERS_FROM_EXTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.GET_EXTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.GET_ALL_EXTERNAL_SQUADS_ERROR: ServerError, + + ErrorCode.CREATE_INTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.INTERNAL_SQUAD_NOT_FOUND: NotFoundError, + ErrorCode.UPDATE_INTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.DELETE_INTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.INTERNAL_SQUAD_NAME_ALREADY_EXISTS: ConflictError, + ErrorCode.GET_INTERNAL_SQUAD_ERROR: ServerError, + ErrorCode.GET_ALL_INTERNAL_SQUADS_ERROR: ServerError, + + ErrorCode.CREATE_SNIPPET_ERROR: ServerError, + ErrorCode.SNIPPET_NOT_FOUND: NotFoundError, + ErrorCode.UPDATE_SNIPPET_ERROR: ServerError, + ErrorCode.DELETE_SNIPPET_ERROR: ServerError, + ErrorCode.SNIPPET_NAME_ALREADY_EXISTS: ConflictError, + ErrorCode.GET_SNIPPET_ERROR: ServerError, + ErrorCode.GET_ALL_SNIPPETS_ERROR: ServerError, + + # Валидационные ошибки + ErrorCode.VALIDATION_ERROR: ValidationError, + ErrorCode.INVALID_UUID_FORMAT: ValidationError, + ErrorCode.INVALID_EMAIL_FORMAT: ValidationError, + ErrorCode.INVALID_DATE_FORMAT: ValidationError, + ErrorCode.REQUIRED_FIELD_MISSING: ValidationError, + ErrorCode.FIELD_TOO_LONG: ValidationError, + ErrorCode.FIELD_TOO_SHORT: ValidationError, + ErrorCode.INVALID_ENUM_VALUE: ValidationError, + ErrorCode.INVALID_REGEX_PATTERN: ValidationError, + ErrorCode.NUMERIC_VALIDATION_ERROR: ValidationError, + + # Сетевые ошибки + ErrorCode.NETWORK_ERROR: NetworkError, + ErrorCode.TIMEOUT_ERROR: NetworkError, + ErrorCode.CONNECTION_ERROR: NetworkError, + ErrorCode.DNS_ERROR: NetworkError, + ErrorCode.SSL_ERROR: NetworkError, + + # Ошибки аутентификации + ErrorCode.INVALID_TOKEN: AuthenticationError, + ErrorCode.TOKEN_EXPIRED: AuthenticationError, + ErrorCode.INVALID_CREDENTIALS: AuthenticationError, + ErrorCode.TWO_FACTOR_REQUIRED: AuthenticationError, + ErrorCode.ACCOUNT_LOCKED: AuthenticationError, + ErrorCode.PASSWORD_COMPLEXITY_ERROR: ValidationError, + + # Бизнес-логика + ErrorCode.TRAFFIC_LIMIT_EXCEEDED: BusinessLogicError, + ErrorCode.USER_LIMIT_EXCEEDED: BusinessLogicError, + ErrorCode.SUBSCRIPTION_EXPIRED: BusinessLogicError, + ErrorCode.FEATURE_NOT_AVAILABLE: BusinessLogicError, + ErrorCode.QUOTA_EXCEEDED: BusinessLogicError, + ErrorCode.RESOURCE_LOCKED: ConflictError, + + # Системные ошибки + ErrorCode.SYSTEM_STATS_ERROR: ServerError, + ErrorCode.SYSTEM_HEALTH_ERROR: ServerError, + ErrorCode.NODES_METRICS_ERROR: ServerError, + ErrorCode.X25519_KEYGEN_ERROR: ServerError, + ErrorCode.HAPP_CRYPTO_ERROR: ServerError, + ErrorCode.SRR_MATCHER_ERROR: ServerError, + + # Настройки Remnawave + ErrorCode.GET_REMNAWAVE_SETTINGS_ERROR: ServerError, + ErrorCode.UPDATE_REMNAWAVE_SETTINGS_ERROR: ServerError, + ErrorCode.OAUTH_ERROR: AuthenticationError, + ErrorCode.PASSKEY_SETTINGS_ERROR: ServerError, + ErrorCode.TELEGRAM_AUTH_ERROR: AuthenticationError, + ErrorCode.BRANDING_SETTINGS_ERROR: ServerError, } def handle_api_error(response: httpx.Response) -> None: + """Handle API error responses and raise appropriate exceptions""" if response.status_code >= 400: try: error_data = response.json() @@ -103,7 +199,7 @@ def handle_api_error(response: httpx.Response) -> None: if error_response.timestamp is None: error_response.timestamp = datetime.now() if error_response.path is None: - error_response.path = response.request.url.path + error_response.path = str(response.request.url.path) if error_response.code is None: # Use status_code or default to UNKNOWN if error_response.status_code: @@ -111,32 +207,46 @@ def handle_api_error(response: httpx.Response) -> None: else: error_response.code = "UNKNOWN" + # Map error code to exception class if error_response.code in ERRORS: exception_class = ERRORS[error_response.code] else: - if response.status_code == 400: - exception_class = BadRequestError - elif response.status_code == 401: - exception_class = UnauthorizedError - elif response.status_code == 403: - exception_class = ForbiddenError - elif response.status_code == 404: - exception_class = NotFoundError - elif response.status_code == 409: - exception_class = ConflictError - elif response.status_code >= 500: - exception_class = ServerError - else: - exception_class = ApiError + # Fallback based on HTTP status code + exception_class = _get_exception_by_status_code(response.status_code) raise exception_class(response.status_code, error_response) + except ValueError: + # JSON parsing failed, create generic error raise ApiError( response.status_code, ApiErrorResponse( timestamp=datetime.now(), - path=response.request.url.path, - message="Unknown error " + response.text, + path=str(response.request.url.path), + message=f"Unknown error: {response.text}", code="UNKNOWN", + status_code=response.status_code, ), ) + + +def _get_exception_by_status_code(status_code: int) -> Type[ApiError]: + """Get exception class based on HTTP status code""" + if status_code == 400: + return BadRequestError + elif status_code == 401: + return UnauthorizedError + elif status_code == 403: + return ForbiddenError + elif status_code == 404: + return NotFoundError + elif status_code == 409: + return ConflictError + elif status_code == 422: + return ValidationError + elif status_code == 429: + return BadRequestError # Rate limit + elif status_code >= 500: + return ServerError + else: + return ApiError \ No newline at end of file diff --git a/remnawave/models/__init__.py b/remnawave/models/__init__.py index e63ca12..b428c12 100644 --- a/remnawave/models/__init__.py +++ b/remnawave/models/__init__.py @@ -172,6 +172,8 @@ from .nodes import ( UpdateNodeResponseDto, RestartAllNodesRequestDto, # Legacy alias, RestartAllNodesRequestBodyDto, + ResetNodeTrafficRequestDto, + ResetNodeTrafficResponseDto ) from .nodes_usage_history import ( GetNodeUserUsageByRangeResponseDto, @@ -384,6 +386,8 @@ __all__ = [ "NodeConfigProfileRequestDto", "RestartAllNodesRequestDto", # Legacy alias "RestartAllNodesRequestBodyDto", + "ResetNodeTrafficRequestDto", + "ResetNodeTrafficResponseDto", # Hosts models "CreateHostRequestDto", "CreateHostResponseDto", diff --git a/remnawave/models/external_squads.py b/remnawave/models/external_squads.py index 665e237..5b327e9 100644 --- a/remnawave/models/external_squads.py +++ b/remnawave/models/external_squads.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import StrEnum -from typing import List, Optional +from typing import Dict, List, Optional from uuid import UUID from pydantic import BaseModel, Field @@ -41,22 +41,30 @@ class ExternalSquadSubscriptionSettingsDto(BaseModel): randomize_hosts: bool = Field(alias="randomizeHosts") +# НОВЫЕ МОДЕЛИ +class ExternalSquadHostOverridesDto(BaseModel): + """External squad host overrides""" + server_description: Optional[str] = Field(None, alias="serverDescription", max_length=30) + vless_route_id: Optional[int] = Field(None, alias="vlessRouteId", ge=0, le=65535) + + class ExternalSquadDto(BaseModel): """External squad data model""" uuid: UUID name: str info: ExternalSquadInfoDto templates: List[ExternalSquadTemplateDto] - subscription_settings: Optional[ExternalSquadSubscriptionSettingsDto] = Field(alias="subscriptionSettings") + subscription_settings: Optional[ExternalSquadSubscriptionSettingsDto] = Field(None, alias="subscriptionSettings") + host_overrides: Optional[ExternalSquadHostOverridesDto] = Field(None, alias="hostOverrides") + response_headers: Optional[Dict[str, str]] = Field(None, alias="responseHeaders") created_at: datetime = Field(alias="createdAt") updated_at: datetime = Field(alias="updatedAt") # Request/Response models -class GetExternalSquadsResponseDto(BaseModel): +class GetExternalSquadsResponseDto(ExternalSquadDto): """Response with all external squads""" - total: float - external_squads: List[ExternalSquadDto] = Field(alias="externalSquads") + pass class GetExternalSquadByUuidResponseDto(ExternalSquadDto): @@ -80,6 +88,8 @@ class UpdateExternalSquadRequestDto(BaseModel): name: Optional[str] = Field(None, min_length=2, max_length=30, pattern=r"^[A-Za-z0-9_\s-]+$") templates: Optional[List[ExternalSquadTemplateDto]] = None subscription_settings: Optional[ExternalSquadSubscriptionSettingsDto] = Field(None, serialization_alias="subscriptionSettings") + host_overrides: Optional[ExternalSquadHostOverridesDto] = Field(None, serialization_alias="hostOverrides") + response_headers: Optional[Dict[str, str]] = Field(None, serialization_alias="responseHeaders") class UpdateExternalSquadResponseDto(ExternalSquadDto): diff --git a/remnawave/models/nodes.py b/remnawave/models/nodes.py index a3773a1..94720ad 100644 --- a/remnawave/models/nodes.py +++ b/remnawave/models/nodes.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Annotated, List, Optional +from typing import Annotated, List, Optional, Union from uuid import UUID from pydantic import BaseModel, Field, StringConstraints, RootModel @@ -208,7 +208,12 @@ class DeleteNodeResponseDto(BaseModel): class RestartAllNodesRequestBodyDto(BaseModel): force_restart: bool = Field(default=False, alias="forceRestart") + +class ResetNodeTrafficRequestDto(BaseModel): + uuid: Union[str, UUID] = Field(alias="uuid") +class ResetNodeTrafficResponseDto(RestartEventResponse): + pass # Для обратной совместимости RestartAllNodesRequestDto = RestartAllNodesRequestBodyDto diff --git a/remnawave/models/users.py b/remnawave/models/users.py index 4df4b4a..78dd483 100644 --- a/remnawave/models/users.py +++ b/remnawave/models/users.py @@ -134,9 +134,6 @@ class UserResponseDto(BaseModel): external_squad_uuid: UUID | None = Field(None, alias="externalSquadUuid") created_at: datetime = Field(alias="createdAt") updated_at: datetime = Field(alias="updatedAt") - - model_config = {"alias_generator": to_camel, "populate_by_name": True} - class EmailUserResponseDto(RootModel[list[UserResponseDto]]): def __iter__(self): From 00225e9284fa8eda1ec885443861ad4d0ee1460b Mon Sep 17 00:00:00 2001 From: Artem Date: Wed, 12 Nov 2025 00:17:05 +0100 Subject: [PATCH 3/3] fix: update serialization aliases in user models --- .gitignore | 1 + poetry.lock | 281 ++++++++++++++++++++++---------------- remnawave/models/users.py | 6 +- 3 files changed, 172 insertions(+), 116 deletions(-) diff --git a/.gitignore b/.gitignore index 93fa963..d072f50 100644 --- a/.gitignore +++ b/.gitignore @@ -181,3 +181,4 @@ openapi/ requirements.txt requirements.in test_raw.py +tests/test_one_time.py diff --git a/poetry.lock b/poetry.lock index 2d88d02..e029d68 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "annotated-types" @@ -119,6 +119,43 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dnspython" +version = "2.8.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af"}, + {file = "dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f"}, +] + +[package.extras] +dev = ["black (>=25.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.17.0)", "mypy (>=1.17)", "pylint (>=3)", "pytest (>=8.4)", "pytest-cov (>=6.2.0)", "quart-trio (>=0.12.0)", "sphinx (>=8.2.0)", "sphinx-rtd-theme (>=3.0.0)", "twine (>=6.1.0)", "wheel (>=0.45.0)"] +dnssec = ["cryptography (>=45)"] +doh = ["h2 (>=4.2.0)", "httpcore (>=1.0.0)", "httpx (>=0.28.0)"] +doq = ["aioquic (>=1.2.0)"] +idna = ["idna (>=3.10)"] +trio = ["trio (>=0.30)"] +wmi = ["wmi (>=1.5.1) ; platform_system == \"Windows\""] + +[[package]] +name = "email-validator" +version = "2.3.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4"}, + {file = "email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + [[package]] name = "h11" version = "0.14.0" @@ -366,20 +403,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.10" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, + {file = "pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a"}, + {file = "pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" +email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} +pydantic-core = "2.33.2" typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -387,112 +426,111 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] [package.dependencies] @@ -640,18 +678,33 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "test"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] markers = {test = "python_version < \"3.13\""} +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [metadata] lock-version = "2.1" python-versions = ">=3.11,<4.0" -content-hash = "d1a3aaf405dbd83f352a1909aab7049ce3302f78c988e6cb7dd2442d6e803438" +content-hash = "b3ba7b60ddeb451f296060a09b5ef40ff0cc2ec6464c8520aa3ed40a9bb3ceb0" diff --git a/remnawave/models/users.py b/remnawave/models/users.py index 78dd483..7cd03f4 100644 --- a/remnawave/models/users.py +++ b/remnawave/models/users.py @@ -69,7 +69,7 @@ class CreateUserRequestDto(BaseModel): active_internal_squads: list[UUID] | None = Field( None, serialization_alias="activeInternalSquads" ) - external_squad_uuid: UUID | None = Field(None, alias="externalSquadUuid") + external_squad_uuid: UUID | None = Field(None, serialization_alias="externalSquadUuid") uuid: Optional[UUID] = Field(None, description="UUID of the user. Optional. If not provided, a new UUID will be generated by Remnawave.") @@ -118,7 +118,7 @@ class UserResponseDto(BaseModel): telegram_id: int | None = Field(None, alias="telegramId") email: str | None = None hwid_device_limit: int | None = Field( - None, serialization_alias="hwidDeviceLimit", strict=True, ge=0 + None, alias="hwidDeviceLimit", strict=True, ge=0 ) active_internal_squads: list[ActiveInternalSquadDto] | None = Field( None, alias="activeInternalSquads" @@ -134,6 +134,8 @@ class UserResponseDto(BaseModel): external_squad_uuid: UUID | None = Field(None, alias="externalSquadUuid") created_at: datetime = Field(alias="createdAt") updated_at: datetime = Field(alias="updatedAt") + + model_config = {"alias_generator": to_camel, "populate_by_name": True} class EmailUserResponseDto(RootModel[list[UserResponseDto]]): def __iter__(self):