neptune-sdk 0.0.1.dev960__tar.gz → 0.0.1.dev982__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {neptune_sdk-0.0.1.dev960 → neptune_sdk-0.0.1.dev982}/PKG-INFO +2 -2
- {neptune_sdk-0.0.1.dev960 → neptune_sdk-0.0.1.dev982}/pyproject.toml +2 -6
- {neptune_sdk-0.0.1.dev960 → neptune_sdk-0.0.1.dev982}/src/neptune_sdk/client.py +5 -5
- neptune_sdk-0.0.1.dev960/tests/__init__.py +0 -0
- neptune_sdk-0.0.1.dev960/tests/test_client.py +0 -317
- {neptune_sdk-0.0.1.dev960 → neptune_sdk-0.0.1.dev982}/src/neptune_sdk/__init__.py +0 -0
- {neptune_sdk-0.0.1.dev960 → neptune_sdk-0.0.1.dev982}/src/neptune_sdk/exceptions.py +0 -0
- {neptune_sdk-0.0.1.dev960 → neptune_sdk-0.0.1.dev982}/src/neptune_sdk/models.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: neptune-sdk
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev982
|
|
4
4
|
Summary: Python SDK for Neptune BitTorrent client JSON-RPC API
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
|
-
Requires-Dist:
|
|
7
|
+
Requires-Dist: httpx2>=2.12.0
|
|
8
8
|
Requires-Dist: pyserde>=0.31.7
|
|
9
9
|
|
|
@@ -11,15 +11,13 @@ description = "Python SDK for Neptune BitTorrent client JSON-RPC API"
|
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
license = "MIT"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"
|
|
14
|
+
"httpx2>=2.12.0",
|
|
15
15
|
"pyserde>=0.31.7",
|
|
16
16
|
]
|
|
17
|
-
version = "0.0.1.
|
|
17
|
+
version = "0.0.1.dev982"
|
|
18
18
|
|
|
19
19
|
[dependency-groups]
|
|
20
20
|
dev = [
|
|
21
|
-
"pytest==9.1.1",
|
|
22
|
-
"respx==0.23.1",
|
|
23
21
|
"ruff==0.16.1",
|
|
24
22
|
"ty==0.0.65",
|
|
25
23
|
]
|
|
@@ -32,8 +30,6 @@ version_format = "_version_helper:format_version"
|
|
|
32
30
|
[tool.pdm.build]
|
|
33
31
|
package-dir = "src"
|
|
34
32
|
|
|
35
|
-
[tool.pytest.ini_options]
|
|
36
|
-
|
|
37
33
|
[tool.ruff]
|
|
38
34
|
target-version = "py310"
|
|
39
35
|
|
|
@@ -9,7 +9,7 @@ from dataclasses import asdict
|
|
|
9
9
|
from functools import cache
|
|
10
10
|
from typing import Any, TypeVar
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import httpx2
|
|
13
13
|
from serde import coerce, deserialize, from_dict
|
|
14
14
|
|
|
15
15
|
from .exceptions import NeptuneConnectionError, NeptuneRPCError
|
|
@@ -87,15 +87,15 @@ class NeptuneClient:
|
|
|
87
87
|
timeout: float = 30.0,
|
|
88
88
|
) -> None:
|
|
89
89
|
if url.startswith("unix://"):
|
|
90
|
-
transport =
|
|
91
|
-
self._client =
|
|
90
|
+
transport = httpx2.HTTPTransport(uds=url[7:])
|
|
91
|
+
self._client = httpx2.Client(
|
|
92
92
|
transport=transport,
|
|
93
93
|
headers={"Authorization": token},
|
|
94
94
|
timeout=timeout,
|
|
95
95
|
)
|
|
96
96
|
self._url = "http://neptune/json_rpc"
|
|
97
97
|
else:
|
|
98
|
-
self._client =
|
|
98
|
+
self._client = httpx2.Client(
|
|
99
99
|
headers={"Authorization": token},
|
|
100
100
|
timeout=timeout,
|
|
101
101
|
)
|
|
@@ -135,7 +135,7 @@ class NeptuneClient:
|
|
|
135
135
|
headers={"Content-Type": "application/json"},
|
|
136
136
|
)
|
|
137
137
|
resp.raise_for_status()
|
|
138
|
-
except
|
|
138
|
+
except httpx2.HTTPError as exc:
|
|
139
139
|
raise NeptuneConnectionError(str(exc)) from exc
|
|
140
140
|
|
|
141
141
|
body = resp.json()
|
|
File without changes
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
"""Tests for NeptuneClient using respx to mock HTTP transport."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import base64
|
|
6
|
-
import json
|
|
7
|
-
|
|
8
|
-
import httpx
|
|
9
|
-
import pytest
|
|
10
|
-
import respx
|
|
11
|
-
|
|
12
|
-
from neptune_sdk import (
|
|
13
|
-
AddTorrentRequest,
|
|
14
|
-
MainDataTorrent,
|
|
15
|
-
NeptuneClient,
|
|
16
|
-
NeptuneRPCError,
|
|
17
|
-
TorrentFile,
|
|
18
|
-
TorrentState,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
BASE_URL = "http://127.0.0.1:8002"
|
|
22
|
-
RPC_URL = f"{BASE_URL}/json_rpc"
|
|
23
|
-
TOKEN = "test-token"
|
|
24
|
-
|
|
25
|
-
# ── helpers ───────────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _ok(result, *, id=1):
|
|
29
|
-
return httpx.Response(200, json={"jsonrpc": "2.0", "result": result, "id": id})
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _rpc_error(code=-32600, message="bad request", *, id=1):
|
|
33
|
-
return httpx.Response(
|
|
34
|
-
200,
|
|
35
|
-
json={"jsonrpc": "2.0", "error": {"code": code, "message": message}, "id": id},
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
TORRENT_JSON = {
|
|
40
|
-
"hash": "aabb",
|
|
41
|
-
"name": "test",
|
|
42
|
-
"state": TorrentState.Downloading,
|
|
43
|
-
"comment": "",
|
|
44
|
-
"directory_base": "/downloads/test",
|
|
45
|
-
"message": "",
|
|
46
|
-
"tracker_errors": {},
|
|
47
|
-
"tags": [],
|
|
48
|
-
"custom": {},
|
|
49
|
-
"download_rate": 0,
|
|
50
|
-
"download_total": 0,
|
|
51
|
-
"upload_rate": 0,
|
|
52
|
-
"upload_total": 0,
|
|
53
|
-
"connection_count": 0,
|
|
54
|
-
"completed": 0,
|
|
55
|
-
"corrupted": 0,
|
|
56
|
-
"total_length": 100,
|
|
57
|
-
"selected_size": 100,
|
|
58
|
-
"add_at": 0,
|
|
59
|
-
"completed_at": 0,
|
|
60
|
-
"total_seeding": 0,
|
|
61
|
-
"total_downloading": 1,
|
|
62
|
-
"connected_seeding": 0,
|
|
63
|
-
"connected_downloading": 2,
|
|
64
|
-
"private": False,
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# ── tests ─────────────────────────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@pytest.fixture()
|
|
72
|
-
def mock_api():
|
|
73
|
-
with respx.mock(base_url=BASE_URL) as rspx:
|
|
74
|
-
yield rspx
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@pytest.fixture()
|
|
78
|
-
def client():
|
|
79
|
-
with NeptuneClient(RPC_URL, token=TOKEN) as c:
|
|
80
|
-
yield c
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def test_ping(mock_api, client):
|
|
84
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
85
|
-
client.ping()
|
|
86
|
-
req = mock_api.calls.last.request
|
|
87
|
-
payload = json.loads(req.content)
|
|
88
|
-
assert payload["method"] == "system.ping"
|
|
89
|
-
assert req.headers["authorization"] == TOKEN
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def test_transfer_summary(mock_api, client):
|
|
93
|
-
mock_api.post("/json_rpc").mock(
|
|
94
|
-
return_value=_ok(
|
|
95
|
-
{
|
|
96
|
-
"download_rate": 100,
|
|
97
|
-
"download_total": 200,
|
|
98
|
-
"upload_rate": 50,
|
|
99
|
-
"upload_total": 80,
|
|
100
|
-
}
|
|
101
|
-
)
|
|
102
|
-
)
|
|
103
|
-
result = client.transfer_summary()
|
|
104
|
-
assert result.download_rate == 100
|
|
105
|
-
assert result.upload_total == 80
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def test_torrent_list(mock_api, client):
|
|
109
|
-
mock_api.post("/json_rpc").mock(return_value=_ok({"torrents": [TORRENT_JSON]}))
|
|
110
|
-
result = client.torrent_list()
|
|
111
|
-
assert len(result.torrents) == 1
|
|
112
|
-
assert isinstance(result.torrents[0], MainDataTorrent)
|
|
113
|
-
assert result.torrents[0].hash == "aabb"
|
|
114
|
-
assert result.torrents[0].name == "test"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def test_torrent_get(mock_api, client):
|
|
118
|
-
mock_api.post("/json_rpc").mock(
|
|
119
|
-
return_value=_ok(
|
|
120
|
-
{"name": "my_torrent", "tags": ["a", "b"], "custom": {"key1": "val1"}}
|
|
121
|
-
)
|
|
122
|
-
)
|
|
123
|
-
result = client.torrent_get("aabb")
|
|
124
|
-
assert result.name == "my_torrent"
|
|
125
|
-
assert result.tags == ["a", "b"]
|
|
126
|
-
assert result.custom == {"key1": "val1"}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def test_torrent_files(mock_api, client):
|
|
130
|
-
mock_api.post("/json_rpc").mock(
|
|
131
|
-
return_value=_ok(
|
|
132
|
-
{
|
|
133
|
-
"files": [
|
|
134
|
-
{
|
|
135
|
-
"path": ["dir", "file.txt"],
|
|
136
|
-
"index": 0,
|
|
137
|
-
"progress": 0,
|
|
138
|
-
"size": 1024,
|
|
139
|
-
}
|
|
140
|
-
]
|
|
141
|
-
}
|
|
142
|
-
)
|
|
143
|
-
)
|
|
144
|
-
result = client.torrent_files("aabb")
|
|
145
|
-
assert len(result.files) == 1
|
|
146
|
-
assert isinstance(result.files[0], TorrentFile)
|
|
147
|
-
assert result.files[0].path == ["dir", "file.txt"]
|
|
148
|
-
assert result.files[0].progress == 0.0
|
|
149
|
-
assert isinstance(result.files[0].progress, float)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def test_torrent_peers(mock_api, client):
|
|
153
|
-
mock_api.post("/json_rpc").mock(
|
|
154
|
-
return_value=_ok(
|
|
155
|
-
{
|
|
156
|
-
"peers": [
|
|
157
|
-
{
|
|
158
|
-
"address": "1.2.3.4:5678",
|
|
159
|
-
"client": "qBittorrent",
|
|
160
|
-
"progress": 0.9,
|
|
161
|
-
"download_rate": 1000,
|
|
162
|
-
"upload_rate": 500,
|
|
163
|
-
"is_incoming": False,
|
|
164
|
-
"encrypted": False,
|
|
165
|
-
}
|
|
166
|
-
]
|
|
167
|
-
}
|
|
168
|
-
)
|
|
169
|
-
)
|
|
170
|
-
result = client.torrent_peers("aabb")
|
|
171
|
-
assert result.peers[0].address == "1.2.3.4:5678"
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def test_torrent_trackers(mock_api, client):
|
|
175
|
-
mock_api.post("/json_rpc").mock(
|
|
176
|
-
return_value=_ok(
|
|
177
|
-
{
|
|
178
|
-
"trackers": [
|
|
179
|
-
{"url": "http://t.example.com/announce", "tier": 0, "message": ""}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
)
|
|
183
|
-
)
|
|
184
|
-
result = client.torrent_trackers("aabb")
|
|
185
|
-
assert result.trackers[0].url == "http://t.example.com/announce"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def test_torrent_add_encodes_base64(mock_api, client):
|
|
189
|
-
mock_api.post("/json_rpc").mock(return_value=_ok({"info_hash": "aa" * 20}))
|
|
190
|
-
|
|
191
|
-
torrent_bytes = b"d8:announce3:url..."
|
|
192
|
-
req = AddTorrentRequest(torrent_file=torrent_bytes, tags=["linux"])
|
|
193
|
-
result = client.torrent_add(req)
|
|
194
|
-
|
|
195
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
196
|
-
assert payload["params"]["torrent_file"] == base64.b64encode(torrent_bytes).decode()
|
|
197
|
-
assert payload["params"]["tags"] == ["linux"]
|
|
198
|
-
assert result.info_hash == "aa" * 20
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def test_torrent_remove(mock_api, client):
|
|
202
|
-
mock_api.post("/json_rpc").mock(return_value=_ok({}))
|
|
203
|
-
client.torrent_remove("aabb", delete_data=True)
|
|
204
|
-
|
|
205
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
206
|
-
assert payload["params"]["delete_data"] is True
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def test_torrent_move(mock_api, client):
|
|
210
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
211
|
-
client.torrent_move("aabb", "/new/path")
|
|
212
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
213
|
-
assert payload["method"] == "torrent.move"
|
|
214
|
-
assert payload["params"]["info_hash"] == "aabb"
|
|
215
|
-
assert payload["params"]["target_base_path"] == "/new/path"
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def test_torrent_start(mock_api, client):
|
|
219
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
220
|
-
client.torrent_start("aabb")
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def test_torrent_stop(mock_api, client):
|
|
224
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
225
|
-
client.torrent_stop("aabb")
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def test_torrent_add_tags(mock_api, client):
|
|
229
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
230
|
-
client.torrent_add_tags("aabb", ["tag1", "tag2"])
|
|
231
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
232
|
-
assert payload["params"]["tags"] == ["tag1", "tag2"]
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def test_torrent_remove_tags(mock_api, client):
|
|
236
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
237
|
-
client.torrent_remove_tags("aabb", ["old"])
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def test_torrent_set_download_limit(mock_api, client):
|
|
241
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
242
|
-
client.torrent_set_download_limit("aabb", 1024 * 1024)
|
|
243
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
244
|
-
assert payload["params"]["limit"] == 1048576
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def test_torrent_set_upload_limit(mock_api, client):
|
|
248
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
249
|
-
client.torrent_set_upload_limit("aabb", 512000)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def test_client_set_download_limit(mock_api, client):
|
|
253
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
254
|
-
client.client_set_download_limit(0)
|
|
255
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
256
|
-
assert payload["method"] == "client.set_download_limit"
|
|
257
|
-
assert payload["params"]["limit"] == 0
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def test_client_set_upload_limit(mock_api, client):
|
|
261
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
262
|
-
client.client_set_upload_limit(-1)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def test_torrent_set_file_priority(mock_api, client):
|
|
266
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
267
|
-
client.torrent_set_file_priority("aabb", [0, 2], 1)
|
|
268
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
269
|
-
assert payload["params"]["file_ids"] == [0, 2]
|
|
270
|
-
assert payload["params"]["priority"] == 1
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
def test_rpc_error_raises(mock_api, client):
|
|
274
|
-
mock_api.post("/json_rpc").mock(return_value=_rpc_error(-32601, "method not found"))
|
|
275
|
-
with pytest.raises(NeptuneRPCError) as exc_info:
|
|
276
|
-
client.ping()
|
|
277
|
-
assert exc_info.value.code == -32601
|
|
278
|
-
assert "method not found" in exc_info.value.message
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def test_empty_torrent_list(mock_api, client):
|
|
282
|
-
mock_api.post("/json_rpc").mock(return_value=_ok({"torrents": []}))
|
|
283
|
-
result = client.torrent_list()
|
|
284
|
-
assert result.torrents == []
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def test_torrent_custom_set(mock_api, client):
|
|
288
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
289
|
-
client.torrent_custom_set("aabb", "label", "my-label")
|
|
290
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
291
|
-
assert payload["method"] == "torrent.custom.set"
|
|
292
|
-
assert payload["params"]["key"] == "label"
|
|
293
|
-
assert payload["params"]["value"] == "my-label"
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def test_torrent_custom_update(mock_api, client):
|
|
297
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
298
|
-
client.torrent_custom_update("aabb", {"k1": "v1", "k2": "v2"})
|
|
299
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
300
|
-
assert payload["method"] == "torrent.custom.update"
|
|
301
|
-
assert payload["params"]["custom"] == {"k1": "v1", "k2": "v2"}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
def test_torrent_custom_del(mock_api, client):
|
|
305
|
-
mock_api.post("/json_rpc").mock(return_value=_ok(None))
|
|
306
|
-
client.torrent_custom_del("aabb", "label")
|
|
307
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
308
|
-
assert payload["method"] == "torrent.custom.del"
|
|
309
|
-
assert payload["params"]["key"] == "label"
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
def test_torrent_list_with_keys(mock_api, client):
|
|
313
|
-
mock_api.post("/json_rpc").mock(return_value=_ok({"torrents": [TORRENT_JSON]}))
|
|
314
|
-
result = client.torrent_list(keys=["label"])
|
|
315
|
-
assert len(result.torrents) == 1
|
|
316
|
-
payload = json.loads(mock_api.calls.last.request.content)
|
|
317
|
-
assert payload["params"]["keys"] == ["label"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|