databaset 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.
- databaset-0.1.0/PKG-INFO +39 -0
- databaset-0.1.0/README.md +29 -0
- databaset-0.1.0/databaset/__init__.py +7 -0
- databaset-0.1.0/databaset/client.py +89 -0
- databaset-0.1.0/databaset/errors.py +15 -0
- databaset-0.1.0/databaset/memory.py +137 -0
- databaset-0.1.0/databaset.egg-info/PKG-INFO +39 -0
- databaset-0.1.0/databaset.egg-info/SOURCES.txt +11 -0
- databaset-0.1.0/databaset.egg-info/dependency_links.txt +1 -0
- databaset-0.1.0/databaset.egg-info/requires.txt +3 -0
- databaset-0.1.0/databaset.egg-info/top_level.txt +1 -0
- databaset-0.1.0/pyproject.toml +18 -0
- databaset-0.1.0/setup.cfg +4 -0
databaset-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: databaset
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Databaset memory SDK for Python
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
10
|
+
|
|
11
|
+
# Databaset Python SDK
|
|
12
|
+
|
|
13
|
+
Default API: **`https://api.databaset.com`**
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e ./sdks/python
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import os
|
|
21
|
+
from databaset import Memory
|
|
22
|
+
|
|
23
|
+
memory = Memory(api_key=os.environ["DATABASET_API_KEY"])
|
|
24
|
+
|
|
25
|
+
memory.store("user_123", "Prefers dark mode", metadata={"source": "api"})
|
|
26
|
+
context = memory.recall("user_123", "editor preferences")
|
|
27
|
+
print(context)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Set `DATABASET_API_KEY=db_...`. Optional `DATABASET_API_URL` (defaults to production).
|
|
31
|
+
|
|
32
|
+
Local backend:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
memory = Memory(
|
|
36
|
+
api_key=os.environ["DATABASET_API_KEY"],
|
|
37
|
+
base_url="http://localhost:8085",
|
|
38
|
+
)
|
|
39
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Databaset Python SDK
|
|
2
|
+
|
|
3
|
+
Default API: **`https://api.databaset.com`**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install -e ./sdks/python
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import os
|
|
11
|
+
from databaset import Memory
|
|
12
|
+
|
|
13
|
+
memory = Memory(api_key=os.environ["DATABASET_API_KEY"])
|
|
14
|
+
|
|
15
|
+
memory.store("user_123", "Prefers dark mode", metadata={"source": "api"})
|
|
16
|
+
context = memory.recall("user_123", "editor preferences")
|
|
17
|
+
print(context)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Set `DATABASET_API_KEY=db_...`. Optional `DATABASET_API_URL` (defaults to production).
|
|
21
|
+
|
|
22
|
+
Local backend:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
memory = Memory(
|
|
26
|
+
api_key=os.environ["DATABASET_API_KEY"],
|
|
27
|
+
base_url="http://localhost:8085",
|
|
28
|
+
)
|
|
29
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Databaset Python SDK — AI memory store, recall, and forget."""
|
|
2
|
+
|
|
3
|
+
from databaset.errors import ApiError, DatabasetError, ValidationError
|
|
4
|
+
from databaset.memory import Memory
|
|
5
|
+
|
|
6
|
+
__all__ = ["Memory", "DatabasetError", "ValidationError", "ApiError"]
|
|
7
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import urllib.error
|
|
6
|
+
import urllib.parse
|
|
7
|
+
import urllib.request
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from databaset.errors import ApiError, DatabasetError
|
|
11
|
+
|
|
12
|
+
DEFAULT_BASE_URL = "https://api.databaset.com"
|
|
13
|
+
API_KEY_PREFIX = "db_"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DatabasetClient:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
api_key: str,
|
|
20
|
+
*,
|
|
21
|
+
base_url: str | None = None,
|
|
22
|
+
timeout: float = 30.0,
|
|
23
|
+
):
|
|
24
|
+
self.api_key = _assert_api_key(api_key)
|
|
25
|
+
self.base_url = _normalize_base_url(base_url or os.environ.get("DATABASET_API_URL") or DEFAULT_BASE_URL)
|
|
26
|
+
self.timeout = timeout
|
|
27
|
+
|
|
28
|
+
def request(
|
|
29
|
+
self,
|
|
30
|
+
method: str,
|
|
31
|
+
path: str,
|
|
32
|
+
*,
|
|
33
|
+
json_body: dict[str, Any] | None = None,
|
|
34
|
+
params: dict[str, Any] | None = None,
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
url = f"{self.base_url}{path}"
|
|
37
|
+
if params:
|
|
38
|
+
filtered = {k: v for k, v in params.items() if v is not None and v != ""}
|
|
39
|
+
if filtered:
|
|
40
|
+
url = f"{url}?{urllib.parse.urlencode(filtered)}"
|
|
41
|
+
|
|
42
|
+
data = None
|
|
43
|
+
headers = {
|
|
44
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
45
|
+
"Accept": "application/json",
|
|
46
|
+
}
|
|
47
|
+
if json_body is not None:
|
|
48
|
+
data = json.dumps(json_body).encode("utf-8")
|
|
49
|
+
headers["Content-Type"] = "application/json"
|
|
50
|
+
|
|
51
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method.upper())
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as res:
|
|
55
|
+
raw = res.read().decode("utf-8")
|
|
56
|
+
if not raw:
|
|
57
|
+
return {}
|
|
58
|
+
return json.loads(raw)
|
|
59
|
+
except urllib.error.HTTPError as e:
|
|
60
|
+
raw = e.read().decode("utf-8", errors="replace")
|
|
61
|
+
body: dict[str, Any] = {}
|
|
62
|
+
if raw:
|
|
63
|
+
try:
|
|
64
|
+
body = json.loads(raw)
|
|
65
|
+
except json.JSONDecodeError:
|
|
66
|
+
body = {"raw": raw}
|
|
67
|
+
message = body.get("error") or body.get("message") or f"HTTP {e.code}"
|
|
68
|
+
raise ApiError(message, status=e.code, code=body.get("code"), body=body) from e
|
|
69
|
+
except urllib.error.URLError as e:
|
|
70
|
+
raise DatabasetError(
|
|
71
|
+
f"Network error — is the API running at {self.base_url}? ({e.reason})"
|
|
72
|
+
) from e
|
|
73
|
+
except json.JSONDecodeError as e:
|
|
74
|
+
raise DatabasetError("Invalid JSON response from API") from e
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _normalize_base_url(base_url: str) -> str:
|
|
78
|
+
if not base_url or not base_url.strip():
|
|
79
|
+
raise DatabasetError("base_url must be a non-empty string")
|
|
80
|
+
return base_url.rstrip("/")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _assert_api_key(api_key: str) -> str:
|
|
84
|
+
if not api_key or not str(api_key).strip():
|
|
85
|
+
raise DatabasetError("api_key is required (or set DATABASET_API_KEY)")
|
|
86
|
+
key = str(api_key).strip()
|
|
87
|
+
if not key.startswith(API_KEY_PREFIX):
|
|
88
|
+
raise DatabasetError(f'api_key must start with "{API_KEY_PREFIX}"')
|
|
89
|
+
return key
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class DatabasetError(Exception):
|
|
2
|
+
def __init__(self, message: str, *, status: int | None = None, code: str | None = None, body=None):
|
|
3
|
+
super().__init__(message)
|
|
4
|
+
self.status = status
|
|
5
|
+
self.code = code
|
|
6
|
+
self.body = body
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ValidationError(DatabasetError):
|
|
10
|
+
def __init__(self, message: str):
|
|
11
|
+
super().__init__(message, code="validation_error")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ApiError(DatabasetError):
|
|
15
|
+
pass
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from databaset.client import DatabasetClient
|
|
7
|
+
from databaset.errors import ValidationError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _require(value: Any, field: str) -> str:
|
|
11
|
+
if value is None or str(value).strip() == "":
|
|
12
|
+
raise ValidationError(f"{field} is required")
|
|
13
|
+
return str(value).strip()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _clamp_int(value: Any, min_v: int, max_v: int, default: int) -> int:
|
|
17
|
+
try:
|
|
18
|
+
n = int(value)
|
|
19
|
+
except (TypeError, ValueError):
|
|
20
|
+
return default
|
|
21
|
+
return max(min_v, min(max_v, n))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _clamp_float(value: Any, min_v: float, max_v: float, default: float) -> float:
|
|
25
|
+
try:
|
|
26
|
+
n = float(value)
|
|
27
|
+
except (TypeError, ValueError):
|
|
28
|
+
return default
|
|
29
|
+
return max(min_v, min(max_v, n))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Memory:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
api_key: str | None = None,
|
|
36
|
+
*,
|
|
37
|
+
base_url: str | None = None,
|
|
38
|
+
app_id: str | None = None,
|
|
39
|
+
timeout: float = 30.0,
|
|
40
|
+
client: DatabasetClient | None = None,
|
|
41
|
+
):
|
|
42
|
+
key = api_key or os.environ.get("DATABASET_API_KEY")
|
|
43
|
+
self.default_app_id = app_id
|
|
44
|
+
self.client = client or DatabasetClient(key, base_url=base_url, timeout=timeout)
|
|
45
|
+
|
|
46
|
+
def store(
|
|
47
|
+
self,
|
|
48
|
+
user_id: str,
|
|
49
|
+
text: str,
|
|
50
|
+
*,
|
|
51
|
+
app_id: str | None = None,
|
|
52
|
+
metadata: dict[str, Any] | None = None,
|
|
53
|
+
) -> dict[str, Any]:
|
|
54
|
+
uid = _require(user_id, "user_id")
|
|
55
|
+
content = _require(text, "text")
|
|
56
|
+
body: dict[str, Any] = {"userId": uid, "text": content}
|
|
57
|
+
resolved_app = app_id or self.default_app_id
|
|
58
|
+
if resolved_app:
|
|
59
|
+
body["appId"] = resolved_app
|
|
60
|
+
if metadata is not None:
|
|
61
|
+
if not isinstance(metadata, dict):
|
|
62
|
+
raise ValidationError("metadata must be a dict")
|
|
63
|
+
body["metadata"] = metadata
|
|
64
|
+
return self.client.request("POST", "/v1/memories", json_body=body)
|
|
65
|
+
|
|
66
|
+
def recall(
|
|
67
|
+
self,
|
|
68
|
+
user_id: str,
|
|
69
|
+
query: str,
|
|
70
|
+
*,
|
|
71
|
+
app_id: str | None = None,
|
|
72
|
+
limit: int | None = None,
|
|
73
|
+
min_score: float | None = None,
|
|
74
|
+
format: str = "string",
|
|
75
|
+
) -> str | list[dict[str, Any]]:
|
|
76
|
+
uid = _require(user_id, "user_id")
|
|
77
|
+
q = _require(query, "query")
|
|
78
|
+
result = self.client.request(
|
|
79
|
+
"GET",
|
|
80
|
+
"/v1/memories/recall",
|
|
81
|
+
params={
|
|
82
|
+
"userId": uid,
|
|
83
|
+
"query": q,
|
|
84
|
+
"appId": app_id or self.default_app_id,
|
|
85
|
+
"limit": _clamp_int(limit, 1, 100, 5),
|
|
86
|
+
"minScore": _clamp_float(min_score, 0.0, 1.0, 0.7),
|
|
87
|
+
"format": format,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
if format == "array":
|
|
91
|
+
return result.get("memories") or []
|
|
92
|
+
return result.get("context") or ""
|
|
93
|
+
|
|
94
|
+
def recall_raw(self, user_id: str, query: str, **kwargs: Any) -> list[dict[str, Any]]:
|
|
95
|
+
out = self.recall(user_id, query, format="array", **kwargs)
|
|
96
|
+
return out if isinstance(out, list) else []
|
|
97
|
+
|
|
98
|
+
def list(
|
|
99
|
+
self,
|
|
100
|
+
user_id: str,
|
|
101
|
+
*,
|
|
102
|
+
app_id: str | None = None,
|
|
103
|
+
page: int | None = None,
|
|
104
|
+
page_size: int | None = None,
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
uid = _require(user_id, "user_id")
|
|
107
|
+
return self.client.request(
|
|
108
|
+
"GET",
|
|
109
|
+
"/v1/memories",
|
|
110
|
+
params={
|
|
111
|
+
"userId": uid,
|
|
112
|
+
"appId": app_id or self.default_app_id,
|
|
113
|
+
"page": _clamp_int(page, 0, 10_000, 0),
|
|
114
|
+
"pageSize": _clamp_int(page_size, 1, 100, 20),
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def forget(self, memory_id: str) -> dict[str, Any]:
|
|
119
|
+
mid = _require(memory_id, "memory_id")
|
|
120
|
+
return self.client.request("DELETE", f"/v1/memories/{mid}")
|
|
121
|
+
|
|
122
|
+
def forget_user(self, user_id: str, *, app_id: str | None = None) -> dict[str, Any]:
|
|
123
|
+
uid = _require(user_id, "user_id")
|
|
124
|
+
return self.client.request(
|
|
125
|
+
"DELETE",
|
|
126
|
+
"/v1/memories",
|
|
127
|
+
params={"userId": uid, "appId": app_id or self.default_app_id},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def forget_bulk(self, ids: list[str]) -> dict[str, Any]:
|
|
131
|
+
if not ids:
|
|
132
|
+
raise ValidationError("ids must be a non-empty list")
|
|
133
|
+
return self.client.request(
|
|
134
|
+
"DELETE",
|
|
135
|
+
"/v1/memories/bulk",
|
|
136
|
+
json_body={"ids": [str(i).strip() for i in ids]},
|
|
137
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: databaset
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Databaset memory SDK for Python
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
10
|
+
|
|
11
|
+
# Databaset Python SDK
|
|
12
|
+
|
|
13
|
+
Default API: **`https://api.databaset.com`**
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e ./sdks/python
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import os
|
|
21
|
+
from databaset import Memory
|
|
22
|
+
|
|
23
|
+
memory = Memory(api_key=os.environ["DATABASET_API_KEY"])
|
|
24
|
+
|
|
25
|
+
memory.store("user_123", "Prefers dark mode", metadata={"source": "api"})
|
|
26
|
+
context = memory.recall("user_123", "editor preferences")
|
|
27
|
+
print(context)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Set `DATABASET_API_KEY=db_...`. Optional `DATABASET_API_URL` (defaults to production).
|
|
31
|
+
|
|
32
|
+
Local backend:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
memory = Memory(
|
|
36
|
+
api_key=os.environ["DATABASET_API_KEY"],
|
|
37
|
+
base_url="http://localhost:8085",
|
|
38
|
+
)
|
|
39
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
databaset/__init__.py
|
|
4
|
+
databaset/client.py
|
|
5
|
+
databaset/errors.py
|
|
6
|
+
databaset/memory.py
|
|
7
|
+
databaset.egg-info/PKG-INFO
|
|
8
|
+
databaset.egg-info/SOURCES.txt
|
|
9
|
+
databaset.egg-info/dependency_links.txt
|
|
10
|
+
databaset.egg-info/requires.txt
|
|
11
|
+
databaset.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
databaset
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "databaset"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Databaset memory SDK for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
dev = ["pytest>=8.0"]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages.find]
|
|
17
|
+
where = ["."]
|
|
18
|
+
include = ["databaset*"]
|