Skip to content

Commit 98cbd2b

Browse files
authored
Merge pull request #22 from remnawave:development
Add support for alias generation and update API models
2 parents 7b2a4de + 00225e9 commit 98cbd2b

File tree

16 files changed

+603
-172
lines changed

16 files changed

+603
-172
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,4 @@ openapi/
181181
requirements.txt
182182
requirements.in
183183
test_raw.py
184+
tests/test_one_time.py

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
6363

6464
| Contract Version | Remnawave Panel Version |
6565
| ---------------- | ----------------------- |
66-
| 2.2.13 | >=2.2.0 |
66+
| 2.2.6 | >=2.2.6 |
67+
| 2.2.3 | >=2.2.13 |
6768
| 2.1.19 | >=2.1.19, <2.2.0 |
6869
| 2.1.18 | >=2.1.18 |
6970
| 2.1.17 | >=2.1.16, <=2.1.17 |

poetry.lock

Lines changed: 167 additions & 114 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "remnawave"
3-
version = "2.2.3.post2"
4-
description = "A Python SDK for interacting with the Remnawave API v2.2.3."
3+
version = "2.2.6"
4+
description = "A Python SDK for interacting with the Remnawave API v2.2.6."
55
authors = [
66
{name = "Artem",email = "[email protected]"}
77
]

remnawave/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ def _prepare_headers(self) -> dict:
137137
if self.custom_headers:
138138
headers.update(self.custom_headers)
139139

140+
if "http://" in self.base_url:
141+
headers["x-forwarded-proto"] = "https"
142+
headers["x-forwarded-for"] = "127.0.0.1"
143+
140144
return headers
141145

142146
def _prepare_url(self) -> str:

remnawave/controllers/nodes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
UpdateNodeRequestDto,
1919
UpdateNodeResponseDto,
2020
RestartAllNodesRequestBodyDto,
21+
ResetNodeTrafficRequestDto,
22+
ResetNodeTrafficResponseDto
2123
)
2224
from remnawave.rapid import BaseController, delete, get, patch, post
2325

@@ -100,4 +102,12 @@ async def reorder_nodes(
100102
body: Annotated[ReorderNodeRequestDto, PydanticBody()],
101103
) -> ReorderNodeResponseDto:
102104
"""Reorder Nodes"""
105+
...
106+
107+
@post("/nodes/actions/reset-traffic", response_class=ResetNodeTrafficResponseDto)
108+
async def reset_traffic_all_nodes(
109+
self,
110+
body: Annotated[ResetNodeTrafficRequestDto, PydanticBody()],
111+
) -> ResetNodeTrafficResponseDto:
112+
"""Reset Traffic All Nodes"""
103113
...

remnawave/enums/error_code.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,156 @@ class ErrorCode(StrEnum):
7878
SUBSCRIPTION_SETTINGS_NOT_FOUND = "A071"
7979
GET_SUBSCRIPTION_SETTINGS_ERROR = "A072"
8080
UPDATE_SUBSCRIPTION_SETTINGS_ERROR = "A073"
81+
CREATE_INBOUND_ERROR = "A074"
82+
DELETE_INBOUND_ERROR = "A075"
83+
GET_INBOUND_ERROR = "A076"
84+
INBOUND_NOT_FOUND = "A077"
85+
INBOUND_TAG_ALREADY_EXISTS = "A078"
86+
CREATE_HOST_BULK_ACTION_ERROR = "A079"
87+
DELETE_HOST_BULK_ACTION_ERROR = "A080"
88+
UPDATE_HOST_BULK_ACTION_ERROR = "A081"
89+
BULK_ACTION_NOT_FOUND = "A082"
90+
GET_USERS_STATS_ERROR = "A083"
91+
RESET_USERS_TRAFFIC_BULK_ERROR = "A084"
92+
UPDATE_USERS_BULK_ERROR = "A085"
93+
DELETE_USERS_BULK_ERROR = "A086"
94+
GET_USERS_BULK_ERROR = "A087"
95+
CREATE_TEMPLATE_ERROR = "A088"
96+
TEMPLATE_NOT_FOUND = "A089"
97+
UPDATE_TEMPLATE_ERROR = "A090"
98+
DELETE_TEMPLATE_ERROR = "A091"
99+
TEMPLATE_NAME_ALREADY_EXISTS = "A092"
100+
GET_TEMPLATE_ERROR = "A093"
101+
GET_ALL_TEMPLATES_ERROR = "A094"
102+
GENERATE_CONFIG_ERROR = "A095"
103+
INVALID_TEMPLATE_TYPE = "A096"
104+
CREATE_EXTERNAL_SQUAD_ERROR = "A097"
105+
EXTERNAL_SQUAD_NOT_FOUND = "A098"
106+
UPDATE_EXTERNAL_SQUAD_ERROR = "A099"
107+
DELETE_EXTERNAL_SQUAD_ERROR = "A100"
108+
EXTERNAL_SQUAD_NAME_ALREADY_EXISTS = "A101"
109+
ADD_USERS_TO_EXTERNAL_SQUAD_ERROR = "A102"
110+
REMOVE_USERS_FROM_EXTERNAL_SQUAD_ERROR = "A103"
111+
GET_EXTERNAL_SQUAD_ERROR = "A104"
112+
GET_ALL_EXTERNAL_SQUADS_ERROR = "A105"
113+
CREATE_INTERNAL_SQUAD_ERROR = "A106"
114+
INTERNAL_SQUAD_NOT_FOUND = "A107"
115+
UPDATE_INTERNAL_SQUAD_ERROR = "A108"
116+
DELETE_INTERNAL_SQUAD_ERROR = "A109"
117+
INTERNAL_SQUAD_NAME_ALREADY_EXISTS = "A110"
118+
GET_INTERNAL_SQUAD_ERROR = "A111"
119+
GET_ALL_INTERNAL_SQUADS_ERROR = "A112"
120+
CREATE_WEBHOOK_ERROR = "A113"
121+
WEBHOOK_NOT_FOUND = "A114"
122+
UPDATE_WEBHOOK_ERROR = "A115"
123+
DELETE_WEBHOOK_ERROR = "A116"
124+
WEBHOOK_URL_ALREADY_EXISTS = "A117"
125+
GET_WEBHOOK_ERROR = "A118"
126+
GET_ALL_WEBHOOKS_ERROR = "A119"
127+
WEBHOOK_DELIVERY_ERROR = "A120"
128+
CREATE_PASSKEY_ERROR = "A121"
129+
PASSKEY_NOT_FOUND = "A122"
130+
DELETE_PASSKEY_ERROR = "A123"
131+
GET_PASSKEY_ERROR = "A124"
132+
GET_ALL_PASSKEYS_ERROR = "A125"
133+
PASSKEY_ALREADY_EXISTS = "A126"
134+
CREATE_SNIPPET_ERROR = "A127"
135+
SNIPPET_NOT_FOUND = "A128"
136+
UPDATE_SNIPPET_ERROR = "A129"
137+
DELETE_SNIPPET_ERROR = "A130"
138+
SNIPPET_NAME_ALREADY_EXISTS = "A131"
139+
GET_SNIPPET_ERROR = "A132"
140+
GET_ALL_SNIPPETS_ERROR = "A133"
141+
HWID_RESET_ERROR = "A134"
142+
HWID_NOT_FOUND = "A135"
143+
GET_HWID_ERROR = "A136"
144+
GET_ALL_HWIDS_ERROR = "A137"
145+
DELETE_HWID_ERROR = "A138"
146+
BANDWIDTH_STATS_ERROR = "A139"
147+
GET_NODES_USAGE_STATS_ERROR = "A140"
148+
SUBSCRIPTION_REQUEST_ERROR = "A141"
149+
SUBSCRIPTION_REQUEST_NOT_FOUND = "A142"
150+
GET_SUBSCRIPTION_REQUEST_ERROR = "A143"
151+
GET_ALL_SUBSCRIPTION_REQUESTS_ERROR = "A144"
152+
APPROVE_SUBSCRIPTION_REQUEST_ERROR = "A145"
153+
REJECT_SUBSCRIPTION_REQUEST_ERROR = "A146"
154+
CREATE_SUBSCRIPTION_REQUEST_HISTORY_ERROR = "A147"
155+
GET_SUBSCRIPTION_REQUEST_HISTORY_ERROR = "A148"
156+
KEYGEN_ERROR = "A149"
157+
GENERATE_KEYS_ERROR = "A150"
158+
INVALID_KEY_TYPE = "A151"
159+
SYSTEM_STATS_ERROR = "A152"
160+
SYSTEM_HEALTH_ERROR = "A153"
161+
NODES_METRICS_ERROR = "A154"
162+
X25519_KEYGEN_ERROR = "A155"
163+
HAPP_CRYPTO_ERROR = "A156"
164+
SRR_MATCHER_ERROR = "A157"
165+
GET_REMNAWAVE_SETTINGS_ERROR = "A158"
166+
UPDATE_REMNAWAVE_SETTINGS_ERROR = "A159"
167+
OAUTH_ERROR = "A160"
168+
PASSKEY_SETTINGS_ERROR = "A161"
169+
TELEGRAM_AUTH_ERROR = "A162"
170+
BRANDING_SETTINGS_ERROR = "A163"
171+
CONFIG_PROFILE_ERROR = "A164"
172+
CONFIG_PROFILE_NOT_FOUND = "A165"
173+
CREATE_CONFIG_PROFILE_ERROR = "A166"
174+
UPDATE_CONFIG_PROFILE_ERROR = "A167"
175+
DELETE_CONFIG_PROFILE_ERROR = "A168"
176+
GET_CONFIG_PROFILE_ERROR = "A169"
177+
GET_ALL_CONFIG_PROFILES_ERROR = "A170"
178+
XRAY_CONFIG_ERROR = "A171"
179+
XRAY_CONFIG_VALIDATION_ERROR = "A172"
180+
INFRA_BILLING_ERROR = "A173"
181+
INFRA_BILLING_NOT_FOUND = "A174"
182+
GET_INFRA_BILLING_ERROR = "A175"
183+
UPDATE_INFRA_BILLING_ERROR = "A176"
184+
CALCULATE_BILLING_ERROR = "A177"
185+
BILLING_PERIOD_ERROR = "A178"
186+
187+
# Добавляем новые коды из failed тестов
188+
CREATE_SUBSCRIPTION_TEMPLATE_ERROR = "A179"
189+
SUBSCRIPTION_TEMPLATE_NOT_FOUND = "A180"
190+
UPDATE_SUBSCRIPTION_TEMPLATE_ERROR = "A181"
191+
DELETE_SUBSCRIPTION_TEMPLATE_ERROR = "A182"
192+
GET_SUBSCRIPTION_TEMPLATE_ERROR = "A183"
193+
194+
# Валидационные ошибки
195+
VALIDATION_ERROR = "V001"
196+
INVALID_UUID_FORMAT = "V002"
197+
INVALID_EMAIL_FORMAT = "V003"
198+
INVALID_DATE_FORMAT = "V004"
199+
REQUIRED_FIELD_MISSING = "V005"
200+
FIELD_TOO_LONG = "V006"
201+
FIELD_TOO_SHORT = "V007"
202+
INVALID_ENUM_VALUE = "V008"
203+
INVALID_REGEX_PATTERN = "V009"
204+
NUMERIC_VALIDATION_ERROR = "V010"
205+
206+
# Сетевые ошибки
207+
NETWORK_ERROR = "N003"
208+
TIMEOUT_ERROR = "N004"
209+
CONNECTION_ERROR = "N005"
210+
DNS_ERROR = "N006"
211+
SSL_ERROR = "N007"
212+
213+
# Ошибки аутентификации и авторизации
214+
INVALID_TOKEN = "AUTH001"
215+
TOKEN_EXPIRED = "AUTH002"
216+
INVALID_CREDENTIALS = "AUTH003"
217+
TWO_FACTOR_REQUIRED = "AUTH004"
218+
ACCOUNT_LOCKED = "AUTH005"
219+
PASSWORD_COMPLEXITY_ERROR = "AUTH006"
220+
221+
# Ошибки бизнес-логики
222+
TRAFFIC_LIMIT_EXCEEDED = "BL001"
223+
USER_LIMIT_EXCEEDED = "BL002"
224+
SUBSCRIPTION_EXPIRED = "BL003"
225+
FEATURE_NOT_AVAILABLE = "BL004"
226+
QUOTA_EXCEEDED = "BL005"
227+
RESOURCE_LOCKED = "BL006"
228+
229+
# Общие коды
230+
UNKNOWN = "UNKNOWN"
231+
NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
232+
MAINTENANCE_MODE = "MAINTENANCE"
233+
RATE_LIMIT_EXCEEDED = "RATE_LIMIT"

remnawave/exceptions/__init__.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,39 @@
1-
from .handler import handle_api_error
21
from .general import (
3-
ConflictError,
2+
ApiError,
43
ApiErrorResponse,
4+
AuthenticationError,
55
BadRequestError,
6-
NotFoundError,
6+
BusinessLogicError,
7+
ConflictError,
8+
FeatureNotAvailableError,
79
ForbiddenError,
8-
UnauthorizedError,
10+
MaintenanceError,
11+
NetworkError,
12+
NotFoundError,
13+
QuotaExceededError,
14+
RateLimitError,
915
ServerError,
10-
ApiError,
16+
UnauthorizedError,
17+
ValidationError,
1118
)
19+
from .handler import handle_api_error
1220

1321
__all__ = [
14-
"handle_api_error",
1522
"ApiError",
1623
"ApiErrorResponse",
17-
"NotFoundError",
24+
"AuthenticationError",
1825
"BadRequestError",
19-
"ForbiddenError",
20-
"UnauthorizedError",
26+
"BusinessLogicError",
2127
"ConflictError",
28+
"FeatureNotAvailableError",
29+
"ForbiddenError",
30+
"MaintenanceError",
31+
"NetworkError",
32+
"NotFoundError",
33+
"QuotaExceededError",
34+
"RateLimitError",
2235
"ServerError",
23-
]
36+
"UnauthorizedError",
37+
"ValidationError",
38+
"handle_api_error",
39+
]

remnawave/exceptions/general.py

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from datetime import datetime
2+
from typing import Any, List, Optional
23

34
from pydantic import AliasChoices, BaseModel, Field
45

56
from remnawave.enums import ErrorCode
67

78

8-
from typing import Any, List, Optional
9-
10-
119
class ApiErrorResponse(BaseModel):
10+
"""Standard API error response model"""
1211
timestamp: Optional[datetime] = Field(None, description="Время возникновения ошибки")
1312
path: Optional[str] = Field(None, description="Путь запроса")
1413
message: str = Field(..., description="Сообщение об ошибке")
@@ -23,45 +22,102 @@ class ApiErrorResponse(BaseModel):
2322

2423

2524
class ApiError(Exception):
25+
"""Base API error exception"""
26+
2627
def __init__(self, status_code: int, error: ApiErrorResponse):
2728
self.status_code = status_code
2829
self.error = error
2930
super().__init__(
3031
f"API Error {error.code}: {error.message} (HTTP {status_code})"
3132
)
3233

34+
@property
35+
def code(self) -> Optional[str]:
36+
"""Get error code"""
37+
return self.error.code
38+
39+
@property
40+
def message(self) -> str:
41+
"""Get error message"""
42+
return self.error.message
43+
44+
@property
45+
def timestamp(self) -> Optional[datetime]:
46+
"""Get error timestamp"""
47+
return self.error.timestamp
48+
49+
@property
50+
def path(self) -> Optional[str]:
51+
"""Get request path"""
52+
return self.error.path
53+
3354

3455
class BadRequestError(ApiError):
3556
"""Ошибки клиента (400)"""
36-
3757
pass
3858

3959

4060
class UnauthorizedError(ApiError):
4161
"""Ошибка авторизации (401)"""
42-
4362
pass
4463

4564

4665
class ForbiddenError(ApiError):
4766
"""Доступ запрещен (403)"""
48-
4967
pass
5068

5169

5270
class NotFoundError(ApiError):
5371
"""Ресурс не найден (404)"""
54-
5572
pass
5673

5774

5875
class ConflictError(ApiError):
5976
"""Конфликт (409)"""
77+
pass
6078

79+
80+
class ValidationError(ApiError):
81+
"""Ошибка валидации данных (422)"""
6182
pass
6283

6384

6485
class ServerError(ApiError):
65-
"""Серверная ошибка (500)"""
86+
"""Серверная ошибка (500+)"""
87+
pass
6688

89+
90+
# Новые специализированные исключения
91+
class NetworkError(ApiError):
92+
"""Сетевые ошибки"""
6793
pass
94+
95+
96+
class AuthenticationError(ApiError):
97+
"""Ошибки аутентификации"""
98+
pass
99+
100+
101+
class BusinessLogicError(ApiError):
102+
"""Ошибки бизнес-логики"""
103+
pass
104+
105+
106+
class RateLimitError(BadRequestError):
107+
"""Превышен лимит запросов"""
108+
pass
109+
110+
111+
class MaintenanceError(ServerError):
112+
"""Режим обслуживания"""
113+
pass
114+
115+
116+
class QuotaExceededError(BusinessLogicError):
117+
"""Превышена квота"""
118+
pass
119+
120+
121+
class FeatureNotAvailableError(BusinessLogicError):
122+
"""Функция недоступна"""
123+
pass

0 commit comments

Comments
 (0)