|
11 | 11 | client.add(session_id="s1", messages=[{"role": "user", "content": "I love hiking"}]) |
12 | 12 | hits = client.search("outdoor hobbies") |
13 | 13 |
|
14 | | -Naming: memory is the unprefixed default (``add`` / ``search`` / ``get`` / ``flush`` / |
15 | | -``edit`` / ``delete``, frozen since 1.0.0); every other resource carries its own prefix |
16 | | -— ``kb_`` / ``doc_`` / ``task_`` / ``tag_`` — so an editor's completion list groups by |
17 | | -resource, and no facade method collides with the generated method it wraps. |
| 14 | +Naming: the nine methods 1.0.0 shipped are bare verbs and stay that way — ``add`` / |
| 15 | +``search`` / ``get`` / ``flush`` / ``edit`` / ``delete`` (memory), ``presign`` / |
| 16 | +``upload`` (storage), ``close``. They are public API and cannot be renamed. Everything |
| 17 | +added since carries its resource as a prefix — ``kb_`` / ``doc_`` / ``task_`` / |
| 18 | +``tag_`` — so completion groups by resource and no facade method collides with the |
| 19 | +generated method it wraps. The two styles sitting side by side is a consequence of that |
| 20 | +freeze, not a convention worth copying. |
18 | 21 |
|
19 | 22 | Errors: every failure raised by this facade derives from :class:`EverOSError` — |
20 | 23 | ``EverOSAPIError`` for HTTP errors, ``EverOSStorageError`` for object-upload failures, |
|
69 | 72 | # on one blip is worse than useless — the caller has no way to resume it. |
70 | 73 | _TRANSIENT_STATUS = frozenset({429, 500, 502, 503, 504}) |
71 | 74 |
|
| 75 | +# Generated method name -> the facade method that covers it. Used only to answer an |
| 76 | +# AttributeError helpfully: the generated per-endpoint docs (docs/*.md) are the largest |
| 77 | +# body of documentation and they describe ONLY the generated clients, so a reader who |
| 78 | +# arrives from there types `client.create_knowledge_base(...)` and would otherwise get a |
| 79 | +# bare AttributeError with nowhere to go. |
| 80 | +_FACADE_FOR = { |
| 81 | + "add_memory": "add", "search_memory": "search", "get_memory": "get", |
| 82 | + "flush_memory": "flush", "edit_profile": "edit", "delete_memory": "delete", |
| 83 | + "sign_objects": "presign", |
| 84 | + "bind_tags": "tag_bind", "unbind_tags": "tag_unbind", "replace_tags": "tag_replace", |
| 85 | + "create_knowledge_base": "kb_create", "list_knowledge_bases": "kb_list", |
| 86 | + "get_knowledge_base": "kb_get", "update_knowledge_base": "kb_update", |
| 87 | + "delete_knowledge_base": "kb_delete", "search_knowledge": "kb_search", |
| 88 | + "create_document": "doc_ingest", "replace_document": "doc_ingest", |
| 89 | + "list_documents": "doc_list", "get_document": "doc_get", |
| 90 | + "update_document": "doc_update", "delete_document": "doc_delete", |
| 91 | + "get_task_status": "task_get", "list_tasks": "task_list", |
| 92 | +} |
| 93 | +_API_ATTRS = ("memory", "storage", "knowledge", "tasks") |
| 94 | + |
72 | 95 | # Statuses the gateway reports as finished. Deliberately paired with a `finished_at` |
73 | 96 | # check in `_task_finished`: `status` is an OPEN set on a response field (a new |
74 | 97 | # terminal state such as `cancelled` must not make `task_wait` spin until timeout), |
@@ -174,6 +197,26 @@ def __init__( |
174 | 197 | self._project_id = project_id |
175 | 198 | self._timeout = timeout |
176 | 199 |
|
| 200 | + def __getattr__(self, name: str) -> Any: |
| 201 | + """Point a generated method name at where it actually lives. |
| 202 | +
|
| 203 | + Python calls this only after normal lookup fails, so it costs nothing on the |
| 204 | + happy path. Reads ``__dict__`` directly rather than ``getattr`` to stay safe |
| 205 | + during ``__init__`` and to avoid recursing through itself. |
| 206 | + """ |
| 207 | + for attr in _API_ATTRS: |
| 208 | + api = self.__dict__.get(attr) |
| 209 | + if api is not None and hasattr(api, name): |
| 210 | + facade = _FACADE_FOR.get(name) |
| 211 | + hint = f"The facade covers it as `client.{facade}(...)`. " if facade else "" |
| 212 | + raise AttributeError( |
| 213 | + f"{type(self).__name__!r} has no attribute {name!r} — that is a " |
| 214 | + f"generated method name, not a facade one. {hint}" |
| 215 | + f"The generated client is at `client.{attr}.{name}(...)`, which " |
| 216 | + f"returns the response envelope rather than its `.data`." |
| 217 | + ) |
| 218 | + raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}") |
| 219 | + |
177 | 220 | # -- lifecycle ----------------------------------------------------------- |
178 | 221 | def __enter__(self) -> "EverOS": |
179 | 222 | return self |
|
0 commit comments