hippius 0.1.14__py3-none-any.whl → 0.2.1__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,216 @@
1
+ import json
2
+ import os
3
+ from typing import Any, Dict
4
+
5
+ import httpx
6
+
7
+
8
+ class AsyncIPFSClient:
9
+ """
10
+ Asynchronous IPFS client using httpx.
11
+ """
12
+
13
+ def __init__(
14
+ self, api_url: str = "http://localhost:5001", gateway: str = "https://ipfs.io"
15
+ ):
16
+ # Handle multiaddr format
17
+ if api_url and api_url.startswith("/"):
18
+ # Extract host and port from multiaddr
19
+ try:
20
+ parts = api_url.split("/")
21
+ # Handle /ip4/127.0.0.1/tcp/5001
22
+ if len(parts) >= 5 and parts[1] in ["ip4", "ip6"]:
23
+ host = parts[2]
24
+ port = parts[4]
25
+ api_url = f"https://{host}:{port}"
26
+ print(f"Converted multiaddr {api_url} to HTTP URL {api_url}")
27
+ else:
28
+ print(f"Warning: Unsupported multiaddr format: {api_url}")
29
+ print("Falling back to default: http://localhost:5001")
30
+ api_url = "http://localhost:5001"
31
+ except Exception as e:
32
+ print(f"Error parsing multiaddr: {e}")
33
+ print("Falling back to default: http://localhost:5001")
34
+ api_url = "http://localhost:5001"
35
+ self.api_url = api_url
36
+ self.gateway = gateway
37
+ self.client = httpx.AsyncClient(timeout=60.0)
38
+
39
+ async def close(self):
40
+ """Close the httpx client."""
41
+ await self.client.aclose()
42
+
43
+ async def __aenter__(self):
44
+ return self
45
+
46
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
47
+ await self.close()
48
+
49
+ async def add_file(self, file_path: str) -> Dict[str, Any]:
50
+ """
51
+ Add a file to IPFS.
52
+
53
+ Args:
54
+ file_path: Path to the file to add
55
+
56
+ Returns:
57
+ Dict containing the CID and other information
58
+ """
59
+ with open(file_path, "rb") as f:
60
+ files = {"file": f}
61
+ response = await self.client.post(f"{self.api_url}/api/v0/add", files=files)
62
+ response.raise_for_status()
63
+ return response.json()
64
+
65
+ async def add_bytes(self, data: bytes, filename: str = "file") -> Dict[str, Any]:
66
+ """
67
+ Add bytes to IPFS.
68
+
69
+ Args:
70
+ data: Bytes to add
71
+ filename: Name to give the file (default: "file")
72
+
73
+ Returns:
74
+ Dict containing the CID and other information
75
+ """
76
+ files = {"file": (filename, data)}
77
+ response = await self.client.post(f"{self.api_url}/api/v0/add", files=files)
78
+ response.raise_for_status()
79
+ return response.json()
80
+
81
+ async def add_str(self, content: str, filename: str = "file") -> Dict[str, Any]:
82
+ """
83
+ Add a string to IPFS.
84
+
85
+ Args:
86
+ content: String to add
87
+ filename: Name to give the file (default: "file")
88
+
89
+ Returns:
90
+ Dict containing the CID and other information
91
+ """
92
+ return await self.add_bytes(content.encode(), filename)
93
+
94
+ async def cat(self, cid: str) -> bytes:
95
+ """
96
+ Retrieve content from IPFS by its CID.
97
+
98
+ Args:
99
+ cid: Content Identifier to retrieve
100
+
101
+ Returns:
102
+ Content as bytes
103
+ """
104
+ response = await self.client.post(f"{self.api_url}/api/v0/cat?arg={cid}")
105
+ response.raise_for_status()
106
+ return response.content
107
+
108
+ async def pin(self, cid: str) -> Dict[str, Any]:
109
+ """
110
+ Pin content by CID.
111
+
112
+ Args:
113
+ cid: Content Identifier to pin
114
+
115
+ Returns:
116
+ Response from the IPFS node
117
+ """
118
+ response = await self.client.post(f"{self.api_url}/api/v0/pin/add?arg={cid}")
119
+ response.raise_for_status()
120
+ return response.json()
121
+
122
+ async def ls(self, cid: str) -> Dict[str, Any]:
123
+ """
124
+ List objects linked to the specified CID.
125
+
126
+ Args:
127
+ cid: Content Identifier
128
+
129
+ Returns:
130
+ Dict with links information
131
+ """
132
+ response = await self.client.post(f"{self.api_url}/api/v0/ls?arg={cid}")
133
+ response.raise_for_status()
134
+ return response.json()
135
+
136
+ async def exists(self, cid: str) -> bool:
137
+ """
138
+ Check if content exists.
139
+
140
+ Args:
141
+ cid: Content Identifier to check
142
+
143
+ Returns:
144
+ True if content exists, False otherwise
145
+ """
146
+ try:
147
+ await self.client.head(f"{self.gateway}/ipfs/{cid}")
148
+ return True
149
+ except httpx.HTTPError:
150
+ return False
151
+
152
+ async def download_file(self, cid: str, output_path: str) -> str:
153
+ """
154
+ Download content from IPFS to a file.
155
+
156
+ Args:
157
+ cid: Content identifier
158
+ output_path: Path where to save the file
159
+
160
+ Returns:
161
+ Path to the saved file
162
+ """
163
+ content = await self.cat(cid)
164
+ with open(output_path, "wb") as f:
165
+ f.write(content)
166
+ return output_path
167
+
168
+ async def add_directory(
169
+ self, dir_path: str, recursive: bool = True
170
+ ) -> Dict[str, Any]:
171
+ """
172
+ Add a directory to IPFS.
173
+
174
+ Args:
175
+ dir_path: Path to the directory to add
176
+
177
+ Returns:
178
+ Dict containing the CID and other information about the directory
179
+
180
+ Raises:
181
+ FileNotFoundError: If the directory doesn't exist
182
+ httpx.HTTPError: If the IPFS API request fails
183
+ """
184
+ if not os.path.isdir(dir_path):
185
+ raise FileNotFoundError(f"Directory {dir_path} not found")
186
+
187
+ # Collect all files in the directory
188
+ files = []
189
+ for root, _, filenames in os.walk(dir_path):
190
+ for filename in filenames:
191
+ file_path = os.path.join(root, filename)
192
+ rel_path = os.path.relpath(file_path, dir_path)
193
+
194
+ with open(file_path, "rb") as f:
195
+ file_content = f.read()
196
+
197
+ # Add the file to the multipart request
198
+ files.append(
199
+ ("file", (rel_path, file_content, "application/octet-stream"))
200
+ )
201
+
202
+ # Make the request with directory flags
203
+ response = await self.client.post(
204
+ f"{self.api_url}/api/v0/add?recursive=true&wrap-with-directory=true",
205
+ files=files,
206
+ timeout=300.0, # 5 minute timeout for directory uploads
207
+ )
208
+ response.raise_for_status()
209
+
210
+ # The IPFS API returns a JSON object for each file, one per line
211
+ # The last one should be the directory itself
212
+ lines = response.text.strip().split("\n")
213
+ if not lines:
214
+ raise ValueError("Empty response from IPFS API")
215
+
216
+ return json.loads(lines[-1])