0
# Storage Backends
1
2
Pluggable storage system for persisting translation work-in-progress data, supporting session-based, cache-based, or custom storage implementations. Storage backends handle temporary data during translation sessions, including unsaved changes, filters, and pagination state.
3
4
## Capabilities
5
6
### Base Storage Interface
7
8
Abstract base class defining the storage backend interface that all implementations must follow.
9
10
```python { .api }
11
class BaseRosettaStorage:
12
"""
13
Abstract base class for storage backends.
14
15
Defines the interface that all Rosetta storage backends must implement
16
for persisting translation session data.
17
"""
18
19
def __init__(self, request):
20
"""
21
Initialize storage backend with request context.
22
23
Parameters:
24
- request: Django HttpRequest instance
25
"""
26
27
def set(self, key: str, value) -> None:
28
"""
29
Store a value with the given key.
30
31
Parameters:
32
- key: Storage key string
33
- value: Value to store (must be serializable)
34
"""
35
raise NotImplementedError
36
37
def get(self, key: str, default=None):
38
"""
39
Retrieve a value by key.
40
41
Parameters:
42
- key: Storage key string
43
- default: Default value if key not found
44
45
Returns:
46
Stored value or default
47
"""
48
raise NotImplementedError
49
50
def has(self, key: str) -> bool:
51
"""
52
Check if key exists in storage.
53
54
Parameters:
55
- key: Storage key string
56
57
Returns:
58
Boolean indicating key existence
59
"""
60
raise NotImplementedError
61
62
def delete(self, key: str) -> None:
63
"""
64
Delete a key from storage.
65
66
Parameters:
67
- key: Storage key string to delete
68
"""
69
raise NotImplementedError
70
```
71
72
### Session Storage Backend
73
74
Session-based storage implementation using Django's session framework.
75
76
```python { .api }
77
class SessionRosettaStorage(BaseRosettaStorage):
78
"""
79
Session-based storage backend.
80
81
Stores translation data in Django sessions, persisting across
82
browser sessions but limited to single user/browser. Good for
83
development and single-user deployments.
84
"""
85
86
def __init__(self, request):
87
"""Initialize with request session."""
88
self.request = request
89
90
def set(self, key: str, value) -> None:
91
"""Store value in session."""
92
93
def get(self, key: str, default=None):
94
"""Retrieve value from session."""
95
96
def has(self, key: str) -> bool:
97
"""Check if key exists in session."""
98
99
def delete(self, key: str) -> None:
100
"""Delete key from session."""
101
```
102
103
### Cache Storage Backend
104
105
Cache-based storage implementation using Django's caching framework (default).
106
107
```python { .api }
108
class CacheRosettaStorage(BaseRosettaStorage):
109
"""
110
Cache-based storage backend (default).
111
112
Uses Django's caching framework for storage, supporting multiple
113
cache backends (Redis, Memcached, database, etc.). Recommended
114
for production deployments with multiple users.
115
"""
116
117
def __init__(self, request):
118
"""Initialize with cache instance."""
119
self.request = request
120
self.cache = caches[rosetta_settings.CACHE_NAME]
121
122
def set(self, key: str, value) -> None:
123
"""Store value in cache."""
124
125
def get(self, key: str, default=None):
126
"""Retrieve value from cache."""
127
128
def has(self, key: str) -> bool:
129
"""Check if key exists in cache."""
130
131
def delete(self, key: str) -> None:
132
"""Delete key from cache."""
133
```
134
135
### Dummy Storage Backend
136
137
No-operation storage implementation for testing or minimal setups.
138
139
```python { .api }
140
class DummyRosettaStorage(BaseRosettaStorage):
141
"""
142
No-operation storage backend.
143
144
Provides storage interface but doesn't actually persist data.
145
Useful for testing or environments where persistence isn't needed.
146
"""
147
148
def set(self, key: str, value) -> None:
149
"""No-op set operation."""
150
pass
151
152
def get(self, key: str, default=None):
153
"""Always returns default value."""
154
return default
155
156
def has(self, key: str) -> bool:
157
"""Always returns False."""
158
return False
159
160
def delete(self, key: str) -> None:
161
"""No-op delete operation."""
162
pass
163
```
164
165
### Storage Factory
166
167
Factory function for creating storage backend instances.
168
169
```python { .api }
170
def get_storage(request) -> BaseRosettaStorage:
171
"""
172
Get configured storage instance for request.
173
174
Creates and returns storage backend instance based on
175
ROSETTA_STORAGE_CLASS setting.
176
177
Parameters:
178
- request: Django HttpRequest instance
179
180
Returns:
181
Storage backend instance implementing BaseRosettaStorage
182
183
Raises:
184
ImportError: If configured storage class cannot be imported
185
"""
186
187
# Cache instance for storage operations
188
cache = caches[rosetta_settings.CACHE_NAME]
189
"""Django cache instance used by cache storage backend."""
190
```
191
192
## Configuration
193
194
Storage backends are configured through Django settings:
195
196
```python { .api }
197
ROSETTA_STORAGE_CLASS: str = 'rosetta.storage.CacheRosettaStorage'
198
"""
199
Full Python path to storage backend class.
200
Available options:
201
- 'rosetta.storage.CacheRosettaStorage' (default, recommended)
202
- 'rosetta.storage.SessionRosettaStorage'
203
- 'rosetta.storage.DummyRosettaStorage'
204
- Custom implementation path
205
"""
206
207
ROSETTA_CACHE_NAME: str = 'default'
208
"""
209
Name of Django cache backend to use with CacheRosettaStorage.
210
Must correspond to a cache defined in CACHES setting.
211
"""
212
```
213
214
## Usage Examples
215
216
### Basic Storage Configuration
217
218
```python
219
# settings.py - Use default cache storage (recommended)
220
ROSETTA_STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'
221
ROSETTA_CACHE_NAME = 'default'
222
223
# Ensure you have a cache configured
224
CACHES = {
225
'default': {
226
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
227
'LOCATION': 'redis://127.0.0.1:6379/1',
228
}
229
}
230
```
231
232
### Session Storage Configuration
233
234
```python
235
# settings.py - Use session storage for development
236
ROSETTA_STORAGE_CLASS = 'rosetta.storage.SessionRosettaStorage'
237
238
# Ensure sessions are properly configured
239
MIDDLEWARE = [
240
'django.contrib.sessions.middleware.SessionMiddleware',
241
# ... other middleware
242
]
243
244
INSTALLED_APPS = [
245
'django.contrib.sessions',
246
# ... other apps
247
]
248
```
249
250
### Custom Cache Backend
251
252
```python
253
# settings.py - Use dedicated cache for Rosetta
254
CACHES = {
255
'default': {
256
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
257
'LOCATION': 'redis://127.0.0.1:6379/1',
258
},
259
'rosetta': {
260
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
261
'LOCATION': 'redis://127.0.0.1:6379/2',
262
'TIMEOUT': 3600, # 1 hour timeout for translation sessions
263
}
264
}
265
266
ROSETTA_STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'
267
ROSETTA_CACHE_NAME = 'rosetta'
268
```
269
270
### Custom Storage Backend Implementation
271
272
```python
273
# myapp/storage.py - Custom storage backend
274
from rosetta.storage import BaseRosettaStorage
275
import json
276
import os
277
278
class FileRosettaStorage(BaseRosettaStorage):
279
"""File-based storage backend for Rosetta."""
280
281
def __init__(self, request):
282
self.request = request
283
self.user_id = request.user.id if request.user.is_authenticated else 'anonymous'
284
self.storage_dir = f'/tmp/rosetta_storage/{self.user_id}'
285
os.makedirs(self.storage_dir, exist_ok=True)
286
287
def _get_file_path(self, key):
288
return os.path.join(self.storage_dir, f'{key}.json')
289
290
def set(self, key, value):
291
file_path = self._get_file_path(key)
292
with open(file_path, 'w') as f:
293
json.dump(value, f)
294
295
def get(self, key, default=None):
296
file_path = self._get_file_path(key)
297
try:
298
with open(file_path, 'r') as f:
299
return json.load(f)
300
except (FileNotFoundError, json.JSONDecodeError):
301
return default
302
303
def has(self, key):
304
return os.path.exists(self._get_file_path(key))
305
306
def delete(self, key):
307
file_path = self._get_file_path(key)
308
try:
309
os.remove(file_path)
310
except FileNotFoundError:
311
pass
312
313
# settings.py
314
ROSETTA_STORAGE_CLASS = 'myapp.storage.FileRosettaStorage'
315
```
316
317
### Programmatic Storage Usage
318
319
```python
320
from rosetta.storage import get_storage
321
322
def my_view(request):
323
"""Example of programmatic storage usage."""
324
325
# Get storage instance for request
326
storage = get_storage(request)
327
328
# Store translation session data
329
storage.set('current_filter', {
330
'language': 'fr',
331
'status': 'untranslated'
332
})
333
334
# Retrieve stored data
335
filter_data = storage.get('current_filter', {})
336
337
# Check if key exists
338
if storage.has('user_preferences'):
339
preferences = storage.get('user_preferences')
340
341
# Delete temporary data
342
storage.delete('temp_data')
343
344
return render(request, 'template.html')
345
```
346
347
### Database Storage Backend
348
349
```python
350
# myapp/storage.py - Database-backed storage
351
from rosetta.storage import BaseRosettaStorage
352
from django.core.cache import cache
353
from myapp.models import RosettaSessionData
354
355
class DatabaseRosettaStorage(BaseRosettaStorage):
356
"""Database-backed storage for persistent sessions."""
357
358
def __init__(self, request):
359
self.request = request
360
self.user_id = request.user.id if request.user.is_authenticated else None
361
self.session_key = request.session.session_key
362
363
def _get_session_data(self):
364
"""Get or create session data object."""
365
if self.user_id:
366
obj, created = RosettaSessionData.objects.get_or_create(
367
user_id=self.user_id,
368
defaults={'data': {}}
369
)
370
else:
371
obj, created = RosettaSessionData.objects.get_or_create(
372
session_key=self.session_key,
373
defaults={'data': {}}
374
)
375
return obj
376
377
def set(self, key, value):
378
obj = self._get_session_data()
379
obj.data[key] = value
380
obj.save()
381
382
def get(self, key, default=None):
383
obj = self._get_session_data()
384
return obj.data.get(key, default)
385
386
def has(self, key):
387
obj = self._get_session_data()
388
return key in obj.data
389
390
def delete(self, key):
391
obj = self._get_session_data()
392
obj.data.pop(key, None)
393
obj.save()
394
395
# myapp/models.py
396
from django.db import models
397
from django.contrib.auth.models import User
398
399
class RosettaSessionData(models.Model):
400
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
401
session_key = models.CharField(max_length=40, null=True, blank=True)
402
data = models.JSONField(default=dict)
403
created_at = models.DateTimeField(auto_now_add=True)
404
updated_at = models.DateTimeField(auto_now=True)
405
406
class Meta:
407
unique_together = [['user'], ['session_key']]
408
```
409
410
### Testing Storage Backends
411
412
```python
413
# tests.py - Testing storage implementations
414
from django.test import TestCase, RequestFactory
415
from django.contrib.auth.models import User
416
from rosetta.storage import get_storage, CacheRosettaStorage, SessionRosettaStorage
417
418
class StorageBackendTests(TestCase):
419
def setUp(self):
420
self.factory = RequestFactory()
421
self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
422
423
def test_cache_storage(self):
424
"""Test cache storage backend."""
425
request = self.factory.get('/')
426
request.user = self.user
427
428
storage = CacheRosettaStorage(request)
429
430
# Test set/get
431
storage.set('test_key', 'test_value')
432
self.assertEqual(storage.get('test_key'), 'test_value')
433
434
# Test has
435
self.assertTrue(storage.has('test_key'))
436
self.assertFalse(storage.has('nonexistent_key'))
437
438
# Test delete
439
storage.delete('test_key')
440
self.assertFalse(storage.has('test_key'))
441
442
def test_session_storage(self):
443
"""Test session storage backend."""
444
request = self.factory.get('/')
445
request.user = self.user
446
request.session = {}
447
448
storage = SessionRosettaStorage(request)
449
450
# Test operations
451
storage.set('session_key', {'data': 'value'})
452
self.assertEqual(storage.get('session_key'), {'data': 'value'})
453
454
# Test default value
455
self.assertEqual(storage.get('missing_key', 'default'), 'default')
456
```