chuk-artifacts 0.2.2__py3-none-any.whl → 0.4__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.
@@ -36,6 +36,7 @@ class _MemoryS3Client:
36
36
  If None, creates isolated per-instance storage.
37
37
  """
38
38
  self._store: Dict[str, Dict[str, Any]] = shared_store if shared_store is not None else {}
39
+ self._is_shared_store = shared_store is not None
39
40
  self._lock = asyncio.Lock()
40
41
  self._closed = False
41
42
 
@@ -231,8 +232,11 @@ class _MemoryS3Client:
231
232
  async def close(self):
232
233
  """Clean up resources and mark client as closed."""
233
234
  if not self._closed:
234
- async with self._lock:
235
- self._store.clear()
235
+ # Only clear the store if it's NOT shared
236
+ # If it's shared, other clients may still need the data
237
+ if not self._is_shared_store:
238
+ async with self._lock:
239
+ self._store.clear()
236
240
  self._closed = True
237
241
 
238
242
  # ------------------------------------------------------------
@@ -253,6 +257,10 @@ class _MemoryS3Client:
253
257
  "total_objects": total_objects,
254
258
  "total_bytes": total_bytes,
255
259
  "closed": self._closed,
260
+ "is_shared_store": self._is_shared_store,
261
+ "store_id": id(self._store), # Memory address for debugging
262
+ "client_id": id(self), # Client instance ID
263
+ "store_keys": list(self._store.keys())[:5], # First 5 keys for debugging
256
264
  }
257
265
 
258
266
  @classmethod
@@ -263,17 +271,32 @@ class _MemoryS3Client:
263
271
 
264
272
  # ---- public factory -------------------------------------------------------
265
273
 
274
+ # Global shared storage for memory provider when used as default
275
+ _default_shared_store: Dict[str, Dict[str, Any]] = {}
276
+
266
277
  def factory(shared_store: Optional[Dict[str, Dict[str, Any]]] = None) -> Callable[[], AsyncContextManager]:
267
278
  """
268
279
  Return a **zero-arg** factory that yields an async-context client.
269
280
 
281
+ Key behavior for memory provider:
282
+ - If shared_store is provided, uses that specific storage
283
+ - If shared_store is None, ALWAYS uses the global shared storage
284
+ - This ensures all memory clients in the same process share data
285
+ - Prevents issues where ArtifactStore operations can't see each other's data
286
+
270
287
  Parameters
271
288
  ----------
272
289
  shared_store : dict, optional
273
290
  If provided, all clients created by this factory will share
274
- the same storage dict. Useful for testing scenarios where
275
- you need multiple clients to see the same data.
291
+ the same storage dict. If None, will use a global shared store
292
+ to ensure consistency across operations within the same process.
276
293
  """
294
+
295
+ # CRITICAL: Always use global shared storage when none specified
296
+ # This prevents the common issue where each ArtifactStore operation
297
+ # gets a different isolated storage and can't see each other's data
298
+ if shared_store is None:
299
+ shared_store = _default_shared_store
277
300
 
278
301
  @asynccontextmanager
279
302
  async def _ctx():
@@ -307,6 +330,11 @@ async def clear_all_memory_stores():
307
330
  Emergency cleanup function that clears all active memory stores.
308
331
  Useful for test teardown.
309
332
  """
333
+ # Clear the global shared store
334
+ global _default_shared_store
335
+ _default_shared_store.clear()
336
+
337
+ # Close all active instances
310
338
  instances = list(_MemoryS3Client._instances)
311
339
  for instance in instances:
312
340
  try:
chuk_artifacts/store.py CHANGED
@@ -12,7 +12,9 @@ Grid Architecture:
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- import os, logging, uuid
15
+ import os
16
+ import logging
17
+ import uuid
16
18
  from datetime import datetime
17
19
  from typing import Any, Dict, List, Callable, AsyncContextManager, Optional, Union
18
20
  from chuk_sessions.session_manager import SessionManager
@@ -37,7 +39,6 @@ except ImportError:
37
39
  from .exceptions import ArtifactStoreError, ProviderError
38
40
 
39
41
  # Import chuk_sessions instead of local session manager
40
- from chuk_sessions.session_manager import SessionManager
41
42
 
42
43
  # Configure structured logging
43
44
  logger = logging.getLogger(__name__)
@@ -161,6 +162,32 @@ class ArtifactStore:
161
162
  session_id=session_id,
162
163
  ttl=ttl,
163
164
  )
165
+
166
+ async def update_file(
167
+ self,
168
+ artifact_id: str,
169
+ *,
170
+ data: Optional[bytes] = None,
171
+ meta: Optional[Dict[str, Any]] = None,
172
+ filename: Optional[str] = None,
173
+ summary: Optional[str] = None,
174
+ mime: Optional[str] = None,
175
+ ) -> bool:
176
+ """
177
+ Update an artifact's content, metadata, filename, summary, or mime type.
178
+ All parameters are optional. At least one must be provided.
179
+ """
180
+ if not any([data, meta, filename, summary, mime]):
181
+ raise ValueError("At least one update parameter must be provided.")
182
+
183
+ return await self._core.update_file(
184
+ artifact_id=artifact_id,
185
+ new_data=data,
186
+ meta=meta,
187
+ filename=filename,
188
+ summary=summary,
189
+ mime=mime,
190
+ )
164
191
 
165
192
  async def retrieve(self, artifact_id: str) -> bytes:
166
193
  """Retrieve artifact data."""
@@ -606,4 +633,50 @@ class ArtifactStore:
606
633
  return self
607
634
 
608
635
  async def __aexit__(self, exc_type, exc_val, exc_tb):
609
- await self.close()
636
+ await self.close()
637
+
638
+ async def get_sandbox_info(self) -> Dict[str, Any]:
639
+ """
640
+ Get sandbox information and metadata.
641
+
642
+ Returns
643
+ -------
644
+ Dict[str, Any]
645
+ Dictionary containing sandbox information including:
646
+ - sandbox_id: The current sandbox identifier
647
+ - bucket: The storage bucket name
648
+ - storage_provider: The storage provider type
649
+ - session_provider: The session provider type
650
+ - session_ttl_hours: Default session TTL
651
+ - grid_prefix_pattern: The grid path pattern for this sandbox
652
+ - created_at: Timestamp of when this info was retrieved
653
+ """
654
+ from datetime import datetime
655
+
656
+ # Get session manager stats if available
657
+ session_stats = {}
658
+ try:
659
+ session_stats = self._session_manager.get_cache_stats()
660
+ except Exception:
661
+ pass # Session manager might not have stats
662
+
663
+ # Get storage stats if available
664
+ storage_stats = {}
665
+ try:
666
+ storage_stats = await self._admin.get_stats()
667
+ except Exception:
668
+ pass # Storage might not have stats
669
+
670
+ return {
671
+ "sandbox_id": self.sandbox_id,
672
+ "bucket": self.bucket,
673
+ "storage_provider": self._storage_provider_name,
674
+ "session_provider": self._session_provider_name,
675
+ "session_ttl_hours": self.session_ttl_hours,
676
+ "max_retries": self.max_retries,
677
+ "grid_prefix_pattern": self.get_session_prefix_pattern(),
678
+ "created_at": datetime.utcnow().isoformat() + "Z",
679
+ "session_stats": session_stats,
680
+ "storage_stats": storage_stats,
681
+ "closed": self._closed,
682
+ }