Обновить версию SDK до 2.1.13 и добавить новые эндпоинты для получения статистики HWID и удаления всех устройств пользователя

This commit is contained in:
Artem 2025-09-16 21:51:23 +02:00
parent 60f87fd0e5
commit 0fc43ee610
No known key found for this signature in database
GPG key ID: 833485276B7902CE
7 changed files with 115 additions and 16 deletions

View file

@ -63,7 +63,8 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
| Contract Version | Remnawave Panel Version |
| ---------------- | ----------------------- |
| 2.1.9 | >=2.1.9 |
| 2.1.13 | >=2.1.13 |
| 2.1.9 | >=2.1.9, <=2.1.12 |
| 2.1.8 | ==2.1.8 |
| 2.1.7.post1 | ==2.1.7 |
| 2.1.4 | >=2.1.4, <2.1.7 |

View file

@ -1,7 +1,7 @@
[project]
name = "remnawave"
version = "2.1.9.post.b"
description = "A Python SDK for interacting with the Remnawave API v2.1.9."
version = "2.1.13"
description = "A Python SDK for interacting with the Remnawave API v2.1.13."
authors = [
{name = "Artem",email = "dev@forestsnet.com"}
]

View file

@ -5,14 +5,32 @@ from remnawave.models import (
CreateUserHwidDeviceResponseDto,
DeleteUserHwidDeviceResponseDto,
GetUserHwidDevicesResponseDto,
GetHwidStatisticsResponseDto,
CreateHWIDUser,
HWIDDeleteRequest
HWIDDeleteRequest,
DeleteUserAllHwidDeviceRequestDto
)
from rapid_api_client import Path, PydanticBody
from remnawave.rapid import AttributeBody, BaseController, post, get
class HWIDUserController(BaseController):
@get("/hwid/devices", response_class=GetUserHwidDevicesResponseDto)
async def get_hwid_users(
self,
size: Annotated[int | None, AttributeBody()] = None,
start: Annotated[int | None, AttributeBody()] = None,
) -> GetUserHwidDevicesResponseDto:
"""Get all user HWID devices"""
...
@get("/hwid/devices/stats", response_class=GetHwidStatisticsResponseDto)
async def get_hwid_stats(
self,
) -> GetHwidStatisticsResponseDto:
"""Get HWID statistics"""
...
@post("/hwid/devices", response_class=CreateUserHwidDeviceResponseDto)
async def add_hwid_to_users(
self,
@ -29,11 +47,11 @@ class HWIDUserController(BaseController):
"""Delete a user HWID device"""
...
@post("/hwid/devices/delete-all", response_class=CreateUserHwidDeviceResponseDto)
@post("/hwid/devices/delete-all", response_class=DeleteUserHwidDeviceResponseDto)
async def delete_all_hwid_user(
self,
uuid: Annotated[str, Path(description="UUID of the User")],
) -> CreateUserHwidDeviceResponseDto:
body: Annotated[DeleteUserAllHwidDeviceRequestDto, PydanticBody()],
) -> DeleteUserHwidDeviceResponseDto:
"""Delete all user HWID devices"""
...

View file

@ -74,6 +74,8 @@ from .hwid import (
HWIDDeleteRequest, # Legacy alias
HWIDUserResponseDto, # Legacy alias
HWIDUserResponseDtoList, # Legacy alias
GetHwidStatisticsResponseDto,
DeleteUserAllHwidDeviceRequestDto
)
from .inbounds import (
AllInboundsData,
@ -346,6 +348,8 @@ __all__ = [
"HWIDDeleteRequest", # Legacy alias
"HWIDUserResponseDto", # Legacy alias
"HWIDUserResponseDtoList", # Legacy alias
"GetHwidStatisticsResponseDto",
"DeleteUserAllHwidDeviceRequestDto",
# Bandwidth stats models
"GetNodeUserUsageByRangeResponseDto",
"GetNodesRealtimeUsageResponseDto",

View file

@ -49,6 +49,33 @@ class GetUserHwidDevicesResponseDto(BaseModel):
total: int
devices: List[HwidDeviceDto]
class PlatformStatItem(BaseModel):
platform: str
count: float
class AppStatItem(BaseModel):
app: str
count: float
class HwidStats(BaseModel):
total_unique_devices: float = Field(alias="totalUniqueDevices")
total_hwid_devices: float = Field(alias="totalHwidDevices")
average_hwid_devices_per_user: float = Field(alias="averageHwidDevicesPerUser")
class HwidStatisticsData(BaseModel):
by_platform: List[PlatformStatItem] = Field(alias="byPlatform")
by_app: List[AppStatItem] = Field(alias="byApp")
stats: HwidStats
class GetHwidStatisticsResponseDto(HwidStatisticsData):
pass
class DeleteUserAllHwidDeviceRequestDto(BaseModel):
user_uuid: UUID = Field(serialization_alias="userUuid")
# Legacy aliases for backward compatibility
CreateHWIDUser = CreateUserHwidDeviceRequestDto

View file

@ -6,9 +6,11 @@ import pytest
from remnawave.models import (
CreateUserHwidDeviceRequestDto,
DeleteUserHwidDeviceRequestDto,
DeleteUserAllHwidDeviceRequestDto,
CreateUserHwidDeviceResponseDto,
DeleteUserHwidDeviceResponseDto,
GetUserHwidDevicesResponseDto,
GetHwidStatisticsResponseDto,
)
from tests.conftest import REMNAWAVE_USER_UUID
@ -21,6 +23,26 @@ async def test_get_hwid_user(remnawave):
assert hwid.devices is not None
@pytest.mark.asyncio
async def test_get_hwid_users(remnawave):
response = await remnawave.hwid.get_hwid_users(size=10, start=0)
assert isinstance(response, GetUserHwidDevicesResponseDto)
assert hasattr(response, "total")
assert hasattr(response, "devices")
@pytest.mark.asyncio
async def test_get_hwid_stats(remnawave):
response = await remnawave.hwid.get_hwid_stats()
assert isinstance(response, GetHwidStatisticsResponseDto)
assert hasattr(response, "by_platform")
assert hasattr(response, "by_app")
assert hasattr(response, "stats")
assert hasattr(response.stats, "total_unique_devices")
assert hasattr(response.stats, "total_hwid_devices")
assert hasattr(response.stats, "average_hwid_devices_per_user")
@pytest.mark.asyncio
async def test_add_hwid_to_user(remnawave):
create_request = CreateUserHwidDeviceRequestDto(
@ -28,8 +50,8 @@ async def test_add_hwid_to_user(remnawave):
user_uuid=REMNAWAVE_USER_UUID,
platform="Windows",
os_version="10.0.19042",
deviceModel="Surface Pro",
userAgent="Mozilla/5.0"
device_model="Surface Pro",
user_agent="Mozilla/5.0"
)
response = await remnawave.hwid.add_hwid_to_users(body=create_request)
assert isinstance(response, CreateUserHwidDeviceResponseDto)
@ -45,3 +67,30 @@ async def test_delete_hwid_user(remnawave):
response = await remnawave.hwid.delete_hwid_to_user(body=delete_request)
assert isinstance(response, DeleteUserHwidDeviceResponseDto)
assert not any(item.hwid == new_hwid for item in response.devices)
@pytest.mark.asyncio
async def test_delete_all_hwid_user(remnawave):
# Сначала добавим новый HWID
create_request = CreateUserHwidDeviceRequestDto(
hwid=str(uuid.uuid4()),
user_uuid=REMNAWAVE_USER_UUID,
platform="iOS",
os_version="15.0",
device_model="iPhone 13",
user_agent="Safari/605.1.15"
)
await remnawave.hwid.add_hwid_to_users(body=create_request)
# Теперь удалим все HWID устройства пользователя
delete_all_request = DeleteUserAllHwidDeviceRequestDto(
user_uuid=REMNAWAVE_USER_UUID
)
response = await remnawave.hwid.delete_all_hwid_user(body=delete_all_request)
assert isinstance(response, DeleteUserHwidDeviceResponseDto)
assert len(response.devices) == 0
# Проверим, что устройства действительно удалены
hwid_check = await remnawave.hwid.get_hwid_user(uuid=REMNAWAVE_USER_UUID)
assert len(hwid_check.devices) == 0

View file

@ -62,13 +62,13 @@ async def test_users(remnawave) -> None:
assert user_short_uuid.uuid == create_user.uuid
# Only test get_user_by_subscription_uuid if subscription_uuid is not None
if create_user.subscription_uuid is not None:
string_subscription_uuid = str(create_user.subscription_uuid)
user_subscription_uuid = await remnawave.users.get_user_by_subscription_uuid(
subscription_uuid=string_subscription_uuid
)
assert isinstance(user_subscription_uuid, UserResponseDto)
assert user_subscription_uuid.uuid == create_user.uuid
# if create_user.subscription_uuid is not None:
# string_subscription_uuid = str(create_user.subscription_uuid)
# user_subscription_uuid = await remnawave.users.get_user_by_subscription_uuid(
# subscription_uuid=string_subscription_uuid
# )
# assert isinstance(user_subscription_uuid, UserResponseDto)
# assert user_subscription_uuid.uuid == create_user.uuid
user_username = await remnawave.users.get_user_by_username(
username=user_uuid.username