Skip to content

Commit 09b7294

Browse files
authored
Merge pull request #44 from remnawave:development
Update SDK for Python compatibility and add IP control features
2 parents f075a37 + 7300b57 commit 09b7294

11 files changed

Lines changed: 303 additions & 30 deletions

File tree

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ ipython_config.py
9999
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100100
# This is especially recommended for binary packages to ensure reproducibility, and is more
101101
# commonly ignored for libraries.
102-
#uv.lock
102+
uv.lock
103103

104104
# poetry
105105
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
@@ -178,7 +178,7 @@ test.py
178178
docs/
179179
openapi/
180180
.DS_Store
181-
requirements.txt
181+
requirements.txt
182182
requirements.in
183183
test_raw.py
184184
tests/test_one_time.py

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
5050

5151
| Contract Version | Remnawave Panel Version |
5252
| ---------------- | ----------------------- |
53+
| 2.6.2 | >=2.6.2 |
5354
| 2.6.1 | >=2.6.0 |
5455
| 2.4.4 | >=2.4.0 |
5556
| 2.3.2 | >=2.3.0, <2.4.0 |
@@ -104,7 +105,7 @@ async def main():
104105
remnawave = RemnawaveSDK(base_url=base_url, token=token)
105106

106107
# Fetch all users
107-
response: UsersResponseDto = await remnawave.users.get_all_users_v2()
108+
response: UsersResponseDto = await remnawave.users.get_all_users()
108109
total_users: int = response.total
109110
users: list[UserResponseDto] = response.users
110111
print("Total users: ", total_users)
@@ -122,4 +123,4 @@ This SDK was originally developed by [@kesevone](https://github.com/kesevone) fo
122123

123124
Previously maintained by [@sm1ky](https://github.com/sm1ky) at [`sm1ky/remnawave-api`](https://github.com/sm1ky/remnawave-api).
124125

125-
Now officially maintained by the Remnawave Community at [`remnawave/python-sdk`](https://github.com/remnawave/python-sdk).
126+
Now officially maintained by the Remnawave Community at [`remnawave/python-sdk`](https://github.com/remnawave/python-sdk).

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
[project]
22
name = "remnawave"
3-
version = "2.6.1"
4-
description = "A Python SDK for interacting with the Remnawave API v2.6.1."
3+
version = "2.6.2"
4+
description = "A Python SDK for interacting with the Remnawave API v2.6.2."
55
authors = [
66
{name = "Artem",email = "dev@forestsnet.com"}
77
]
88
license = { text = "MIT" }
99
readme = "README.md"
10-
requires-python = ">=3.11,<4.0"
10+
requires-python = ">=3.11,<3.14"
1111
dependencies = [
1212
"rapid-api-client (==0.6.0)",
1313
"orjson (>=3.10.15,<4.0.0)",

remnawave/__init__.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
SnippetsController,
3333
RemnawaveSettingsController,
3434
SubscriptionPageConfigController,
35+
IpControlController,
3536
)
3637

3738

@@ -44,6 +45,7 @@ def __init__(
4445
caddy_token: Optional[str] = None,
4546
ssl_ignore: Optional[bool] = False,
4647
custom_headers: Optional[dict] = None,
48+
cookies: Optional[dict] = None,
4749
):
4850
"""
4951
Remnawave SDK init
@@ -55,13 +57,15 @@ def __init__(
5557
caddy_token (Optional[str]): - Token for Caddy Auth (Headers). Defaults to None.
5658
ssl_ignore (Optional[bool]): - Whether to ignore SSL certificate errors. Defaults to False.
5759
custom_headers (Optional[dict]): - Custom headers to include in the requests. Defaults to None.
60+
cookies (Optional[dict]): - Cookies for Reverse Proxy authorization. Defaults to None.
5861
"""
5962
self._client = client
6063
self._token = token
6164
self.base_url = base_url
6265
self.caddy_token = caddy_token
6366
self.ssl_ignore = ssl_ignore
6467
self.custom_headers = custom_headers
68+
self.cookies = cookies
6569

6670
self._validate_params()
6771

@@ -96,6 +100,7 @@ def __init__(
96100
self.snippets = SnippetsController(self._client)
97101
self.remnawave_settings = RemnawaveSettingsController(self._client)
98102
self.subscription_page_config = SubscriptionPageConfigController(self._client)
103+
self.ip_control = IpControlController(self._client)
99104

100105
def _validate_params(self) -> None:
101106
if self._client is None:
@@ -108,13 +113,21 @@ def _validate_params(self) -> None:
108113
logging.warning(
109114
"base_url and token will be ignored if client is provided"
110115
)
116+
if self.cookies is not None:
117+
logging.warning(
118+
"cookies will be ignored if client is provided"
119+
)
111120

112121
def _prepare_client(self) -> httpx.AsyncClient:
113-
return httpx.AsyncClient(
114-
base_url=self._prepare_url(),
115-
headers=self._prepare_headers(),
116-
verify=not self.ssl_ignore,
117-
)
122+
client_kwargs = {
123+
"base_url": self._prepare_url(),
124+
"headers": self._prepare_headers(),
125+
"verify": not self.ssl_ignore,
126+
}
127+
if self.cookies is not None:
128+
client_kwargs["cookies"] = self.cookies
129+
130+
return httpx.AsyncClient(**client_kwargs)
118131

119132
def _prepare_headers(self) -> dict:
120133
headers = {}

remnawave/controllers/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .snippets import SnippetsController
2727
from .remnawave_settings import RemnawaveSettingsController
2828
from .subscription_page import SubscriptionPageConfigController
29+
from .ip_control import IpControlController
2930

3031
__all__ = [
3132
"APITokensManagementController",
@@ -55,5 +56,6 @@
5556
"ExternalSquadsController",
5657
"SnippetsController",
5758
"RemnawaveSettingsController",
58-
"SubscriptionPageConfigController"
59+
"SubscriptionPageConfigController",
60+
"IpControlController",
5961
]
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import Annotated
2+
3+
from rapid_api_client import Path
4+
from rapid_api_client.annotations import PydanticBody
5+
6+
from remnawave.models import (
7+
DropConnectionsRequestDto,
8+
DropConnectionsResponseDto,
9+
FetchIpsResponseDto,
10+
FetchIpsResultResponseDto,
11+
)
12+
from remnawave.rapid import BaseController, get, post
13+
14+
15+
class IpControlController(BaseController):
16+
@post("/ip-control/fetch-ips/{uuid}", response_class=FetchIpsResponseDto)
17+
async def fetch_user_ips(
18+
self,
19+
uuid: Annotated[str, Path(description="UUID of the user")],
20+
) -> FetchIpsResponseDto:
21+
"""Request IP List for User.
22+
23+
Starts a background job that queries all connected nodes for the IPs
24+
used by the given user. The returned ``job_id`` must be passed to
25+
:meth:`get_fetch_ips_result` to retrieve the actual list once the job
26+
is complete.
27+
"""
28+
...
29+
30+
@get("/ip-control/fetch-ips/result/{jobId}", response_class=FetchIpsResultResponseDto)
31+
async def get_fetch_ips_result(
32+
self,
33+
jobId: Annotated[str, Path(description="Job ID returned by fetch_user_ips")],
34+
) -> FetchIpsResultResponseDto:
35+
"""Get IP List Result by Job ID.
36+
37+
Poll this endpoint after calling :meth:`fetch_user_ips`. When
38+
``is_completed`` is ``True`` the ``result`` field contains per-node
39+
IP lists. When ``is_failed`` is ``True`` the job encountered an
40+
error.
41+
"""
42+
...
43+
44+
@post("/ip-control/drop-connections", response_class=DropConnectionsResponseDto)
45+
async def drop_connections(
46+
self,
47+
body: Annotated[DropConnectionsRequestDto, PydanticBody()],
48+
) -> DropConnectionsResponseDto:
49+
"""Drop active connections.
50+
51+
Sends a drop-connections event to the target nodes. You can specify
52+
the connections to drop either by user UUIDs or by IP addresses, and
53+
you can target all connected nodes or a specific subset.
54+
"""
55+
...

remnawave/models/__init__.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@
382382
BaseUserDto,
383383
UserDto,
384384
NodeDto,
385+
WebhookNodeConfigProfileDto,
385386
ConfigProfileInboundDto,
386387
InfraProviderDto,
387388
LoginAttemptDto,
@@ -467,6 +468,25 @@
467468
UpdateSubscriptionPageConfigRequestDto,
468469
UpdateSubscriptionPageConfigResponseDto,
469470
)
471+
from .ip_control import (
472+
# Request DTOs
473+
DropConnectionsRequestDto,
474+
DropByUserUuids,
475+
DropByIpAddresses,
476+
TargetAllNodes,
477+
TargetSpecificNodes,
478+
# Response DTOs
479+
FetchIpsResponseDto,
480+
FetchIpsResultResponseDto,
481+
DropConnectionsResponseDto,
482+
# Data models
483+
FetchIpsJobData,
484+
FetchIpsProgressData,
485+
FetchIpsNodeResult,
486+
FetchIpsResult,
487+
FetchIpsResultData,
488+
DropConnectionsResponseData,
489+
)
470490

471491
__all__ = [
472492
# Auth models
@@ -829,6 +849,7 @@
829849
"ConfigProfileInboundDto",
830850
"InfraProviderDto",
831851
"NodeDto",
852+
"WebhookNodeConfigProfileDto",
832853
"NodeEventDto",
833854

834855
# ERROR EVENTS
@@ -916,4 +937,20 @@
916937
"SubscriptionPageConfigDto",
917938
"UpdateSubscriptionPageConfigRequestDto",
918939
"UpdateSubscriptionPageConfigResponseDto",
940+
941+
# IP Control models
942+
"DropConnectionsRequestDto",
943+
"DropByUserUuids",
944+
"DropByIpAddresses",
945+
"TargetAllNodes",
946+
"TargetSpecificNodes",
947+
"FetchIpsResponseDto",
948+
"FetchIpsResultResponseDto",
949+
"DropConnectionsResponseDto",
950+
"FetchIpsJobData",
951+
"FetchIpsProgressData",
952+
"FetchIpsNodeResult",
953+
"FetchIpsResult",
954+
"FetchIpsResultData",
955+
"DropConnectionsResponseData",
919956
]

remnawave/models/external_squads.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ class ExternalSquadTemplateDto(BaseModel):
3030

3131
class ExternalSquadSubscriptionSettingsDto(BaseModel):
3232
"""External squad subscription settings"""
33-
profile_title: str = Field(alias="profileTitle")
34-
support_link: str = Field(alias="supportLink")
35-
profile_update_interval: int = Field(alias="profileUpdateInterval", ge=1)
36-
is_profile_webpage_url_enabled: bool = Field(alias="isProfileWebpageUrlEnabled")
37-
serve_json_at_base_subscription: bool = Field(alias="serveJsonAtBaseSubscription")
38-
is_show_custom_remarks: bool = Field(alias="isShowCustomRemarks")
33+
profile_title: Optional[str] = Field(None, alias="profileTitle")
34+
support_link: Optional[str] = Field(None, alias="supportLink")
35+
profile_update_interval: Optional[int] = Field(None, alias="profileUpdateInterval", ge=1)
36+
is_profile_webpage_url_enabled: Optional[bool] = Field(None, alias="isProfileWebpageUrlEnabled")
37+
serve_json_at_base_subscription: Optional[bool] = Field(None, alias="serveJsonAtBaseSubscription")
38+
is_show_custom_remarks: Optional[bool] = Field(None, alias="isShowCustomRemarks")
3939
happ_announce: Optional[str] = Field(None, alias="happAnnounce")
4040
happ_routing: Optional[str] = Field(None, alias="happRouting")
41-
randomize_hosts: bool = Field(alias="randomizeHosts")
41+
randomize_hosts: Optional[bool] = Field(None, alias="randomizeHosts")
4242

4343
class ExternalSquadHostOverridesDto(BaseModel):
4444
"""External squad host overrides"""

0 commit comments

Comments
 (0)