feat: обновить версию SDK до 2.6.1 и добавить поддержку тегов узлов и обновления паролей

This commit is contained in:
Artem 2026-02-18 01:13:33 +01:00
parent 5f20e55bd1
commit 6c9fa202ad
No known key found for this signature in database
GPG key ID: 833485276B7902CE
16 changed files with 133 additions and 41 deletions

View file

@ -50,6 +50,7 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
| Contract Version | Remnawave Panel Version |
| ---------------- | ----------------------- |
| 2.6.1 | >=2.6.0 |
| 2.4.4 | >=2.4.0 |
| 2.3.2 | >=2.3.0, <2.4.0 |
| 2.3.0 | >=2.3.0, <2.3.2 |

View file

@ -1,7 +1,7 @@
[project]
name = "remnawave"
version = "2.4.4"
description = "A Python SDK for interacting with the Remnawave API v2.4.4."
version = "2.6.1"
description = "A Python SDK for interacting with the Remnawave API v2.6.1."
authors = [
{name = "Artem",email = "dev@forestsnet.com"}
]

View file

@ -10,6 +10,7 @@ from remnawave.models import (
DisableNodeResponseDto,
EnableNodeResponseDto,
GetAllNodesResponseDto,
GetAllNodesTagsResponseDto,
GetOneNodeResponseDto,
ReorderNodeRequestDto,
ReorderNodeResponseDto,
@ -29,6 +30,13 @@ from remnawave.rapid import BaseController, delete, get, patch, post
class NodesController(BaseController):
@get("/nodes/tags", response_class=GetAllNodesTagsResponseDto)
async def get_all_nodes_tags(
self,
) -> GetAllNodesTagsResponseDto:
"""Get all nodes tags"""
...
@post("/nodes", response_class=CreateNodeResponseDto)
async def create_node(
self,

View file

@ -7,10 +7,12 @@ from remnawave.models import (
DeletePasskeyResponseDto,
GetAllPasskeysResponseDto,
GetPasskeyRegistrationOptionsResponseDto,
UpdatePasskeyRequestDto,
UpdatePasskeyResponseDto,
VerifyPasskeyRegistrationRequestDto,
VerifyPasskeyRegistrationResponseDto,
)
from remnawave.rapid import BaseController, delete, get, post
from remnawave.rapid import BaseController, delete, get, patch, post
class PasskeysController(BaseController):
@ -42,4 +44,12 @@ class PasskeysController(BaseController):
body: Annotated[DeletePasskeyRequestDto, PydanticBody()],
) -> DeletePasskeyResponseDto:
"""Delete a passkey by ID"""
...
@patch("/passkeys", response_class=UpdatePasskeyResponseDto)
async def update_passkey(
self,
body: Annotated[UpdatePasskeyRequestDto, PydanticBody()],
) -> UpdatePasskeyResponseDto:
"""Update a passkey name"""
...

View file

@ -4,4 +4,5 @@ class OAuth2Provider(StrEnum):
"""OAuth2 Provider enum"""
GITHUB = "github"
POCKETID = "pocketid"
YANDEX = "yandex"
YANDEX = "yandex"
KEYCLOAK = "keycloak"

View file

@ -179,6 +179,7 @@ from .nodes import (
EnableNodeResponseDto,
ExcludedInbounds,
GetAllNodesResponseDto,
GetAllNodesTagsResponseDto,
GetOneNodeResponseDto,
NodeConfigProfileDto,
NodeConfigProfileRequestDto,
@ -388,6 +389,8 @@ from .passkeys import (
GetAllPasskeysResponseDto,
GetPasskeyRegistrationOptionsResponseDto,
PasskeyDto,
UpdatePasskeyRequestDto,
UpdatePasskeyResponseDto,
VerifyPasskeyRegistrationRequestDto,
VerifyPasskeyRegistrationResponseDto,
)
@ -422,6 +425,8 @@ from .remnawave_settings import (
BrandingSettings,
GetRemnawaveSettingsResponseDto,
GitHubOAuth2Settings,
GenericOAuth2Settings,
KeycloakOAuth2Settings,
OAuth2Settings,
PasskeySettings,
PasswordSettings,
@ -474,6 +479,7 @@ __all__ = [
"EnableNodeResponseDto",
"ExcludedInbounds",
"GetAllNodesResponseDto",
"GetAllNodesTagsResponseDto",
"GetOneNodeResponseDto",
"NodeResponseDto",
"NodesResponseDto", # Legacy alias
@ -815,6 +821,8 @@ __all__ = [
"GetAllPasskeysResponseDto",
"GetPasskeyRegistrationOptionsResponseDto",
"PasskeyDto",
"UpdatePasskeyRequestDto",
"UpdatePasskeyResponseDto",
"VerifyPasskeyRegistrationRequestDto",
"VerifyPasskeyRegistrationResponseDto",
@ -850,7 +858,9 @@ __all__ = [
"BrandingSettings",
"GetRemnawaveSettingsResponseDto",
"GenericOAuth2Settings",
"GitHubOAuth2Settings",
"KeycloakOAuth2Settings",
"OAuth2Settings",
"PasskeySettings",
"PasswordSettings",

View file

@ -12,7 +12,7 @@ class InboundDto(BaseModel):
type: str
network: Optional[str] = None
security: Optional[str] = None
port: Optional[int] = None
port: Optional[float] = None
raw_inbound: Optional[Any] = Field(None, alias="rawInbound")
class NodesProfileDto(BaseModel):
@ -23,6 +23,7 @@ class NodesProfileDto(BaseModel):
class ConfigProfileDto(BaseModel):
uuid: UUID
name: str
view_position: int = Field(alias="viewPosition")
config: Dict[str, Any]
inbounds: List[InboundDto]
nodes: List[NodesProfileDto] = []

View file

@ -49,6 +49,7 @@ class ExternalSquadHostOverridesDto(BaseModel):
class ExternalSquadDto(BaseModel):
"""External squad data model"""
uuid: UUID
view_position: int = Field(alias="viewPosition")
name: str
info: ExternalSquadInfoDto
templates: List[ExternalSquadTemplateDto]

View file

@ -65,18 +65,18 @@ class HostResponseDto(BaseModel):
remark: str
address: str
port: int
path: Optional[str] = None
sni: Optional[str] = None
host: Optional[str] = None
alpn: Optional[str] = None
fingerprint: Optional[str] = None
x_http_extra_params: Optional[Dict[str, Any]] = Field(None, alias="xHttpExtraParams")
mux_params: Optional[Dict[str, Any]] = Field(None, alias="muxParams")
sockopt_params: Optional[Dict[str, Any]] = Field(None, alias="sockoptParams")
path: str | None = Field(alias="path")
sni: str | None = Field(alias="sni")
host: str | None = Field(alias="host")
alpn: str | None = Field(alias="alpn")
fingerprint: str | None = Field(alias="fingerprint")
x_http_extra_params: Dict[str, Any] | None = Field(alias="xHttpExtraParams")
mux_params: Dict[str, Any] | None = Field(alias="muxParams")
sockopt_params: Dict[str, Any] | None = Field(alias="sockoptParams")
inbound: HostInboundData
server_description: Optional[str] = Field(None, alias="serverDescription")
tag: Optional[str] = None
vless_route_id: Optional[int] = Field(None, alias="vlessRouteId")
server_description: str | None = Field(alias="serverDescription")
tag: str | None = Field(alias="tag")
vless_route_id: int | None = Field(alias="vlessRouteId")
shuffle_host: bool = Field(alias="shuffleHost")
mihomo_x25519: bool = Field(alias="mihomoX25519")
nodes: List[UUID]
@ -86,7 +86,7 @@ class HostResponseDto(BaseModel):
override_sni_from_address: bool = Field(False, alias="overrideSniFromAddress")
keep_blank_sni: bool = Field(False, alias="keepBlankSni")
allow_insecure: bool = Field(False, alias="allowInsecure")
xray_json_template_uuid: Optional[UUID] = Field(None, alias="xrayJsonTemplateUuid")
xray_json_template_uuid: UUID | None = Field(alias="xrayJsonTemplateUuid")
excluded_internal_squads: List[UUID] = Field(default_factory=list, alias="excludedInternalSquads")
@property

View file

@ -10,10 +10,10 @@ class InboundsDto(BaseModel):
profile_uuid: UUID = Field(alias="profileUuid")
tag: str
type: str
network: Optional[str] = Field(default=None)
security: Optional[str] = Field(default=None)
port: Optional[float] = Field(default=None)
raw_inbound: Optional[dict] = Field(default=None, alias="rawInbound")
network: Optional[str] = None
security: Optional[str] = None
port: Optional[float] = None
raw_inbound: Optional[dict] = Field(None, alias="rawInbound")
class InfoDto(BaseModel):
@ -23,6 +23,7 @@ class InfoDto(BaseModel):
class InternalSquadDto(BaseModel):
uuid: UUID
view_position: int = Field(alias="viewPosition")
name: str
info: Optional[InfoDto] = Field(default=None)
inbounds: List[InboundsDto] = Field(default_factory=list)

View file

@ -28,6 +28,11 @@ class ReorderNodeItem(BaseModel):
uuid: UUID
class GetAllNodesTagsResponseDto(BaseModel):
"""Response with all nodes tags"""
tags: List[str]
class NodeProviderDto(BaseModel):
"""Node provider information"""
uuid: UUID
@ -79,6 +84,11 @@ class CreateNodeRequestDto(BaseModel):
serialization_alias="configProfile"
)
provider_uuid: Optional[UUID] = Field(None, serialization_alias="providerUuid")
tags: Optional[List[Annotated[str, StringConstraints(max_length=36, pattern=r'^[A-Z0-9_:]+$')]]] = Field(
None,
serialization_alias="tags",
max_length=10
)
class UpdateNodeRequestDto(BaseModel):
@ -111,6 +121,11 @@ class UpdateNodeRequestDto(BaseModel):
None, serialization_alias="configProfile"
)
provider_uuid: Optional[UUID] = Field(None, serialization_alias="providerUuid")
tags: Optional[List[Annotated[str, StringConstraints(max_length=36, pattern=r'^[A-Z0-9_:]+$')]]] = Field(
None,
serialization_alias="tags",
max_length=10
)
class ReorderNodeRequestDto(BaseModel):
@ -147,6 +162,7 @@ class NodeResponseDto(BaseModel):
config_profile: NodeConfigProfileDto = Field(alias="configProfile")
provider_uuid: Optional[UUID] = Field(None, alias="providerUuid")
provider: Optional[NodeProviderDto] = None
tags: List[str] = Field(default_factory=list, alias="tags")
class CreateNodeResponseDto(NodeResponseDto):

View file

@ -43,4 +43,15 @@ class DeletePasskeyRequestDto(BaseModel):
class DeletePasskeyResponseDto(BaseModel):
"""Response with updated passkeys list after deletion"""
passkeys: List[PasskeyDto]
passkeys: List[PasskeyDto]
class UpdatePasskeyRequestDto(BaseModel):
"""Request to update a passkey"""
id: str
name: str
class UpdatePasskeyResponseDto(BaseModel):
"""Response with updated passkey information"""
passkey: PasskeyDto

View file

@ -6,32 +6,55 @@ from pydantic import BaseModel, Field, HttpUrl
class PasskeySettings(BaseModel):
"""Passkey authentication settings"""
enabled: bool
rp_id: Optional[str] = Field(None, alias="rpId")
origin: Optional[str] = None
rp_id: str | None = Field(alias="rpId")
origin: str | None
class GitHubOAuth2Settings(BaseModel):
"""GitHub OAuth2 settings"""
enabled: bool
client_id: Optional[str] = Field(None, alias="clientId")
client_secret: Optional[str] = Field(None, alias="clientSecret")
client_id: str | None = Field(alias="clientId")
client_secret: str | None = Field(alias="clientSecret")
allowed_emails: List[str] = Field(alias="allowedEmails")
class PocketIdOAuth2Settings(BaseModel):
"""PocketID OAuth2 settings"""
enabled: bool
client_id: Optional[str] = Field(None, alias="clientId")
client_secret: Optional[str] = Field(None, alias="clientSecret")
plain_domain: Optional[str] = Field(None, alias="plainDomain")
client_id: str | None = Field(alias="clientId")
client_secret: str | None = Field(alias="clientSecret")
plain_domain: str | None = Field(alias="plainDomain")
allowed_emails: List[str] = Field(alias="allowedEmails")
class YandexOAuth2Settings(BaseModel):
"""Yandex OAuth2 settings"""
enabled: bool
client_id: Optional[str] = Field(None, alias="clientId")
client_secret: Optional[str] = Field(None, alias="clientSecret")
client_id: str | None = Field(alias="clientId")
client_secret: str | None = Field(alias="clientSecret")
allowed_emails: List[str] = Field(alias="allowedEmails")
class KeycloakOAuth2Settings(BaseModel):
"""Keycloak OAuth2 settings"""
enabled: bool
realm: str | None
client_id: str | None = Field(alias="clientId")
client_secret: str | None = Field(alias="clientSecret")
frontend_domain: str | None = Field(alias="frontendDomain")
keycloak_domain: str | None = Field(alias="keycloakDomain")
allowed_emails: List[str] = Field(alias="allowedEmails")
class GenericOAuth2Settings(BaseModel):
"""Generic OAuth2 settings"""
enabled: bool
client_id: str | None = Field(alias="clientId")
client_secret: str | None = Field(alias="clientSecret")
with_pkce: bool = Field(alias="withPkce")
authorization_url: str | None = Field(alias="authorizationUrl")
token_url: str | None = Field(alias="tokenUrl")
frontend_domain: str | None = Field(alias="frontendDomain")
allowed_emails: List[str] = Field(alias="allowedEmails")
@ -40,12 +63,14 @@ class OAuth2Settings(BaseModel):
github: GitHubOAuth2Settings
pocketid: PocketIdOAuth2Settings
yandex: YandexOAuth2Settings
keycloak: KeycloakOAuth2Settings
generic: GenericOAuth2Settings
class TelegramAuthSettings(BaseModel):
"""Telegram authentication settings"""
enabled: bool
bot_token: Optional[str] = Field(None, alias="botToken")
bot_token: str | None = Field(alias="botToken")
admin_ids: List[str] = Field(alias="adminIds")
@ -62,9 +87,9 @@ class BrandingSettings(BaseModel):
class RemnawaveSettingsData(BaseModel):
"""Remnawave settings data"""
passkey_settings: Optional[PasskeySettings] = Field(None, alias="passkeySettings")
oauth2_settings: Optional[OAuth2Settings] = Field(None, alias="oauth2Settings")
tg_auth_settings: Optional[TelegramAuthSettings] = Field(None, alias="tgAuthSettings")
passkey_settings: PasskeySettings | None = Field(alias="passkeySettings")
oauth2_settings: OAuth2Settings | None = Field(alias="oauth2Settings")
tg_auth_settings: TelegramAuthSettings | None = Field(alias="tgAuthSettings")
password_settings: Optional[PasswordSettings] = Field(None, alias="passwordSettings")
branding_settings: Optional[BrandingSettings] = Field(None, alias="brandingSettings")

View file

@ -182,7 +182,7 @@ class UserSubscription(BaseModel):
lifetime_traffic_used: str = Field(alias="lifetimeTrafficUsed")
traffic_used_bytes: str = Field(alias="trafficUsedBytes")
traffic_limit_bytes: str = Field(alias="trafficLimitBytes")
lifetime_traffic_used_bytes: int = Field(alias="lifetimeTrafficUsedBytes")
lifetime_traffic_used_bytes: str = Field(alias="lifetimeTrafficUsedBytes")
traffic_limit_strategy: TrafficLimitStrategy = Field(alias="trafficLimitStrategy")
expires_at: datetime = Field(alias="expiresAt")
user_status: UserStatus = Field(alias="userStatus")

View file

@ -11,7 +11,7 @@ class SubscriptionPageConfigDto(BaseModel):
uuid: UUID
view_position: int = Field(alias="viewPosition")
name: str
config: Optional[Any] = None
config: Any | None
class GetSubscriptionPageConfigsData(BaseModel):
@ -25,9 +25,14 @@ class GetSubscriptionPageConfigsResponseDto(GetSubscriptionPageConfigsData):
pass
class GetSubscriptionPageConfigResponseDto(SubscriptionPageConfigDto):
class GetSubscriptionPageConfigResponseDto(BaseModel):
"""Response with single subscription page config"""
pass
model_config = ConfigDict(populate_by_name=True)
uuid: UUID
view_position: int = Field(alias="viewPosition")
name: str
config: Any
class CreateSubscriptionPageConfigRequestDto(BaseModel):

View file

@ -9,15 +9,17 @@ from remnawave.enums import TemplateType
class TemplateResponseDto(BaseModel):
uuid: UUID
name: str
view_position: int = Field(alias="viewPosition")
template_type: TemplateType = Field(alias="templateType")
template_json: Optional[Any] = Field(None, alias="templateJson")
encoded_template_yaml: Optional[str] = Field(None, alias="encodedTemplateYaml")
template_json: Any | None = Field(alias="templateJson")
encoded_template_yaml: str | None = Field(alias="encodedTemplateYaml")
class TemplateInfoDto(BaseModel):
"""Template info without content - used in list responses"""
uuid: UUID
name: str
view_position: int = Field(alias="viewPosition")
template_type: TemplateType = Field(alias="templateType")
template_json: Optional[Any] = Field(None, alias="templateJson")
encoded_template_yaml: Optional[str] = Field(None, alias="encodedTemplateYaml")