ipfs-node 0.1.6__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.
ipfs_node/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .ipfs_node import IpfsNode
2
+ from .ipfs_pubsub import IPFSMessage, IPFSSubscription
3
+ from .ipfs_tunnels import NodeTunnels
4
+ __all__ = ["IpfsNode", "IPFSMessage", "IPFSSubscription", "NodeTunnels"]
@@ -0,0 +1,233 @@
1
+ import os
2
+ import tempfile
3
+ import ctypes
4
+ import shutil
5
+ import platform
6
+ import json
7
+ import time
8
+ import threading
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Optional, Union, List, Dict, Any, Callable, Tuple, Iterator, Set
12
+ from libkubo import libkubo, c_str, c_bool, from_c_str, ffi
13
+
14
+ from ipfs_tk_generics.files import BaseFiles
15
+
16
+
17
+ class NodeFiles(BaseFiles):
18
+ def __init__(self, node):
19
+ self._node = node
20
+ self._repo_path = self._node._repo_path
21
+
22
+
23
+ def read(self, cid: str, *args, **kwargs) -> bytes:
24
+ """
25
+ Get bytes data from IPFS.
26
+
27
+ Args:
28
+ cid: The Content Identifier of the data to retrieve.
29
+ Note: This method only works with file content, not directories.
30
+ For directories, use the download() method instead.
31
+
32
+ Returns:
33
+ bytes: The retrieved data.
34
+ """
35
+ temp_file = None
36
+ temp_file_path = None
37
+ try:
38
+ # Create a temporary file to store the retrieved data
39
+ temp_file = tempfile.NamedTemporaryFile(delete=False)
40
+ temp_file_path = temp_file.name
41
+ temp_file.close()
42
+
43
+ # Get the file from IPFS
44
+ success = self.download(cid, temp_file_path)
45
+ if not success:
46
+ raise RuntimeError(f"Failed to retrieve data for CID: {cid}")
47
+
48
+ # Read the data from the temporary file
49
+ with open(temp_file_path, 'rb') as f:
50
+ return f.read()
51
+ except Exception as e:
52
+ raise RuntimeError(f"Error retrieving bytes from IPFS: {e}")
53
+ finally:
54
+ # Clean up the temporary file
55
+ if temp_file_path is not None and os.path.exists(temp_file_path):
56
+ try:
57
+ os.unlink(temp_file_path)
58
+ except Exception:
59
+ # Silently ignore cleanup errors
60
+ pass
61
+
62
+ def publish(self, file_path: str, *args, **kwargs) -> str:
63
+ return self._add(file_path)
64
+ def _add(self, file_path: str, only_hash: bool = False, *args, **kwargs) -> str:
65
+ """
66
+ Add a file to IPFS.
67
+
68
+ Args:
69
+ file_path: Path to the file to add.
70
+
71
+ Returns:
72
+ str: The CID (Content Identifier) of the added file.
73
+ """
74
+ if not os.path.exists(file_path):
75
+ raise FileNotFoundError(f"File not found: {file_path}")
76
+
77
+ repo_path = c_str(self._repo_path.encode('utf-8'))
78
+ file_path_c = c_str(os.path.abspath(file_path).encode('utf-8'))
79
+
80
+ try:
81
+ cid_ptr = libkubo.AddFile(
82
+ repo_path, file_path_c, c_bool(only_hash))
83
+ if not cid_ptr:
84
+ raise RuntimeError("Failed to add file to IPFS")
85
+
86
+ # Copy the string content before freeing the pointer
87
+ cid = from_c_str(cid_ptr)
88
+
89
+ # Store the memory freeing operation in a separate try block
90
+ try:
91
+ # Free the memory allocated by C.CString in Go
92
+ libkubo.FreeString(cid_ptr)
93
+ except Exception as e:
94
+ print(f"Warning: Failed to free memory: {e}")
95
+
96
+ if not cid:
97
+ raise RuntimeError("Failed to add file to IPFS")
98
+
99
+ return cid
100
+ except Exception as e:
101
+ # Handle any exceptions during the process
102
+ raise RuntimeError(f"Error adding file to IPFS: {e}")
103
+
104
+ def download(self, cid: str, dest_path: str=".", **kwargs) -> bool:
105
+ """
106
+ Retrieve a file or directory from IPFS by its CID.
107
+
108
+ Args:
109
+ cid: The Content Identifier of the content to retrieve.
110
+ dest_path: Destination path where the file or directory will be saved.
111
+ - For a file: The complete file path including filename.
112
+ - For a directory: The path where the directory and its contents
113
+ will be placed. All directory contents will be created inside
114
+ this path.
115
+
116
+ Returns:
117
+ bool: True if the content was successfully retrieved, False otherwise.
118
+ """
119
+ try:
120
+ dest_path = os.path.abspath(dest_path)
121
+ repo_path = c_str(self._repo_path.encode('utf-8'))
122
+ cid_c = c_str(cid.encode('utf-8'))
123
+ dest_path_c = c_str(os.path.abspath(dest_path).encode('utf-8'))
124
+
125
+ result = libkubo.Download(repo_path, cid_c, dest_path_c)
126
+
127
+ return result == 0
128
+ except Exception as e:
129
+ # Handle any exceptions during the process
130
+ raise RuntimeError(f"Error retrieving file from IPFS: {e}")
131
+
132
+ def pin(self, cid: str, recursive: bool = True) -> bool:
133
+ """
134
+ Pin a CID to the local IPFS node.
135
+
136
+ Args:
137
+ cid: The Content Identifier to pin.
138
+ recursive: Whether to recursively pin the object and its references.
139
+ Currently, only recursive pinning is supported.
140
+
141
+ Returns:
142
+ bool: True if the CID was successfully pinned, False otherwise.
143
+ """
144
+ try:
145
+ repo_path = c_str(self._repo_path.encode('utf-8'))
146
+ cid_c = c_str(cid.encode('utf-8'))
147
+
148
+ result = libkubo.PinCID(repo_path, cid_c)
149
+
150
+ return result == 0
151
+ except Exception as e:
152
+ # Handle any exceptions during the process
153
+ raise RuntimeError(f"Error pinning CID: {e}")
154
+
155
+ def unpin(self, cid: str, recursive: bool = True) -> bool:
156
+ """
157
+ Unpin a CID from the local IPFS node.
158
+
159
+ Args:
160
+ cid: The Content Identifier to unpin.
161
+ recursive: Whether to recursively unpin the object and its references.
162
+ Currently, this parameter is ignored as all unpinning is recursive.
163
+
164
+ Returns:
165
+ bool: True if the CID was successfully unpinned, False otherwise.
166
+ """
167
+ try:
168
+ repo_path = c_str(self._repo_path.encode('utf-8'))
169
+ cid_c = c_str(cid.encode('utf-8'))
170
+
171
+ result = libkubo.UnpinCID(repo_path, cid_c)
172
+
173
+ return result == 0
174
+ except Exception as e:
175
+ # Handle any exceptions during the process
176
+ raise RuntimeError(f"Error unpinning CID: {e}")
177
+
178
+ def list_pins(self, *args, **kwargs) -> list[str]:
179
+ """
180
+ List all pinned CIDs in the local IPFS node.
181
+
182
+ Returns:
183
+ list[str]: A list of pinned CIDs.
184
+ """
185
+ try:
186
+ repo_path = c_str(self._repo_path.encode('utf-8'))
187
+
188
+ pins_json_ptr = libkubo.ListPins(repo_path)
189
+ if not pins_json_ptr:
190
+ raise RuntimeError("Failed to list pins")
191
+
192
+ # Copy the string content before freeing the pointer
193
+ pins_json = from_c_str(pins_json_ptr)
194
+
195
+ # Store the memory freeing operation in a separate try block
196
+ try:
197
+ # Free the memory allocated by C.CString in Go
198
+ libkubo.FreeString(pins_json_ptr)
199
+ except Exception as e:
200
+ print(f"Warning: Failed to free memory: {e}")
201
+
202
+ if not pins_json:
203
+ return []
204
+
205
+ # Parse the JSON string into a Python list
206
+ pins = json.loads(pins_json)
207
+ return pins
208
+ except Exception as e:
209
+ # Handle any exceptions during the process
210
+ raise RuntimeError(f"Error listing pins: {e}")
211
+
212
+ def remove(self, cid: str) -> bool:
213
+ """
214
+ Remove a pinned object.
215
+
216
+ This is an alias for unpin that makes the API more intuitive.
217
+
218
+ Args:
219
+ cid: The Content Identifier to remove.
220
+
221
+ Returns:
222
+ bool: True if the CID was successfully removed, False otherwise.
223
+ """
224
+ return self.unpin(cid)
225
+
226
+ def predict_cid(self, filepath: str, *args, **kwargs):
227
+ return self._add(filepath, only_hash=True)
228
+
229
+
230
+ def terminate(self):
231
+ pass
232
+ def __del__(self):
233
+ self.terminate()
ipfs_node/ipfs_node.py ADDED
@@ -0,0 +1,230 @@
1
+ import os
2
+ import tempfile
3
+ import ctypes
4
+ import shutil
5
+ import platform
6
+ import json
7
+ import time
8
+ import threading
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Optional, Union, List, Dict, Any, Callable, Tuple, Iterator, Set
12
+ from .ipfs_pubsub import IPFSMessage, IPFSSubscription, NodePubsub
13
+ from libkubo import libkubo, c_str, from_c_str, ffi
14
+ from .ipfs_tunnels import NodeTunnels
15
+ from .ipfs_files import NodeFiles
16
+ from .ipfs_peers import NodePeers
17
+
18
+ from ipfs_tk_generics.client import IpfsClient
19
+ class IpfsNode(IpfsClient):
20
+ """
21
+ Python wrapper for a Kubo IPFS node.
22
+
23
+ This class provides an interface to work with IPFS functionality
24
+ through the Kubo implementation.
25
+ """
26
+
27
+ def __init__(self, repo_path: Optional[str] = None, online: bool = True, enable_pubsub: bool = True):
28
+ """
29
+ Initialize an IPFS node with a specific repository path.
30
+
31
+ Args:
32
+ repo_path: Path to the IPFS repository. If None, a temporary
33
+ repository will be created.
34
+ online: Whether the node should connect to the IPFS network.
35
+ enable_pubsub: Whether to enable pubsub functionality.
36
+ """
37
+ self._temp_dir = None
38
+ self._repo_path = repo_path
39
+ self._online = online
40
+ self._enable_pubsub = enable_pubsub
41
+ self._peer_id = None # Will be set when connecting to the network
42
+ # If no repo path is provided, create a temporary directory
43
+ if self._repo_path is None:
44
+ self._temp_dir = tempfile.TemporaryDirectory()
45
+ self._repo_path = self._temp_dir.name
46
+
47
+
48
+ # Initialize the repository if it doesn't exist
49
+ if not os.path.exists(os.path.join(self._repo_path, "config")):
50
+ self._init_repo()
51
+ else:
52
+ print("Loading existing IPFS repo")
53
+ libkubo.RunNode(c_str(self._repo_path.encode('utf-8')))
54
+
55
+ # Get the node ID if online
56
+ if self._online:
57
+ self._peer_id = self.get_node_id()
58
+ self._pubsub = NodePubsub(self)
59
+ self._tunnels = NodeTunnels(self)
60
+ self._files = NodeFiles(self)
61
+ self._peers = NodePeers(self)
62
+
63
+ # Enable pubsub if requested
64
+ if self._enable_pubsub and self._online:
65
+ self.pubsub._enable_pubsub_config()
66
+ @property
67
+ def tunnels(self)->NodeTunnels:
68
+ return self._tunnels
69
+ @property
70
+ def pubsub(self)->NodePubsub:
71
+ return self._pubsub
72
+ @property
73
+ def files(self)->NodeFiles:
74
+ return self._files
75
+ @property
76
+ def peers (self)->NodePeers:
77
+ return self._peers
78
+ def _run(self):
79
+ pass
80
+
81
+ def _stop(self):
82
+ pass
83
+
84
+ def _init_repo(self):
85
+ """Initialize the IPFS repository."""
86
+ repo_path = c_str(self._repo_path.encode('utf-8'))
87
+ result = libkubo.CreateRepo(repo_path)
88
+
89
+ if result < 0:
90
+ raise RuntimeError(
91
+ f"Failed to initialize IPFS repository: {result}")
92
+ # print(f"Initalised repo at: {repo_path}")
93
+
94
+
95
+
96
+
97
+
98
+ def terminate(self):
99
+ """Close the IPFS node and clean up resources."""
100
+ self._pubsub.terminate()
101
+ self._tunnels.terminate()
102
+ self._files.terminate()
103
+ self._peers.terminate()
104
+ # Force cleanup of the node in Go
105
+ if self._repo_path:
106
+ try:
107
+ repo_path = c_str(self._repo_path.encode('utf-8'))
108
+ print("Cleaning up node...")
109
+ libkubo.CleanupNode(repo_path)
110
+ # print(f"Node for repo {self._repo_path} explicitly cleaned up")
111
+ except Exception as e:
112
+ print(f"Warning: Error cleaning up node: {e}")
113
+
114
+ # Clean up temporary directory if one was created
115
+ if self._temp_dir is not None:
116
+ self._temp_dir.cleanup()
117
+ self._temp_dir = None
118
+
119
+ def __enter__(self):
120
+ """Support for context manager protocol."""
121
+ return self
122
+
123
+ def __exit__(self, exc_type, exc_val, exc_tb):
124
+ """Clean up when exiting the context manager."""
125
+ self.terminate()
126
+
127
+
128
+ def _ipfs_host_ip(self,):
129
+ return "127.0.0.1"
130
+
131
+ def test_get_string(self) -> str:
132
+ """Test function to check basic string passing from Go to Python"""
133
+ try:
134
+ id_ptr = libkubo.TestGetString()
135
+ if not id_ptr:
136
+ print("TEST: No string returned from TestGetString")
137
+ return ""
138
+
139
+ test_str = from_c_str(id_ptr)
140
+ # print(f"TEST: String from Go: '{test_str}', length: {len(test_str)}")
141
+ return test_str
142
+ except Exception as e:
143
+ print(f"TEST ERROR: {e}")
144
+ return f"ERROR: {e}"
145
+
146
+ def get_node_id(self) -> str:
147
+ """
148
+ Get the peer ID of this IPFS node.
149
+
150
+ Returns:
151
+ str: The peer ID of the node, or empty string if not available.
152
+ """
153
+ if not self._online:
154
+ print("IPFS: not online")
155
+ return ""
156
+
157
+ # try to get the node ID
158
+ try:
159
+ repo_path = c_str(self._repo_path.encode('utf-8'))
160
+
161
+ id_ptr = libkubo.GetNodeID(repo_path)
162
+
163
+ if not id_ptr:
164
+ print("IPFS: NO ID_PTR")
165
+ return ""
166
+
167
+ # Copy the string content
168
+ peer_id = from_c_str(id_ptr)
169
+
170
+ # Don't free the memory - let Go's finalizer handle it
171
+ # The memory will be freed when Go's garbage collector runs
172
+
173
+ # Strip the prefix we added for debugging
174
+ if peer_id.startswith("ID:"):
175
+ peer_id = peer_id[3:]
176
+
177
+ return peer_id
178
+ except Exception as e:
179
+ print(f"IPFS ERROR in get_node_id: {e}")
180
+ return f"ERROR: {e}"
181
+
182
+ @property
183
+ def peer_id(self) -> str:
184
+ """Get the peer ID of this node."""
185
+ if not self._peer_id:
186
+ self._peer_id = self.get_node_id()
187
+ return self._peer_id
188
+
189
+ @classmethod
190
+ def ephemeral(cls, online: bool = True, enable_pubsub: bool = True):
191
+ """
192
+ Create an ephemeral IPFS node with a temporary repository.
193
+
194
+ Args:
195
+ online: Whether the node should connect to the IPFS network.
196
+ enable_pubsub: Whether to enable pubsub functionality.
197
+
198
+ Returns:
199
+ IpfsNode: A new IPFS node instance with a temporary repository.
200
+ """
201
+ return cls(None, online, enable_pubsub)
202
+ def get_addrs(self):
203
+ if not self._online:
204
+ print("IPFS: not online")
205
+ return ""
206
+
207
+ # try to get the node ID
208
+ try:
209
+ repo_path = c_str(self._repo_path.encode('utf-8'))
210
+
211
+ id_ptr = libkubo.GetNodeMultiAddrs(repo_path)
212
+
213
+ if not id_ptr:
214
+ print("IPFS: NO ID_PTR")
215
+ return ""
216
+
217
+ # Copy the string content
218
+ json_data = from_c_str(id_ptr)
219
+
220
+ # Don't free the memory - let Go's finalizer handle it
221
+ # The memory will be freed when Go's garbage collector runs
222
+
223
+ # Strip the prefix we added for debugging
224
+ return json.loads(json_data)
225
+ except Exception as e:
226
+ print(f"IPFS ERROR in get_node_id: {e}")
227
+ return f"ERROR: {e}"
228
+
229
+ def __del__(self):
230
+ self.terminate()
@@ -0,0 +1,71 @@
1
+ import os
2
+ import tempfile
3
+ import ctypes
4
+ import shutil
5
+ import platform
6
+ import json
7
+ import time
8
+ import threading
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Optional, Union, List, Dict, Any, Callable, Tuple, Iterator, Set
12
+ from libkubo import libkubo, c_str, from_c_str, ffi
13
+ DEF_FIND_TIMEOUT=10
14
+ from ipfs_tk_generics.peers import BasePeers
15
+ class NodePeers(BasePeers):
16
+ def __init__(self, node):
17
+ self._node = node
18
+ self._repo_path = self._node._repo_path
19
+ def find(self, peer_id:str, timeout=DEF_FIND_TIMEOUT)->list[str]:
20
+ data = from_c_str(
21
+ libkubo.FindPeer(c_str(self._repo_path), c_str(peer_id), timeout),
22
+ )
23
+ return json.loads(data)
24
+ def list_peers(self)->list[str]:
25
+ data = from_c_str(
26
+ libkubo.ListPeers(c_str(self._repo_path))
27
+ )
28
+
29
+ return json.loads(data)
30
+ def list_ids(self)->list[str]:
31
+ data = from_c_str(
32
+ libkubo.ListPeersIDs(c_str(self._repo_path))
33
+ )
34
+
35
+ return json.loads(data)
36
+ def connect(self, peer_addr: str) -> bool:
37
+ """
38
+ Connect to an IPFS peer.
39
+
40
+ Args:
41
+ peer_addr: Multiaddress of the peer to connect to.
42
+
43
+ Returns:
44
+ bool: True if successfully connected, False otherwise.
45
+ """
46
+ if not self._node._online:
47
+ raise RuntimeError("Cannot connect to peers in offline mode")
48
+
49
+ try:
50
+ repo_path = c_str(self._repo_path.encode('utf-8'))
51
+ peer_addr_c = c_str(peer_addr.encode('utf-8'))
52
+
53
+ result = libkubo.ConnectToPeer(repo_path, peer_addr_c)
54
+
55
+ return result == 0
56
+ except Exception as e:
57
+ # Handle any exceptions during the process
58
+ raise RuntimeError(f"Error connecting to peer: {e}")
59
+ def list_peers(self):
60
+ data = from_c_str(
61
+ libkubo.ListPeers(c_str(self._repo_path))
62
+ )
63
+
64
+ return json.loads(data)
65
+ def is_connected(self, peer_id:str, *args, **kwargs):
66
+ #TODO: replace with ping
67
+ return peer_id in self.list_ids()
68
+ def terminate(self):
69
+ pass
70
+ def __del__(self):
71
+ self.terminate()