chuk-artifacts 0.1.0__py3-none-any.whl → 0.1.2__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.
- chuk_artifacts/metadata.py +149 -124
- chuk_artifacts/session_operations.py +367 -0
- chuk_artifacts/store.py +115 -17
- {chuk_artifacts-0.1.0.dist-info → chuk_artifacts-0.1.2.dist-info}/METADATA +335 -144
- {chuk_artifacts-0.1.0.dist-info → chuk_artifacts-0.1.2.dist-info}/RECORD +8 -7
- {chuk_artifacts-0.1.0.dist-info → chuk_artifacts-0.1.2.dist-info}/WHEEL +0 -0
- {chuk_artifacts-0.1.0.dist-info → chuk_artifacts-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {chuk_artifacts-0.1.0.dist-info → chuk_artifacts-0.1.2.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: chuk-artifacts
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.2
|
4
4
|
Summary: Add your description here
|
5
5
|
License: MIT
|
6
6
|
Requires-Python: >=3.11
|
@@ -27,18 +27,20 @@ Dynamic: license-file
|
|
27
27
|
[](https://python.org)
|
28
28
|
[](LICENSE)
|
29
29
|
|
30
|
-
**Asynchronous, multi-backend artifact storage with
|
30
|
+
**Asynchronous, multi-backend artifact storage with session-based security and presigned URLs**
|
31
31
|
|
32
|
-
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
|
32
|
+
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**.
|
33
33
|
|
34
34
|
## ✨ Key Features
|
35
35
|
|
36
|
-
- 🏗️ **Modular Architecture**:
|
36
|
+
- 🏗️ **Modular Architecture**: 6 specialized operation modules for clean separation of concerns
|
37
|
+
- 🔒 **Session-Based Security**: Strict isolation with no cross-session operations allowed
|
37
38
|
- 🔄 **Multi-Backend Support**: Memory, filesystem, S3, IBM COS with seamless switching
|
38
|
-
- ⚡ **Fully Async**: Built with async/await for high performance
|
39
|
+
- ⚡ **Fully Async**: Built with async/await for high performance (3,000+ ops/sec)
|
39
40
|
- 🔗 **Presigned URLs**: Secure, time-limited access without credential exposure
|
40
41
|
- 📊 **Batch Operations**: Efficient multi-file uploads and processing
|
41
42
|
- 🗃️ **Metadata Caching**: Fast lookups with Redis or memory-based sessions
|
43
|
+
- 📁 **Directory-Like Operations**: Organize files with path-based prefixes
|
42
44
|
- 🔧 **Zero Configuration**: Works out of the box with sensible defaults
|
43
45
|
- 🌍 **Production Ready**: Battle-tested with comprehensive error handling
|
44
46
|
|
@@ -65,7 +67,8 @@ artifact_id = await store.store(
|
|
65
67
|
data=b"Hello, world!",
|
66
68
|
mime="text/plain",
|
67
69
|
summary="A simple greeting",
|
68
|
-
filename="hello.txt"
|
70
|
+
filename="hello.txt",
|
71
|
+
session_id="user_123" # Session-based isolation
|
69
72
|
)
|
70
73
|
|
71
74
|
# Retrieve it
|
@@ -76,6 +79,41 @@ print(data.decode()) # "Hello, world!"
|
|
76
79
|
download_url = await store.presign_medium(artifact_id) # 1 hour
|
77
80
|
```
|
78
81
|
|
82
|
+
### Session-Based File Management
|
83
|
+
|
84
|
+
```python
|
85
|
+
# Create files in user sessions
|
86
|
+
doc_id = await store.write_file(
|
87
|
+
content="# User's Document\n\nPrivate content here.",
|
88
|
+
filename="docs/private.md",
|
89
|
+
mime="text/markdown",
|
90
|
+
session_id="user_alice"
|
91
|
+
)
|
92
|
+
|
93
|
+
# List files in a session
|
94
|
+
files = await store.list_by_session("user_alice")
|
95
|
+
print(f"Alice has {len(files)} files")
|
96
|
+
|
97
|
+
# List directory-like contents
|
98
|
+
docs = await store.get_directory_contents("user_alice", "docs/")
|
99
|
+
print(f"Alice's docs: {len(docs)} files")
|
100
|
+
|
101
|
+
# Copy within same session (allowed)
|
102
|
+
backup_id = await store.copy_file(
|
103
|
+
doc_id,
|
104
|
+
new_filename="docs/private_backup.md"
|
105
|
+
)
|
106
|
+
|
107
|
+
# Cross-session operations are BLOCKED for security
|
108
|
+
try:
|
109
|
+
await store.copy_file(
|
110
|
+
doc_id,
|
111
|
+
target_session_id="user_bob" # This will fail
|
112
|
+
)
|
113
|
+
except ArtifactStoreError:
|
114
|
+
print("Cross-session operations blocked!")
|
115
|
+
```
|
116
|
+
|
79
117
|
### With Configuration
|
80
118
|
|
81
119
|
```python
|
@@ -104,7 +142,8 @@ Chuk Artifacts uses a modular architecture with specialized operation modules:
|
|
104
142
|
ArtifactStore (Main Coordinator)
|
105
143
|
├── CoreStorageOperations # store() and retrieve()
|
106
144
|
├── PresignedURLOperations # URL generation and upload workflows
|
107
|
-
├── MetadataOperations # metadata, exists, delete, update
|
145
|
+
├── MetadataOperations # metadata, exists, delete, update, list_by_session
|
146
|
+
├── SessionOperations # session-based file operations (NEW)
|
108
147
|
├── BatchOperations # store_batch() for multiple files
|
109
148
|
└── AdminOperations # validate_configuration, get_stats
|
110
149
|
```
|
@@ -114,6 +153,43 @@ This design provides:
|
|
114
153
|
- **Enhanced maintainability**: Clear separation of concerns
|
115
154
|
- **Easy extensibility**: Add new operation types without touching core
|
116
155
|
- **Improved debugging**: Isolated functionality for easier troubleshooting
|
156
|
+
- **Session security**: Dedicated module for secure session operations
|
157
|
+
|
158
|
+
## 🔒 Session-Based Security
|
159
|
+
|
160
|
+
### Strict Session Isolation
|
161
|
+
```python
|
162
|
+
# Users can only access their own files
|
163
|
+
alice_files = await store.list_by_session("user_alice")
|
164
|
+
bob_files = await store.list_by_session("user_bob")
|
165
|
+
|
166
|
+
# Cross-session operations are blocked
|
167
|
+
await store.copy_file(alice_file_id, target_session_id="user_bob") # ❌ Blocked
|
168
|
+
await store.move_file(alice_file_id, new_session_id="user_bob") # ❌ Blocked
|
169
|
+
```
|
170
|
+
|
171
|
+
### Multi-Tenant Safe
|
172
|
+
```python
|
173
|
+
# Perfect for SaaS applications
|
174
|
+
company_a_files = await store.list_by_session("company_a")
|
175
|
+
company_b_files = await store.list_by_session("company_b")
|
176
|
+
|
177
|
+
# Companies cannot access each other's data
|
178
|
+
# Compliance-ready: GDPR, SOX, HIPAA
|
179
|
+
```
|
180
|
+
|
181
|
+
### Directory-Like Organization
|
182
|
+
```python
|
183
|
+
# Organize files with path-like prefixes
|
184
|
+
await store.write_file(content, filename="docs/reports/q1_sales.pdf", session_id="user_123")
|
185
|
+
await store.write_file(content, filename="docs/contracts/client_a.pdf", session_id="user_123")
|
186
|
+
await store.write_file(content, filename="images/profile.jpg", session_id="user_123")
|
187
|
+
|
188
|
+
# List by directory
|
189
|
+
docs = await store.get_directory_contents("user_123", "docs/")
|
190
|
+
reports = await store.get_directory_contents("user_123", "docs/reports/")
|
191
|
+
images = await store.get_directory_contents("user_123", "images/")
|
192
|
+
```
|
117
193
|
|
118
194
|
## 📦 Storage Providers
|
119
195
|
|
@@ -124,7 +200,7 @@ store = ArtifactStore(storage_provider="memory")
|
|
124
200
|
- Perfect for development and testing
|
125
201
|
- Zero configuration required
|
126
202
|
- Non-persistent (data lost on restart)
|
127
|
-
-
|
203
|
+
- Session listing returns empty (graceful degradation)
|
128
204
|
|
129
205
|
### Filesystem Provider
|
130
206
|
```python
|
@@ -135,6 +211,7 @@ os.environ["ARTIFACT_FS_ROOT"] = "./my-artifacts"
|
|
135
211
|
- Local disk storage
|
136
212
|
- Persistent across restarts
|
137
213
|
- `file://` URLs for local access
|
214
|
+
- **Full session listing support**
|
138
215
|
- Great for development and small deployments
|
139
216
|
|
140
217
|
### AWS S3 Provider
|
@@ -151,6 +228,7 @@ os.environ.update({
|
|
151
228
|
- Industry-standard cloud storage
|
152
229
|
- Native presigned URL support
|
153
230
|
- Highly scalable and durable
|
231
|
+
- **Full session listing support**
|
154
232
|
- Perfect for production workloads
|
155
233
|
|
156
234
|
### IBM Cloud Object Storage
|
@@ -192,6 +270,40 @@ os.environ["SESSION_REDIS_URL"] = "redis://localhost:6379/0"
|
|
192
270
|
|
193
271
|
## 🎯 Common Use Cases
|
194
272
|
|
273
|
+
### MCP Server Integration
|
274
|
+
|
275
|
+
```python
|
276
|
+
from chuk_artifacts import ArtifactStore
|
277
|
+
|
278
|
+
# Initialize for MCP server
|
279
|
+
store = ArtifactStore(
|
280
|
+
storage_provider="filesystem", # or "s3" for production
|
281
|
+
session_provider="redis"
|
282
|
+
)
|
283
|
+
|
284
|
+
# MCP tool: Upload file
|
285
|
+
async def upload_file(data_base64: str, filename: str, mime: str, session_id: str):
|
286
|
+
data = base64.b64decode(data_base64)
|
287
|
+
artifact_id = await store.store(
|
288
|
+
data=data,
|
289
|
+
mime=mime,
|
290
|
+
summary=f"Uploaded: {filename}",
|
291
|
+
filename=filename,
|
292
|
+
session_id=session_id # Session isolation
|
293
|
+
)
|
294
|
+
return {"artifact_id": artifact_id}
|
295
|
+
|
296
|
+
# MCP tool: List session files
|
297
|
+
async def list_session_files(session_id: str, prefix: str = ""):
|
298
|
+
files = await store.list_by_prefix(session_id, prefix)
|
299
|
+
return {"files": files}
|
300
|
+
|
301
|
+
# MCP tool: Copy file (within session only)
|
302
|
+
async def copy_file(artifact_id: str, new_filename: str):
|
303
|
+
new_id = await store.copy_file(artifact_id, new_filename=new_filename)
|
304
|
+
return {"new_artifact_id": new_id}
|
305
|
+
```
|
306
|
+
|
195
307
|
### Web Framework Integration
|
196
308
|
|
197
309
|
```python
|
@@ -203,13 +315,14 @@ store = ArtifactStore(
|
|
203
315
|
session_provider="redis"
|
204
316
|
)
|
205
317
|
|
206
|
-
async def upload_file(file_content: bytes, filename: str, content_type: str):
|
207
|
-
"""Handle file upload in FastAPI/Flask"""
|
318
|
+
async def upload_file(file_content: bytes, filename: str, content_type: str, user_id: str):
|
319
|
+
"""Handle file upload in FastAPI/Flask with user isolation"""
|
208
320
|
artifact_id = await store.store(
|
209
321
|
data=file_content,
|
210
322
|
mime=content_type,
|
211
323
|
summary=f"Uploaded: {filename}",
|
212
|
-
filename=filename
|
324
|
+
filename=filename,
|
325
|
+
session_id=f"user_{user_id}" # User-specific session
|
213
326
|
)
|
214
327
|
|
215
328
|
# Return download URL
|
@@ -218,6 +331,73 @@ async def upload_file(file_content: bytes, filename: str, content_type: str):
|
|
218
331
|
"artifact_id": artifact_id,
|
219
332
|
"download_url": download_url
|
220
333
|
}
|
334
|
+
|
335
|
+
async def list_user_files(user_id: str, directory: str = ""):
|
336
|
+
"""List files for a specific user"""
|
337
|
+
return await store.get_directory_contents(f"user_{user_id}", directory)
|
338
|
+
```
|
339
|
+
|
340
|
+
### Multi-Tenant SaaS Application
|
341
|
+
|
342
|
+
```python
|
343
|
+
# Tenant isolation
|
344
|
+
async def create_tenant_workspace(tenant_id: str):
|
345
|
+
"""Create isolated workspace for tenant"""
|
346
|
+
|
347
|
+
# Create tenant directory structure
|
348
|
+
directories = ["documents/", "images/", "reports/", "config/"]
|
349
|
+
|
350
|
+
for directory in directories:
|
351
|
+
# Create a marker file for the directory
|
352
|
+
await store.write_file(
|
353
|
+
content=f"# {directory.rstrip('/')} Directory\n\nTenant workspace created.",
|
354
|
+
filename=f"{directory}README.md",
|
355
|
+
session_id=f"tenant_{tenant_id}"
|
356
|
+
)
|
357
|
+
|
358
|
+
return {"tenant_id": tenant_id, "directories": directories}
|
359
|
+
|
360
|
+
async def get_tenant_usage(tenant_id: str):
|
361
|
+
"""Get storage usage for a tenant"""
|
362
|
+
files = await store.list_by_session(f"tenant_{tenant_id}")
|
363
|
+
total_bytes = sum(file.get('bytes', 0) for file in files)
|
364
|
+
|
365
|
+
return {
|
366
|
+
"tenant_id": tenant_id,
|
367
|
+
"file_count": len(files),
|
368
|
+
"total_bytes": total_bytes,
|
369
|
+
"total_mb": round(total_bytes / 1024 / 1024, 2)
|
370
|
+
}
|
371
|
+
```
|
372
|
+
|
373
|
+
### Advanced File Operations
|
374
|
+
|
375
|
+
```python
|
376
|
+
# Read file content directly
|
377
|
+
content = await store.read_file(artifact_id, as_text=True)
|
378
|
+
print(f"File content: {content}")
|
379
|
+
|
380
|
+
# Write file with content
|
381
|
+
new_id = await store.write_file(
|
382
|
+
content="# New Document\n\nThis is a new file.",
|
383
|
+
filename="documents/new_doc.md",
|
384
|
+
mime="text/markdown",
|
385
|
+
session_id="user_123"
|
386
|
+
)
|
387
|
+
|
388
|
+
# Move/rename file within session
|
389
|
+
await store.move_file(
|
390
|
+
artifact_id,
|
391
|
+
new_filename="documents/renamed_doc.md"
|
392
|
+
)
|
393
|
+
|
394
|
+
# Update file content (overwrite)
|
395
|
+
updated_id = await store.write_file(
|
396
|
+
content="# Updated Document\n\nThis content replaces the old file.",
|
397
|
+
filename="documents/updated_doc.md",
|
398
|
+
session_id="user_123",
|
399
|
+
overwrite_artifact_id=old_artifact_id
|
400
|
+
)
|
221
401
|
```
|
222
402
|
|
223
403
|
### Batch Processing
|
@@ -229,45 +409,18 @@ items = [
|
|
229
409
|
"data": file1_content,
|
230
410
|
"mime": "image/png",
|
231
411
|
"summary": "Product image 1",
|
232
|
-
"filename": "product1.png"
|
412
|
+
"filename": "images/product1.png"
|
233
413
|
},
|
234
414
|
{
|
235
415
|
"data": file2_content,
|
236
416
|
"mime": "image/png",
|
237
417
|
"summary": "Product image 2",
|
238
|
-
"filename": "product2.png"
|
418
|
+
"filename": "images/product2.png"
|
239
419
|
}
|
240
420
|
]
|
241
421
|
|
242
|
-
# Store all at once
|
243
|
-
artifact_ids = await store.store_batch(items, session_id="product-
|
244
|
-
```
|
245
|
-
|
246
|
-
### Advanced Metadata Management
|
247
|
-
|
248
|
-
```python
|
249
|
-
# Store with custom metadata
|
250
|
-
artifact_id = await store.store(
|
251
|
-
data=image_data,
|
252
|
-
mime="image/png",
|
253
|
-
summary="Product photo",
|
254
|
-
filename="product.png",
|
255
|
-
meta={
|
256
|
-
"product_id": "12345",
|
257
|
-
"photographer": "John Doe",
|
258
|
-
"category": "electronics"
|
259
|
-
}
|
260
|
-
)
|
261
|
-
|
262
|
-
# Update metadata later
|
263
|
-
await store.update_metadata(
|
264
|
-
artifact_id,
|
265
|
-
summary="Updated product photo",
|
266
|
-
meta={"edited": True, "version": 2}
|
267
|
-
)
|
268
|
-
|
269
|
-
# Extend TTL
|
270
|
-
await store.extend_ttl(artifact_id, 3600) # Add 1 hour
|
422
|
+
# Store all at once with session isolation
|
423
|
+
artifact_ids = await store.store_batch(items, session_id="product-catalog")
|
271
424
|
```
|
272
425
|
|
273
426
|
### Context Manager Usage
|
@@ -277,11 +430,52 @@ async with ArtifactStore() as store:
|
|
277
430
|
artifact_id = await store.store(
|
278
431
|
data=b"Temporary data",
|
279
432
|
mime="text/plain",
|
280
|
-
summary="Auto-cleanup example"
|
433
|
+
summary="Auto-cleanup example",
|
434
|
+
session_id="temp_session"
|
281
435
|
)
|
282
436
|
# Store automatically closed on exit
|
283
437
|
```
|
284
438
|
|
439
|
+
## 🧪 Testing
|
440
|
+
|
441
|
+
### Run All Tests
|
442
|
+
```bash
|
443
|
+
# Comprehensive smoke test (64+ test scenarios)
|
444
|
+
uv run examples/artifact_smoke_test.py
|
445
|
+
|
446
|
+
# Usage examples with all providers
|
447
|
+
uv run examples/artifact_usage_examples.py
|
448
|
+
|
449
|
+
# Session operations demo (NEW)
|
450
|
+
uv run examples/session_operations_demo.py
|
451
|
+
```
|
452
|
+
|
453
|
+
### Session Security Testing
|
454
|
+
```bash
|
455
|
+
# Run secure session operations demo
|
456
|
+
uv run examples/session_operations_demo.py
|
457
|
+
|
458
|
+
# Output shows:
|
459
|
+
# ✅ Cross-session copy correctly blocked
|
460
|
+
# ✅ Cross-session move correctly blocked
|
461
|
+
# ✅ Cross-session overwrite correctly blocked
|
462
|
+
# 🛡️ ALL SECURITY TESTS PASSED!
|
463
|
+
```
|
464
|
+
|
465
|
+
### Development Setup
|
466
|
+
```python
|
467
|
+
from chuk_artifacts.config import development_setup
|
468
|
+
|
469
|
+
store = development_setup() # Uses memory providers
|
470
|
+
```
|
471
|
+
|
472
|
+
### Testing Setup
|
473
|
+
```python
|
474
|
+
from chuk_artifacts.config import testing_setup
|
475
|
+
|
476
|
+
store = testing_setup("./test-artifacts") # Uses filesystem
|
477
|
+
```
|
478
|
+
|
285
479
|
## 🔧 Configuration
|
286
480
|
|
287
481
|
### Environment Variables
|
@@ -328,91 +522,41 @@ configure_redis_session("redis://prod-redis:6379/1")
|
|
328
522
|
store = ArtifactStore()
|
329
523
|
```
|
330
524
|
|
331
|
-
## 🧪 Testing
|
332
|
-
|
333
|
-
### Run All Tests
|
334
|
-
```bash
|
335
|
-
# Comprehensive smoke test (64 test scenarios)
|
336
|
-
uv run examples/artifact_smoke_test.py
|
337
|
-
|
338
|
-
# Usage examples
|
339
|
-
uv run examples/artifact_usage_examples.py
|
340
|
-
```
|
341
|
-
|
342
|
-
### Development Setup
|
343
|
-
```python
|
344
|
-
from chuk_artifacts.config import development_setup
|
345
|
-
|
346
|
-
store = development_setup() # Uses memory providers
|
347
|
-
```
|
348
|
-
|
349
|
-
### Testing Setup
|
350
|
-
```python
|
351
|
-
from chuk_artifacts.config import testing_setup
|
352
|
-
|
353
|
-
store = testing_setup("./test-artifacts") # Uses filesystem
|
354
|
-
```
|
355
|
-
|
356
525
|
## 🚀 Performance
|
357
526
|
|
527
|
+
- **High Throughput**: 3,000+ file operations per second
|
358
528
|
- **Async/Await**: Non-blocking I/O for high concurrency
|
359
529
|
- **Connection Pooling**: Efficient resource usage with aioboto3
|
360
|
-
- **Metadata Caching**:
|
530
|
+
- **Metadata Caching**: Sub-millisecond lookups with Redis
|
361
531
|
- **Batch Operations**: Reduced overhead for multiple files
|
362
532
|
- **Streaming**: Large file support with streaming reads/writes
|
533
|
+
- **Session Listing**: Optimized prefix-based queries
|
534
|
+
|
535
|
+
### Performance Benchmarks
|
536
|
+
```
|
537
|
+
✅ Created 50 files in 0.02 seconds (2,933 files/sec)
|
538
|
+
✅ Listed 50 files in 0.006 seconds
|
539
|
+
✅ Listed directory (50 files) in 0.005 seconds
|
540
|
+
✅ Read 10 files in 0.002 seconds (5,375 reads/sec)
|
541
|
+
```
|
363
542
|
|
364
543
|
## 🔒 Security
|
365
544
|
|
545
|
+
- **Session Isolation**: Strict boundaries prevent cross-session access
|
546
|
+
- **No Cross-Session Operations**: Copy, move, overwrite blocked across sessions
|
366
547
|
- **Presigned URLs**: Time-limited access without credential sharing
|
367
548
|
- **Secure Defaults**: Conservative TTL and expiration settings
|
368
549
|
- **Credential Isolation**: Environment-based configuration
|
369
550
|
- **Error Handling**: No sensitive data in logs or exceptions
|
551
|
+
- **Multi-Tenant Ready**: Perfect for SaaS applications
|
370
552
|
|
371
|
-
|
372
|
-
|
373
|
-
### Custom Providers
|
374
|
-
```python
|
375
|
-
# Create custom storage provider
|
376
|
-
def my_custom_factory():
|
377
|
-
@asynccontextmanager
|
378
|
-
async def _ctx():
|
379
|
-
client = MyCustomClient()
|
380
|
-
try:
|
381
|
-
yield client
|
382
|
-
finally:
|
383
|
-
await client.close()
|
384
|
-
return _ctx
|
385
|
-
|
386
|
-
store = ArtifactStore(s3_factory=my_custom_factory())
|
387
|
-
```
|
388
|
-
|
389
|
-
### Error Handling
|
390
|
-
```python
|
391
|
-
from chuk_artifacts import (
|
392
|
-
ArtifactNotFoundError,
|
393
|
-
ArtifactExpiredError,
|
394
|
-
ProviderError
|
395
|
-
)
|
396
|
-
|
397
|
-
try:
|
398
|
-
data = await store.retrieve("invalid-id")
|
399
|
-
except ArtifactNotFoundError:
|
400
|
-
print("Artifact not found or expired")
|
401
|
-
except ProviderError as e:
|
402
|
-
print(f"Storage provider error: {e}")
|
403
|
-
```
|
404
|
-
|
405
|
-
### Validation and Monitoring
|
553
|
+
### Security Validation
|
406
554
|
```python
|
407
|
-
#
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
# Get statistics
|
413
|
-
stats = await store.get_stats()
|
414
|
-
print(f"Provider: {stats['storage_provider']}")
|
415
|
-
print(f"Bucket: {stats['bucket']}")
|
555
|
+
# All these operations are blocked for security
|
556
|
+
await store.copy_file(user_a_file, target_session_id="user_b") # ❌ Blocked
|
557
|
+
await store.move_file(user_a_file, new_session_id="user_b") # ❌ Blocked
|
558
|
+
await store.write_file(content, session_id="user_b",
|
559
|
+
overwrite_artifact_id=user_a_file) # ❌ Blocked
|
416
560
|
```
|
417
561
|
|
418
562
|
## 📝 API Reference
|
@@ -422,39 +566,37 @@ print(f"Bucket: {stats['bucket']}")
|
|
422
566
|
#### `store(data, *, mime, summary, meta=None, filename=None, session_id=None, ttl=900)`
|
423
567
|
Store artifact data with metadata.
|
424
568
|
|
425
|
-
**Parameters:**
|
426
|
-
- `data` (bytes): The artifact data
|
427
|
-
- `mime` (str): MIME type (e.g., "text/plain", "image/png")
|
428
|
-
- `summary` (str): Human-readable description
|
429
|
-
- `meta` (dict, optional): Additional metadata
|
430
|
-
- `filename` (str, optional): Original filename
|
431
|
-
- `session_id` (str, optional): Session identifier for organization
|
432
|
-
- `ttl` (int, optional): Metadata TTL in seconds (default: 900)
|
433
|
-
|
434
|
-
**Returns:** `str` - Unique artifact identifier
|
435
|
-
|
436
569
|
#### `retrieve(artifact_id)`
|
437
570
|
Retrieve artifact data by ID.
|
438
571
|
|
439
|
-
**Parameters:**
|
440
|
-
- `artifact_id` (str): The artifact identifier
|
441
|
-
|
442
|
-
**Returns:** `bytes` - The artifact data
|
443
|
-
|
444
572
|
#### `metadata(artifact_id)`
|
445
573
|
Get artifact metadata.
|
446
574
|
|
447
|
-
|
575
|
+
#### `exists(artifact_id)` / `delete(artifact_id)`
|
576
|
+
Check existence or delete artifacts.
|
577
|
+
|
578
|
+
### Session Operations (NEW)
|
579
|
+
|
580
|
+
#### `list_by_session(session_id, limit=100)`
|
581
|
+
List all artifacts in a session.
|
582
|
+
|
583
|
+
#### `list_by_prefix(session_id, prefix="", limit=100)`
|
584
|
+
List artifacts with filename prefix (directory-like).
|
585
|
+
|
586
|
+
#### `get_directory_contents(session_id, directory_prefix="", limit=100)`
|
587
|
+
Get files in a directory-like structure.
|
448
588
|
|
449
|
-
#### `
|
450
|
-
|
589
|
+
#### `copy_file(artifact_id, *, new_filename=None, target_session_id=None, new_meta=None)`
|
590
|
+
Copy file within same session only (cross-session blocked).
|
451
591
|
|
452
|
-
|
592
|
+
#### `move_file(artifact_id, *, new_filename=None, new_session_id=None, new_meta=None)`
|
593
|
+
Move/rename file within same session only (cross-session blocked).
|
453
594
|
|
454
|
-
#### `
|
455
|
-
|
595
|
+
#### `read_file(artifact_id, *, encoding="utf-8", as_text=True)`
|
596
|
+
Read file content directly as text or binary.
|
456
597
|
|
457
|
-
|
598
|
+
#### `write_file(content, *, filename, mime="text/plain", session_id=None, overwrite_artifact_id=None)`
|
599
|
+
Write content to new file or overwrite existing (within same session).
|
458
600
|
|
459
601
|
### Presigned URLs
|
460
602
|
|
@@ -467,18 +609,11 @@ Generate URLs with predefined durations (15min/1hr/24hr).
|
|
467
609
|
#### `presign_upload(session_id=None, filename=None, mime_type="application/octet-stream", expires=3600)`
|
468
610
|
Generate presigned URL for upload.
|
469
611
|
|
470
|
-
**Returns:** `tuple[str, str]` - (upload_url, artifact_id)
|
471
|
-
|
472
612
|
### Batch Operations
|
473
613
|
|
474
614
|
#### `store_batch(items, session_id=None, ttl=900)`
|
475
615
|
Store multiple artifacts efficiently.
|
476
616
|
|
477
|
-
**Parameters:**
|
478
|
-
- `items` (list): List of dicts with keys: data, mime, summary, meta, filename
|
479
|
-
|
480
|
-
**Returns:** `list[str]` - List of artifact IDs
|
481
|
-
|
482
617
|
### Admin Operations
|
483
618
|
|
484
619
|
#### `validate_configuration()`
|
@@ -487,13 +622,64 @@ Validate storage and session provider connectivity.
|
|
487
622
|
#### `get_stats()`
|
488
623
|
Get storage statistics and configuration info.
|
489
624
|
|
625
|
+
## 🛠️ Advanced Features
|
626
|
+
|
627
|
+
### Custom Providers
|
628
|
+
```python
|
629
|
+
# Create custom storage provider
|
630
|
+
def my_custom_factory():
|
631
|
+
@asynccontextmanager
|
632
|
+
async def _ctx():
|
633
|
+
client = MyCustomClient()
|
634
|
+
try:
|
635
|
+
yield client
|
636
|
+
finally:
|
637
|
+
await client.close()
|
638
|
+
return _ctx
|
639
|
+
|
640
|
+
store = ArtifactStore(s3_factory=my_custom_factory())
|
641
|
+
```
|
642
|
+
|
643
|
+
### Error Handling
|
644
|
+
```python
|
645
|
+
from chuk_artifacts import (
|
646
|
+
ArtifactNotFoundError,
|
647
|
+
ArtifactExpiredError,
|
648
|
+
ProviderError,
|
649
|
+
ArtifactStoreError # NEW: for session security violations
|
650
|
+
)
|
651
|
+
|
652
|
+
try:
|
653
|
+
await store.copy_file(artifact_id, target_session_id="other_session")
|
654
|
+
except ArtifactStoreError as e:
|
655
|
+
print(f"Security violation: {e}")
|
656
|
+
except ArtifactNotFoundError:
|
657
|
+
print("Artifact not found or expired")
|
658
|
+
except ProviderError as e:
|
659
|
+
print(f"Storage provider error: {e}")
|
660
|
+
```
|
661
|
+
|
662
|
+
### Validation and Monitoring
|
663
|
+
```python
|
664
|
+
# Validate configuration
|
665
|
+
config_status = await store.validate_configuration()
|
666
|
+
print(f"Storage: {config_status['storage']['status']}")
|
667
|
+
print(f"Session: {config_status['session']['status']}")
|
668
|
+
|
669
|
+
# Get statistics
|
670
|
+
stats = await store.get_stats()
|
671
|
+
print(f"Provider: {stats['storage_provider']}")
|
672
|
+
print(f"Bucket: {stats['bucket']}")
|
673
|
+
```
|
674
|
+
|
490
675
|
## 🤝 Contributing
|
491
676
|
|
492
677
|
1. Fork the repository
|
493
678
|
2. Create a feature branch: `git checkout -b feature-name`
|
494
679
|
3. Make your changes
|
495
680
|
4. Run tests: `uv run examples/artifact_smoke_test.py`
|
496
|
-
5.
|
681
|
+
5. Test session operations: `uv run examples/session_operations_demo.py`
|
682
|
+
6. Submit a pull request
|
497
683
|
|
498
684
|
## 📄 License
|
499
685
|
|
@@ -507,6 +693,9 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
507
693
|
|
508
694
|
## 🎯 Roadmap
|
509
695
|
|
696
|
+
- [x] **Session-based security** with strict isolation
|
697
|
+
- [x] **Directory-like operations** with prefix filtering
|
698
|
+
- [x] **High-performance operations** (3,000+ ops/sec)
|
510
699
|
- [ ] Azure Blob Storage provider
|
511
700
|
- [ ] Google Cloud Storage provider
|
512
701
|
- [ ] Encryption at rest
|
@@ -517,3 +706,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
517
706
|
---
|
518
707
|
|
519
708
|
**Made with ❤️ by the Chuk team**
|
709
|
+
|
710
|
+
*Secure, fast, and reliable artifact storage for modern applications*
|
@@ -5,19 +5,20 @@ chuk_artifacts/batch.py,sha256=I5WgWajuzrvqTCkQYYgpzrL1WduzxJchA3Ihv3Xvhcw,4264
|
|
5
5
|
chuk_artifacts/config.py,sha256=MaUzHzKPoBUyERviEpv8JVvPybMzSksgLyj0b7AO3Sc,7664
|
6
6
|
chuk_artifacts/core.py,sha256=rS5VWjen6aRlnAIJIhLi639VfZ2u8ORaFxrPJMfpPYE,8296
|
7
7
|
chuk_artifacts/exceptions.py,sha256=f-s7Mg7c8vMXsbgqO2B6lMHdXcJQNvsESAY4GhJaV4g,814
|
8
|
-
chuk_artifacts/metadata.py,sha256=
|
8
|
+
chuk_artifacts/metadata.py,sha256=cBMMwCuyRtmJHjqHWuKMbK1G-kg_G4QpfqW-7RxhIzw,11858
|
9
9
|
chuk_artifacts/models.py,sha256=_foXlkr0DprqgztDw5WtlDc-s1OouLgYNp4XM1Ghp-g,837
|
10
10
|
chuk_artifacts/presigned.py,sha256=qonNg7WMd7VmOEXAzF6GssXuPs5be2s8IJhtuFul7JM,9638
|
11
11
|
chuk_artifacts/provider_factory.py,sha256=T0IXx1C8gygJzp417oB44_DxEaZoZR7jcdwQy8FghRE,3398
|
12
|
-
chuk_artifacts/
|
12
|
+
chuk_artifacts/session_operations.py,sha256=dolSkSnm4W_9ZElZJy25RmsKuLjUnnkfH8SvZWkbW5A,14232
|
13
|
+
chuk_artifacts/store.py,sha256=0RKSAkSfTM1v-qg3ERv3-8VaRiBVMxdGy2qxjFVz9MY,19749
|
13
14
|
chuk_artifacts/providers/__init__.py,sha256=3lN1lAy1ETT1mQslJo1f22PPR1W4CyxmsqJBclzH4NE,317
|
14
15
|
chuk_artifacts/providers/filesystem.py,sha256=F4EjE-_ItPg0RWe7CqameVpOMjU-b7AigEBkm_ZoNrc,15280
|
15
16
|
chuk_artifacts/providers/ibm_cos.py,sha256=K1-VAX4UVV9tA161MOeDXOKloQ0hB77jdw1-p46FwmU,4445
|
16
17
|
chuk_artifacts/providers/ibm_cos_iam.py,sha256=VtwvCi9rMMcZx6i9l21ob6wM8jXseqvjzgCnAA82RkY,3186
|
17
18
|
chuk_artifacts/providers/memory.py,sha256=B1C-tR1PcNz-UuDfGm1bhjPz3oITVATIMPekVbE7nm4,10487
|
18
19
|
chuk_artifacts/providers/s3.py,sha256=eWhBhFSaobpRbazn7ySfU_7D8rm_xCfdSVqRtzXzXRY,2858
|
19
|
-
chuk_artifacts-0.1.
|
20
|
-
chuk_artifacts-0.1.
|
21
|
-
chuk_artifacts-0.1.
|
22
|
-
chuk_artifacts-0.1.
|
23
|
-
chuk_artifacts-0.1.
|
20
|
+
chuk_artifacts-0.1.2.dist-info/licenses/LICENSE,sha256=SG9BmgtPBagPV0d-Fep-msdAGl-E1CeoBL7-EDRH2qA,1066
|
21
|
+
chuk_artifacts-0.1.2.dist-info/METADATA,sha256=RYa5j8aK37uuKQeqSp9OKpP1KGU4PoJXLUStrwq_pZY,20975
|
22
|
+
chuk_artifacts-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
+
chuk_artifacts-0.1.2.dist-info/top_level.txt,sha256=1_PVMtWXR0A-ZmeH6apF9mPaMtU0i23JE6wmN4GBRDI,15
|
24
|
+
chuk_artifacts-0.1.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|