membase-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.
- membase/__init__.py +40 -0
- membase/_version.py +1 -0
- membase/client.py +247 -0
- membase/errors.py +106 -0
- membase/py.typed +0 -0
- membase_sdk-0.1.0.dist-info/METADATA +51 -0
- membase_sdk-0.1.0.dist-info/RECORD +9 -0
- membase_sdk-0.1.0.dist-info/WHEEL +5 -0
- membase_sdk-0.1.0.dist-info/top_level.txt +1 -0
membase/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Membase — your memory, from code. ``pip install membase-sdk``.
|
|
2
|
+
|
|
3
|
+
from membase import Membase
|
|
4
|
+
client = Membase() # MEMBASE_API_KEY
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._version import __version__
|
|
8
|
+
from .client import DEFAULT_BASE_URL, Membase
|
|
9
|
+
from .errors import (
|
|
10
|
+
APIConnectionError,
|
|
11
|
+
APIStatusError,
|
|
12
|
+
APITimeoutError,
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
BadRequestError,
|
|
15
|
+
ConflictError,
|
|
16
|
+
InternalServerError,
|
|
17
|
+
MembaseError,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
PermissionDeniedError,
|
|
20
|
+
RateLimitError,
|
|
21
|
+
UnprocessableEntityError,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"DEFAULT_BASE_URL",
|
|
26
|
+
"Membase",
|
|
27
|
+
"MembaseError",
|
|
28
|
+
"APIConnectionError",
|
|
29
|
+
"APITimeoutError",
|
|
30
|
+
"APIStatusError",
|
|
31
|
+
"BadRequestError",
|
|
32
|
+
"AuthenticationError",
|
|
33
|
+
"PermissionDeniedError",
|
|
34
|
+
"NotFoundError",
|
|
35
|
+
"ConflictError",
|
|
36
|
+
"UnprocessableEntityError",
|
|
37
|
+
"RateLimitError",
|
|
38
|
+
"InternalServerError",
|
|
39
|
+
"__version__",
|
|
40
|
+
]
|
membase/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
membase/client.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""The Membase client: one developer key, the account's memory.
|
|
2
|
+
|
|
3
|
+
from membase import Membase
|
|
4
|
+
|
|
5
|
+
client = Membase() # MEMBASE_API_KEY, optional MEMBASE_BASE_URL
|
|
6
|
+
client.add("Call notes …", container="mv-…", custom_id="call-1")
|
|
7
|
+
client.search("what did we decide about the ledger")
|
|
8
|
+
client.profile(q="working hours")
|
|
9
|
+
|
|
10
|
+
Every method is one operation of the agent protocol (the REST namespace under
|
|
11
|
+
``https://api.app.membase.io/v1``); the access level, reach and confirmation rules are enforced
|
|
12
|
+
server-side, so nothing here can do what the key cannot.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import random
|
|
19
|
+
import time
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from ._version import __version__
|
|
25
|
+
from .errors import APIConnectionError, APITimeoutError, error_for
|
|
26
|
+
|
|
27
|
+
DEFAULT_BASE_URL = "https://api.app.membase.io"
|
|
28
|
+
#: A search is a turn inside the user's memory; the first one after a quiet spell can take up
|
|
29
|
+
#: to a minute while the memory wakes, so the default is generous.
|
|
30
|
+
DEFAULT_TIMEOUT = 90.0
|
|
31
|
+
DEFAULT_MAX_RETRIES = 2
|
|
32
|
+
_RETRY_STATUSES = {408, 409, 429}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Membase:
|
|
36
|
+
"""The client. Thread-safe for reads; one instance per process is fine."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
api_key: str | None = None,
|
|
41
|
+
*,
|
|
42
|
+
base_url: str | None = None,
|
|
43
|
+
timeout: float | None = DEFAULT_TIMEOUT,
|
|
44
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
45
|
+
http_client: httpx.Client | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
api_key = api_key if api_key is not None else os.environ.get("MEMBASE_API_KEY")
|
|
48
|
+
if not api_key:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
"no API key: pass api_key= or set MEMBASE_API_KEY "
|
|
51
|
+
"(Connect › Developer keys in the Membase app)"
|
|
52
|
+
)
|
|
53
|
+
self.api_key = api_key
|
|
54
|
+
self.base_url = (base_url or os.environ.get("MEMBASE_BASE_URL") or DEFAULT_BASE_URL).rstrip(
|
|
55
|
+
"/"
|
|
56
|
+
)
|
|
57
|
+
self.max_retries = max(0, int(max_retries))
|
|
58
|
+
self._owns_http = http_client is None
|
|
59
|
+
self._http = http_client or httpx.Client(base_url=self.base_url, timeout=timeout)
|
|
60
|
+
self.containers = _Containers(self)
|
|
61
|
+
self.documents = _Documents(self)
|
|
62
|
+
self.memories = _Memories(self)
|
|
63
|
+
|
|
64
|
+
# -- the four verbs most code needs ---------------------------------------------------
|
|
65
|
+
|
|
66
|
+
def add(
|
|
67
|
+
self,
|
|
68
|
+
content: str | None = None,
|
|
69
|
+
*,
|
|
70
|
+
container: str,
|
|
71
|
+
url: str | None = None,
|
|
72
|
+
title: str = "",
|
|
73
|
+
metadata: dict | None = None,
|
|
74
|
+
custom_id: str = "",
|
|
75
|
+
) -> dict:
|
|
76
|
+
"""Hand a document (its text, or a public ``url`` to fetch) to a container. Returns at
|
|
77
|
+
once with ``status: queued`` and a ``document_id``; the container learns it in the
|
|
78
|
+
background — ``documents.get(id)["learned"]`` turns true when it has. The same
|
|
79
|
+
``custom_id`` again is a no-op, so retries are safe."""
|
|
80
|
+
body: dict[str, Any] = {"container": container, "title": title, "custom_id": custom_id}
|
|
81
|
+
if content is not None:
|
|
82
|
+
body["content"] = content
|
|
83
|
+
if url is not None:
|
|
84
|
+
body["url"] = url
|
|
85
|
+
if metadata is not None:
|
|
86
|
+
body["metadata"] = metadata
|
|
87
|
+
return self._request("POST", "/v1/documents", json=body)
|
|
88
|
+
|
|
89
|
+
def search(self, q: str, *, container: str | None = None, limit: int | None = None) -> dict:
|
|
90
|
+
"""Passages the containers hold on ``q``, most relevant first, each naming its
|
|
91
|
+
container. ``container`` omitted searches every container in reach. Retrieval, not an
|
|
92
|
+
answer — ``ask`` is the agent's answer. ``containers[]`` in the result marks any
|
|
93
|
+
container that could not answer yet (still waking)."""
|
|
94
|
+
body: dict[str, Any] = {"q": q}
|
|
95
|
+
if container is not None:
|
|
96
|
+
body["container"] = container
|
|
97
|
+
if limit is not None:
|
|
98
|
+
body["limit"] = limit
|
|
99
|
+
return self._request("POST", "/v1/search", json=body)
|
|
100
|
+
|
|
101
|
+
def profile(self, q: str | None = None) -> dict:
|
|
102
|
+
"""Who the user is: ``static`` (standing facts), ``dynamic`` (the most recently
|
|
103
|
+
changed facts) and, with ``q``, ``results`` relevant to the topic. Needs a key minted
|
|
104
|
+
with the profile tick."""
|
|
105
|
+
return self._request("GET", "/v1/profile", params={"q": q} if q else None)
|
|
106
|
+
|
|
107
|
+
def ask(self, message: str, *, model: str | None = None) -> dict:
|
|
108
|
+
"""The exposed agent's answer to ``message`` (an agent-endpoint credential)."""
|
|
109
|
+
body: dict[str, Any] = {"message": message}
|
|
110
|
+
if model is not None:
|
|
111
|
+
body["model"] = model
|
|
112
|
+
return self._request("POST", "/v1/ask", json=body)
|
|
113
|
+
|
|
114
|
+
def rules(self) -> dict:
|
|
115
|
+
"""The user's standing rules for how their memory is used by this credential."""
|
|
116
|
+
return self._request("GET", "/v1/rules")
|
|
117
|
+
|
|
118
|
+
# -- plumbing -------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
def close(self) -> None:
|
|
121
|
+
if self._owns_http:
|
|
122
|
+
self._http.close()
|
|
123
|
+
|
|
124
|
+
def __enter__(self) -> Membase:
|
|
125
|
+
return self
|
|
126
|
+
|
|
127
|
+
def __exit__(self, *exc: object) -> None:
|
|
128
|
+
self.close()
|
|
129
|
+
|
|
130
|
+
def _request(
|
|
131
|
+
self,
|
|
132
|
+
method: str,
|
|
133
|
+
path: str,
|
|
134
|
+
*,
|
|
135
|
+
json: dict | None = None,
|
|
136
|
+
params: dict | None = None,
|
|
137
|
+
) -> Any:
|
|
138
|
+
headers = {
|
|
139
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
140
|
+
"Accept": "application/json",
|
|
141
|
+
"User-Agent": f"membase-sdk-python/{__version__}",
|
|
142
|
+
}
|
|
143
|
+
attempt = 0
|
|
144
|
+
while True:
|
|
145
|
+
try:
|
|
146
|
+
r = self._http.request(method, path, json=json, params=params, headers=headers)
|
|
147
|
+
except httpx.TimeoutException as e:
|
|
148
|
+
if attempt < self.max_retries:
|
|
149
|
+
attempt += 1
|
|
150
|
+
time.sleep(_backoff(attempt))
|
|
151
|
+
continue
|
|
152
|
+
raise APITimeoutError(f"{method} {path} timed out") from e
|
|
153
|
+
except httpx.HTTPError as e:
|
|
154
|
+
if attempt < self.max_retries:
|
|
155
|
+
attempt += 1
|
|
156
|
+
time.sleep(_backoff(attempt))
|
|
157
|
+
continue
|
|
158
|
+
raise APIConnectionError(f"{method} {path}: {e}") from e
|
|
159
|
+
if 200 <= r.status_code < 300:
|
|
160
|
+
return r.json() if r.content else None
|
|
161
|
+
if (
|
|
162
|
+
r.status_code in _RETRY_STATUSES or r.status_code >= 500
|
|
163
|
+
) and attempt < self.max_retries:
|
|
164
|
+
attempt += 1
|
|
165
|
+
time.sleep(_backoff(attempt, r.headers.get("Retry-After")))
|
|
166
|
+
continue
|
|
167
|
+
try:
|
|
168
|
+
body = r.json()
|
|
169
|
+
except ValueError:
|
|
170
|
+
body = {"error": {"message": r.text}}
|
|
171
|
+
raise error_for(r.status_code, body)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _backoff(attempt: int, retry_after: str | None = None) -> float:
|
|
175
|
+
if retry_after:
|
|
176
|
+
try:
|
|
177
|
+
return min(float(retry_after), 30.0)
|
|
178
|
+
except ValueError:
|
|
179
|
+
pass
|
|
180
|
+
return min(0.5 * (2 ** (attempt - 1)), 8.0) * (0.5 + random.random())
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class _Containers:
|
|
184
|
+
def __init__(self, client: Membase) -> None:
|
|
185
|
+
self._c = client
|
|
186
|
+
|
|
187
|
+
def list(self) -> dict:
|
|
188
|
+
"""The containers this key may use — every one, for the account's owner."""
|
|
189
|
+
return self._c._request("GET", "/v1/containers")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class _Documents:
|
|
193
|
+
def __init__(self, client: Membase) -> None:
|
|
194
|
+
self._c = client
|
|
195
|
+
|
|
196
|
+
def list(self, *, container: str | None = None) -> dict:
|
|
197
|
+
"""The documents the containers in reach have read, newest first, each with
|
|
198
|
+
``learned``."""
|
|
199
|
+
return self._c._request(
|
|
200
|
+
"GET", "/v1/documents", params={"container": container} if container else None
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def get(self, document_id: str) -> dict:
|
|
204
|
+
"""One document, with whether its container has learned it yet."""
|
|
205
|
+
return self._c._request("GET", f"/v1/documents/{document_id}")
|
|
206
|
+
|
|
207
|
+
def delete(self, document_id: str, *, confirm: bool = False) -> dict:
|
|
208
|
+
"""Remove one document everywhere (the file goes to the Files trash). Needs Full
|
|
209
|
+
access and ``confirm=True``; without it the answer is ``status:
|
|
210
|
+
confirmation_required`` with a ``how`` sentence to relay, not an error."""
|
|
211
|
+
return self._c._request(
|
|
212
|
+
"DELETE", f"/v1/documents/{document_id}", params={"confirm": _flag(confirm)}
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class _Memories:
|
|
217
|
+
def __init__(self, client: Membase) -> None:
|
|
218
|
+
self._c = client
|
|
219
|
+
|
|
220
|
+
def add(
|
|
221
|
+
self,
|
|
222
|
+
content: str,
|
|
223
|
+
*,
|
|
224
|
+
container: str | None = None,
|
|
225
|
+
static: bool = False,
|
|
226
|
+
title: str = "",
|
|
227
|
+
) -> dict:
|
|
228
|
+
"""Save one fact. ``static=True`` is a standing fact about the user and goes to their
|
|
229
|
+
profile; otherwise a note the container reads. ``container`` may be omitted when
|
|
230
|
+
exactly one is in reach."""
|
|
231
|
+
body: dict[str, Any] = {"content": content, "static": static, "title": title}
|
|
232
|
+
if container is not None:
|
|
233
|
+
body["container"] = container
|
|
234
|
+
return self._c._request("POST", "/v1/memories", json=body)
|
|
235
|
+
|
|
236
|
+
def forget(
|
|
237
|
+
self, memory_id: str, *, container: str | None = None, confirm: bool = False
|
|
238
|
+
) -> dict:
|
|
239
|
+
"""Forget one learned fact. Same confirmation rule as ``documents.delete``."""
|
|
240
|
+
params: dict[str, Any] = {"confirm": _flag(confirm)}
|
|
241
|
+
if container is not None:
|
|
242
|
+
params["container"] = container
|
|
243
|
+
return self._c._request("DELETE", f"/v1/memories/{memory_id}", params=params)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _flag(v: bool) -> str:
|
|
247
|
+
return "true" if v else "false"
|
membase/errors.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Errors, one class per HTTP status class, all carrying the platform's error envelope.
|
|
2
|
+
|
|
3
|
+
The API answers every failure as ``{"error": {"code", "message", "details", "retryable",
|
|
4
|
+
"trace_id"}}``. The status decides the class (so a caller can ``except RateLimitError``), the
|
|
5
|
+
``code`` says which rule refused (``unauthorized`` for a container outside the key's reach,
|
|
6
|
+
``capability_unavailable`` when the account's memory cannot run a turn, …), and ``trace_id`` is
|
|
7
|
+
what to quote when reporting a problem.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MembaseError(Exception):
|
|
16
|
+
"""Base class of everything this package raises."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, message: str, *, status: int | None = None, body: Any = None) -> None:
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
self.message = message
|
|
21
|
+
self.status = status
|
|
22
|
+
self.body = body
|
|
23
|
+
err = body.get("error") if isinstance(body, dict) else None
|
|
24
|
+
err = err if isinstance(err, dict) else {}
|
|
25
|
+
self.code: str = str(err.get("code") or "")
|
|
26
|
+
self.details: Any = err.get("details")
|
|
27
|
+
self.retryable: bool = bool(err.get("retryable", False))
|
|
28
|
+
self.trace_id: str = str(err.get("trace_id") or "")
|
|
29
|
+
|
|
30
|
+
def __str__(self) -> str: # pragma: no cover - formatting
|
|
31
|
+
bits = [self.message]
|
|
32
|
+
if self.status is not None:
|
|
33
|
+
bits.append(f"[{self.status}{' ' + self.code if self.code else ''}]")
|
|
34
|
+
if self.trace_id:
|
|
35
|
+
bits.append(f"trace_id={self.trace_id}")
|
|
36
|
+
return " ".join(bits)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class APIConnectionError(MembaseError):
|
|
40
|
+
"""The request never got an answer (DNS, connection refused, reset)."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class APITimeoutError(APIConnectionError):
|
|
44
|
+
"""The request ran past the client's timeout."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class APIStatusError(MembaseError):
|
|
48
|
+
"""A non-2xx answer. Subclasses per status class below."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BadRequestError(APIStatusError):
|
|
52
|
+
"""400 — a malformed request: a missing ``q``, both ``content`` and ``url``, an ambiguous
|
|
53
|
+
``container``."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AuthenticationError(APIStatusError):
|
|
57
|
+
"""401 — the request carried no bearer at all."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PermissionDeniedError(APIStatusError):
|
|
61
|
+
"""403 (``code: unauthorized``) — an unknown, expired or revoked key; a container outside
|
|
62
|
+
the key's reach; or a verb above its access level. Rotate the key, switch the memory on under
|
|
63
|
+
Reach on the key's page, or mint a key at a higher level."""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class NotFoundError(APIStatusError):
|
|
67
|
+
"""404 — an unknown document or memory id."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ConflictError(APIStatusError):
|
|
71
|
+
"""409."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class UnprocessableEntityError(APIStatusError):
|
|
75
|
+
"""422 — the account's memory cannot run a turn on this deployment (``capability_unavailable``:
|
|
76
|
+
no agent container, no model), or a body FastAPI could not parse."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class RateLimitError(APIStatusError):
|
|
80
|
+
"""429 — the account's turn budget is spent for now. Retried automatically; retry later."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class InternalServerError(APIStatusError):
|
|
84
|
+
"""5xx."""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
_BY_STATUS: dict[int, type[APIStatusError]] = {
|
|
88
|
+
400: BadRequestError,
|
|
89
|
+
401: AuthenticationError,
|
|
90
|
+
403: PermissionDeniedError,
|
|
91
|
+
404: NotFoundError,
|
|
92
|
+
409: ConflictError,
|
|
93
|
+
422: UnprocessableEntityError,
|
|
94
|
+
429: RateLimitError,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def error_for(status: int, body: Any, fallback: str = "") -> APIStatusError:
|
|
99
|
+
"""The exception for a non-2xx answer."""
|
|
100
|
+
err = body.get("error") if isinstance(body, dict) else None
|
|
101
|
+
message = (err or {}).get("message") if isinstance(err, dict) else None
|
|
102
|
+
if not message and isinstance(body, dict) and body.get("detail"):
|
|
103
|
+
message = str(body["detail"]) # FastAPI's own 422 shape
|
|
104
|
+
message = message or fallback or f"HTTP {status}"
|
|
105
|
+
cls = _BY_STATUS.get(status) or (InternalServerError if status >= 500 else APIStatusError)
|
|
106
|
+
return cls(message, status=status, body=body)
|
membase/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: membase-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Membase — your memory, from code. The official Python client for the Membase API.
|
|
5
|
+
Author: Unibase
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.app.membase.io
|
|
8
|
+
Project-URL: Documentation, https://www.app.membase.io/skill
|
|
9
|
+
Keywords: membase,memory,ai,agents,mcp
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: httpx<1,>=0.25
|
|
18
|
+
|
|
19
|
+
# membase-sdk
|
|
20
|
+
|
|
21
|
+
Your memory, from code. The official Python client for the [Membase](https://www.app.membase.io) API.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install membase-sdk
|
|
25
|
+
export MEMBASE_API_KEY="mbk_…" # Connect › Developer keys in the Membase app
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from membase import Membase
|
|
30
|
+
|
|
31
|
+
client = Membase()
|
|
32
|
+
|
|
33
|
+
client.add("Call notes with Acme: they want SSO before the pilot.", container="mv-…", custom_id="call-2026-09-24")
|
|
34
|
+
hits = client.search("what does Acme need before the pilot", limit=5)
|
|
35
|
+
print(hits["results"][0]["content"])
|
|
36
|
+
|
|
37
|
+
client.profile(q="working hours") # who the user is (needs the profile tick on the key)
|
|
38
|
+
client.containers.list()
|
|
39
|
+
client.documents.list(container="mv-…")
|
|
40
|
+
client.memories.add("The user prefers dark mode.", static=True)
|
|
41
|
+
client.documents.delete("srcitem_…", confirm=True) # Full access; confirm means the person agreed
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Every method is one operation of the Membase agent protocol; access level, reach and confirmation
|
|
45
|
+
rules are enforced server-side. Errors are one class per HTTP status (`PermissionDeniedError`,
|
|
46
|
+
`RateLimitError`, …) and carry the API's `code` and `trace_id`. 429 and 5xx answers are retried
|
|
47
|
+
twice with backoff; the default timeout is 90 s, because the first search after a quiet spell
|
|
48
|
+
waits for the user's memory to wake.
|
|
49
|
+
|
|
50
|
+
Docs: the developer documentation's *SDK Quickstart*. Works against a self-hosted Membase too:
|
|
51
|
+
`Membase(base_url="http://localhost:8080")`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
membase/__init__.py,sha256=XiUTUyFFbYimefhiWIyw0-RQXSV7NP1KKmDovuz_vt4,879
|
|
2
|
+
membase/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
3
|
+
membase/client.py,sha256=0UX983LKUL6EDyJGd25xf_9G7QjEVblELHuFJYLWGek,9496
|
|
4
|
+
membase/errors.py,sha256=nMEk_oBR8ctOucT_aufJtXBV2MNfL5UPez6pryEUNd4,3831
|
|
5
|
+
membase/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
membase_sdk-0.1.0.dist-info/METADATA,sha256=5nDUjkZZvVaBnG6ksfsulqNcnxrWM8FrfZTX9kgF0i8,2075
|
|
7
|
+
membase_sdk-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
membase_sdk-0.1.0.dist-info/top_level.txt,sha256=oerSw-WxE1-6RSn8wevBEM4W2vE83HLzciSxIfMGYhQ,8
|
|
9
|
+
membase_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
membase
|