cubememory 0.1.0__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.
- cubememory-0.1.0/PKG-INFO +24 -0
- cubememory-0.1.0/cubememory/__init__.py +24 -0
- cubememory-0.1.0/cubememory/client.py +118 -0
- cubememory-0.1.0/cubememory/exceptions.py +8 -0
- cubememory-0.1.0/cubememory/http.py +82 -0
- cubememory-0.1.0/cubememory/index.py +122 -0
- cubememory-0.1.0/cubememory/memory.py +44 -0
- cubememory-0.1.0/cubememory/notes.py +115 -0
- cubememory-0.1.0/cubememory.egg-info/PKG-INFO +24 -0
- cubememory-0.1.0/cubememory.egg-info/SOURCES.txt +13 -0
- cubememory-0.1.0/cubememory.egg-info/dependency_links.txt +1 -0
- cubememory-0.1.0/cubememory.egg-info/requires.txt +7 -0
- cubememory-0.1.0/cubememory.egg-info/top_level.txt +1 -0
- cubememory-0.1.0/pyproject.toml +37 -0
- cubememory-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cubememory
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Cube Memory — semantic search with 100% exact recall
|
|
5
|
+
Author-email: Ronny Guerreiro <ynnor.guerreiro@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://cubememory.com.br
|
|
8
|
+
Project-URL: Documentation, https://cubememory.com.br/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/cubememory/sdk-python
|
|
10
|
+
Keywords: vector-search,semantic-search,rag,llm-memory,mcp,cube-memory
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: requests>=2.28
|
|
21
|
+
Provides-Extra: async
|
|
22
|
+
Requires-Dist: httpx>=0.25; extra == "async"
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: httpx>=0.25; extra == "all"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cubememory — Python SDK for Cube Memory
|
|
3
|
+
Semantic search with 100% exact recall · isolated islands · self-hosted
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .client import CubeClient, createCubeClient
|
|
7
|
+
from .index import IndexClient, SearchResult, InsertResult
|
|
8
|
+
from .memory import MemoryClient
|
|
9
|
+
from .notes import NotesClient, Note, NoteDetail
|
|
10
|
+
from .exceptions import CubeError
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CubeClient",
|
|
15
|
+
"createCubeClient",
|
|
16
|
+
"IndexClient",
|
|
17
|
+
"MemoryClient",
|
|
18
|
+
"NotesClient",
|
|
19
|
+
"SearchResult",
|
|
20
|
+
"InsertResult",
|
|
21
|
+
"Note",
|
|
22
|
+
"NoteDetail",
|
|
23
|
+
"CubeError",
|
|
24
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .http import SyncHttp, AsyncHttp
|
|
6
|
+
from .index import IndexClient, AsyncIndexClient
|
|
7
|
+
from .memory import MemoryClient, AsyncMemoryClient
|
|
8
|
+
from .notes import NotesClient, AsyncNotesClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CubeClient:
|
|
12
|
+
"""Synchronous Cube Memory client.
|
|
13
|
+
|
|
14
|
+
Usage::
|
|
15
|
+
|
|
16
|
+
from cubememory import CubeClient
|
|
17
|
+
|
|
18
|
+
cube = CubeClient(
|
|
19
|
+
base_url="https://cubememory.com.br/gateway",
|
|
20
|
+
api_key="cm_live_xxx",
|
|
21
|
+
project_id="proj_xxx",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Semantic search (100% exact recall)
|
|
25
|
+
results = cube.index("my-docs").search("calçado esportivo", limit=10)
|
|
26
|
+
|
|
27
|
+
# LLM Memory
|
|
28
|
+
cube.memory.store("User prefers dark mode")
|
|
29
|
+
ctx = cube.memory.search("user preferences")
|
|
30
|
+
|
|
31
|
+
# PKM Notes
|
|
32
|
+
cube.notes.upsert("Meeting Notes", "Discussed Q3 roadmap", tags=["meeting"])
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
base_url: str,
|
|
38
|
+
api_key: str,
|
|
39
|
+
project_id: str = "",
|
|
40
|
+
timeout: float = 30.0,
|
|
41
|
+
) -> None:
|
|
42
|
+
self._http = SyncHttp(base_url, api_key, project_id, timeout)
|
|
43
|
+
self.memory = MemoryClient(self._http)
|
|
44
|
+
self.notes = NotesClient(self._http)
|
|
45
|
+
|
|
46
|
+
def index(self, name: str) -> IndexClient:
|
|
47
|
+
return IndexClient(name, self._http)
|
|
48
|
+
|
|
49
|
+
def ping(self) -> bool:
|
|
50
|
+
try:
|
|
51
|
+
self._http.get("/v1/health")
|
|
52
|
+
return True
|
|
53
|
+
except Exception:
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AsyncCubeClient:
|
|
58
|
+
"""Async Cube Memory client (requires httpx).
|
|
59
|
+
|
|
60
|
+
Usage::
|
|
61
|
+
|
|
62
|
+
from cubememory import AsyncCubeClient
|
|
63
|
+
|
|
64
|
+
cube = AsyncCubeClient(
|
|
65
|
+
base_url="https://cubememory.com.br/gateway",
|
|
66
|
+
api_key="cm_live_xxx",
|
|
67
|
+
project_id="proj_xxx",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
results = await cube.index("my-docs").search("calçado esportivo")
|
|
71
|
+
await cube.memory.store("User prefers dark mode")
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
base_url: str,
|
|
77
|
+
api_key: str,
|
|
78
|
+
project_id: str = "",
|
|
79
|
+
timeout: float = 30.0,
|
|
80
|
+
) -> None:
|
|
81
|
+
self._http = AsyncHttp(base_url, api_key, project_id, timeout)
|
|
82
|
+
self.memory = AsyncMemoryClient(self._http)
|
|
83
|
+
self.notes = AsyncNotesClient(self._http)
|
|
84
|
+
|
|
85
|
+
def index(self, name: str) -> AsyncIndexClient:
|
|
86
|
+
return AsyncIndexClient(name, self._http)
|
|
87
|
+
|
|
88
|
+
async def ping(self) -> bool:
|
|
89
|
+
try:
|
|
90
|
+
await self._http.get("/v1/health")
|
|
91
|
+
return True
|
|
92
|
+
except Exception:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def createCubeClient(
|
|
97
|
+
base_url: str,
|
|
98
|
+
api_key: str,
|
|
99
|
+
project_id: str = "",
|
|
100
|
+
*,
|
|
101
|
+
async_mode: bool = False,
|
|
102
|
+
timeout: float = 30.0,
|
|
103
|
+
) -> "CubeClient | AsyncCubeClient":
|
|
104
|
+
"""Factory function — similar to Supabase's createClient().
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
base_url: Gateway URL, e.g. "https://cubememory.com.br/gateway"
|
|
108
|
+
api_key: Your API key (cm_live_xxx)
|
|
109
|
+
project_id: Your project ID (proj_xxx)
|
|
110
|
+
async_mode: Set True to get AsyncCubeClient (requires httpx)
|
|
111
|
+
timeout: Request timeout in seconds
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
CubeClient (sync) or AsyncCubeClient (async)
|
|
115
|
+
"""
|
|
116
|
+
if async_mode:
|
|
117
|
+
return AsyncCubeClient(base_url, api_key, project_id, timeout)
|
|
118
|
+
return CubeClient(base_url, api_key, project_id, timeout)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""HTTP client — sync (requests) + async (httpx) backends."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from .exceptions import CubeError
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import httpx
|
|
11
|
+
_HAS_HTTPX = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
_HAS_HTTPX = False
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import requests as _requests
|
|
17
|
+
_HAS_REQUESTS = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
_HAS_REQUESTS = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SyncHttp:
|
|
23
|
+
def __init__(self, base_url: str, api_key: str, project_id: str, timeout: float = 30.0) -> None:
|
|
24
|
+
if not _HAS_REQUESTS:
|
|
25
|
+
raise ImportError("Install 'requests' for sync usage: pip install cubememory[sync]")
|
|
26
|
+
self._base = base_url.rstrip("/")
|
|
27
|
+
self._headers = {
|
|
28
|
+
"Authorization": f"Bearer {api_key}",
|
|
29
|
+
"X-Project-Id": project_id,
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
}
|
|
32
|
+
self._timeout = timeout
|
|
33
|
+
|
|
34
|
+
def get(self, path: str) -> Any:
|
|
35
|
+
r = _requests.get(f"{self._base}{path}", headers=self._headers, timeout=self._timeout)
|
|
36
|
+
if not r.ok:
|
|
37
|
+
raise CubeError(r.status_code, r.text)
|
|
38
|
+
return r.json()
|
|
39
|
+
|
|
40
|
+
def post(self, path: str, body: Dict[str, Any]) -> Any:
|
|
41
|
+
r = _requests.post(f"{self._base}{path}", json=body, headers=self._headers, timeout=self._timeout)
|
|
42
|
+
if not r.ok:
|
|
43
|
+
raise CubeError(r.status_code, r.text)
|
|
44
|
+
return r.json()
|
|
45
|
+
|
|
46
|
+
def delete(self, path: str) -> None:
|
|
47
|
+
r = _requests.delete(f"{self._base}{path}", headers=self._headers, timeout=self._timeout)
|
|
48
|
+
if not r.ok:
|
|
49
|
+
raise CubeError(r.status_code, r.text)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AsyncHttp:
|
|
53
|
+
def __init__(self, base_url: str, api_key: str, project_id: str, timeout: float = 30.0) -> None:
|
|
54
|
+
if not _HAS_HTTPX:
|
|
55
|
+
raise ImportError("Install 'httpx' for async usage: pip install cubememory[async]")
|
|
56
|
+
self._base = base_url.rstrip("/")
|
|
57
|
+
self._headers = {
|
|
58
|
+
"Authorization": f"Bearer {api_key}",
|
|
59
|
+
"X-Project-Id": project_id,
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
}
|
|
62
|
+
self._timeout = timeout
|
|
63
|
+
|
|
64
|
+
async def get(self, path: str) -> Any:
|
|
65
|
+
async with httpx.AsyncClient(timeout=self._timeout) as c:
|
|
66
|
+
r = await c.get(f"{self._base}{path}", headers=self._headers)
|
|
67
|
+
if not r.is_success:
|
|
68
|
+
raise CubeError(r.status_code, r.text)
|
|
69
|
+
return r.json()
|
|
70
|
+
|
|
71
|
+
async def post(self, path: str, body: Dict[str, Any]) -> Any:
|
|
72
|
+
async with httpx.AsyncClient(timeout=self._timeout) as c:
|
|
73
|
+
r = await c.post(f"{self._base}{path}", json=body, headers=self._headers)
|
|
74
|
+
if not r.is_success:
|
|
75
|
+
raise CubeError(r.status_code, r.text)
|
|
76
|
+
return r.json()
|
|
77
|
+
|
|
78
|
+
async def delete(self, path: str) -> None:
|
|
79
|
+
async with httpx.AsyncClient(timeout=self._timeout) as c:
|
|
80
|
+
r = await c.delete(f"{self._base}{path}", headers=self._headers)
|
|
81
|
+
if not r.is_success:
|
|
82
|
+
raise CubeError(r.status_code, r.text)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from .http import SyncHttp, AsyncHttp
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class SearchResult:
|
|
11
|
+
id: str
|
|
12
|
+
score: float
|
|
13
|
+
text_preview: str = ""
|
|
14
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def _from(cls, d: Dict[str, Any]) -> "SearchResult":
|
|
18
|
+
return cls(
|
|
19
|
+
id=d.get("id", ""),
|
|
20
|
+
score=d.get("score", 0.0),
|
|
21
|
+
text_preview=d.get("text_preview", ""),
|
|
22
|
+
metadata=d.get("metadata", {}),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class InsertResult:
|
|
28
|
+
status: str
|
|
29
|
+
id: str
|
|
30
|
+
docs_total: Optional[int] = None
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def _from(cls, d: Dict[str, Any]) -> "InsertResult":
|
|
34
|
+
return cls(status=d.get("status", "ok"), id=d.get("id", ""), docs_total=d.get("docs_total"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ─── Sync ─────────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
class IndexClient:
|
|
40
|
+
def __init__(self, name: str, http: SyncHttp) -> None:
|
|
41
|
+
self._name = name
|
|
42
|
+
self._http = http
|
|
43
|
+
|
|
44
|
+
def insert(self, id: str, text: str, metadata: Optional[Dict[str, Any]] = None) -> InsertResult:
|
|
45
|
+
body: Dict[str, Any] = {"id": id, "text": text}
|
|
46
|
+
if metadata:
|
|
47
|
+
body["metadata"] = metadata
|
|
48
|
+
return InsertResult._from(self._http.post(f"/v1/indexes/{self._name}/insert", body))
|
|
49
|
+
|
|
50
|
+
def insert_many(self, docs: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
51
|
+
return self._http.post(f"/v1/indexes/{self._name}/batch", {"docs": docs})
|
|
52
|
+
|
|
53
|
+
def search(
|
|
54
|
+
self,
|
|
55
|
+
query: str,
|
|
56
|
+
limit: int = 10,
|
|
57
|
+
threshold: Optional[float] = None,
|
|
58
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
59
|
+
) -> List[SearchResult]:
|
|
60
|
+
body: Dict[str, Any] = {"query": query, "limit": limit}
|
|
61
|
+
if threshold is not None:
|
|
62
|
+
body["threshold"] = threshold
|
|
63
|
+
if filter:
|
|
64
|
+
body["filter"] = filter
|
|
65
|
+
data = self._http.post(f"/v1/indexes/{self._name}/search", body)
|
|
66
|
+
return [SearchResult._from(r) for r in data.get("results", [])]
|
|
67
|
+
|
|
68
|
+
def get(self, id: str) -> Optional[Dict[str, Any]]:
|
|
69
|
+
try:
|
|
70
|
+
return self._http.get(f"/v1/indexes/{self._name}/docs/{id}")
|
|
71
|
+
except Exception:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def delete(self, id: str) -> None:
|
|
75
|
+
self._http.delete(f"/v1/indexes/{self._name}/docs/{id}")
|
|
76
|
+
|
|
77
|
+
def stats(self) -> Dict[str, Any]:
|
|
78
|
+
return self._http.get(f"/v1/indexes/{self._name}/stats")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ─── Async ────────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
class AsyncIndexClient:
|
|
84
|
+
def __init__(self, name: str, http: AsyncHttp) -> None:
|
|
85
|
+
self._name = name
|
|
86
|
+
self._http = http
|
|
87
|
+
|
|
88
|
+
async def insert(self, id: str, text: str, metadata: Optional[Dict[str, Any]] = None) -> InsertResult:
|
|
89
|
+
body: Dict[str, Any] = {"id": id, "text": text}
|
|
90
|
+
if metadata:
|
|
91
|
+
body["metadata"] = metadata
|
|
92
|
+
return InsertResult._from(await self._http.post(f"/v1/indexes/{self._name}/insert", body))
|
|
93
|
+
|
|
94
|
+
async def insert_many(self, docs: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
95
|
+
return await self._http.post(f"/v1/indexes/{self._name}/batch", {"docs": docs})
|
|
96
|
+
|
|
97
|
+
async def search(
|
|
98
|
+
self,
|
|
99
|
+
query: str,
|
|
100
|
+
limit: int = 10,
|
|
101
|
+
threshold: Optional[float] = None,
|
|
102
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
103
|
+
) -> List[SearchResult]:
|
|
104
|
+
body: Dict[str, Any] = {"query": query, "limit": limit}
|
|
105
|
+
if threshold is not None:
|
|
106
|
+
body["threshold"] = threshold
|
|
107
|
+
if filter:
|
|
108
|
+
body["filter"] = filter
|
|
109
|
+
data = await self._http.post(f"/v1/indexes/{self._name}/search", body)
|
|
110
|
+
return [SearchResult._from(r) for r in data.get("results", [])]
|
|
111
|
+
|
|
112
|
+
async def get(self, id: str) -> Optional[Dict[str, Any]]:
|
|
113
|
+
try:
|
|
114
|
+
return await self._http.get(f"/v1/indexes/{self._name}/docs/{id}")
|
|
115
|
+
except Exception:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
async def delete(self, id: str) -> None:
|
|
119
|
+
await self._http.delete(f"/v1/indexes/{self._name}/docs/{id}")
|
|
120
|
+
|
|
121
|
+
async def stats(self) -> Dict[str, Any]:
|
|
122
|
+
return await self._http.get(f"/v1/indexes/{self._name}/stats")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from .http import SyncHttp, AsyncHttp
|
|
6
|
+
from .index import SearchResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MemoryClient:
|
|
10
|
+
def __init__(self, http: SyncHttp) -> None:
|
|
11
|
+
self._http = http
|
|
12
|
+
|
|
13
|
+
def store(self, text: str, layer: Literal["long-term", "short-term"] = "long-term") -> Dict[str, Any]:
|
|
14
|
+
return self._http.post("/v1/memory", {"text": text, "layer": layer})
|
|
15
|
+
|
|
16
|
+
def search(self, query: str, limit: int = 5) -> List[SearchResult]:
|
|
17
|
+
data = self._http.post("/v1/memory/search", {"query": query, "limit": limit})
|
|
18
|
+
return [SearchResult._from(r) for r in data.get("results", [])]
|
|
19
|
+
|
|
20
|
+
def list(self, limit: int = 20) -> List[Dict[str, Any]]:
|
|
21
|
+
data = self._http.get(f"/v1/memory?limit={limit}")
|
|
22
|
+
return data.get("memories", [])
|
|
23
|
+
|
|
24
|
+
def delete(self, id: str) -> None:
|
|
25
|
+
self._http.delete(f"/v1/memory/{id}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AsyncMemoryClient:
|
|
29
|
+
def __init__(self, http: AsyncHttp) -> None:
|
|
30
|
+
self._http = http
|
|
31
|
+
|
|
32
|
+
async def store(self, text: str, layer: Literal["long-term", "short-term"] = "long-term") -> Dict[str, Any]:
|
|
33
|
+
return await self._http.post("/v1/memory", {"text": text, "layer": layer})
|
|
34
|
+
|
|
35
|
+
async def search(self, query: str, limit: int = 5) -> List[SearchResult]:
|
|
36
|
+
data = await self._http.post("/v1/memory/search", {"query": query, "limit": limit})
|
|
37
|
+
return [SearchResult._from(r) for r in data.get("results", [])]
|
|
38
|
+
|
|
39
|
+
async def list(self, limit: int = 20) -> List[Dict[str, Any]]:
|
|
40
|
+
data = await self._http.get(f"/v1/memory?limit={limit}")
|
|
41
|
+
return data.get("memories", [])
|
|
42
|
+
|
|
43
|
+
async def delete(self, id: str) -> None:
|
|
44
|
+
await self._http.delete(f"/v1/memory/{id}")
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from .http import SyncHttp, AsyncHttp
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Note:
|
|
11
|
+
id: str
|
|
12
|
+
title: str
|
|
13
|
+
tags: List[str] = field(default_factory=list)
|
|
14
|
+
text_preview: str = ""
|
|
15
|
+
updated_at: Optional[float] = None
|
|
16
|
+
score: Optional[float] = None
|
|
17
|
+
matched_section: Optional[str] = None
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def _from(cls, d: Dict[str, Any]) -> "Note":
|
|
21
|
+
return cls(
|
|
22
|
+
id=d.get("id", ""),
|
|
23
|
+
title=d.get("title", "Untitled"),
|
|
24
|
+
tags=d.get("tags", []),
|
|
25
|
+
text_preview=d.get("text_preview", ""),
|
|
26
|
+
updated_at=d.get("updated_at"),
|
|
27
|
+
score=d.get("score"),
|
|
28
|
+
matched_section=d.get("matched_section"),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class NoteDetail(Note):
|
|
34
|
+
text: str = ""
|
|
35
|
+
tiptap_json: Optional[str] = None
|
|
36
|
+
related_notes: List[Dict[str, Any]] = field(default_factory=list)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def _from(cls, d: Dict[str, Any]) -> "NoteDetail": # type: ignore[override]
|
|
40
|
+
return cls(
|
|
41
|
+
id=d.get("id", ""),
|
|
42
|
+
title=d.get("title", "Untitled"),
|
|
43
|
+
tags=d.get("tags", []),
|
|
44
|
+
text_preview=d.get("text_preview", ""),
|
|
45
|
+
updated_at=d.get("updated_at"),
|
|
46
|
+
text=d.get("text", ""),
|
|
47
|
+
tiptap_json=d.get("tiptap_json"),
|
|
48
|
+
related_notes=d.get("related_notes", []),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class NotesClient:
|
|
53
|
+
def __init__(self, http: SyncHttp) -> None:
|
|
54
|
+
self._http = http
|
|
55
|
+
|
|
56
|
+
def upsert(
|
|
57
|
+
self,
|
|
58
|
+
title: str,
|
|
59
|
+
text: str,
|
|
60
|
+
id: Optional[str] = None,
|
|
61
|
+
tags: Optional[List[str]] = None,
|
|
62
|
+
) -> Dict[str, Any]:
|
|
63
|
+
body: Dict[str, Any] = {"title": title, "text": text}
|
|
64
|
+
if id:
|
|
65
|
+
body["id"] = id
|
|
66
|
+
if tags:
|
|
67
|
+
body["tags"] = tags
|
|
68
|
+
return self._http.post("/v1/notes", body)
|
|
69
|
+
|
|
70
|
+
def list(self) -> List[Note]:
|
|
71
|
+
data = self._http.get("/v1/notes")
|
|
72
|
+
return [Note._from(n) for n in data.get("notes", [])]
|
|
73
|
+
|
|
74
|
+
def get(self, id: str) -> NoteDetail:
|
|
75
|
+
return NoteDetail._from(self._http.get(f"/v1/notes/{id}"))
|
|
76
|
+
|
|
77
|
+
def search(self, query: str, limit: int = 10) -> List[Note]:
|
|
78
|
+
data = self._http.post("/v1/notes/search", {"query": query, "limit": limit})
|
|
79
|
+
return [Note._from(n) for n in data.get("results", [])]
|
|
80
|
+
|
|
81
|
+
def delete(self, id: str) -> None:
|
|
82
|
+
self._http.delete(f"/v1/notes/{id}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class AsyncNotesClient:
|
|
86
|
+
def __init__(self, http: AsyncHttp) -> None:
|
|
87
|
+
self._http = http
|
|
88
|
+
|
|
89
|
+
async def upsert(
|
|
90
|
+
self,
|
|
91
|
+
title: str,
|
|
92
|
+
text: str,
|
|
93
|
+
id: Optional[str] = None,
|
|
94
|
+
tags: Optional[List[str]] = None,
|
|
95
|
+
) -> Dict[str, Any]:
|
|
96
|
+
body: Dict[str, Any] = {"title": title, "text": text}
|
|
97
|
+
if id:
|
|
98
|
+
body["id"] = id
|
|
99
|
+
if tags:
|
|
100
|
+
body["tags"] = tags
|
|
101
|
+
return await self._http.post("/v1/notes", body)
|
|
102
|
+
|
|
103
|
+
async def list(self) -> List[Note]:
|
|
104
|
+
data = await self._http.get("/v1/notes")
|
|
105
|
+
return [Note._from(n) for n in data.get("notes", [])]
|
|
106
|
+
|
|
107
|
+
async def get(self, id: str) -> NoteDetail:
|
|
108
|
+
return NoteDetail._from(await self._http.get(f"/v1/notes/{id}"))
|
|
109
|
+
|
|
110
|
+
async def search(self, query: str, limit: int = 10) -> List[Note]:
|
|
111
|
+
data = await self._http.post("/v1/notes/search", {"query": query, "limit": limit})
|
|
112
|
+
return [Note._from(n) for n in data.get("results", [])]
|
|
113
|
+
|
|
114
|
+
async def delete(self, id: str) -> None:
|
|
115
|
+
await self._http.delete(f"/v1/notes/{id}")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cubememory
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Cube Memory — semantic search with 100% exact recall
|
|
5
|
+
Author-email: Ronny Guerreiro <ynnor.guerreiro@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://cubememory.com.br
|
|
8
|
+
Project-URL: Documentation, https://cubememory.com.br/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/cubememory/sdk-python
|
|
10
|
+
Keywords: vector-search,semantic-search,rag,llm-memory,mcp,cube-memory
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: requests>=2.28
|
|
21
|
+
Provides-Extra: async
|
|
22
|
+
Requires-Dist: httpx>=0.25; extra == "async"
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: httpx>=0.25; extra == "all"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
cubememory/__init__.py
|
|
3
|
+
cubememory/client.py
|
|
4
|
+
cubememory/exceptions.py
|
|
5
|
+
cubememory/http.py
|
|
6
|
+
cubememory/index.py
|
|
7
|
+
cubememory/memory.py
|
|
8
|
+
cubememory/notes.py
|
|
9
|
+
cubememory.egg-info/PKG-INFO
|
|
10
|
+
cubememory.egg-info/SOURCES.txt
|
|
11
|
+
cubememory.egg-info/dependency_links.txt
|
|
12
|
+
cubememory.egg-info/requires.txt
|
|
13
|
+
cubememory.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cubememory
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cubememory"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for Cube Memory — semantic search with 100% exact recall"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
authors = [{ name = "Ronny Guerreiro", email = "ynnor.guerreiro@gmail.com" }]
|
|
11
|
+
keywords = ["vector-search", "semantic-search", "rag", "llm-memory", "mcp", "cube-memory"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.9",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
|
+
]
|
|
22
|
+
requires-python = ">=3.9"
|
|
23
|
+
dependencies = [
|
|
24
|
+
"requests>=2.28",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
async = ["httpx>=0.25"]
|
|
29
|
+
all = ["httpx>=0.25"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://cubememory.com.br"
|
|
33
|
+
Documentation = "https://cubememory.com.br/docs"
|
|
34
|
+
Repository = "https://github.com/cubememory/sdk-python"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["cubememory"]
|