neptune-sdk 0.0.1.dev580__py3-none-any.whl

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.
@@ -0,0 +1,69 @@
1
+ """Neptune SDK — async Python client for the Neptune BitTorrent JSON-RPC API."""
2
+
3
+ from .client import NeptuneClient
4
+ from .exceptions import NeptuneConnectionError, NeptuneError, NeptuneRPCError
5
+ from .models import (
6
+ AddTorrentRequest,
7
+ AddTorrentResponse,
8
+ AddTrackerRequest,
9
+ DelCustomRequest,
10
+ InfoHashRequest,
11
+ ListTorrentRequest,
12
+ MainDataTorrent,
13
+ MoveTorrentRequest,
14
+ Peer,
15
+ RemoveTorrentRequest,
16
+ RemoveTrackerRequest,
17
+ ReplaceTrackersRequest,
18
+ SetCustomRequest,
19
+ SetFilePriorityRequest,
20
+ SetGlobalSpeedLimitRequest,
21
+ SetSpeedLimitRequest,
22
+ TagsRequest,
23
+ TorrentFile,
24
+ TorrentFilesResponse,
25
+ TorrentInfo,
26
+ TorrentListResponse,
27
+ TorrentPeersResponse,
28
+ TorrentTrackersResponse,
29
+ Tracker,
30
+ TransferSummary,
31
+ UpdateCustomRequest,
32
+ )
33
+
34
+ __all__ = [
35
+ # client
36
+ "NeptuneClient",
37
+ # exceptions
38
+ "NeptuneError",
39
+ "NeptuneRPCError",
40
+ "NeptuneConnectionError",
41
+ # request models
42
+ "AddTorrentRequest",
43
+ "AddTrackerRequest",
44
+ "DelCustomRequest",
45
+ "InfoHashRequest",
46
+ "ListTorrentRequest",
47
+ "MoveTorrentRequest",
48
+ "RemoveTorrentRequest",
49
+ "RemoveTrackerRequest",
50
+ "ReplaceTrackersRequest",
51
+ "SetCustomRequest",
52
+ "SetFilePriorityRequest",
53
+ "SetGlobalSpeedLimitRequest",
54
+ "SetSpeedLimitRequest",
55
+ "TagsRequest",
56
+ "UpdateCustomRequest",
57
+ # response / domain models
58
+ "AddTorrentResponse",
59
+ "MainDataTorrent",
60
+ "Peer",
61
+ "TorrentFile",
62
+ "TorrentFilesResponse",
63
+ "TorrentInfo",
64
+ "TorrentListResponse",
65
+ "TorrentPeersResponse",
66
+ "TorrentTrackersResponse",
67
+ "Tracker",
68
+ "TransferSummary",
69
+ ]
neptune_sdk/client.py ADDED
@@ -0,0 +1,311 @@
1
+ """Sync Python client for the Neptune BitTorrent JSON-RPC API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import json
7
+ import random
8
+ from dataclasses import asdict
9
+ from functools import cache
10
+ from typing import Any, TypeVar
11
+
12
+ import httpx
13
+ from pydantic import TypeAdapter
14
+
15
+ from .exceptions import NeptuneConnectionError, NeptuneRPCError
16
+ from .models import (
17
+ AddTorrentRequest,
18
+ AddTorrentResponse,
19
+ AddTrackerRequest,
20
+ DelCustomRequest,
21
+ InfoHashRequest,
22
+ ListTorrentRequest,
23
+ MoveTorrentRequest,
24
+ RemoveTorrentRequest,
25
+ RemoveTrackerRequest,
26
+ ReplaceTrackersRequest,
27
+ SetCustomRequest,
28
+ SetFilePriorityRequest,
29
+ SetGlobalSpeedLimitRequest,
30
+ SetSpeedLimitRequest,
31
+ TagsRequest,
32
+ TorrentFilesResponse,
33
+ TorrentInfo,
34
+ TorrentListResponse,
35
+ TorrentPeersResponse,
36
+ TorrentTrackersResponse,
37
+ TransferSummary,
38
+ UpdateCustomRequest,
39
+ )
40
+
41
+ T = TypeVar("T")
42
+
43
+
44
+ def _json_default(obj: Any) -> Any:
45
+ if isinstance(obj, (bytes, bytearray)):
46
+ return base64.b64encode(obj).decode()
47
+ raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
48
+
49
+
50
+ @cache
51
+ def _adapter(cls: type[T]) -> TypeAdapter[T]:
52
+ return TypeAdapter(cls)
53
+
54
+
55
+ def _validate(cls: type[T], data: Any) -> T:
56
+ return _adapter(cls).validate_python(data)
57
+
58
+
59
+ class NeptuneClient:
60
+ """Sync JSON-RPC client for Neptune.
61
+
62
+ Example::
63
+
64
+ with NeptuneClient("http://127.0.0.1:8002", token="secret") as c:
65
+ torrents = c.torrent_list()
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ base_url: str,
71
+ *,
72
+ token: str,
73
+ timeout: float = 30.0,
74
+ ) -> None:
75
+ self._client = httpx.Client(
76
+ base_url=base_url.rstrip("/"),
77
+ headers={"Authorization": token},
78
+ timeout=timeout,
79
+ )
80
+
81
+ # ── lifecycle ──────────────────────────────────────────────────────
82
+
83
+ def __enter__(self) -> NeptuneClient:
84
+ return self
85
+
86
+ def __exit__(self, *exc: Any) -> None:
87
+ self.close()
88
+
89
+ def close(self) -> None:
90
+ self._client.close()
91
+
92
+ # ── transport ──────────────────────────────────────────────────────
93
+
94
+ def _call(self, method: str, params: Any = None) -> Any:
95
+ """Send a JSON-RPC request and return the result field."""
96
+ payload: dict[str, Any] = {
97
+ "jsonrpc": "2.0",
98
+ "method": method,
99
+ "id": random.randint(1, 2**31),
100
+ }
101
+ if params is not None:
102
+ payload["params"] = asdict(params)
103
+
104
+ body_bytes = json.dumps(payload, default=_json_default).encode()
105
+
106
+ try:
107
+ resp = self._client.post(
108
+ "/json_rpc",
109
+ content=body_bytes,
110
+ headers={"Content-Type": "application/json"},
111
+ )
112
+ resp.raise_for_status()
113
+ except httpx.HTTPError as exc:
114
+ raise NeptuneConnectionError(str(exc)) from exc
115
+
116
+ body = resp.json()
117
+
118
+ if "error" in body and body["error"] is not None:
119
+ err = body["error"]
120
+ raise NeptuneRPCError(
121
+ code=err.get("code", -1),
122
+ message=err.get("message", ""),
123
+ data=err.get("data"),
124
+ )
125
+
126
+ return body.get("result")
127
+
128
+ # ── system ─────────────────────────────────────────────────────────
129
+
130
+ def ping(self) -> None:
131
+ """system.ping — health check."""
132
+ self._call("system.ping")
133
+
134
+ # ── transfer ───────────────────────────────────────────────────────
135
+
136
+ def transfer_summary(self) -> TransferSummary:
137
+ """Global download/upload rates and totals."""
138
+ return _validate(TransferSummary, self._call("transfer_summary"))
139
+
140
+ # ── torrent — queries ──────────────────────────────────────────────
141
+
142
+ def torrent_list(self, keys: list[str] | None = None) -> TorrentListResponse:
143
+ """List all torrents. Optionally filter custom keys returned."""
144
+ return _validate(
145
+ TorrentListResponse,
146
+ self._call("torrent.list", ListTorrentRequest(keys=keys or None)),
147
+ )
148
+
149
+ def torrent_get(self, info_hash: str) -> TorrentInfo:
150
+ """Get basic info for a single torrent."""
151
+ return _validate(
152
+ TorrentInfo,
153
+ self._call("torrent.get", InfoHashRequest(info_hash=info_hash)),
154
+ )
155
+
156
+ def torrent_files(self, info_hash: str) -> TorrentFilesResponse:
157
+ """List files in a torrent."""
158
+ return _validate(
159
+ TorrentFilesResponse,
160
+ self._call("torrent.files", InfoHashRequest(info_hash=info_hash)),
161
+ )
162
+
163
+ def torrent_peers(self, info_hash: str) -> TorrentPeersResponse:
164
+ """List connected peers for a torrent."""
165
+ return _validate(
166
+ TorrentPeersResponse,
167
+ self._call("torrent.peers", InfoHashRequest(info_hash=info_hash)),
168
+ )
169
+
170
+ def torrent_trackers(self, info_hash: str) -> TorrentTrackersResponse:
171
+ """List trackers for a torrent."""
172
+ return _validate(
173
+ TorrentTrackersResponse,
174
+ self._call("torrent.trackers", InfoHashRequest(info_hash=info_hash)),
175
+ )
176
+
177
+ def torrent_add_tracker(self, info_hash: str, url: str, *, tier: int = 0) -> None:
178
+ """Add a tracker to a torrent."""
179
+ self._call(
180
+ "torrent.add_tracker",
181
+ AddTrackerRequest(info_hash=info_hash, url=url, tier=tier),
182
+ )
183
+
184
+ def torrent_remove_tracker(self, info_hash: str, url: str) -> None:
185
+ """Remove a tracker from a torrent."""
186
+ self._call(
187
+ "torrent.remove_tracker",
188
+ RemoveTrackerRequest(info_hash=info_hash, url=url),
189
+ )
190
+
191
+ def torrent_replace_trackers(
192
+ self, info_hash: str, replacements: dict[str, str]
193
+ ) -> None:
194
+ """Replace tracker URLs. Keys are old URLs, values are new URLs."""
195
+ self._call(
196
+ "torrent.replace_trackers",
197
+ ReplaceTrackersRequest(info_hash=info_hash, replacements=replacements),
198
+ )
199
+
200
+ # ── torrent — mutations ────────────────────────────────────────────
201
+
202
+ def torrent_add(self, req: AddTorrentRequest) -> AddTorrentResponse:
203
+ """Add a torrent from raw .torrent bytes."""
204
+ return _validate(AddTorrentResponse, self._call("torrent.add", req))
205
+
206
+ def torrent_move(self, info_hash: str, target_base_path: str) -> None:
207
+ """Move torrent data to a new directory."""
208
+ self._call(
209
+ "torrent.move",
210
+ MoveTorrentRequest(info_hash=info_hash, target_base_path=target_base_path),
211
+ )
212
+
213
+ def torrent_remove(
214
+ self, info_hash: str, *, delete_data: bool = False
215
+ ) -> TorrentListResponse:
216
+ """Remove a torrent and return updated list."""
217
+ return _validate(
218
+ TorrentListResponse,
219
+ self._call(
220
+ "torrent.remove",
221
+ RemoveTorrentRequest(info_hash=info_hash, delete_data=delete_data),
222
+ ),
223
+ )
224
+
225
+ def torrent_start(self, info_hash: str) -> None:
226
+ """Start a torrent."""
227
+ self._call("torrent.start", InfoHashRequest(info_hash=info_hash))
228
+
229
+ def torrent_stop(self, info_hash: str) -> None:
230
+ """Stop a torrent."""
231
+ self._call("torrent.stop", InfoHashRequest(info_hash=info_hash))
232
+
233
+ def torrent_recheck(self, info_hash: str) -> None:
234
+ """Recheck torrent data integrity."""
235
+ self._call("torrent.recheck", InfoHashRequest(info_hash=info_hash))
236
+
237
+ # ── torrent — custom ───────────────────────────────────────────────
238
+
239
+ def torrent_custom_set(self, info_hash: str, key: str, value: str) -> None:
240
+ """Set a custom key-value pair on a torrent."""
241
+ self._call(
242
+ "torrent.custom.set",
243
+ SetCustomRequest(info_hash=info_hash, key=key, value=value),
244
+ )
245
+
246
+ def torrent_custom_update(self, info_hash: str, custom: dict[str, str]) -> None:
247
+ """Update multiple custom key-value pairs on a torrent."""
248
+ self._call(
249
+ "torrent.custom.update",
250
+ UpdateCustomRequest(info_hash=info_hash, custom=custom),
251
+ )
252
+
253
+ def torrent_custom_del(self, info_hash: str, key: str) -> None:
254
+ """Delete a custom key from a torrent."""
255
+ self._call(
256
+ "torrent.custom.del",
257
+ DelCustomRequest(info_hash=info_hash, key=key),
258
+ )
259
+
260
+ def torrent_add_tags(self, info_hash: str, tags: list[str]) -> None:
261
+ """Add tags to a torrent."""
262
+ self._call("torrent.add_tags", TagsRequest(info_hash=info_hash, tags=tags))
263
+
264
+ def torrent_remove_tags(self, info_hash: str, tags: list[str]) -> None:
265
+ """Remove tags from a torrent."""
266
+ self._call("torrent.remove_tags", TagsRequest(info_hash=info_hash, tags=tags))
267
+
268
+ # ── torrent — limits ───────────────────────────────────────────────
269
+
270
+ def torrent_set_download_limit(self, info_hash: str, limit: int) -> None:
271
+ """Set per-torrent download speed limit (bytes/s, <=0 = unlimited)."""
272
+ self._call(
273
+ "torrent.set_download_limit",
274
+ SetSpeedLimitRequest(info_hash=info_hash, limit=limit),
275
+ )
276
+
277
+ def torrent_set_upload_limit(self, info_hash: str, limit: int) -> None:
278
+ """Set per-torrent upload speed limit (bytes/s, <=0 = unlimited)."""
279
+ self._call(
280
+ "torrent.set_upload_limit",
281
+ SetSpeedLimitRequest(info_hash=info_hash, limit=limit),
282
+ )
283
+
284
+ def client_set_download_limit(self, limit: int) -> None:
285
+ """Set global download speed limit (bytes/s, <=0 = unlimited)."""
286
+ self._call(
287
+ "client.set_download_limit",
288
+ SetGlobalSpeedLimitRequest(limit=limit),
289
+ )
290
+
291
+ def client_set_upload_limit(self, limit: int) -> None:
292
+ """Set global upload speed limit (bytes/s, <=0 = unlimited)."""
293
+ self._call(
294
+ "client.set_upload_limit",
295
+ SetGlobalSpeedLimitRequest(limit=limit),
296
+ )
297
+
298
+ # ── torrent — file priority ────────────────────────────────────────
299
+
300
+ def torrent_set_file_priority(
301
+ self, info_hash: str, file_ids: list[int], priority: int
302
+ ) -> None:
303
+ """Set file priority (0 = skip, 1 = download)."""
304
+ self._call(
305
+ "torrent.set_file_priority",
306
+ SetFilePriorityRequest(
307
+ info_hash=info_hash,
308
+ file_ids=file_ids,
309
+ priority=priority,
310
+ ),
311
+ )
@@ -0,0 +1,16 @@
1
+ class NeptuneError(Exception):
2
+ """Base exception for Neptune SDK."""
3
+
4
+
5
+ class NeptuneRPCError(NeptuneError):
6
+ """Raised when the JSON-RPC server returns an error response."""
7
+
8
+ def __init__(self, code: int, message: str, data: object = None) -> None:
9
+ self.code = code
10
+ self.message = message
11
+ self.data = data
12
+ super().__init__(f"RPC error {code}: {message}")
13
+
14
+
15
+ class NeptuneConnectionError(NeptuneError):
16
+ """Raised when the HTTP connection to Neptune fails."""
neptune_sdk/models.py ADDED
@@ -0,0 +1,250 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ # ── Shared domain types ──────────────────────────────────────────────
6
+
7
+
8
+ @dataclass(frozen=True, slots=True, kw_only=True)
9
+ class MainDataTorrent:
10
+ """A torrent entry returned by torrent.list / torrent.remove."""
11
+
12
+ hash: str
13
+ name: str
14
+ state: str
15
+ comment: str
16
+ directory_base: str
17
+ message: str
18
+ tracker_errors: dict[str, str]
19
+ tags: list[str]
20
+ custom: dict[str, str]
21
+ download_rate: int
22
+ download_total: int
23
+ upload_rate: int
24
+ upload_total: int
25
+ connection_count: int
26
+ completed: int
27
+ total_length: int
28
+ selected_size: int
29
+ add_at: int
30
+ private: bool
31
+ total_seeding: int
32
+ total_downloading: int
33
+ connected_seeding: int
34
+ connected_downloading: int
35
+
36
+
37
+ @dataclass(frozen=True, slots=True, kw_only=True)
38
+ class TransferSummary:
39
+ """Global transfer rates and totals."""
40
+
41
+ download_rate: int
42
+ download_total: int
43
+ upload_rate: int
44
+ upload_total: int
45
+
46
+
47
+ @dataclass(frozen=True, slots=True, kw_only=True)
48
+ class TorrentFile:
49
+ """A single file inside a torrent."""
50
+
51
+ path: list[str]
52
+ index: int
53
+ progress: float
54
+ size: int
55
+
56
+
57
+ @dataclass(frozen=True, slots=True, kw_only=True)
58
+ class Peer:
59
+ """A connected peer."""
60
+
61
+ address: str
62
+ client: str
63
+ progress: float
64
+ download_rate: int
65
+ upload_rate: int
66
+ is_incoming: bool
67
+
68
+
69
+ @dataclass(frozen=True, slots=True, kw_only=True)
70
+ class Tracker:
71
+ """A tracker entry."""
72
+
73
+ url: str
74
+ tier: int
75
+ message: str
76
+
77
+
78
+ @dataclass(frozen=True, slots=True, kw_only=True)
79
+ class TorrentInfo:
80
+ """Basic torrent metadata from torrent.get."""
81
+
82
+ name: str
83
+ tags: list[str]
84
+ custom: dict[str, str]
85
+
86
+
87
+ # ── Request types ─────────────────────────────────────────────────────
88
+
89
+
90
+ @dataclass(frozen=True, slots=True, kw_only=True)
91
+ class AddTorrentRequest:
92
+ """Parameters for torrent.add."""
93
+
94
+ torrent_file: bytes
95
+ download_dir: str | None = None
96
+ tags: list[str] | None = None
97
+ custom: dict[str, str] | None = None
98
+ selected_files: list[int] | None = None
99
+ is_base_dir: bool = False
100
+ skip_hash_check: bool = False
101
+
102
+
103
+ @dataclass(frozen=True, slots=True, kw_only=True)
104
+ class InfoHashRequest:
105
+ """Common request that only needs an info_hash."""
106
+
107
+ info_hash: str
108
+
109
+
110
+ @dataclass(frozen=True, slots=True, kw_only=True)
111
+ class MoveTorrentRequest:
112
+ """Parameters for torrent.move."""
113
+
114
+ info_hash: str
115
+ target_base_path: str
116
+
117
+
118
+ @dataclass(frozen=True, slots=True, kw_only=True)
119
+ class RemoveTorrentRequest:
120
+ """Parameters for torrent.remove."""
121
+
122
+ info_hash: str
123
+ delete_data: bool = False
124
+
125
+
126
+ @dataclass(frozen=True, slots=True, kw_only=True)
127
+ class TagsRequest:
128
+ """Parameters for torrent.add_tags / torrent.remove_tags."""
129
+
130
+ info_hash: str
131
+ tags: list[str]
132
+
133
+
134
+ @dataclass(frozen=True, slots=True, kw_only=True)
135
+ class SetFilePriorityRequest:
136
+ """Parameters for torrent.set_file_priority."""
137
+
138
+ info_hash: str
139
+ file_ids: list[int]
140
+ priority: int = 0
141
+
142
+
143
+ @dataclass(frozen=True, slots=True, kw_only=True)
144
+ class SetSpeedLimitRequest:
145
+ """Parameters for torrent.set_download_limit / torrent.set_upload_limit."""
146
+
147
+ info_hash: str
148
+ limit: int = 0
149
+
150
+
151
+ @dataclass(frozen=True, slots=True, kw_only=True)
152
+ class SetGlobalSpeedLimitRequest:
153
+ """Parameters for client.set_download_limit / client.set_upload_limit."""
154
+
155
+ limit: int = 0
156
+
157
+
158
+ @dataclass(frozen=True, slots=True, kw_only=True)
159
+ class ListTorrentRequest:
160
+ """Parameters for torrent.list."""
161
+
162
+ keys: list[str] | None = None
163
+
164
+
165
+ @dataclass(frozen=True, slots=True, kw_only=True)
166
+ class SetCustomRequest:
167
+ """Parameters for torrent.custom.set."""
168
+
169
+ info_hash: str
170
+ key: str
171
+ value: str
172
+
173
+
174
+ @dataclass(frozen=True, slots=True, kw_only=True)
175
+ class UpdateCustomRequest:
176
+ """Parameters for torrent.custom.update."""
177
+
178
+ info_hash: str
179
+ custom: dict[str, str]
180
+
181
+
182
+ @dataclass(frozen=True, slots=True, kw_only=True)
183
+ class DelCustomRequest:
184
+ """Parameters for torrent.custom.del."""
185
+
186
+ info_hash: str
187
+ key: str
188
+
189
+
190
+ @dataclass(frozen=True, slots=True, kw_only=True)
191
+ class AddTrackerRequest:
192
+ """Parameters for torrent.add_tracker."""
193
+
194
+ info_hash: str
195
+ url: str
196
+ tier: int = 0
197
+
198
+
199
+ @dataclass(frozen=True, slots=True, kw_only=True)
200
+ class RemoveTrackerRequest:
201
+ """Parameters for torrent.remove_tracker."""
202
+
203
+ info_hash: str
204
+ url: str
205
+
206
+
207
+ @dataclass(frozen=True, slots=True, kw_only=True)
208
+ class ReplaceTrackersRequest:
209
+ """Parameters for torrent.replace_trackers."""
210
+
211
+ info_hash: str
212
+ replacements: dict[str, str]
213
+
214
+
215
+ # ── Response types ────────────────────────────────────────────────────
216
+
217
+
218
+ @dataclass(frozen=True, slots=True, kw_only=True)
219
+ class TorrentListResponse:
220
+ """Response for torrent.list and torrent.remove."""
221
+
222
+ torrents: list[MainDataTorrent]
223
+
224
+
225
+ @dataclass(frozen=True, slots=True, kw_only=True)
226
+ class AddTorrentResponse:
227
+ """Response for torrent.add."""
228
+
229
+ info_hash: str
230
+
231
+
232
+ @dataclass(frozen=True, slots=True, kw_only=True)
233
+ class TorrentFilesResponse:
234
+ """Response for torrent.files."""
235
+
236
+ files: list[TorrentFile]
237
+
238
+
239
+ @dataclass(frozen=True, slots=True, kw_only=True)
240
+ class TorrentPeersResponse:
241
+ """Response for torrent.peers."""
242
+
243
+ peers: list[Peer]
244
+
245
+
246
+ @dataclass(frozen=True, slots=True, kw_only=True)
247
+ class TorrentTrackersResponse:
248
+ """Response for torrent.trackers."""
249
+
250
+ trackers: list[Tracker]
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: neptune-sdk
3
+ Version: 0.0.1.dev580
4
+ Summary: Python SDK for Neptune BitTorrent client JSON-RPC API
5
+ License-Expression: GPL-3.0-only
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: httpx>=0.27
8
+ Requires-Dist: pydantic>=2
9
+
@@ -0,0 +1,8 @@
1
+ neptune_sdk-0.0.1.dev580.dist-info/METADATA,sha256=Z5S1QlqDPFkAurZ1W6iMx99bUHoz8Nh5SxvXppaqZgg,237
2
+ neptune_sdk-0.0.1.dev580.dist-info/WHEEL,sha256=VP-D4TPS230sME9Z3vb3INXvo1yt0924YRm5AOsk_dE,90
3
+ neptune_sdk-0.0.1.dev580.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ neptune_sdk/__init__.py,sha256=LJ3Ll8OXefHysJ6PZM4s8mjhQFt1hCO-S7BTwa522g4,1655
5
+ neptune_sdk/client.py,sha256=5P6eFWU4q9oLihmb_oAmrGnVJ_u32r7WxdroFVc-rIw,11279
6
+ neptune_sdk/exceptions.py,sha256=7nPIjKEzsODbXrxyeD3d4jkbTiIJOEVa2KEwzCrEBUE,504
7
+ neptune_sdk/models.py,sha256=W7RdwGIQ5uiW-iEpJ0WxlXWHEdjNggU4fFhY3be_R7Q,5706
8
+ neptune_sdk-0.0.1.dev580.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: pdm-backend (2.4.9)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+
3
+ [gui_scripts]
4
+