databaset 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.
databaset/__init__.py ADDED
@@ -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"
databaset/client.py ADDED
@@ -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
databaset/errors.py ADDED
@@ -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
databaset/memory.py ADDED
@@ -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,8 @@
1
+ databaset/__init__.py,sha256=mc0bJu73A_j73TDWSr7zwOCRMkIIAjkkNMlP_aMs_-4,269
2
+ databaset/client.py,sha256=dS3ZU63vyyxh7h4Sa8ExEryYCDEGF7Yg1F7B8duP-Bc,2701
3
+ databaset/errors.py,sha256=0KpZ7NG6nuZ7d6iiPEsz_OmENd0YYs9_G01UfeN6eaI,405
4
+ databaset/memory.py,sha256=EKnzYMDJePrln0Hsyw58gF4vAdIalPQXnOiYcXfYxFk,3873
5
+ databaset-0.1.0.dist-info/METADATA,sha256=pU0WbmBZRfZ-KqI1k0y12hzu03VjYXHQmuUUsbXwomg,836
6
+ databaset-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ databaset-0.1.0.dist-info/top_level.txt,sha256=-HICYufy2jNjV5KCvziZlMIkd7p_IFkjjisC9D9E20U,10
8
+ databaset-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ databaset