0
# Storage and Resumability
1
2
URL storage interfaces and implementations for resuming uploads across sessions. TusPy provides a pluggable storage system that allows uploads to be resumed even after application restarts.
3
4
## Capabilities
5
6
### Storage Interface
7
8
Abstract base class defining the storage API for URL persistence.
9
10
```python { .api }
11
from abc import ABC, abstractmethod
12
13
class Storage(ABC):
14
"""Abstract base class for URL storage implementations."""
15
16
@abstractmethod
17
def get_item(self, key: str) -> Optional[str]:
18
"""
19
Return the tus url of a file, identified by the key specified.
20
21
Parameters:
22
- key (str): The unique id for the stored item (upload URL)
23
24
Returns:
25
Optional[str]: The stored URL or None if not found
26
"""
27
28
@abstractmethod
29
def set_item(self, key: str, value: str):
30
"""
31
Store the url value under the unique key.
32
33
Parameters:
34
- key (str): The unique id to store the URL under
35
- value (str): The actual URL value to be stored
36
"""
37
38
@abstractmethod
39
def remove_item(self, key: str):
40
"""
41
Remove/Delete the url value under the unique key from storage.
42
43
Parameters:
44
- key (str): The unique id of the item to remove
45
"""
46
```
47
48
### File Storage Implementation
49
50
File-based storage implementation using TinyDB for URL persistence.
51
52
```python { .api }
53
class FileStorage(Storage):
54
"""File-based implementation of Storage interface using TinyDB."""
55
56
def __init__(self, fp: str):
57
"""
58
Initialize FileStorage with database file path.
59
60
Parameters:
61
- fp (str): File path for the TinyDB database
62
"""
63
64
def get_item(self, key: str) -> Optional[str]:
65
"""
66
Return the tus url of a file, identified by the key specified.
67
68
Parameters:
69
- key (str): The unique id for the stored item
70
71
Returns:
72
Optional[str]: The stored URL or None if not found
73
"""
74
75
def set_item(self, key: str, url: str):
76
"""
77
Store the url value under the unique key.
78
79
Updates existing entry if key already exists, otherwise creates new entry.
80
81
Parameters:
82
- key (str): The unique id to store the URL under
83
- url (str): The actual URL value to be stored
84
"""
85
86
def remove_item(self, key: str):
87
"""
88
Remove/Delete the url value under the unique key from storage.
89
90
Parameters:
91
- key (str): The unique id of the item to remove
92
"""
93
94
def close(self):
95
"""
96
Close the file storage and release all opened files.
97
98
Should be called when done with storage to ensure proper cleanup.
99
"""
100
```
101
102
### Fingerprint Interface
103
104
Abstract base class for generating unique file fingerprints used as storage keys.
105
106
```python { .api }
107
from abc import ABC, abstractmethod
108
from typing import IO
109
110
class Fingerprint(ABC):
111
"""An interface specifying the requirements of a file fingerprint."""
112
113
@abstractmethod
114
def get_fingerprint(self, fs: IO) -> str:
115
"""
116
Return a unique fingerprint string value based on the file stream received.
117
118
Parameters:
119
- fs (IO): The file stream instance to generate fingerprint for
120
121
Returns:
122
str: Unique fingerprint string identifying the file
123
"""
124
```
125
126
### MD5 Fingerprint Implementation
127
128
MD5-based fingerprint implementation using file content and size.
129
130
```python { .api }
131
class Fingerprint(Fingerprint):
132
"""MD5-based fingerprint implementation using file content and size."""
133
134
BLOCK_SIZE = 65536 # Block size for reading file content
135
136
def get_fingerprint(self, fs: IO) -> str:
137
"""
138
Return a unique fingerprint string based on MD5 hash and file size.
139
140
Generates fingerprint using MD5 hash of first block and file size
141
to minimize collision chances while being efficient.
142
143
Parameters:
144
- fs (IO): The file stream instance to generate fingerprint for
145
146
Returns:
147
str: Fingerprint in format "size:{size}--md5:{hash}"
148
"""
149
```
150
151
## Usage Examples
152
153
### Basic Resumable Upload
154
155
```python
156
from tusclient import client
157
from tusclient.storage.filestorage import FileStorage
158
159
# Create file storage for URL persistence
160
storage = FileStorage('/tmp/tuspy_uploads.db')
161
162
# Create client and uploader with resumability enabled
163
my_client = client.TusClient('http://tusd.tusdemo.net/files/')
164
uploader = my_client.uploader(
165
'/path/to/large_file.ext',
166
chunk_size=5*1024*1024, # 5MB chunks
167
store_url=True, # Enable URL storage
168
url_storage=storage # Provide storage implementation
169
)
170
171
try:
172
# Upload will be resumable if interrupted
173
uploader.upload()
174
print("Upload completed successfully")
175
except KeyboardInterrupt:
176
print("Upload interrupted - can be resumed later")
177
finally:
178
storage.close()
179
```
180
181
### Resuming Interrupted Upload
182
183
```python
184
from tusclient import client
185
from tusclient.storage.filestorage import FileStorage
186
187
# Use same storage file as previous upload
188
storage = FileStorage('/tmp/tuspy_uploads.db')
189
190
# Create uploader for same file - will automatically resume
191
my_client = client.TusClient('http://tusd.tusdemo.net/files/')
192
uploader = my_client.uploader(
193
'/path/to/large_file.ext', # Same file path as before
194
chunk_size=5*1024*1024,
195
store_url=True,
196
url_storage=storage
197
)
198
199
# Check if resuming from previous upload
200
if uploader.offset > 0:
201
print(f"Resuming upload from offset {uploader.offset}")
202
progress = (uploader.offset / uploader.get_file_size()) * 100
203
print(f"Already uploaded: {progress:.1f}%")
204
205
try:
206
uploader.upload()
207
print("Upload completed")
208
finally:
209
storage.close()
210
```
211
212
### Custom Storage Implementation
213
214
```python
215
from tusclient.storage.interface import Storage
216
import redis
217
218
class RedisStorage(Storage):
219
"""Redis-based storage implementation."""
220
221
def __init__(self, redis_client):
222
self.redis = redis_client
223
224
def get_item(self, key: str) -> Optional[str]:
225
result = self.redis.get(f"tuspy:{key}")
226
return result.decode('utf-8') if result else None
227
228
def set_item(self, key: str, value: str):
229
self.redis.set(f"tuspy:{key}", value)
230
231
def remove_item(self, key: str):
232
self.redis.delete(f"tuspy:{key}")
233
234
# Use custom storage
235
redis_client = redis.Redis(host='localhost', port=6379, db=0)
236
storage = RedisStorage(redis_client)
237
238
my_client = client.TusClient('http://tusd.tusdemo.net/files/')
239
uploader = my_client.uploader(
240
'/path/to/file.ext',
241
store_url=True,
242
url_storage=storage
243
)
244
245
uploader.upload()
246
```
247
248
### Custom Fingerprint Implementation
249
250
```python
251
from tusclient.fingerprint.interface import Fingerprint
252
import hashlib
253
from typing import IO
254
255
class SHA256Fingerprint(Fingerprint):
256
"""SHA256-based fingerprint implementation."""
257
258
def get_fingerprint(self, fs: IO) -> str:
259
# Read entire small files, first 64KB of large files
260
fs.seek(0)
261
content = fs.read(65536)
262
263
# Get file size
264
fs.seek(0, 2) # Seek to end
265
size = fs.tell()
266
267
# Generate SHA256 hash
268
hasher = hashlib.sha256()
269
hasher.update(content if isinstance(content, bytes) else content.encode('utf-8'))
270
271
return f"size:{size}--sha256:{hasher.hexdigest()}"
272
273
# Use custom fingerprint
274
from tusclient.storage.filestorage import FileStorage
275
276
storage = FileStorage('/tmp/tuspy_uploads.db')
277
custom_fingerprint = SHA256Fingerprint()
278
279
my_client = client.TusClient('http://tusd.tusdemo.net/files/')
280
uploader = my_client.uploader(
281
'/path/to/file.ext',
282
store_url=True,
283
url_storage=storage,
284
fingerprinter=custom_fingerprint
285
)
286
287
uploader.upload()
288
storage.close()
289
```
290
291
### Managing Stored URLs
292
293
```python
294
from tusclient.storage.filestorage import FileStorage
295
from tusclient.fingerprint.fingerprint import Fingerprint
296
297
# Create storage and fingerprint instances
298
storage = FileStorage('/tmp/tuspy_uploads.db')
299
fingerprinter = Fingerprint()
300
301
# Generate fingerprint for a file
302
with open('/path/to/file.ext', 'rb') as fs:
303
key = fingerprinter.get_fingerprint(fs)
304
305
# Check if URL is stored
306
stored_url = storage.get_item(key)
307
if stored_url:
308
print(f"Found stored URL: {stored_url}")
309
else:
310
print("No stored URL found for this file")
311
312
# Manually remove stored URL (e.g., after successful upload)
313
storage.remove_item(key)
314
315
storage.close()
316
```