chuk-artifacts 0.1.3__py3-none-any.whl → 0.1.5__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.
@@ -2,6 +2,7 @@
2
2
  # chuk_artifacts/presigned.py
3
3
  """
4
4
  Presigned URL operations: download URLs, upload URLs, and upload registration.
5
+ Now uses chuk_sessions for session management.
5
6
  """
6
7
 
7
8
  from __future__ import annotations
@@ -28,11 +29,11 @@ class PresignedURLOperations:
28
29
  """Handles all presigned URL operations."""
29
30
 
30
31
  def __init__(self, artifact_store: 'ArtifactStore'):
31
- self.store = artifact_store
32
+ self.artifact_store = artifact_store
32
33
 
33
34
  async def presign(self, artifact_id: str, expires: int = _DEFAULT_PRESIGN_EXPIRES) -> str:
34
35
  """Generate a presigned URL for artifact download."""
35
- if self.store._closed:
36
+ if self.artifact_store._closed:
36
37
  raise ArtifactStoreError("Store is closed")
37
38
 
38
39
  start_time = time.time()
@@ -40,11 +41,11 @@ class PresignedURLOperations:
40
41
  try:
41
42
  record = await self._get_record(artifact_id)
42
43
 
43
- storage_ctx_mgr = self.store._s3_factory()
44
+ storage_ctx_mgr = self.artifact_store._s3_factory()
44
45
  async with storage_ctx_mgr as s3:
45
46
  url = await s3.generate_presigned_url(
46
47
  "get_object",
47
- Params={"Bucket": self.store.bucket, "Key": record["key"]},
48
+ Params={"Bucket": self.artifact_store.bucket, "Key": record["key"]},
48
49
  ExpiresIn=expires,
49
50
  )
50
51
 
@@ -101,28 +102,28 @@ class PresignedURLOperations:
101
102
  expires: int = _DEFAULT_PRESIGN_EXPIRES
102
103
  ) -> tuple[str, str]:
103
104
  """Generate a presigned URL for uploading a new artifact."""
104
- if self.store._closed:
105
+ if self.artifact_store._closed:
105
106
  raise ArtifactStoreError("Store is closed")
106
107
 
107
108
  start_time = time.time()
108
109
 
109
- # Ensure session is allocated
110
+ # Ensure session is allocated using chuk_sessions
110
111
  if session_id is None:
111
- session_id = await self.store._session_manager.allocate_session()
112
+ session_id = await self.artifact_store._session_manager.allocate_session()
112
113
  else:
113
- session_id = await self.store._session_manager.allocate_session(session_id=session_id)
114
+ session_id = await self.artifact_store._session_manager.allocate_session(session_id=session_id)
114
115
 
115
116
  # Generate artifact ID and key path
116
117
  artifact_id = uuid.uuid4().hex
117
- key = self.store.generate_artifact_key(session_id, artifact_id)
118
+ key = self.artifact_store.generate_artifact_key(session_id, artifact_id)
118
119
 
119
120
  try:
120
- storage_ctx_mgr = self.store._s3_factory()
121
+ storage_ctx_mgr = self.artifact_store._s3_factory()
121
122
  async with storage_ctx_mgr as s3:
122
123
  url = await s3.generate_presigned_url(
123
124
  "put_object",
124
125
  Params={
125
- "Bucket": self.store.bucket,
126
+ "Bucket": self.artifact_store.bucket,
126
127
  "Key": key,
127
128
  "ContentType": mime_type
128
129
  },
@@ -174,26 +175,26 @@ class PresignedURLOperations:
174
175
  ttl: int = _DEFAULT_TTL,
175
176
  ) -> bool:
176
177
  """Register metadata for an artifact uploaded via presigned URL."""
177
- if self.store._closed:
178
+ if self.artifact_store._closed:
178
179
  raise ArtifactStoreError("Store is closed")
179
180
 
180
181
  start_time = time.time()
181
182
 
182
- # Ensure session is allocated
183
+ # Ensure session is allocated using chuk_sessions
183
184
  if session_id is None:
184
- session_id = await self.store._session_manager.allocate_session()
185
+ session_id = await self.artifact_store._session_manager.allocate_session()
185
186
  else:
186
- session_id = await self.store._session_manager.allocate_session(session_id=session_id)
187
+ session_id = await self.artifact_store._session_manager.allocate_session(session_id=session_id)
187
188
 
188
189
  # Reconstruct the key path
189
- key = self.store.generate_artifact_key(session_id, artifact_id)
190
+ key = self.artifact_store.generate_artifact_key(session_id, artifact_id)
190
191
 
191
192
  try:
192
193
  # Verify the object exists and get its size
193
- storage_ctx_mgr = self.store._s3_factory()
194
+ storage_ctx_mgr = self.artifact_store._s3_factory()
194
195
  async with storage_ctx_mgr as s3:
195
196
  try:
196
- response = await s3.head_object(Bucket=self.store.bucket, Key=key)
197
+ response = await s3.head_object(Bucket=self.artifact_store.bucket, Key=key)
197
198
  file_size = response.get('ContentLength', 0)
198
199
  except Exception:
199
200
  logger.warning(f"Artifact {artifact_id} not found in storage")
@@ -203,7 +204,7 @@ class PresignedURLOperations:
203
204
  record = {
204
205
  "artifact_id": artifact_id,
205
206
  "session_id": session_id,
206
- "sandbox_id": self.store.sandbox_id,
207
+ "sandbox_id": self.artifact_store.sandbox_id,
207
208
  "key": key,
208
209
  "mime": mime,
209
210
  "summary": summary,
@@ -213,13 +214,13 @@ class PresignedURLOperations:
213
214
  "sha256": None, # We don't have the hash since we didn't upload it directly
214
215
  "stored_at": datetime.utcnow().isoformat() + "Z",
215
216
  "ttl": ttl,
216
- "storage_provider": self.store._storage_provider_name,
217
- "session_provider": self.store._session_provider_name,
217
+ "storage_provider": self.artifact_store._storage_provider_name,
218
+ "session_provider": self.artifact_store._session_provider_name,
218
219
  "uploaded_via_presigned": True, # Flag to indicate upload method
219
220
  }
220
221
 
221
222
  # Cache metadata using session provider
222
- session_ctx_mgr = self.store._session_factory()
223
+ session_ctx_mgr = self.artifact_store._session_factory()
223
224
  async with session_ctx_mgr as session:
224
225
  await session.setex(artifact_id, ttl, json.dumps(record))
225
226
 
@@ -288,7 +289,7 @@ class PresignedURLOperations:
288
289
  async def _get_record(self, artifact_id: str) -> Dict[str, Any]:
289
290
  """Get artifact metadata record."""
290
291
  try:
291
- session_ctx_mgr = self.store._session_factory()
292
+ session_ctx_mgr = self.artifact_store._session_factory()
292
293
  async with session_ctx_mgr as session:
293
294
  raw = await session.get(artifact_id)
294
295
  except Exception as e:
chuk_artifacts/store.py CHANGED
@@ -7,6 +7,7 @@ Grid Architecture:
7
7
  - Mandatory session allocation (no anonymous artifacts)
8
8
  - Grid paths: grid/{sandbox_id}/{session_id}/{artifact_id}
9
9
  - Clean, focused implementation
10
+ - Now uses chuk_sessions for session management
10
11
  """
11
12
 
12
13
  from __future__ import annotations
@@ -32,7 +33,9 @@ except ImportError:
32
33
 
33
34
  # Import exceptions
34
35
  from .exceptions import ArtifactStoreError, ProviderError
35
- from .session.session_manager import SessionManager
36
+
37
+ # Import chuk_sessions instead of local session manager
38
+ from chuk_sessions.session_manager import SessionManager
36
39
 
37
40
  # Configure structured logging
38
41
  logger = logging.getLogger(__name__)
@@ -64,6 +67,7 @@ class ArtifactStore:
64
67
  - Always allocate a session (no anonymous artifacts)
65
68
  - Grid paths only: grid/{sandbox_id}/{session_id}/{artifact_id}
66
69
  - Clean, focused implementation
70
+ - Uses chuk_sessions for session management
67
71
  """
68
72
 
69
73
  def __init__(
@@ -93,10 +97,9 @@ class ArtifactStore:
93
97
  self._session_factory = self._load_session_provider(session_provider)
94
98
  self._session_provider_name = session_provider
95
99
 
96
- # Session manager (always enabled)
100
+ # Session manager (now using chuk_sessions)
97
101
  self._session_manager = SessionManager(
98
102
  sandbox_id=self.sandbox_id,
99
- session_factory=self._session_factory,
100
103
  default_ttl_hours=session_ttl_hours,
101
104
  )
102
105
 
@@ -140,17 +143,14 @@ class ArtifactStore:
140
143
  ttl: int = _DEFAULT_TTL,
141
144
  ) -> str:
142
145
  """Store artifact with mandatory session allocation."""
143
- # Always allocate/validate session
146
+ # Always allocate/validate session using chuk_sessions
144
147
  session_id = await self._session_manager.allocate_session(
145
148
  session_id=session_id,
146
149
  user_id=user_id,
147
150
  )
148
151
 
149
- # Work around the naming conflict in CoreStorageOperations
150
- # where self.store is the artifact store instance, not the method
151
- core_store_method = getattr(self._core.__class__, 'store')
152
- return await core_store_method(
153
- self._core,
152
+ # Store using core operations
153
+ return await self._core.store(
154
154
  data=data,
155
155
  mime=mime,
156
156
  summary=summary,
@@ -181,18 +181,20 @@ class ArtifactStore:
181
181
  return await self._metadata.list_by_session(session_id, limit)
182
182
 
183
183
  # ─────────────────────────────────────────────────────────────────
184
- # Session operations
184
+ # Session operations - now delegated to chuk_sessions
185
185
  # ─────────────────────────────────────────────────────────────────
186
186
 
187
187
  async def create_session(
188
188
  self,
189
189
  user_id: Optional[str] = None,
190
190
  ttl_hours: Optional[int] = None,
191
+ custom_metadata: Optional[Dict[str, Any]] = None,
191
192
  ) -> str:
192
193
  """Create a new session."""
193
194
  return await self._session_manager.allocate_session(
194
195
  user_id=user_id,
195
196
  ttl_hours=ttl_hours,
197
+ custom_metadata=custom_metadata,
196
198
  )
197
199
 
198
200
  async def validate_session(self, session_id: str) -> bool:
@@ -203,8 +205,32 @@ class ArtifactStore:
203
205
  """Get session information."""
204
206
  return await self._session_manager.get_session_info(session_id)
205
207
 
208
+ async def update_session_metadata(
209
+ self,
210
+ session_id: str,
211
+ metadata: Dict[str, Any]
212
+ ) -> bool:
213
+ """Update session metadata."""
214
+ return await self._session_manager.update_session_metadata(session_id, metadata)
215
+
216
+ async def extend_session_ttl(
217
+ self,
218
+ session_id: str,
219
+ additional_hours: int
220
+ ) -> bool:
221
+ """Extend session TTL."""
222
+ return await self._session_manager.extend_session_ttl(session_id, additional_hours)
223
+
224
+ async def delete_session(self, session_id: str) -> bool:
225
+ """Delete session."""
226
+ return await self._session_manager.delete_session(session_id)
227
+
228
+ async def cleanup_expired_sessions(self) -> int:
229
+ """Clean up expired sessions."""
230
+ return await self._session_manager.cleanup_expired_sessions()
231
+
206
232
  # ─────────────────────────────────────────────────────────────────
207
- # Grid operations
233
+ # Grid operations - now delegated to chuk_sessions
208
234
  # ─────────────────────────────────────────────────────────────────
209
235
 
210
236
  def get_canonical_prefix(self, session_id: str) -> str:
@@ -215,6 +241,14 @@ class ArtifactStore:
215
241
  """Generate grid artifact key."""
216
242
  return self._session_manager.generate_artifact_key(session_id, artifact_id)
217
243
 
244
+ def parse_grid_key(self, grid_key: str) -> Optional[Dict[str, Any]]:
245
+ """Parse grid key to extract components."""
246
+ return self._session_manager.parse_grid_key(grid_key)
247
+
248
+ def get_session_prefix_pattern(self) -> str:
249
+ """Get session prefix pattern for this sandbox."""
250
+ return self._session_manager.get_session_prefix_pattern()
251
+
218
252
  # ─────────────────────────────────────────────────────────────────
219
253
  # File operations
220
254
  # ─────────────────────────────────────────────────────────────────
@@ -506,7 +540,13 @@ class ArtifactStore:
506
540
 
507
541
  async def get_stats(self) -> Dict[str, Any]:
508
542
  """Get storage statistics."""
509
- return await self._admin.get_stats()
543
+ stats = await self._admin.get_stats()
544
+
545
+ # Add session manager stats
546
+ session_stats = self._session_manager.get_cache_stats()
547
+ stats["session_manager"] = session_stats
548
+
549
+ return stats
510
550
 
511
551
  # ─────────────────────────────────────────────────────────────────
512
552
  # Helpers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chuk-artifacts
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Chuk Artifacts provides a production-ready, modular artifact storage system that works seamlessly across multiple storage backends (memory, filesystem, AWS S3, IBM Cloud Object Storage) with Redis or memory-based metadata caching and strict session-based security.
5
5
  License: MIT
6
6
  Requires-Python: >=3.11
@@ -12,7 +12,7 @@ Requires-Dist: pyyaml>=6.0.2
12
12
  Requires-Dist: aioboto3>=14.3.0
13
13
  Requires-Dist: redis>=6.2.0
14
14
  Requires-Dist: ibm-cos-sdk>=2.13.5
15
- Requires-Dist: chuk-sessions>=0.1.0
15
+ Requires-Dist: chuk-sessions>=0.1.1
16
16
  Provides-Extra: websocket
17
17
  Requires-Dist: websockets>=10.0; extra == "websocket"
18
18
  Provides-Extra: dev
@@ -0,0 +1,23 @@
1
+ chuk_artifacts/__init__.py,sha256=-4S9FWKVcQSa2ZD3GVbmbpGZPcl0cTQN_TFZLSqV7lQ,3605
2
+ chuk_artifacts/admin.py,sha256=lUgmKBPxJh-0FYrGWjkACXQOl8lbVEDPJaeGVsWZmC4,6071
3
+ chuk_artifacts/base.py,sha256=BtuVnC9M8QI1znyTdBxjZ6knIKP_k0yUfLfh7inGJUc,2559
4
+ chuk_artifacts/batch.py,sha256=x8ARrWJ24I9fAXXodzvh31uMxYrvwZCGGJhUCM4vMJ4,5099
5
+ chuk_artifacts/config.py,sha256=MaUzHzKPoBUyERviEpv8JVvPybMzSksgLyj0b7AO3Sc,7664
6
+ chuk_artifacts/core.py,sha256=hokH7cgGE2ZaEwlV8XMKOov3EMvcLS2HufdApLS6l3M,6699
7
+ chuk_artifacts/exceptions.py,sha256=f-s7Mg7c8vMXsbgqO2B6lMHdXcJQNvsESAY4GhJaV4g,814
8
+ chuk_artifacts/metadata.py,sha256=McyKLviBInpqh2eF621xhqb3Ix7QUj_nVc1gg_tUlqY,7830
9
+ chuk_artifacts/models.py,sha256=_foXlkr0DprqgztDw5WtlDc-s1OouLgYNp4XM1Ghp-g,837
10
+ chuk_artifacts/presigned.py,sha256=-GE8r0CfUZuPNA_jnSGTfX7kuws6kYCPe7C4y6FItdo,11491
11
+ chuk_artifacts/provider_factory.py,sha256=T0IXx1C8gygJzp417oB44_DxEaZoZR7jcdwQy8FghRE,3398
12
+ chuk_artifacts/store.py,sha256=3E_eh7JcgyW7-ikLSn_fFMUV4AwN5A0phkEmF0cMaxw,24779
13
+ chuk_artifacts/providers/__init__.py,sha256=3lN1lAy1ETT1mQslJo1f22PPR1W4CyxmsqJBclzH4NE,317
14
+ chuk_artifacts/providers/filesystem.py,sha256=F4EjE-_ItPg0RWe7CqameVpOMjU-b7AigEBkm_ZoNrc,15280
15
+ chuk_artifacts/providers/ibm_cos.py,sha256=K1-VAX4UVV9tA161MOeDXOKloQ0hB77jdw1-p46FwmU,4445
16
+ chuk_artifacts/providers/ibm_cos_iam.py,sha256=VtwvCi9rMMcZx6i9l21ob6wM8jXseqvjzgCnAA82RkY,3186
17
+ chuk_artifacts/providers/memory.py,sha256=B1C-tR1PcNz-UuDfGm1bhjPz3oITVATIMPekVbE7nm4,10487
18
+ chuk_artifacts/providers/s3.py,sha256=eWhBhFSaobpRbazn7ySfU_7D8rm_xCfdSVqRtzXzXRY,2858
19
+ chuk_artifacts-0.1.5.dist-info/licenses/LICENSE,sha256=SG9BmgtPBagPV0d-Fep-msdAGl-E1CeoBL7-EDRH2qA,1066
20
+ chuk_artifacts-0.1.5.dist-info/METADATA,sha256=LEDEBlXqdLSHIJDut0xjsBPBHlwLP2X8HfWshD2xyeg,21188
21
+ chuk_artifacts-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ chuk_artifacts-0.1.5.dist-info/top_level.txt,sha256=1_PVMtWXR0A-ZmeH6apF9mPaMtU0i23JE6wmN4GBRDI,15
23
+ chuk_artifacts-0.1.5.dist-info/RECORD,,
File without changes
@@ -1,196 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # chuk_artifacts/session/session_manager.py
3
- """
4
- Clean session manager for grid architecture.
5
-
6
- Simple rules:
7
- - Always have a session (auto-allocate if needed)
8
- - Grid paths: grid/{sandbox_id}/{session_id}/{artifact_id}
9
- - No legacy compatibility, clean implementation
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- import uuid
15
- import time
16
- import json
17
- import asyncio
18
- import logging
19
- from datetime import datetime, timedelta
20
- from typing import Optional, Dict, Any, List, AsyncContextManager, Callable
21
- from dataclasses import dataclass, asdict
22
-
23
- from ..exceptions import SessionError, ArtifactStoreError
24
-
25
- logger = logging.getLogger(__name__)
26
-
27
- _DEFAULT_SESSION_TTL_HOURS = 24
28
-
29
-
30
- @dataclass
31
- class SessionMetadata:
32
- """Session metadata for grid operations."""
33
- session_id: str
34
- sandbox_id: str
35
- user_id: Optional[str] = None
36
- created_at: str = None
37
- expires_at: str = None
38
- status: str = "active"
39
- artifact_count: int = 0
40
- total_bytes: int = 0
41
-
42
- def __post_init__(self):
43
- if self.created_at is None:
44
- self.created_at = datetime.utcnow().isoformat() + "Z"
45
-
46
- def is_expired(self) -> bool:
47
- """Check if session has expired."""
48
- if not self.expires_at:
49
- return False
50
- expires = datetime.fromisoformat(self.expires_at.replace("Z", ""))
51
- return datetime.utcnow() > expires
52
-
53
- def to_dict(self) -> Dict[str, Any]:
54
- return asdict(self)
55
-
56
- @classmethod
57
- def from_dict(cls, data: Dict[str, Any]) -> 'SessionMetadata':
58
- return cls(**data)
59
-
60
-
61
- class SessionManager:
62
- """Simple session manager for grid architecture."""
63
-
64
- def __init__(
65
- self,
66
- sandbox_id: str,
67
- session_factory: Callable[[], AsyncContextManager],
68
- default_ttl_hours: int = _DEFAULT_SESSION_TTL_HOURS,
69
- ):
70
- self.sandbox_id = sandbox_id
71
- self.session_factory = session_factory
72
- self.default_ttl_hours = default_ttl_hours
73
-
74
- # Simple in-memory cache
75
- self._session_cache: Dict[str, SessionMetadata] = {}
76
- self._cache_lock = asyncio.Lock()
77
-
78
- logger.info(f"SessionManager initialized for sandbox: {sandbox_id}")
79
-
80
- async def allocate_session(
81
- self,
82
- session_id: Optional[str] = None,
83
- user_id: Optional[str] = None,
84
- ttl_hours: Optional[int] = None,
85
- ) -> str:
86
- """Allocate or validate a session."""
87
- ttl_hours = ttl_hours or self.default_ttl_hours
88
-
89
- if session_id:
90
- # Validate existing session
91
- metadata = await self._get_session_metadata(session_id)
92
- if metadata and not metadata.is_expired():
93
- await self._touch_session(session_id)
94
- return session_id
95
-
96
- # Create new session
97
- if not session_id:
98
- session_id = self._generate_session_id(user_id)
99
-
100
- expires_at = (datetime.utcnow() + timedelta(hours=ttl_hours)).isoformat() + "Z"
101
-
102
- metadata = SessionMetadata(
103
- session_id=session_id,
104
- sandbox_id=self.sandbox_id,
105
- user_id=user_id,
106
- expires_at=expires_at,
107
- )
108
-
109
- await self._store_session_metadata(metadata)
110
-
111
- async with self._cache_lock:
112
- self._session_cache[session_id] = metadata
113
-
114
- logger.info(f"Session allocated: {session_id} (user: {user_id})")
115
- return session_id
116
-
117
- async def validate_session(self, session_id: str) -> bool:
118
- """Check if session is valid."""
119
- try:
120
- metadata = await self._get_session_metadata(session_id)
121
- if metadata and not metadata.is_expired():
122
- await self._touch_session(session_id)
123
- return True
124
- return False
125
- except Exception:
126
- return False
127
-
128
- async def get_session_info(self, session_id: str) -> Optional[Dict[str, Any]]:
129
- """Get session information."""
130
- metadata = await self._get_session_metadata(session_id)
131
- return metadata.to_dict() if metadata else None
132
-
133
- def get_canonical_prefix(self, session_id: str) -> str:
134
- """Get grid path prefix."""
135
- return f"grid/{self.sandbox_id}/{session_id}/"
136
-
137
- def generate_artifact_key(self, session_id: str, artifact_id: str) -> str:
138
- """Generate grid artifact key."""
139
- return f"grid/{self.sandbox_id}/{session_id}/{artifact_id}"
140
-
141
- def _generate_session_id(self, user_id: Optional[str] = None) -> str:
142
- """Generate session ID."""
143
- timestamp = int(time.time())
144
- unique = uuid.uuid4().hex[:8]
145
-
146
- if user_id:
147
- safe_user = "".join(c for c in user_id if c.isalnum())[:8]
148
- return f"sess-{safe_user}-{timestamp}-{unique}"
149
- else:
150
- return f"sess-{timestamp}-{unique}"
151
-
152
- async def _get_session_metadata(self, session_id: str) -> Optional[SessionMetadata]:
153
- """Get session metadata."""
154
- # Check cache
155
- async with self._cache_lock:
156
- if session_id in self._session_cache:
157
- return self._session_cache[session_id]
158
-
159
- # Query session provider
160
- try:
161
- session_ctx_mgr = self.session_factory()
162
- async with session_ctx_mgr as session:
163
- raw_data = await session.get(f"session:{session_id}")
164
- if raw_data:
165
- data = json.loads(raw_data)
166
- metadata = SessionMetadata.from_dict(data)
167
-
168
- # Cache it
169
- async with self._cache_lock:
170
- self._session_cache[session_id] = metadata
171
-
172
- return metadata
173
- except Exception as e:
174
- logger.warning(f"Failed to get session {session_id}: {e}")
175
-
176
- return None
177
-
178
- async def _store_session_metadata(self, metadata: SessionMetadata) -> None:
179
- """Store session metadata."""
180
- try:
181
- session_ctx_mgr = self.session_factory()
182
- async with session_ctx_mgr as session:
183
- key = f"session:{metadata.session_id}"
184
- ttl_seconds = int((datetime.fromisoformat(metadata.expires_at.replace("Z", "")) - datetime.utcnow()).total_seconds())
185
- data = json.dumps(metadata.to_dict())
186
-
187
- await session.setex(key, ttl_seconds, data)
188
- except Exception as e:
189
- raise SessionError(f"Session storage failed: {e}") from e
190
-
191
- async def _touch_session(self, session_id: str) -> None:
192
- """Update last accessed time."""
193
- metadata = await self._get_session_metadata(session_id)
194
- if metadata:
195
- # Simple touch - could update last_accessed if we add that field
196
- await self._store_session_metadata(metadata)