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.
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neptune-sdk
3
- Version: 0.0.1.dev960
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: httpx>=0.27
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
- "httpx>=0.27",
14
+ "httpx2>=2.12.0",
15
15
  "pyserde>=0.31.7",
16
16
  ]
17
- version = "0.0.1.dev960"
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 httpx
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 = httpx.HTTPTransport(uds=url[7:])
91
- self._client = httpx.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 = httpx.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 httpx.HTTPError as exc:
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"]