feedo-sdk 0.1.0__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.
- feedo/__init__.py +4 -0
- feedo/client.py +13 -0
- feedo/modules/__init__.py +1 -0
- feedo/modules/consensus.py +46 -0
- feedo/modules/search.py +40 -0
- feedo/modules/storage.py +44 -0
- feedo/router.py +65 -0
- feedo_sdk-0.1.0.dist-info/METADATA +11 -0
- feedo_sdk-0.1.0.dist-info/RECORD +11 -0
- feedo_sdk-0.1.0.dist-info/WHEEL +5 -0
- feedo_sdk-0.1.0.dist-info/top_level.txt +1 -0
feedo/__init__.py
ADDED
feedo/client.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
from .router import NodeRouter
|
|
3
|
+
from .modules.search import SearchModule
|
|
4
|
+
from .modules.consensus import ConsensusModule
|
|
5
|
+
from .modules.storage import StorageModule
|
|
6
|
+
|
|
7
|
+
class FeedoClient:
|
|
8
|
+
def __init__(self, search_seeds: Optional[List[str]] = None, consensus_seeds: Optional[List[str]] = None, storage_seeds: Optional[List[str]] = None):
|
|
9
|
+
self.router = NodeRouter(search_seeds, consensus_seeds, storage_seeds)
|
|
10
|
+
|
|
11
|
+
self.search = SearchModule(self.router)
|
|
12
|
+
self.consensus = ConsensusModule(self.router)
|
|
13
|
+
self.storage = StorageModule(self.router)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Init modules
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from ..router import NodeRouter
|
|
4
|
+
|
|
5
|
+
class ConsensusModule:
|
|
6
|
+
def __init__(self, router: NodeRouter):
|
|
7
|
+
self.router = router
|
|
8
|
+
|
|
9
|
+
async def _request(self, method: str, path: str, json: Optional[Dict] = None) -> Any:
|
|
10
|
+
base_url = await self.router.get_consensus_node()
|
|
11
|
+
url = f"{base_url}{path}"
|
|
12
|
+
|
|
13
|
+
async with httpx.AsyncClient() as client:
|
|
14
|
+
try:
|
|
15
|
+
response = await client.request(method, url, json=json)
|
|
16
|
+
response.raise_for_status()
|
|
17
|
+
return response.json()
|
|
18
|
+
except Exception:
|
|
19
|
+
print(f"Consensus request failed on {base_url}, finding new node...")
|
|
20
|
+
self.router.invalidate_consensus_node()
|
|
21
|
+
base_url = await self.router.get_consensus_node()
|
|
22
|
+
url = f"{base_url}{path}"
|
|
23
|
+
response = await client.request(method, url, json=json)
|
|
24
|
+
response.raise_for_status()
|
|
25
|
+
return response.json()
|
|
26
|
+
|
|
27
|
+
async def resolve_name(self, name: str):
|
|
28
|
+
return await self._request("GET", f"/resolve/{name}")
|
|
29
|
+
|
|
30
|
+
async def resolve_cid(self, cid: str):
|
|
31
|
+
return await self._request("GET", f"/resolve_cid/{cid}")
|
|
32
|
+
|
|
33
|
+
async def get_did_balance(self, did: str):
|
|
34
|
+
return await self._request("GET", f"/did/{did}/balance")
|
|
35
|
+
|
|
36
|
+
async def register_did(self, pubkey_hex: str, signature_hex: str):
|
|
37
|
+
return await self._request("POST", "/did/register", json={"pubkey_hex": pubkey_hex, "signature_hex": signature_hex})
|
|
38
|
+
|
|
39
|
+
async def register_name(self, name: str, did: str, cid: str, signature_hex: str):
|
|
40
|
+
return await self._request("POST", "/name/register", json={"name": name, "did": did, "cid": cid, "signature_hex": signature_hex})
|
|
41
|
+
|
|
42
|
+
async def update_name_cid(self, name: str, new_cid: str, signature_hex: str):
|
|
43
|
+
return await self._request("POST", "/name/update_cid", json={"name": name, "new_cid": new_cid, "signature_hex": signature_hex})
|
|
44
|
+
|
|
45
|
+
async def list_grants(self):
|
|
46
|
+
return await self._request("GET", "/grants")
|
feedo/modules/search.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from ..router import NodeRouter
|
|
4
|
+
|
|
5
|
+
class SearchModule:
|
|
6
|
+
def __init__(self, router: NodeRouter):
|
|
7
|
+
self.router = router
|
|
8
|
+
|
|
9
|
+
async def _request(self, method: str, path: str, json: Optional[Dict] = None, params: Optional[Dict] = None) -> Any:
|
|
10
|
+
base_url = await self.router.get_search_node()
|
|
11
|
+
url = f"{base_url}{path}"
|
|
12
|
+
|
|
13
|
+
async with httpx.AsyncClient() as client:
|
|
14
|
+
try:
|
|
15
|
+
response = await client.request(method, url, json=json, params=params)
|
|
16
|
+
response.raise_for_status()
|
|
17
|
+
return response.json()
|
|
18
|
+
except Exception as e:
|
|
19
|
+
print(f"Search request failed on {base_url}, finding new node...")
|
|
20
|
+
self.router.invalidate_search_node()
|
|
21
|
+
base_url = await self.router.get_search_node()
|
|
22
|
+
url = f"{base_url}{path}"
|
|
23
|
+
response = await client.request(method, url, json=json, params=params)
|
|
24
|
+
response.raise_for_status()
|
|
25
|
+
return response.json()
|
|
26
|
+
|
|
27
|
+
async def query(self, query_text: str, limit: int = 10):
|
|
28
|
+
return await self._request("GET", "/query", params={"q": query_text, "limit": limit})
|
|
29
|
+
|
|
30
|
+
async def index_document(self, content: str, metadata: Optional[Dict] = None):
|
|
31
|
+
return await self._request("POST", "/index_document", json={"content": content, "metadata": metadata or {}})
|
|
32
|
+
|
|
33
|
+
async def deploy_proxy(self, directory_path: str, domain: str):
|
|
34
|
+
return await self._request("POST", "/proxy/publish_feedo", json={"source_dir": directory_path, "domain": domain})
|
|
35
|
+
|
|
36
|
+
async def unpin(self, cid: str):
|
|
37
|
+
return await self._request("DELETE", f"/proxy/unpin_feedo/{cid}")
|
|
38
|
+
|
|
39
|
+
async def get_stats(self):
|
|
40
|
+
return await self._request("GET", "/explorer/stats")
|
feedo/modules/storage.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from ..router import NodeRouter
|
|
4
|
+
|
|
5
|
+
class StorageModule:
|
|
6
|
+
def __init__(self, router: NodeRouter):
|
|
7
|
+
self.router = router
|
|
8
|
+
|
|
9
|
+
async def _request(self, method: str, path: str, json: Optional[Dict] = None, data: Any = None, files: Any = None) -> Any:
|
|
10
|
+
base_url = await self.router.get_storage_node()
|
|
11
|
+
url = f"{base_url}{path}"
|
|
12
|
+
|
|
13
|
+
async with httpx.AsyncClient() as client:
|
|
14
|
+
try:
|
|
15
|
+
response = await client.request(method, url, json=json, data=data, files=files)
|
|
16
|
+
response.raise_for_status()
|
|
17
|
+
# download endpoint might not return json
|
|
18
|
+
if response.headers.get("content-type") == "application/json":
|
|
19
|
+
return response.json()
|
|
20
|
+
return response.content
|
|
21
|
+
except Exception:
|
|
22
|
+
print(f"Storage request failed on {base_url}, finding new node...")
|
|
23
|
+
self.router.invalidate_storage_node()
|
|
24
|
+
base_url = await self.router.get_storage_node()
|
|
25
|
+
url = f"{base_url}{path}"
|
|
26
|
+
response = await client.request(method, url, json=json, data=data, files=files)
|
|
27
|
+
response.raise_for_status()
|
|
28
|
+
if response.headers.get("content-type") == "application/json":
|
|
29
|
+
return response.json()
|
|
30
|
+
return response.content
|
|
31
|
+
|
|
32
|
+
async def upload_file(self, file_path: str, filename: str = "file"):
|
|
33
|
+
with open(file_path, "rb") as f:
|
|
34
|
+
files = {"file": (filename, f)}
|
|
35
|
+
return await self._request("POST", "/upload", files=files)
|
|
36
|
+
|
|
37
|
+
async def download_file(self, hash_id: str) -> bytes:
|
|
38
|
+
return await self._request("GET", f"/download/{hash_id}")
|
|
39
|
+
|
|
40
|
+
async def ingest_json(self, payload: Dict):
|
|
41
|
+
return await self._request("POST", "/api/v1/ingest/post", json=payload)
|
|
42
|
+
|
|
43
|
+
async def get_recent_files(self):
|
|
44
|
+
return await self._request("GET", "/api/files/recent")
|
feedo/router.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import List, Optional, Dict
|
|
4
|
+
|
|
5
|
+
DEFAULT_SEEDS = {
|
|
6
|
+
"search": ["http://localhost:8000"],
|
|
7
|
+
"consensus": ["http://localhost:8080"],
|
|
8
|
+
"storage": ["http://localhost:8081"]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class NodeRouter:
|
|
12
|
+
def __init__(self, search_seeds: Optional[List[str]] = None, consensus_seeds: Optional[List[str]] = None, storage_seeds: Optional[List[str]] = None):
|
|
13
|
+
self.search_nodes = search_seeds or DEFAULT_SEEDS["search"]
|
|
14
|
+
self.consensus_nodes = consensus_seeds or DEFAULT_SEEDS["consensus"]
|
|
15
|
+
self.storage_nodes = storage_seeds or DEFAULT_SEEDS["storage"]
|
|
16
|
+
|
|
17
|
+
self._active_search_node = None
|
|
18
|
+
self._active_consensus_node = None
|
|
19
|
+
self._active_storage_node = None
|
|
20
|
+
|
|
21
|
+
async def _find_fastest_node(self, nodes: List[str], health_endpoint: str) -> str:
|
|
22
|
+
async def ping(node: str) -> str:
|
|
23
|
+
async with httpx.AsyncClient() as client:
|
|
24
|
+
url = f"{node}{health_endpoint}"
|
|
25
|
+
response = await client.get(url, timeout=3.0)
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
return node
|
|
28
|
+
|
|
29
|
+
tasks = [asyncio.create_task(ping(node)) for node in nodes]
|
|
30
|
+
|
|
31
|
+
while tasks:
|
|
32
|
+
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
|
33
|
+
for task in done:
|
|
34
|
+
try:
|
|
35
|
+
return task.result()
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
tasks = list(pending)
|
|
39
|
+
|
|
40
|
+
print(f"Warning: All seed nodes failed. Falling back to {nodes[0]}")
|
|
41
|
+
return nodes[0]
|
|
42
|
+
|
|
43
|
+
async def get_search_node(self) -> str:
|
|
44
|
+
if not self._active_search_node:
|
|
45
|
+
self._active_search_node = await self._find_fastest_node(self.search_nodes, "/explorer/stats")
|
|
46
|
+
return self._active_search_node
|
|
47
|
+
|
|
48
|
+
async def get_consensus_node(self) -> str:
|
|
49
|
+
if not self._active_consensus_node:
|
|
50
|
+
self._active_consensus_node = await self._find_fastest_node(self.consensus_nodes, "/grants")
|
|
51
|
+
return self._active_consensus_node
|
|
52
|
+
|
|
53
|
+
async def get_storage_node(self) -> str:
|
|
54
|
+
if not self._active_storage_node:
|
|
55
|
+
self._active_storage_node = await self._find_fastest_node(self.storage_nodes, "/api/files/recent")
|
|
56
|
+
return self._active_storage_node
|
|
57
|
+
|
|
58
|
+
def invalidate_search_node(self):
|
|
59
|
+
self._active_search_node = None
|
|
60
|
+
|
|
61
|
+
def invalidate_consensus_node(self):
|
|
62
|
+
self._active_consensus_node = None
|
|
63
|
+
|
|
64
|
+
def invalidate_storage_node(self):
|
|
65
|
+
self._active_storage_node = None
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: feedo-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The official Developer SDK for Feedo Network
|
|
5
|
+
Author: Feedo Network
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: httpx>=0.24.0
|
|
8
|
+
Dynamic: author
|
|
9
|
+
Dynamic: requires-dist
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
Dynamic: summary
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
feedo/__init__.py,sha256=5fpQuHjJKiZjZp8WoGYbTXIthth08clKPLtro-Bl840,104
|
|
2
|
+
feedo/client.py,sha256=y1O68cjdJE5ohaV5uieYvgIOgP_HhJHQt20-5uqhYAo,610
|
|
3
|
+
feedo/router.py,sha256=qSrIC24ZobpBUjJSyOgB62TxvjA3jjWFlQPxrI7UTpE,2543
|
|
4
|
+
feedo/modules/__init__.py,sha256=5osEvcjmclW4Wh0r8-gBp5hnY-KfklOonaDWArvLIsA,15
|
|
5
|
+
feedo/modules/consensus.py,sha256=xZGgGaKD5DQnHtRIc2VXBmdYYEPr5wkZfZXmBrCQEZ0,2088
|
|
6
|
+
feedo/modules/search.py,sha256=TpVeiAowtCgahQuul3nZgWYf5k_IJgzt26VnYojc39g,1834
|
|
7
|
+
feedo/modules/storage.py,sha256=lnT11HAQ-eGoYxxNjT0vxH-CUSb695Rm6zZFernbQpk,1995
|
|
8
|
+
feedo_sdk-0.1.0.dist-info/METADATA,sha256=WptPSj6c0gXQ04KtrZ-KxXqRpHf8v9pHy5k2XzktLDA,273
|
|
9
|
+
feedo_sdk-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
10
|
+
feedo_sdk-0.1.0.dist-info/top_level.txt,sha256=DqxeisJ-yTDMZjgoTGrPdcHSCYHMCEX-WSk4JTJXwWU,6
|
|
11
|
+
feedo_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
feedo
|