atomicmemory 1.0.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.
- atomicmemory/__init__.py +166 -0
- atomicmemory/_version.py +3 -0
- atomicmemory/client/__init__.py +22 -0
- atomicmemory/client/async_memory_client.py +202 -0
- atomicmemory/client/atomic_memory_client.py +181 -0
- atomicmemory/client/memory_client.py +292 -0
- atomicmemory/core/__init__.py +34 -0
- atomicmemory/core/errors.py +122 -0
- atomicmemory/core/events.py +65 -0
- atomicmemory/core/logging.py +37 -0
- atomicmemory/core/retry.py +124 -0
- atomicmemory/core/validation.py +22 -0
- atomicmemory/embeddings/__init__.py +16 -0
- atomicmemory/embeddings/base.py +39 -0
- atomicmemory/embeddings/sentence_transformers.py +104 -0
- atomicmemory/kv_cache/__init__.py +17 -0
- atomicmemory/kv_cache/adapter.py +50 -0
- atomicmemory/kv_cache/memory_storage.py +98 -0
- atomicmemory/kv_cache/sqlite_storage.py +122 -0
- atomicmemory/memory/__init__.py +82 -0
- atomicmemory/memory/filters.py +68 -0
- atomicmemory/memory/pipeline.py +42 -0
- atomicmemory/memory/provider.py +397 -0
- atomicmemory/memory/registry.py +95 -0
- atomicmemory/memory/service.py +199 -0
- atomicmemory/memory/types.py +398 -0
- atomicmemory/providers/__init__.py +5 -0
- atomicmemory/providers/atomicmemory/__init__.py +43 -0
- atomicmemory/providers/atomicmemory/agents.py +156 -0
- atomicmemory/providers/atomicmemory/async_handle_impl.py +198 -0
- atomicmemory/providers/atomicmemory/async_provider.py +245 -0
- atomicmemory/providers/atomicmemory/audit.py +74 -0
- atomicmemory/providers/atomicmemory/config.py +38 -0
- atomicmemory/providers/atomicmemory/config_handle.py +123 -0
- atomicmemory/providers/atomicmemory/handle.py +513 -0
- atomicmemory/providers/atomicmemory/handle_impl.py +325 -0
- atomicmemory/providers/atomicmemory/http.py +255 -0
- atomicmemory/providers/atomicmemory/lessons.py +133 -0
- atomicmemory/providers/atomicmemory/lifecycle.py +202 -0
- atomicmemory/providers/atomicmemory/mappers.py +125 -0
- atomicmemory/providers/atomicmemory/path.py +20 -0
- atomicmemory/providers/atomicmemory/provider.py +300 -0
- atomicmemory/providers/atomicmemory/scope_mapper.py +98 -0
- atomicmemory/providers/mem0/__init__.py +41 -0
- atomicmemory/providers/mem0/async_provider.py +191 -0
- atomicmemory/providers/mem0/config.py +51 -0
- atomicmemory/providers/mem0/http.py +195 -0
- atomicmemory/providers/mem0/mappers.py +145 -0
- atomicmemory/providers/mem0/provider.py +202 -0
- atomicmemory/py.typed +0 -0
- atomicmemory/search/__init__.py +47 -0
- atomicmemory/search/chunking.py +161 -0
- atomicmemory/search/ranking.py +94 -0
- atomicmemory/search/semantic_search.py +130 -0
- atomicmemory/search/similarity.py +110 -0
- atomicmemory/storage/__init__.py +63 -0
- atomicmemory/storage/_mapping.py +305 -0
- atomicmemory/storage/async_client.py +208 -0
- atomicmemory/storage/client.py +339 -0
- atomicmemory/storage/errors.py +115 -0
- atomicmemory/storage/types.py +305 -0
- atomicmemory/utils/__init__.py +5 -0
- atomicmemory/utils/environment.py +23 -0
- atomicmemory-1.0.0.dist-info/METADATA +146 -0
- atomicmemory-1.0.0.dist-info/RECORD +67 -0
- atomicmemory-1.0.0.dist-info/WHEEL +4 -0
- atomicmemory-1.0.0.dist-info/licenses/LICENSE +21 -0
atomicmemory/__init__.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""atomicmemory — Python client SDK for AtomicMemory.
|
|
2
|
+
|
|
3
|
+
Top-level re-exports for the public API. Users typically only need to
|
|
4
|
+
import `AtomicMemoryClient` plus the type they're working with — every
|
|
5
|
+
type they could need is re-exported here.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from atomicmemory._version import __version__
|
|
9
|
+
from atomicmemory.client.async_memory_client import AsyncMemoryClient, AsyncProviderStatus
|
|
10
|
+
from atomicmemory.client.atomic_memory_client import (
|
|
11
|
+
AsyncAtomicMemoryClient,
|
|
12
|
+
AtomicMemoryClient,
|
|
13
|
+
AtomicMemoryClientConfig,
|
|
14
|
+
MemoryNamespaceConfig,
|
|
15
|
+
)
|
|
16
|
+
from atomicmemory.client.memory_client import MemoryClient, ProviderStatus
|
|
17
|
+
from atomicmemory.core.errors import (
|
|
18
|
+
AtomicMemoryError,
|
|
19
|
+
ConfigError,
|
|
20
|
+
NetworkError,
|
|
21
|
+
NotInitializedError,
|
|
22
|
+
ProviderError,
|
|
23
|
+
RateLimitError,
|
|
24
|
+
ValidationError,
|
|
25
|
+
)
|
|
26
|
+
from atomicmemory.memory.filters import FieldFilter, FieldFilterOp, FilterExpr
|
|
27
|
+
from atomicmemory.memory.types import (
|
|
28
|
+
Capabilities,
|
|
29
|
+
CapabilitiesExtensions,
|
|
30
|
+
CapabilitiesRequiredScope,
|
|
31
|
+
ContextPackage,
|
|
32
|
+
GraphEdge,
|
|
33
|
+
GraphNode,
|
|
34
|
+
GraphResult,
|
|
35
|
+
GraphSearchRequest,
|
|
36
|
+
HealthStatus,
|
|
37
|
+
IngestBase,
|
|
38
|
+
IngestInput,
|
|
39
|
+
IngestResult,
|
|
40
|
+
Insight,
|
|
41
|
+
ListRequest,
|
|
42
|
+
ListResultPage,
|
|
43
|
+
Memory,
|
|
44
|
+
MemoryKind,
|
|
45
|
+
MemoryRef,
|
|
46
|
+
MemoryVersion,
|
|
47
|
+
MemoryVersionEvent,
|
|
48
|
+
Message,
|
|
49
|
+
MessageIngest,
|
|
50
|
+
MessageRole,
|
|
51
|
+
PackageFormat,
|
|
52
|
+
PackageRequest,
|
|
53
|
+
Profile,
|
|
54
|
+
Provenance,
|
|
55
|
+
Scope,
|
|
56
|
+
SearchRequest,
|
|
57
|
+
SearchResult,
|
|
58
|
+
SearchResultPage,
|
|
59
|
+
TextIngest,
|
|
60
|
+
VerbatimIngest,
|
|
61
|
+
)
|
|
62
|
+
from atomicmemory.storage import (
|
|
63
|
+
ArtifactHead,
|
|
64
|
+
ArtifactInUseError,
|
|
65
|
+
ArtifactMetadata,
|
|
66
|
+
ArtifactNotFoundError,
|
|
67
|
+
ArtifactRange,
|
|
68
|
+
ArtifactRef,
|
|
69
|
+
AsyncStorageClient,
|
|
70
|
+
DeleteArtifactOptions,
|
|
71
|
+
DeleteArtifactPolicy,
|
|
72
|
+
DeleteArtifactResult,
|
|
73
|
+
FilecoinDirectStorageNotSupportedError,
|
|
74
|
+
PointerContentNotManagedError,
|
|
75
|
+
PutArtifactInput,
|
|
76
|
+
PutManagedInput,
|
|
77
|
+
PutPointerInput,
|
|
78
|
+
StorageArtifactStatus,
|
|
79
|
+
StorageCapabilities,
|
|
80
|
+
StorageClient,
|
|
81
|
+
StorageClientConfig,
|
|
82
|
+
StorageClientError,
|
|
83
|
+
StoredArtifact,
|
|
84
|
+
UnsupportedCapabilityError,
|
|
85
|
+
VerificationResult,
|
|
86
|
+
VerifyArtifactOptions,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
__all__ = [
|
|
90
|
+
"ArtifactHead",
|
|
91
|
+
"ArtifactInUseError",
|
|
92
|
+
"ArtifactMetadata",
|
|
93
|
+
"ArtifactNotFoundError",
|
|
94
|
+
"ArtifactRange",
|
|
95
|
+
"ArtifactRef",
|
|
96
|
+
"AsyncAtomicMemoryClient",
|
|
97
|
+
"AsyncMemoryClient",
|
|
98
|
+
"AsyncProviderStatus",
|
|
99
|
+
"AsyncStorageClient",
|
|
100
|
+
"AtomicMemoryClient",
|
|
101
|
+
"AtomicMemoryClientConfig",
|
|
102
|
+
"AtomicMemoryError",
|
|
103
|
+
"Capabilities",
|
|
104
|
+
"CapabilitiesExtensions",
|
|
105
|
+
"CapabilitiesRequiredScope",
|
|
106
|
+
"ConfigError",
|
|
107
|
+
"ContextPackage",
|
|
108
|
+
"DeleteArtifactOptions",
|
|
109
|
+
"DeleteArtifactPolicy",
|
|
110
|
+
"DeleteArtifactResult",
|
|
111
|
+
"FieldFilter",
|
|
112
|
+
"FieldFilterOp",
|
|
113
|
+
"FilecoinDirectStorageNotSupportedError",
|
|
114
|
+
"FilterExpr",
|
|
115
|
+
"GraphEdge",
|
|
116
|
+
"GraphNode",
|
|
117
|
+
"GraphResult",
|
|
118
|
+
"GraphSearchRequest",
|
|
119
|
+
"HealthStatus",
|
|
120
|
+
"IngestBase",
|
|
121
|
+
"IngestInput",
|
|
122
|
+
"IngestResult",
|
|
123
|
+
"Insight",
|
|
124
|
+
"ListRequest",
|
|
125
|
+
"ListResultPage",
|
|
126
|
+
"Memory",
|
|
127
|
+
"MemoryClient",
|
|
128
|
+
"MemoryKind",
|
|
129
|
+
"MemoryNamespaceConfig",
|
|
130
|
+
"MemoryRef",
|
|
131
|
+
"MemoryVersion",
|
|
132
|
+
"MemoryVersionEvent",
|
|
133
|
+
"Message",
|
|
134
|
+
"MessageIngest",
|
|
135
|
+
"MessageRole",
|
|
136
|
+
"NetworkError",
|
|
137
|
+
"NotInitializedError",
|
|
138
|
+
"PackageFormat",
|
|
139
|
+
"PackageRequest",
|
|
140
|
+
"PointerContentNotManagedError",
|
|
141
|
+
"Profile",
|
|
142
|
+
"Provenance",
|
|
143
|
+
"ProviderError",
|
|
144
|
+
"ProviderStatus",
|
|
145
|
+
"PutArtifactInput",
|
|
146
|
+
"PutManagedInput",
|
|
147
|
+
"PutPointerInput",
|
|
148
|
+
"RateLimitError",
|
|
149
|
+
"Scope",
|
|
150
|
+
"SearchRequest",
|
|
151
|
+
"SearchResult",
|
|
152
|
+
"SearchResultPage",
|
|
153
|
+
"StorageArtifactStatus",
|
|
154
|
+
"StorageCapabilities",
|
|
155
|
+
"StorageClient",
|
|
156
|
+
"StorageClientConfig",
|
|
157
|
+
"StorageClientError",
|
|
158
|
+
"StoredArtifact",
|
|
159
|
+
"TextIngest",
|
|
160
|
+
"UnsupportedCapabilityError",
|
|
161
|
+
"ValidationError",
|
|
162
|
+
"VerbatimIngest",
|
|
163
|
+
"VerificationResult",
|
|
164
|
+
"VerifyArtifactOptions",
|
|
165
|
+
"__version__",
|
|
166
|
+
]
|
atomicmemory/_version.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Public client facades for memory and storage namespaces."""
|
|
2
|
+
|
|
3
|
+
from atomicmemory.client.async_memory_client import AsyncMemoryClient, AsyncProviderStatus
|
|
4
|
+
from atomicmemory.client.atomic_memory_client import (
|
|
5
|
+
AsyncAtomicMemoryClient,
|
|
6
|
+
AtomicMemoryClient,
|
|
7
|
+
AtomicMemoryClientConfig,
|
|
8
|
+
MemoryNamespaceConfig,
|
|
9
|
+
)
|
|
10
|
+
from atomicmemory.client.memory_client import MemoryClient, MemoryProviderConfigs, ProviderStatus
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AsyncAtomicMemoryClient",
|
|
14
|
+
"AsyncMemoryClient",
|
|
15
|
+
"AsyncProviderStatus",
|
|
16
|
+
"AtomicMemoryClient",
|
|
17
|
+
"AtomicMemoryClientConfig",
|
|
18
|
+
"MemoryClient",
|
|
19
|
+
"MemoryNamespaceConfig",
|
|
20
|
+
"MemoryProviderConfigs",
|
|
21
|
+
"ProviderStatus",
|
|
22
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""AsyncMemoryClient — async facade for the V3 memory layer.
|
|
2
|
+
|
|
3
|
+
Mirrors :class:`atomicmemory.client.memory_client.MemoryClient` with
|
|
4
|
+
``async def`` for every I/O method and a ``__aenter__`` / ``__aexit__``
|
|
5
|
+
context manager. Dict coercion + Pydantic-error wrapping is identical
|
|
6
|
+
to the sync client.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from types import TracebackType
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
# Importing the provider packages registers both sync and async factories.
|
|
16
|
+
import atomicmemory.providers.atomicmemory
|
|
17
|
+
import atomicmemory.providers.mem0 # noqa: F401
|
|
18
|
+
from atomicmemory.client.memory_client import (
|
|
19
|
+
_coerce_ingest,
|
|
20
|
+
_coerce_list_request,
|
|
21
|
+
_coerce_package,
|
|
22
|
+
_coerce_ref,
|
|
23
|
+
_coerce_search,
|
|
24
|
+
)
|
|
25
|
+
from atomicmemory.core.errors import ConfigError, NotInitializedError
|
|
26
|
+
from atomicmemory.memory.provider import BaseAsyncMemoryProvider
|
|
27
|
+
from atomicmemory.memory.registry import AsyncProviderRegistry, default_async_registry
|
|
28
|
+
from atomicmemory.memory.service import AsyncMemoryService, MemoryServiceConfig
|
|
29
|
+
from atomicmemory.memory.types import (
|
|
30
|
+
Capabilities,
|
|
31
|
+
ContextPackage,
|
|
32
|
+
IngestInput,
|
|
33
|
+
IngestResult,
|
|
34
|
+
ListRequest,
|
|
35
|
+
ListResultPage,
|
|
36
|
+
Memory,
|
|
37
|
+
MemoryRef,
|
|
38
|
+
PackageRequest,
|
|
39
|
+
SearchRequest,
|
|
40
|
+
SearchResultPage,
|
|
41
|
+
)
|
|
42
|
+
from atomicmemory.providers.atomicmemory.async_handle_impl import AsyncAtomicMemoryHandle
|
|
43
|
+
|
|
44
|
+
MemoryProviderConfigs = dict[str, Any]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class AsyncProviderStatus:
|
|
49
|
+
"""Async-side mirror of :class:`ProviderStatus`."""
|
|
50
|
+
|
|
51
|
+
name: str
|
|
52
|
+
initialized: bool
|
|
53
|
+
capabilities: Capabilities | None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_AsyncProviderStatusList = list[AsyncProviderStatus]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AsyncMemoryClient:
|
|
60
|
+
"""Async entry point for the V3 memory API.
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> async with AsyncMemoryClient(
|
|
64
|
+
... providers={"atomicmemory": {"api_url": "http://localhost:3050"}}
|
|
65
|
+
... ) as memory:
|
|
66
|
+
... await memory.initialize()
|
|
67
|
+
... await memory.ingest({"mode": "text", "content": "hi", "scope": {"user": "u1"}})
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
providers: MemoryProviderConfigs,
|
|
73
|
+
default_provider: str | None = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
if not providers:
|
|
76
|
+
raise ConfigError(
|
|
77
|
+
"AsyncMemoryClient requires at least one provider config. "
|
|
78
|
+
'Pass e.g. {"atomicmemory": {"api_url": "..."}}.'
|
|
79
|
+
)
|
|
80
|
+
chosen_default = default_provider or _pick_first_provider_key(providers)
|
|
81
|
+
if chosen_default is None:
|
|
82
|
+
raise ConfigError("No usable provider config supplied")
|
|
83
|
+
self._service = AsyncMemoryService(
|
|
84
|
+
MemoryServiceConfig(
|
|
85
|
+
default_provider=chosen_default,
|
|
86
|
+
provider_configs=dict(providers),
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
self._initialized = False
|
|
90
|
+
|
|
91
|
+
async def initialize(self, registry: AsyncProviderRegistry | None = None) -> None:
|
|
92
|
+
if self._initialized:
|
|
93
|
+
return
|
|
94
|
+
await self._service.initialize(registry if registry is not None else default_async_registry)
|
|
95
|
+
self._initialized = True
|
|
96
|
+
|
|
97
|
+
async def close(self) -> None:
|
|
98
|
+
if not self._initialized:
|
|
99
|
+
return
|
|
100
|
+
await self._service.close()
|
|
101
|
+
self._initialized = False
|
|
102
|
+
|
|
103
|
+
async def __aenter__(self) -> AsyncMemoryClient:
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
async def __aexit__(
|
|
107
|
+
self,
|
|
108
|
+
exc_type: type[BaseException] | None,
|
|
109
|
+
exc: BaseException | None,
|
|
110
|
+
tb: TracebackType | None,
|
|
111
|
+
) -> None:
|
|
112
|
+
await self.close()
|
|
113
|
+
|
|
114
|
+
async def ingest(self, input: IngestInput | dict[str, Any]) -> IngestResult:
|
|
115
|
+
self._assert_initialized()
|
|
116
|
+
return await self._service.ingest(_coerce_ingest(input))
|
|
117
|
+
|
|
118
|
+
async def ingest_direct(self, input: IngestInput | dict[str, Any]) -> IngestResult:
|
|
119
|
+
self._assert_initialized()
|
|
120
|
+
return await self._service.ingest(_coerce_ingest(input))
|
|
121
|
+
|
|
122
|
+
async def search(self, request: SearchRequest | dict[str, Any]) -> SearchResultPage:
|
|
123
|
+
self._assert_initialized()
|
|
124
|
+
return await self._service.search(_coerce_search(request))
|
|
125
|
+
|
|
126
|
+
async def search_direct(self, request: SearchRequest | dict[str, Any]) -> SearchResultPage:
|
|
127
|
+
self._assert_initialized()
|
|
128
|
+
return await self._service.search(_coerce_search(request))
|
|
129
|
+
|
|
130
|
+
async def package(self, request: PackageRequest | dict[str, Any]) -> ContextPackage:
|
|
131
|
+
self._assert_initialized()
|
|
132
|
+
return await self._service.package(_coerce_package(request))
|
|
133
|
+
|
|
134
|
+
async def package_direct(self, request: PackageRequest | dict[str, Any]) -> ContextPackage:
|
|
135
|
+
self._assert_initialized()
|
|
136
|
+
return await self._service.package(_coerce_package(request))
|
|
137
|
+
|
|
138
|
+
async def get(self, ref: MemoryRef | dict[str, Any]) -> Memory | None:
|
|
139
|
+
self._assert_initialized()
|
|
140
|
+
return await self._service.get(_coerce_ref(ref))
|
|
141
|
+
|
|
142
|
+
async def delete(self, ref: MemoryRef | dict[str, Any]) -> None:
|
|
143
|
+
self._assert_initialized()
|
|
144
|
+
await self._service.delete(_coerce_ref(ref))
|
|
145
|
+
|
|
146
|
+
async def list(self, request: ListRequest | dict[str, Any]) -> ListResultPage:
|
|
147
|
+
self._assert_initialized()
|
|
148
|
+
return await self._service.list(_coerce_list_request(request))
|
|
149
|
+
|
|
150
|
+
def capabilities(self, provider_name: str | None = None) -> Capabilities:
|
|
151
|
+
self._assert_initialized()
|
|
152
|
+
return self._service.get_provider(provider_name).capabilities()
|
|
153
|
+
|
|
154
|
+
def get_extension(self, extension_name: str, provider_name: str | None = None) -> Any | None:
|
|
155
|
+
self._assert_initialized()
|
|
156
|
+
return self._service.get_provider(provider_name).get_extension(extension_name)
|
|
157
|
+
|
|
158
|
+
def get_provider_status(self) -> _AsyncProviderStatusList:
|
|
159
|
+
configured = self._service.get_configured_providers()
|
|
160
|
+
if not self._initialized:
|
|
161
|
+
return [AsyncProviderStatus(name=n, initialized=False, capabilities=None) for n in configured]
|
|
162
|
+
available = set(self._service.get_available_providers())
|
|
163
|
+
statuses: _AsyncProviderStatusList = []
|
|
164
|
+
for n in configured:
|
|
165
|
+
if n not in available:
|
|
166
|
+
statuses.append(AsyncProviderStatus(name=n, initialized=False, capabilities=None))
|
|
167
|
+
continue
|
|
168
|
+
statuses.append(
|
|
169
|
+
AsyncProviderStatus(
|
|
170
|
+
name=n,
|
|
171
|
+
initialized=True,
|
|
172
|
+
capabilities=self._service.get_provider(n).capabilities(),
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
return statuses
|
|
176
|
+
|
|
177
|
+
def get_provider(self, name: str | None = None) -> BaseAsyncMemoryProvider:
|
|
178
|
+
self._assert_initialized()
|
|
179
|
+
return self._service.get_provider(name)
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def atomicmemory(self) -> AsyncAtomicMemoryHandle | None:
|
|
183
|
+
if not self._initialized:
|
|
184
|
+
return None
|
|
185
|
+
if "atomicmemory" not in self._service.get_configured_providers():
|
|
186
|
+
return None
|
|
187
|
+
provider = self._service.get_provider("atomicmemory")
|
|
188
|
+
handle = provider.get_extension("atomicmemory.base")
|
|
189
|
+
if not isinstance(handle, AsyncAtomicMemoryHandle):
|
|
190
|
+
return None
|
|
191
|
+
return handle
|
|
192
|
+
|
|
193
|
+
def _assert_initialized(self) -> None:
|
|
194
|
+
if not self._initialized:
|
|
195
|
+
raise NotInitializedError("AsyncMemoryClient is not initialized. Call await client.initialize() first.")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _pick_first_provider_key(providers: MemoryProviderConfigs) -> str | None:
|
|
199
|
+
for key, value in providers.items():
|
|
200
|
+
if value is not None and key != "default":
|
|
201
|
+
return key
|
|
202
|
+
return None
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Primary clients that expose memory and storage sibling namespaces.
|
|
2
|
+
|
|
3
|
+
This module mirrors the TypeScript SDK's `AtomicMemoryClient`: callers
|
|
4
|
+
configure one core transport boundary and receive both ``memory`` and
|
|
5
|
+
``storage`` namespaces backed by the same API URL, API key, and user
|
|
6
|
+
scope.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from types import TracebackType
|
|
12
|
+
from typing import Any
|
|
13
|
+
from urllib.parse import urlparse
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
16
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
17
|
+
|
|
18
|
+
from atomicmemory.client.async_memory_client import AsyncMemoryClient
|
|
19
|
+
from atomicmemory.client.memory_client import MemoryClient, MemoryProviderConfigs
|
|
20
|
+
from atomicmemory.core.errors import ConfigError
|
|
21
|
+
from atomicmemory.core.validation import sanitized_pydantic_errors
|
|
22
|
+
from atomicmemory.storage import AsyncStorageClient, StorageClient, StorageClientConfig
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MemoryNamespaceConfig(BaseModel):
|
|
26
|
+
"""Configuration for the aggregator's ``memory`` namespace."""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
29
|
+
|
|
30
|
+
providers: MemoryProviderConfigs
|
|
31
|
+
default_provider: str | None = Field(default=None, alias="defaultProvider")
|
|
32
|
+
|
|
33
|
+
@model_validator(mode="after")
|
|
34
|
+
def _require_providers(self) -> MemoryNamespaceConfig:
|
|
35
|
+
if not self.providers:
|
|
36
|
+
raise ValueError("memory.providers must not be empty")
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AtomicMemoryClientConfig(BaseModel):
|
|
41
|
+
"""Configuration for :class:`AtomicMemoryClient` and its async peer."""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
44
|
+
|
|
45
|
+
api_url: str = Field(alias="apiUrl")
|
|
46
|
+
api_key: str = Field(alias="apiKey")
|
|
47
|
+
user_id: str = Field(alias="userId")
|
|
48
|
+
timeout_seconds: float = Field(default=30.0, alias="timeoutSeconds")
|
|
49
|
+
memory: MemoryNamespaceConfig | None = None
|
|
50
|
+
|
|
51
|
+
@field_validator("api_url")
|
|
52
|
+
@classmethod
|
|
53
|
+
def _validate_api_url(cls, value: str) -> str:
|
|
54
|
+
stripped = value.strip()
|
|
55
|
+
parsed = urlparse(stripped)
|
|
56
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
57
|
+
raise ValueError("api_url must be an http(s) URL")
|
|
58
|
+
return stripped
|
|
59
|
+
|
|
60
|
+
@field_validator("api_key", "user_id")
|
|
61
|
+
@classmethod
|
|
62
|
+
def _validate_non_empty_secret(cls, value: str) -> str:
|
|
63
|
+
stripped = value.strip()
|
|
64
|
+
if stripped == "":
|
|
65
|
+
raise ValueError("value must not be empty")
|
|
66
|
+
return stripped
|
|
67
|
+
|
|
68
|
+
@field_validator("timeout_seconds")
|
|
69
|
+
@classmethod
|
|
70
|
+
def _validate_timeout(cls, value: float) -> float:
|
|
71
|
+
if value <= 0:
|
|
72
|
+
raise ValueError("timeout_seconds must be greater than zero")
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
@model_validator(mode="after")
|
|
76
|
+
def _require_non_empty(self) -> AtomicMemoryClientConfig:
|
|
77
|
+
if not self.api_url:
|
|
78
|
+
raise ValueError("api_url is required")
|
|
79
|
+
if not self.api_key:
|
|
80
|
+
raise ValueError("api_key is required")
|
|
81
|
+
if not self.user_id:
|
|
82
|
+
raise ValueError("user_id is required")
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class AtomicMemoryClient:
|
|
87
|
+
"""Sync primary SDK entry point with ``memory`` and ``storage`` namespaces."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, config: AtomicMemoryClientConfig | dict[str, Any]) -> None:
|
|
90
|
+
resolved = _coerce_atomic_config(config)
|
|
91
|
+
memory = _memory_config(resolved)
|
|
92
|
+
self.memory = MemoryClient(memory.providers, memory.default_provider)
|
|
93
|
+
self.storage = StorageClient(_storage_config(resolved))
|
|
94
|
+
|
|
95
|
+
def close(self) -> None:
|
|
96
|
+
try:
|
|
97
|
+
self.memory.close()
|
|
98
|
+
finally:
|
|
99
|
+
self.storage.close()
|
|
100
|
+
|
|
101
|
+
def __enter__(self) -> AtomicMemoryClient:
|
|
102
|
+
return self
|
|
103
|
+
|
|
104
|
+
def __exit__(
|
|
105
|
+
self,
|
|
106
|
+
exc_type: type[BaseException] | None,
|
|
107
|
+
exc: BaseException | None,
|
|
108
|
+
tb: TracebackType | None,
|
|
109
|
+
) -> None:
|
|
110
|
+
self.close()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class AsyncAtomicMemoryClient:
|
|
114
|
+
"""Async primary SDK entry point with ``memory`` and ``storage`` namespaces."""
|
|
115
|
+
|
|
116
|
+
def __init__(self, config: AtomicMemoryClientConfig | dict[str, Any]) -> None:
|
|
117
|
+
resolved = _coerce_atomic_config(config)
|
|
118
|
+
memory = _memory_config(resolved)
|
|
119
|
+
self.memory = AsyncMemoryClient(memory.providers, memory.default_provider)
|
|
120
|
+
self.storage = AsyncStorageClient(_storage_config(resolved))
|
|
121
|
+
|
|
122
|
+
async def close(self) -> None:
|
|
123
|
+
first_error: BaseException | None = None
|
|
124
|
+
try:
|
|
125
|
+
await self.memory.close()
|
|
126
|
+
except BaseException as exc:
|
|
127
|
+
first_error = exc
|
|
128
|
+
try:
|
|
129
|
+
await self.storage.close()
|
|
130
|
+
except BaseException as exc:
|
|
131
|
+
if first_error is not None:
|
|
132
|
+
raise first_error from exc
|
|
133
|
+
raise
|
|
134
|
+
if first_error is not None:
|
|
135
|
+
raise first_error
|
|
136
|
+
|
|
137
|
+
async def __aenter__(self) -> AsyncAtomicMemoryClient:
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
async def __aexit__(
|
|
141
|
+
self,
|
|
142
|
+
exc_type: type[BaseException] | None,
|
|
143
|
+
exc: BaseException | None,
|
|
144
|
+
tb: TracebackType | None,
|
|
145
|
+
) -> None:
|
|
146
|
+
await self.close()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _coerce_atomic_config(config: AtomicMemoryClientConfig | dict[str, Any]) -> AtomicMemoryClientConfig:
|
|
150
|
+
if isinstance(config, AtomicMemoryClientConfig):
|
|
151
|
+
return config
|
|
152
|
+
try:
|
|
153
|
+
return AtomicMemoryClientConfig.model_validate(config)
|
|
154
|
+
except PydanticValidationError as exc:
|
|
155
|
+
raise ConfigError(
|
|
156
|
+
f"Invalid AtomicMemoryClientConfig: {exc}",
|
|
157
|
+
context={"type": "AtomicMemoryClientConfig", "errors": sanitized_pydantic_errors(exc)},
|
|
158
|
+
) from exc
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _memory_config(config: AtomicMemoryClientConfig) -> MemoryNamespaceConfig:
|
|
162
|
+
if config.memory is not None:
|
|
163
|
+
return config.memory
|
|
164
|
+
return MemoryNamespaceConfig(
|
|
165
|
+
providers={
|
|
166
|
+
"atomicmemory": {
|
|
167
|
+
"apiUrl": config.api_url,
|
|
168
|
+
"apiKey": config.api_key,
|
|
169
|
+
"timeoutSeconds": config.timeout_seconds,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _storage_config(config: AtomicMemoryClientConfig) -> StorageClientConfig:
|
|
176
|
+
return StorageClientConfig(
|
|
177
|
+
api_url=config.api_url,
|
|
178
|
+
api_key=config.api_key,
|
|
179
|
+
user_id=config.user_id,
|
|
180
|
+
timeout_seconds=config.timeout_seconds,
|
|
181
|
+
)
|