memcell 0.1.1__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.
memcell/__init__.py ADDED
@@ -0,0 +1,45 @@
1
+ from .auth import AuthManager
2
+ from .client import AsyncMemCell, MemCell
3
+ from .exceptions import MemCellError, RateLimitError
4
+ from .models import (
5
+ FeedbackResponse,
6
+ JobEvent,
7
+ MemoryKind,
8
+ MemoryStatus,
9
+ OrganizationItem,
10
+ OutcomeVerdict,
11
+ RecallResponse,
12
+ RememberResponse,
13
+ ReportResponse,
14
+ ScopedExecutionContext,
15
+ StatementItem,
16
+ )
17
+ from .organization import AsyncOrganizationMemCell, OrganizationMemCell
18
+ from .scoped import AsyncScopedMemCell, ScopedExecutionResult, ScopedMemCell
19
+
20
+ __version__ = "0.1.1"
21
+
22
+ __all__ = [
23
+ "AsyncMemCell",
24
+ "AsyncOrganizationMemCell",
25
+ "AsyncScopedMemCell",
26
+ "AuthManager",
27
+ "FeedbackResponse",
28
+ "JobEvent",
29
+ "MemCell",
30
+ "MemCellError",
31
+ "MemoryKind",
32
+ "MemoryStatus",
33
+ "OrganizationItem",
34
+ "OrganizationMemCell",
35
+ "OutcomeVerdict",
36
+ "RateLimitError",
37
+ "RecallResponse",
38
+ "RememberResponse",
39
+ "ReportResponse",
40
+ "ScopedExecutionContext",
41
+ "ScopedExecutionResult",
42
+ "ScopedMemCell",
43
+ "StatementItem",
44
+ "__version__",
45
+ ]
memcell/auth.py ADDED
@@ -0,0 +1,137 @@
1
+ import time
2
+ from typing import Any
3
+
4
+ import httpx
5
+
6
+ from .exceptions import MemCellError
7
+
8
+
9
+ class AuthManager:
10
+ """Manages authentication tokens, API keys, and OAuth 2.0 M2M client credentials."""
11
+
12
+ def __init__(
13
+ self,
14
+ base_url: str,
15
+ api_key: str | None = None,
16
+ access_token: str | None = None,
17
+ client_id: str | None = None,
18
+ client_secret: str | None = None,
19
+ scope: str | None = None,
20
+ ) -> None:
21
+ self.base_url = base_url.rstrip("/")
22
+ self.api_key = api_key
23
+ self.access_token = access_token
24
+ self.client_id = client_id
25
+ self.client_secret = client_secret
26
+ self.scope = scope
27
+
28
+ self._cached_token: str | None = None
29
+ self._token_expires_at: float = 0.0
30
+
31
+ def clear_cache(self) -> None:
32
+ """Clears cached OAuth M2M access token."""
33
+ self._cached_token = None
34
+ self._token_expires_at = 0.0
35
+
36
+ def get_authorization_header(self, client: httpx.Client | None = None) -> str | None:
37
+ """Synchronously resolves Authorization header value."""
38
+ if self.api_key:
39
+ return f"Bearer {self.api_key}"
40
+ if self.access_token:
41
+ return f"Bearer {self.access_token}"
42
+ if self.client_id and self.client_secret:
43
+ # Check proactive 60s pre-expiry window
44
+ now = time.time()
45
+ if self._cached_token and (now + 60.0) < self._token_expires_at:
46
+ return f"Bearer {self._cached_token}"
47
+
48
+ c = client or httpx.Client()
49
+ should_close = client is None
50
+ try:
51
+ data = {
52
+ "grant_type": "client_credentials",
53
+ "client_id": self.client_id,
54
+ "client_secret": self.client_secret,
55
+ }
56
+ if self.scope:
57
+ data["scope"] = self.scope
58
+
59
+ resp = c.post(
60
+ f"{self.base_url}/oauth2/token",
61
+ data=data,
62
+ headers={
63
+ "Content-Type": "application/x-www-form-urlencoded",
64
+ "Accept": "application/json",
65
+ },
66
+ )
67
+ if not resp.is_success:
68
+ raise MemCellError(
69
+ f"OAuth M2M token exchange failed: HTTP {resp.status_code} {resp.text}",
70
+ status=resp.status_code,
71
+ )
72
+ payload: dict[str, Any] = resp.json()
73
+ token = payload.get("access_token")
74
+ expires_in = payload.get("expires_in", 3600)
75
+ if not token:
76
+ raise MemCellError("OAuth token endpoint returned empty access_token.")
77
+
78
+ self._cached_token = str(token)
79
+ self._token_expires_at = time.time() + float(expires_in)
80
+ return f"Bearer {self._cached_token}"
81
+ finally:
82
+ if should_close:
83
+ c.close()
84
+
85
+ return None
86
+
87
+ async def get_authorization_header_async(
88
+ self, client: httpx.AsyncClient | None = None
89
+ ) -> str | None:
90
+ """Asynchronously resolves Authorization header value."""
91
+ if self.api_key:
92
+ return f"Bearer {self.api_key}"
93
+ if self.access_token:
94
+ return f"Bearer {self.access_token}"
95
+ if self.client_id and self.client_secret:
96
+ now = time.time()
97
+ if self._cached_token and (now + 60.0) < self._token_expires_at:
98
+ return f"Bearer {self._cached_token}"
99
+
100
+ c = client or httpx.AsyncClient()
101
+ should_close = client is None
102
+ try:
103
+ data = {
104
+ "grant_type": "client_credentials",
105
+ "client_id": self.client_id,
106
+ "client_secret": self.client_secret,
107
+ }
108
+ if self.scope:
109
+ data["scope"] = self.scope
110
+
111
+ resp = await c.post(
112
+ f"{self.base_url}/oauth2/token",
113
+ data=data,
114
+ headers={
115
+ "Content-Type": "application/x-www-form-urlencoded",
116
+ "Accept": "application/json",
117
+ },
118
+ )
119
+ if not resp.is_success:
120
+ raise MemCellError(
121
+ f"OAuth M2M token exchange failed: HTTP {resp.status_code} {resp.text}",
122
+ status=resp.status_code,
123
+ )
124
+ payload: dict[str, Any] = resp.json()
125
+ token = payload.get("access_token")
126
+ expires_in = payload.get("expires_in", 3600)
127
+ if not token:
128
+ raise MemCellError("OAuth token endpoint returned empty access_token.")
129
+
130
+ self._cached_token = str(token)
131
+ self._token_expires_at = time.time() + float(expires_in)
132
+ return f"Bearer {self._cached_token}"
133
+ finally:
134
+ if should_close:
135
+ await c.aclose()
136
+
137
+ return None