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.
Files changed (67) hide show
  1. atomicmemory/__init__.py +166 -0
  2. atomicmemory/_version.py +3 -0
  3. atomicmemory/client/__init__.py +22 -0
  4. atomicmemory/client/async_memory_client.py +202 -0
  5. atomicmemory/client/atomic_memory_client.py +181 -0
  6. atomicmemory/client/memory_client.py +292 -0
  7. atomicmemory/core/__init__.py +34 -0
  8. atomicmemory/core/errors.py +122 -0
  9. atomicmemory/core/events.py +65 -0
  10. atomicmemory/core/logging.py +37 -0
  11. atomicmemory/core/retry.py +124 -0
  12. atomicmemory/core/validation.py +22 -0
  13. atomicmemory/embeddings/__init__.py +16 -0
  14. atomicmemory/embeddings/base.py +39 -0
  15. atomicmemory/embeddings/sentence_transformers.py +104 -0
  16. atomicmemory/kv_cache/__init__.py +17 -0
  17. atomicmemory/kv_cache/adapter.py +50 -0
  18. atomicmemory/kv_cache/memory_storage.py +98 -0
  19. atomicmemory/kv_cache/sqlite_storage.py +122 -0
  20. atomicmemory/memory/__init__.py +82 -0
  21. atomicmemory/memory/filters.py +68 -0
  22. atomicmemory/memory/pipeline.py +42 -0
  23. atomicmemory/memory/provider.py +397 -0
  24. atomicmemory/memory/registry.py +95 -0
  25. atomicmemory/memory/service.py +199 -0
  26. atomicmemory/memory/types.py +398 -0
  27. atomicmemory/providers/__init__.py +5 -0
  28. atomicmemory/providers/atomicmemory/__init__.py +43 -0
  29. atomicmemory/providers/atomicmemory/agents.py +156 -0
  30. atomicmemory/providers/atomicmemory/async_handle_impl.py +198 -0
  31. atomicmemory/providers/atomicmemory/async_provider.py +245 -0
  32. atomicmemory/providers/atomicmemory/audit.py +74 -0
  33. atomicmemory/providers/atomicmemory/config.py +38 -0
  34. atomicmemory/providers/atomicmemory/config_handle.py +123 -0
  35. atomicmemory/providers/atomicmemory/handle.py +513 -0
  36. atomicmemory/providers/atomicmemory/handle_impl.py +325 -0
  37. atomicmemory/providers/atomicmemory/http.py +255 -0
  38. atomicmemory/providers/atomicmemory/lessons.py +133 -0
  39. atomicmemory/providers/atomicmemory/lifecycle.py +202 -0
  40. atomicmemory/providers/atomicmemory/mappers.py +125 -0
  41. atomicmemory/providers/atomicmemory/path.py +20 -0
  42. atomicmemory/providers/atomicmemory/provider.py +300 -0
  43. atomicmemory/providers/atomicmemory/scope_mapper.py +98 -0
  44. atomicmemory/providers/mem0/__init__.py +41 -0
  45. atomicmemory/providers/mem0/async_provider.py +191 -0
  46. atomicmemory/providers/mem0/config.py +51 -0
  47. atomicmemory/providers/mem0/http.py +195 -0
  48. atomicmemory/providers/mem0/mappers.py +145 -0
  49. atomicmemory/providers/mem0/provider.py +202 -0
  50. atomicmemory/py.typed +0 -0
  51. atomicmemory/search/__init__.py +47 -0
  52. atomicmemory/search/chunking.py +161 -0
  53. atomicmemory/search/ranking.py +94 -0
  54. atomicmemory/search/semantic_search.py +130 -0
  55. atomicmemory/search/similarity.py +110 -0
  56. atomicmemory/storage/__init__.py +63 -0
  57. atomicmemory/storage/_mapping.py +305 -0
  58. atomicmemory/storage/async_client.py +208 -0
  59. atomicmemory/storage/client.py +339 -0
  60. atomicmemory/storage/errors.py +115 -0
  61. atomicmemory/storage/types.py +305 -0
  62. atomicmemory/utils/__init__.py +5 -0
  63. atomicmemory/utils/environment.py +23 -0
  64. atomicmemory-1.0.0.dist-info/METADATA +146 -0
  65. atomicmemory-1.0.0.dist-info/RECORD +67 -0
  66. atomicmemory-1.0.0.dist-info/WHEEL +4 -0
  67. atomicmemory-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -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
+ ]
@@ -0,0 +1,3 @@
1
+ """Version metadata for the atomicmemory Python SDK."""
2
+
3
+ __version__ = "1.0.0"
@@ -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
+ )