cc12703-diskcache 5.7.3__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.
diskcache/core.py ADDED
@@ -0,0 +1,2541 @@
1
+ """Core disk and file backed cache API.
2
+ """
3
+
4
+ import codecs
5
+ import contextlib as cl
6
+ import errno
7
+ import functools as ft
8
+ import io
9
+ import json
10
+ import os
11
+ import os.path as op
12
+ import pickle
13
+ import pickletools
14
+ import sqlite3
15
+ import struct
16
+ import tempfile
17
+ import threading
18
+ import time
19
+ import warnings
20
+ import zlib
21
+
22
+
23
+ def full_name(func):
24
+ """Return full name of `func` by adding the module and function name."""
25
+ return func.__module__ + '.' + func.__qualname__
26
+
27
+
28
+ class Constant(tuple):
29
+ """Pretty display of immutable constant."""
30
+
31
+ def __new__(cls, name):
32
+ return tuple.__new__(cls, (name,))
33
+
34
+ def __repr__(self):
35
+ return '%s' % self[0]
36
+
37
+
38
+ DBNAME = 'cache.db'
39
+ ENOVAL = Constant('ENOVAL')
40
+ UNKNOWN = Constant('UNKNOWN')
41
+
42
+ MODE_NONE = 0
43
+ MODE_RAW = 1
44
+ MODE_BINARY = 2
45
+ MODE_TEXT = 3
46
+ MODE_PICKLE = 4
47
+
48
+ DEFAULT_SETTINGS = {
49
+ 'statistics': 0, # False
50
+ 'tag_index': 0, # False
51
+ 'eviction_policy': 'least-recently-stored',
52
+ 'size_limit': 2**30, # 1gb
53
+ 'cull_limit': 10,
54
+ 'sqlite_auto_vacuum': 1, # FULL
55
+ 'sqlite_cache_size': 2**13, # 8,192 pages
56
+ 'sqlite_journal_mode': 'wal',
57
+ 'sqlite_mmap_size': 2**26, # 64mb
58
+ 'sqlite_synchronous': 1, # NORMAL
59
+ 'disk_min_file_size': 2**15, # 32kb
60
+ 'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL,
61
+ }
62
+
63
+ METADATA = {
64
+ 'count': 0,
65
+ 'size': 0,
66
+ 'hits': 0,
67
+ 'misses': 0,
68
+ }
69
+
70
+ EVICTION_POLICY = {
71
+ 'none': {
72
+ 'init': None,
73
+ 'get': None,
74
+ 'cull': None,
75
+ },
76
+ 'least-recently-stored': {
77
+ 'init': (
78
+ 'CREATE INDEX IF NOT EXISTS Cache_store_time ON'
79
+ ' Cache (store_time)'
80
+ ),
81
+ 'get': None,
82
+ 'cull': 'SELECT {fields} FROM Cache ORDER BY store_time LIMIT ?',
83
+ },
84
+ 'least-recently-used': {
85
+ 'init': (
86
+ 'CREATE INDEX IF NOT EXISTS Cache_access_time ON'
87
+ ' Cache (access_time)'
88
+ ),
89
+ 'get': 'access_time = {now}',
90
+ 'cull': 'SELECT {fields} FROM Cache ORDER BY access_time LIMIT ?',
91
+ },
92
+ 'least-frequently-used': {
93
+ 'init': (
94
+ 'CREATE INDEX IF NOT EXISTS Cache_access_count ON'
95
+ ' Cache (access_count)'
96
+ ),
97
+ 'get': 'access_count = access_count + 1',
98
+ 'cull': 'SELECT {fields} FROM Cache ORDER BY access_count LIMIT ?',
99
+ },
100
+ }
101
+
102
+
103
+ def _prefix_bounds(prefix):
104
+ """Return ``(lower, upper_or_None)`` byte/char range for prefix filtering.
105
+
106
+ ``lower`` is always ``prefix`` itself. ``upper`` is the smallest successor
107
+ string/bytes such that all keys starting with ``prefix`` fall in
108
+ ``[lower, upper)``. Returns ``None`` for ``upper`` when ``prefix`` has no
109
+ finite successor (trailing ``0xFF`` bytes or ``0x10FFFF`` code points),
110
+ in which case callers omit the upper-bound clause.
111
+
112
+ """
113
+ if isinstance(prefix, str):
114
+ idx = len(prefix) - 1
115
+ while idx >= 0 and ord(prefix[idx]) == 0x10FFFF:
116
+ idx -= 1
117
+ if idx < 0:
118
+ return prefix, None
119
+ upper = prefix[:idx] + chr(ord(prefix[idx]) + 1)
120
+ elif isinstance(prefix, (bytes, bytearray)):
121
+ seq = bytes(prefix)
122
+ idx = len(seq) - 1
123
+ while idx >= 0 and seq[idx] == 0xFF:
124
+ idx -= 1
125
+ if idx < 0:
126
+ return seq, None
127
+ upper = seq[:idx] + bytes([seq[idx] + 1])
128
+ else:
129
+ raise TypeError(
130
+ 'prefix must be str or bytes, not %s' % type(prefix).__name__
131
+ )
132
+ return prefix, upper
133
+
134
+
135
+ class Disk:
136
+ """Cache key and value serialization for SQLite database and files."""
137
+
138
+ def __init__(self, directory, min_file_size=0, pickle_protocol=0):
139
+ """Initialize disk instance.
140
+
141
+ :param str directory: directory path
142
+ :param int min_file_size: minimum size for file use
143
+ :param int pickle_protocol: pickle protocol for serialization
144
+
145
+ """
146
+ self._directory = directory
147
+ self.min_file_size = min_file_size
148
+ self.pickle_protocol = pickle_protocol
149
+
150
+ def hash(self, key):
151
+ """Compute portable hash for `key`.
152
+
153
+ :param key: key to hash
154
+ :return: hash value
155
+
156
+ """
157
+ mask = 0xFFFFFFFF
158
+ disk_key, _ = self.put(key)
159
+ type_disk_key = type(disk_key)
160
+
161
+ if type_disk_key is sqlite3.Binary:
162
+ return zlib.adler32(disk_key) & mask
163
+ elif type_disk_key is str:
164
+ return zlib.adler32(disk_key.encode('utf-8')) & mask # noqa
165
+ elif type_disk_key is int:
166
+ return disk_key % mask
167
+ else:
168
+ assert type_disk_key is float
169
+ return zlib.adler32(struct.pack('!d', disk_key)) & mask
170
+
171
+ def put(self, key):
172
+ """Convert `key` to fields key and raw for Cache table.
173
+
174
+ :param key: key to convert
175
+ :return: (database key, raw boolean) pair
176
+
177
+ """
178
+ # pylint: disable=unidiomatic-typecheck
179
+ type_key = type(key)
180
+
181
+ if type_key is bytes:
182
+ return sqlite3.Binary(key), True
183
+ elif (
184
+ (type_key is str)
185
+ or (
186
+ type_key is int
187
+ and -9223372036854775808 <= key <= 9223372036854775807
188
+ )
189
+ or (type_key is float)
190
+ ):
191
+ return key, True
192
+ else:
193
+ data = pickle.dumps(key, protocol=self.pickle_protocol)
194
+ result = pickletools.optimize(data)
195
+ return sqlite3.Binary(result), False
196
+
197
+ def get(self, key, raw):
198
+ """Convert fields `key` and `raw` from Cache table to key.
199
+
200
+ :param key: database key to convert
201
+ :param bool raw: flag indicating raw database storage
202
+ :return: corresponding Python key
203
+
204
+ """
205
+ # pylint: disable=unidiomatic-typecheck
206
+ if raw:
207
+ return bytes(key) if type(key) is sqlite3.Binary else key
208
+ else:
209
+ return pickle.load(io.BytesIO(key))
210
+
211
+ def store(self, value, read, key=UNKNOWN):
212
+ """Convert `value` to fields size, mode, filename, and value for Cache
213
+ table.
214
+
215
+ :param value: value to convert
216
+ :param bool read: True when value is file-like object
217
+ :param key: key for item (default UNKNOWN)
218
+ :return: (size, mode, filename, value) tuple for Cache table
219
+
220
+ """
221
+ # pylint: disable=unidiomatic-typecheck
222
+ type_value = type(value)
223
+ min_file_size = self.min_file_size
224
+
225
+ if (
226
+ (type_value is str and len(value) < min_file_size)
227
+ or (
228
+ type_value is int
229
+ and -9223372036854775808 <= value <= 9223372036854775807
230
+ )
231
+ or (type_value is float)
232
+ ):
233
+ return 0, MODE_RAW, None, value
234
+ elif type_value is bytes:
235
+ if len(value) < min_file_size:
236
+ return 0, MODE_RAW, None, sqlite3.Binary(value)
237
+ else:
238
+ filename, full_path = self.filename(key, value)
239
+ self._write(full_path, io.BytesIO(value), 'xb')
240
+ return len(value), MODE_BINARY, filename, None
241
+ elif type_value is str:
242
+ filename, full_path = self.filename(key, value)
243
+ self._write(full_path, io.StringIO(value), 'x', 'UTF-8')
244
+ size = op.getsize(full_path)
245
+ return size, MODE_TEXT, filename, None
246
+ elif read:
247
+ reader = ft.partial(value.read, 2**22)
248
+ filename, full_path = self.filename(key, value)
249
+ iterator = iter(reader, b'')
250
+ size = self._write(full_path, iterator, 'xb')
251
+ return size, MODE_BINARY, filename, None
252
+ else:
253
+ result = pickle.dumps(value, protocol=self.pickle_protocol)
254
+
255
+ if len(result) < min_file_size:
256
+ return 0, MODE_PICKLE, None, sqlite3.Binary(result)
257
+ else:
258
+ filename, full_path = self.filename(key, value)
259
+ self._write(full_path, io.BytesIO(result), 'xb')
260
+ return len(result), MODE_PICKLE, filename, None
261
+
262
+ def _write(self, full_path, iterator, mode, encoding=None):
263
+ full_dir, _ = op.split(full_path)
264
+
265
+ for count in range(1, 11):
266
+ with cl.suppress(OSError):
267
+ os.makedirs(full_dir)
268
+
269
+ try:
270
+ # Another cache may have deleted the directory before
271
+ # the file could be opened.
272
+ writer = open(full_path, mode, encoding=encoding)
273
+ except OSError:
274
+ if count == 10:
275
+ # Give up after 10 tries to open the file.
276
+ raise
277
+ continue
278
+
279
+ with writer:
280
+ size = 0
281
+ for chunk in iterator:
282
+ size += len(chunk)
283
+ writer.write(chunk)
284
+ return size
285
+
286
+ def fetch(self, mode, filename, value, read):
287
+ """Convert fields `mode`, `filename`, and `value` from Cache table to
288
+ value.
289
+
290
+ :param int mode: value mode raw, binary, text, or pickle
291
+ :param str filename: filename of corresponding value
292
+ :param value: database value
293
+ :param bool read: when True, return an open file handle
294
+ :return: corresponding Python value
295
+ :raises: IOError if the value cannot be read
296
+
297
+ """
298
+ # pylint: disable=unidiomatic-typecheck,consider-using-with
299
+ if mode == MODE_RAW:
300
+ return bytes(value) if type(value) is sqlite3.Binary else value
301
+ elif mode == MODE_BINARY:
302
+ if read:
303
+ return open(op.join(self._directory, filename), 'rb')
304
+ else:
305
+ with open(op.join(self._directory, filename), 'rb') as reader:
306
+ return reader.read()
307
+ elif mode == MODE_TEXT:
308
+ full_path = op.join(self._directory, filename)
309
+ with open(full_path, 'r', encoding='UTF-8') as reader:
310
+ return reader.read()
311
+ elif mode == MODE_PICKLE:
312
+ if value is None:
313
+ with open(op.join(self._directory, filename), 'rb') as reader:
314
+ return pickle.load(reader)
315
+ else:
316
+ return pickle.load(io.BytesIO(value))
317
+
318
+ def filename(self, key=UNKNOWN, value=UNKNOWN):
319
+ """Return filename and full-path tuple for file storage.
320
+
321
+ Filename will be a randomly generated 28 character hexadecimal string
322
+ with ".val" suffixed. Two levels of sub-directories will be used to
323
+ reduce the size of directories. On older filesystems, lookups in
324
+ directories with many files may be slow.
325
+
326
+ The default implementation ignores the `key` and `value` parameters.
327
+
328
+ In some scenarios, for example :meth:`Cache.push
329
+ <diskcache.Cache.push>`, the `key` or `value` may not be known when the
330
+ item is stored in the cache.
331
+
332
+ :param key: key for item (default UNKNOWN)
333
+ :param value: value for item (default UNKNOWN)
334
+
335
+ """
336
+ # pylint: disable=unused-argument
337
+ hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8')
338
+ sub_dir = op.join(hex_name[:2], hex_name[2:4])
339
+ name = hex_name[4:] + '.val'
340
+ filename = op.join(sub_dir, name)
341
+ full_path = op.join(self._directory, filename)
342
+ return filename, full_path
343
+
344
+ def remove(self, file_path):
345
+ """Remove a file given by `file_path`.
346
+
347
+ This method is cross-thread and cross-process safe. If an OSError
348
+ occurs, it is suppressed.
349
+
350
+ :param str file_path: relative path to file
351
+
352
+ """
353
+ full_path = op.join(self._directory, file_path)
354
+ full_dir, _ = op.split(full_path)
355
+
356
+ # Suppress OSError that may occur if two caches attempt to delete the
357
+ # same file or directory at the same time.
358
+
359
+ with cl.suppress(OSError):
360
+ os.remove(full_path)
361
+
362
+ with cl.suppress(OSError):
363
+ os.removedirs(full_dir)
364
+
365
+
366
+ class JSONDisk(Disk):
367
+ """Cache key and value using JSON serialization with zlib compression."""
368
+
369
+ def __init__(self, directory, compress_level=1, **kwargs):
370
+ """Initialize JSON disk instance.
371
+
372
+ Keys and values are compressed using the zlib library. The
373
+ `compress_level` is an integer from 0 to 9 controlling the level of
374
+ compression; 1 is fastest and produces the least compression, 9 is
375
+ slowest and produces the most compression, and 0 is no compression.
376
+
377
+ :param str directory: directory path
378
+ :param int compress_level: zlib compression level (default 1)
379
+ :param kwargs: super class arguments
380
+
381
+ """
382
+ self.compress_level = compress_level
383
+ super().__init__(directory, **kwargs)
384
+
385
+ def put(self, key):
386
+ json_bytes = json.dumps(key).encode('utf-8')
387
+ data = zlib.compress(json_bytes, self.compress_level)
388
+ return super().put(data)
389
+
390
+ def get(self, key, raw):
391
+ data = super().get(key, raw)
392
+ return json.loads(zlib.decompress(data).decode('utf-8'))
393
+
394
+ def store(self, value, read, key=UNKNOWN):
395
+ if not read:
396
+ json_bytes = json.dumps(value).encode('utf-8')
397
+ value = zlib.compress(json_bytes, self.compress_level)
398
+ return super().store(value, read, key=key)
399
+
400
+ def fetch(self, mode, filename, value, read):
401
+ data = super().fetch(mode, filename, value, read)
402
+ if not read:
403
+ data = json.loads(zlib.decompress(data).decode('utf-8'))
404
+ return data
405
+
406
+
407
+ class Timeout(Exception):
408
+ """Database timeout expired."""
409
+
410
+
411
+ class UnknownFileWarning(UserWarning):
412
+ """Warning used by Cache.check for unknown files."""
413
+
414
+
415
+ class EmptyDirWarning(UserWarning):
416
+ """Warning used by Cache.check for empty directories."""
417
+
418
+
419
+ def args_to_key(base, args, kwargs, typed, ignore):
420
+ """Create cache key out of function arguments.
421
+
422
+ :param tuple base: base of key
423
+ :param tuple args: function arguments
424
+ :param dict kwargs: function keyword arguments
425
+ :param bool typed: include types in cache key
426
+ :param set ignore: positional or keyword args to ignore
427
+ :return: cache key tuple
428
+
429
+ """
430
+ args = tuple(arg for index, arg in enumerate(args) if index not in ignore)
431
+ key = base + args + (None,)
432
+
433
+ if kwargs:
434
+ kwargs = {key: val for key, val in kwargs.items() if key not in ignore}
435
+ sorted_items = sorted(kwargs.items())
436
+
437
+ for item in sorted_items:
438
+ key += item
439
+
440
+ if typed:
441
+ key += tuple(type(arg) for arg in args)
442
+
443
+ if kwargs:
444
+ key += tuple(type(value) for _, value in sorted_items)
445
+
446
+ return key
447
+
448
+
449
+ class Cache:
450
+ """Disk and file backed cache."""
451
+
452
+ def __init__(self, directory=None, timeout=60, disk=Disk, **settings):
453
+ """Initialize cache instance.
454
+
455
+ :param str directory: cache directory
456
+ :param float timeout: SQLite connection timeout
457
+ :param disk: Disk type or subclass for serialization
458
+ :param settings: any of DEFAULT_SETTINGS
459
+
460
+ """
461
+ try:
462
+ assert issubclass(disk, Disk)
463
+ except (TypeError, AssertionError):
464
+ raise ValueError('disk must subclass diskcache.Disk') from None
465
+
466
+ if directory is None:
467
+ directory = tempfile.mkdtemp(prefix='diskcache-')
468
+ directory = str(directory)
469
+ directory = op.expanduser(directory)
470
+ directory = op.expandvars(directory)
471
+
472
+ self._directory = directory
473
+ self._timeout = 0 # Manually handle retries during initialization.
474
+ self._local = threading.local()
475
+ self._txn_id = None
476
+
477
+ if not op.isdir(directory):
478
+ try:
479
+ os.makedirs(directory, 0o755)
480
+ except OSError as error:
481
+ if error.errno != errno.EEXIST:
482
+ raise EnvironmentError(
483
+ error.errno,
484
+ 'Cache directory "%s" does not exist'
485
+ ' and could not be created' % self._directory,
486
+ ) from None
487
+
488
+ sql = self._sql_retry
489
+
490
+ # Setup Settings table.
491
+
492
+ try:
493
+ current_settings = dict(
494
+ sql('SELECT key, value FROM Settings').fetchall()
495
+ )
496
+ except sqlite3.OperationalError:
497
+ current_settings = {}
498
+
499
+ sets = DEFAULT_SETTINGS.copy()
500
+ sets.update(current_settings)
501
+ sets.update(settings)
502
+
503
+ for key in METADATA:
504
+ sets.pop(key, None)
505
+
506
+ # Chance to set pragmas before any tables are created.
507
+
508
+ for key, value in sorted(sets.items()):
509
+ if key.startswith('sqlite_'):
510
+ self.reset(key, value, update=False)
511
+
512
+ sql(
513
+ 'CREATE TABLE IF NOT EXISTS Settings ('
514
+ ' key TEXT NOT NULL UNIQUE,'
515
+ ' value)'
516
+ )
517
+
518
+ # Setup Disk object (must happen after settings initialized).
519
+
520
+ kwargs = {
521
+ key[5:]: value
522
+ for key, value in sets.items()
523
+ if key.startswith('disk_')
524
+ }
525
+ self._disk = disk(directory, **kwargs)
526
+
527
+ # Set cached attributes: updates settings and sets pragmas.
528
+
529
+ for key, value in sets.items():
530
+ query = 'INSERT OR REPLACE INTO Settings VALUES (?, ?)'
531
+ sql(query, (key, value))
532
+ self.reset(key, value)
533
+
534
+ for key, value in METADATA.items():
535
+ query = 'INSERT OR IGNORE INTO Settings VALUES (?, ?)'
536
+ sql(query, (key, value))
537
+ self.reset(key)
538
+
539
+ ((self._page_size,),) = sql('PRAGMA page_size').fetchall()
540
+
541
+ # Setup Cache table.
542
+
543
+ sql(
544
+ 'CREATE TABLE IF NOT EXISTS Cache ('
545
+ ' rowid INTEGER PRIMARY KEY,'
546
+ ' key BLOB,'
547
+ ' raw INTEGER,'
548
+ ' store_time REAL,'
549
+ ' expire_time REAL,'
550
+ ' access_time REAL,'
551
+ ' access_count INTEGER DEFAULT 0,'
552
+ ' tag BLOB,'
553
+ ' size INTEGER DEFAULT 0,'
554
+ ' mode INTEGER DEFAULT 0,'
555
+ ' filename TEXT,'
556
+ ' value BLOB)'
557
+ )
558
+
559
+ sql(
560
+ 'CREATE UNIQUE INDEX IF NOT EXISTS Cache_key_raw ON'
561
+ ' Cache(key, raw)'
562
+ )
563
+
564
+ sql(
565
+ 'CREATE INDEX IF NOT EXISTS Cache_expire_time ON'
566
+ ' Cache (expire_time) WHERE expire_time IS NOT NULL'
567
+ )
568
+
569
+ query = EVICTION_POLICY[self.eviction_policy]['init']
570
+
571
+ if query is not None:
572
+ sql(query)
573
+
574
+ # Use triggers to keep Metadata updated.
575
+
576
+ sql(
577
+ 'CREATE TRIGGER IF NOT EXISTS Settings_count_insert'
578
+ ' AFTER INSERT ON Cache FOR EACH ROW BEGIN'
579
+ ' UPDATE Settings SET value = value + 1'
580
+ ' WHERE key = "count"; END'
581
+ )
582
+
583
+ sql(
584
+ 'CREATE TRIGGER IF NOT EXISTS Settings_count_delete'
585
+ ' AFTER DELETE ON Cache FOR EACH ROW BEGIN'
586
+ ' UPDATE Settings SET value = value - 1'
587
+ ' WHERE key = "count"; END'
588
+ )
589
+
590
+ sql(
591
+ 'CREATE TRIGGER IF NOT EXISTS Settings_size_insert'
592
+ ' AFTER INSERT ON Cache FOR EACH ROW BEGIN'
593
+ ' UPDATE Settings SET value = value + NEW.size'
594
+ ' WHERE key = "size"; END'
595
+ )
596
+
597
+ sql(
598
+ 'CREATE TRIGGER IF NOT EXISTS Settings_size_update'
599
+ ' AFTER UPDATE ON Cache FOR EACH ROW BEGIN'
600
+ ' UPDATE Settings'
601
+ ' SET value = value + NEW.size - OLD.size'
602
+ ' WHERE key = "size"; END'
603
+ )
604
+
605
+ sql(
606
+ 'CREATE TRIGGER IF NOT EXISTS Settings_size_delete'
607
+ ' AFTER DELETE ON Cache FOR EACH ROW BEGIN'
608
+ ' UPDATE Settings SET value = value - OLD.size'
609
+ ' WHERE key = "size"; END'
610
+ )
611
+
612
+ # Create tag index if requested.
613
+
614
+ if self.tag_index: # pylint: disable=no-member
615
+ self.create_tag_index()
616
+ else:
617
+ self.drop_tag_index()
618
+
619
+ # Close and re-open database connection with given timeout.
620
+
621
+ self.close()
622
+ self._timeout = timeout
623
+ self._sql # pylint: disable=pointless-statement
624
+
625
+ @property
626
+ def directory(self):
627
+ """Cache directory."""
628
+ return self._directory
629
+
630
+ @property
631
+ def timeout(self):
632
+ """SQLite connection timeout value in seconds."""
633
+ return self._timeout
634
+
635
+ @property
636
+ def disk(self):
637
+ """Disk used for serialization."""
638
+ return self._disk
639
+
640
+ @property
641
+ def _con(self):
642
+ # Check process ID to support process forking. If the process
643
+ # ID changes, close the connection and update the process ID.
644
+
645
+ local_pid = getattr(self._local, 'pid', None)
646
+ pid = os.getpid()
647
+
648
+ if local_pid != pid:
649
+ self.close()
650
+ self._local.pid = pid
651
+
652
+ con = getattr(self._local, 'con', None)
653
+
654
+ if con is None:
655
+ con = self._local.con = sqlite3.connect(
656
+ op.join(self._directory, DBNAME),
657
+ timeout=self._timeout,
658
+ isolation_level=None,
659
+ )
660
+
661
+ # Some SQLite pragmas work on a per-connection basis so
662
+ # query the Settings table and reset the pragmas. The
663
+ # Settings table may not exist so catch and ignore the
664
+ # OperationalError that may occur.
665
+
666
+ try:
667
+ select = 'SELECT key, value FROM Settings'
668
+ settings = con.execute(select).fetchall()
669
+ except sqlite3.OperationalError:
670
+ pass
671
+ else:
672
+ for key, value in settings:
673
+ if key.startswith('sqlite_'):
674
+ self.reset(key, value, update=False)
675
+
676
+ return con
677
+
678
+ @property
679
+ def _sql(self):
680
+ return self._con.execute
681
+
682
+ @property
683
+ def _sql_retry(self):
684
+ sql = self._sql
685
+
686
+ # 2018-11-01 GrantJ - Some SQLite builds/versions handle
687
+ # the SQLITE_BUSY return value and connection parameter
688
+ # "timeout" differently. For a more reliable duration,
689
+ # manually retry the statement for 60 seconds. Only used
690
+ # by statements which modify the database and do not use
691
+ # a transaction (like those in ``__init__`` or ``reset``).
692
+ # See Issue #85 for and tests/issue_85.py for more details.
693
+
694
+ def _execute_with_retry(statement, *args, **kwargs):
695
+ start = time.time()
696
+ while True:
697
+ try:
698
+ return sql(statement, *args, **kwargs)
699
+ except sqlite3.OperationalError as exc:
700
+ if str(exc) != 'database is locked':
701
+ raise
702
+ diff = time.time() - start
703
+ if diff > 60:
704
+ raise
705
+ time.sleep(0.001)
706
+
707
+ return _execute_with_retry
708
+
709
+ @cl.contextmanager
710
+ def transact(self, retry=False):
711
+ """Context manager to perform a transaction by locking the cache.
712
+
713
+ While the cache is locked, no other write operation is permitted.
714
+ Transactions should therefore be as short as possible. Read and write
715
+ operations performed in a transaction are atomic. Read operations may
716
+ occur concurrent to a transaction.
717
+
718
+ Transactions may be nested and may not be shared between threads.
719
+
720
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
721
+ `False` (default).
722
+
723
+ >>> cache = Cache()
724
+ >>> with cache.transact(): # Atomically increment two keys.
725
+ ... _ = cache.incr('total', 123.4)
726
+ ... _ = cache.incr('count', 1)
727
+ >>> with cache.transact(): # Atomically calculate average.
728
+ ... average = cache['total'] / cache['count']
729
+ >>> average
730
+ 123.4
731
+
732
+ :param bool retry: retry if database timeout occurs (default False)
733
+ :return: context manager for use in `with` statement
734
+ :raises Timeout: if database timeout occurs
735
+
736
+ """
737
+ with self._transact(retry=retry):
738
+ yield
739
+
740
+ @cl.contextmanager
741
+ def _transact(self, retry=False, filename=None):
742
+ sql = self._sql
743
+ filenames = []
744
+ _disk_remove = self._disk.remove
745
+ tid = threading.get_ident()
746
+ txn_id = self._txn_id
747
+
748
+ if tid == txn_id:
749
+ begin = False
750
+ else:
751
+ while True:
752
+ try:
753
+ sql('BEGIN IMMEDIATE')
754
+ begin = True
755
+ self._txn_id = tid
756
+ break
757
+ except sqlite3.OperationalError:
758
+ if retry:
759
+ continue
760
+ if filename is not None:
761
+ _disk_remove(filename)
762
+ raise Timeout from None
763
+
764
+ try:
765
+ yield sql, filenames.append
766
+ except BaseException:
767
+ if begin:
768
+ assert self._txn_id == tid
769
+ self._txn_id = None
770
+ sql('ROLLBACK')
771
+ raise
772
+ else:
773
+ if begin:
774
+ assert self._txn_id == tid
775
+ self._txn_id = None
776
+ sql('COMMIT')
777
+ for name in filenames:
778
+ if name is not None:
779
+ _disk_remove(name)
780
+
781
+ def set(self, key, value, expire=None, read=False, tag=None, retry=False):
782
+ """Set `key` and `value` item in cache.
783
+
784
+ When `read` is `True`, `value` should be a file-like object opened
785
+ for reading in binary mode.
786
+
787
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
788
+ `False` (default).
789
+
790
+ :param key: key for item
791
+ :param value: value for item
792
+ :param float expire: seconds until item expires
793
+ (default None, no expiry)
794
+ :param bool read: read value as bytes from file (default False)
795
+ :param str tag: text to associate with key (default None)
796
+ :param bool retry: retry if database timeout occurs (default False)
797
+ :return: True if item was set
798
+ :raises Timeout: if database timeout occurs
799
+
800
+ """
801
+ now = time.time()
802
+ db_key, raw = self._disk.put(key)
803
+ expire_time = None if expire is None else now + expire
804
+ size, mode, filename, db_value = self._disk.store(value, read, key=key)
805
+ columns = (expire_time, tag, size, mode, filename, db_value)
806
+
807
+ # The order of SELECT, UPDATE, and INSERT is important below.
808
+ #
809
+ # Typical cache usage pattern is:
810
+ #
811
+ # value = cache.get(key)
812
+ # if value is None:
813
+ # value = expensive_calculation()
814
+ # cache.set(key, value)
815
+ #
816
+ # Cache.get does not evict expired keys to avoid writes during lookups.
817
+ # Commonly used/expired keys will therefore remain in the cache making
818
+ # an UPDATE the preferred path.
819
+ #
820
+ # The alternative is to assume the key is not present by first trying
821
+ # to INSERT and then handling the IntegrityError that occurs from
822
+ # violating the UNIQUE constraint. This optimistic approach was
823
+ # rejected based on the common cache usage pattern.
824
+ #
825
+ # INSERT OR REPLACE aka UPSERT is not used because the old filename may
826
+ # need cleanup.
827
+
828
+ with self._transact(retry, filename) as (sql, cleanup):
829
+ rows = sql(
830
+ 'SELECT rowid, filename FROM Cache'
831
+ ' WHERE key = ? AND raw = ?',
832
+ (db_key, raw),
833
+ ).fetchall()
834
+
835
+ if rows:
836
+ ((rowid, old_filename),) = rows
837
+ cleanup(old_filename)
838
+ self._row_update(rowid, now, columns)
839
+ else:
840
+ self._row_insert(db_key, raw, now, columns)
841
+
842
+ self._cull(now, sql, cleanup)
843
+
844
+ return True
845
+
846
+ def __setitem__(self, key, value):
847
+ """Set corresponding `value` for `key` in cache.
848
+
849
+ :param key: key for item
850
+ :param value: value for item
851
+ :return: corresponding value
852
+ :raises KeyError: if key is not found
853
+
854
+ """
855
+ self.set(key, value, retry=True)
856
+
857
+ def _row_update(self, rowid, now, columns):
858
+ sql = self._sql
859
+ expire_time, tag, size, mode, filename, value = columns
860
+ sql(
861
+ 'UPDATE Cache SET'
862
+ ' store_time = ?,'
863
+ ' expire_time = ?,'
864
+ ' access_time = ?,'
865
+ ' access_count = ?,'
866
+ ' tag = ?,'
867
+ ' size = ?,'
868
+ ' mode = ?,'
869
+ ' filename = ?,'
870
+ ' value = ?'
871
+ ' WHERE rowid = ?',
872
+ (
873
+ now, # store_time
874
+ expire_time,
875
+ now, # access_time
876
+ 0, # access_count
877
+ tag,
878
+ size,
879
+ mode,
880
+ filename,
881
+ value,
882
+ rowid,
883
+ ),
884
+ )
885
+
886
+ def _row_insert(self, key, raw, now, columns):
887
+ sql = self._sql
888
+ expire_time, tag, size, mode, filename, value = columns
889
+ sql(
890
+ 'INSERT INTO Cache('
891
+ ' key, raw, store_time, expire_time, access_time,'
892
+ ' access_count, tag, size, mode, filename, value'
893
+ ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
894
+ (
895
+ key,
896
+ raw,
897
+ now, # store_time
898
+ expire_time,
899
+ now, # access_time
900
+ 0, # access_count
901
+ tag,
902
+ size,
903
+ mode,
904
+ filename,
905
+ value,
906
+ ),
907
+ )
908
+
909
+ def _cull(self, now, sql, cleanup, limit=None):
910
+ cull_limit = self.cull_limit if limit is None else limit
911
+
912
+ if cull_limit == 0:
913
+ return
914
+
915
+ # Evict expired keys.
916
+
917
+ select_expired_template = (
918
+ 'SELECT %s FROM Cache'
919
+ ' WHERE expire_time IS NOT NULL AND expire_time < ?'
920
+ ' ORDER BY expire_time LIMIT ?'
921
+ )
922
+
923
+ select_expired = select_expired_template % 'filename'
924
+ rows = sql(select_expired, (now, cull_limit)).fetchall()
925
+
926
+ if rows:
927
+ delete_expired = 'DELETE FROM Cache WHERE rowid IN (%s)' % (
928
+ select_expired_template % 'rowid'
929
+ )
930
+ sql(delete_expired, (now, cull_limit))
931
+
932
+ for (filename,) in rows:
933
+ cleanup(filename)
934
+
935
+ cull_limit -= len(rows)
936
+
937
+ if cull_limit == 0:
938
+ return
939
+
940
+ # Evict keys by policy.
941
+
942
+ select_policy = EVICTION_POLICY[self.eviction_policy]['cull']
943
+
944
+ if select_policy is None or self.volume() < self.size_limit:
945
+ return
946
+
947
+ select_filename = select_policy.format(fields='filename', now=now)
948
+ rows = sql(select_filename, (cull_limit,)).fetchall()
949
+
950
+ if rows:
951
+ delete = 'DELETE FROM Cache WHERE rowid IN (%s)' % (
952
+ select_policy.format(fields='rowid', now=now)
953
+ )
954
+ sql(delete, (cull_limit,))
955
+
956
+ for (filename,) in rows:
957
+ cleanup(filename)
958
+
959
+ def touch(self, key, expire=None, retry=False):
960
+ """Touch `key` in cache and update `expire` time.
961
+
962
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
963
+ `False` (default).
964
+
965
+ :param key: key for item
966
+ :param float expire: seconds until item expires
967
+ (default None, no expiry)
968
+ :param bool retry: retry if database timeout occurs (default False)
969
+ :return: True if key was touched
970
+ :raises Timeout: if database timeout occurs
971
+
972
+ """
973
+ now = time.time()
974
+ db_key, raw = self._disk.put(key)
975
+ expire_time = None if expire is None else now + expire
976
+
977
+ with self._transact(retry) as (sql, _):
978
+ rows = sql(
979
+ 'SELECT rowid, expire_time FROM Cache'
980
+ ' WHERE key = ? AND raw = ?',
981
+ (db_key, raw),
982
+ ).fetchall()
983
+
984
+ if rows:
985
+ ((rowid, old_expire_time),) = rows
986
+
987
+ if old_expire_time is None or old_expire_time > now:
988
+ sql(
989
+ 'UPDATE Cache SET expire_time = ? WHERE rowid = ?',
990
+ (expire_time, rowid),
991
+ )
992
+ return True
993
+
994
+ return False
995
+
996
+ def add(self, key, value, expire=None, read=False, tag=None, retry=False):
997
+ """Add `key` and `value` item to cache.
998
+
999
+ Similar to `set`, but only add to cache if key not present.
1000
+
1001
+ Operation is atomic. Only one concurrent add operation for a given key
1002
+ will succeed.
1003
+
1004
+ When `read` is `True`, `value` should be a file-like object opened
1005
+ for reading in binary mode.
1006
+
1007
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1008
+ `False` (default).
1009
+
1010
+ :param key: key for item
1011
+ :param value: value for item
1012
+ :param float expire: seconds until the key expires
1013
+ (default None, no expiry)
1014
+ :param bool read: read value as bytes from file (default False)
1015
+ :param str tag: text to associate with key (default None)
1016
+ :param bool retry: retry if database timeout occurs (default False)
1017
+ :return: True if item was added
1018
+ :raises Timeout: if database timeout occurs
1019
+
1020
+ """
1021
+ now = time.time()
1022
+ db_key, raw = self._disk.put(key)
1023
+ expire_time = None if expire is None else now + expire
1024
+ size, mode, filename, db_value = self._disk.store(value, read, key=key)
1025
+ columns = (expire_time, tag, size, mode, filename, db_value)
1026
+
1027
+ with self._transact(retry, filename) as (sql, cleanup):
1028
+ rows = sql(
1029
+ 'SELECT rowid, filename, expire_time FROM Cache'
1030
+ ' WHERE key = ? AND raw = ?',
1031
+ (db_key, raw),
1032
+ ).fetchall()
1033
+
1034
+ if rows:
1035
+ ((rowid, old_filename, old_expire_time),) = rows
1036
+
1037
+ if old_expire_time is None or old_expire_time > now:
1038
+ cleanup(filename)
1039
+ return False
1040
+
1041
+ cleanup(old_filename)
1042
+ self._row_update(rowid, now, columns)
1043
+ else:
1044
+ self._row_insert(db_key, raw, now, columns)
1045
+
1046
+ self._cull(now, sql, cleanup)
1047
+
1048
+ return True
1049
+
1050
+ def incr(self, key, delta=1, default=0, retry=False):
1051
+ """Increment value by delta for item with key.
1052
+
1053
+ If key is missing and default is None then raise KeyError. Else if key
1054
+ is missing and default is not None then use default for value.
1055
+
1056
+ Operation is atomic. All concurrent increment operations will be
1057
+ counted individually.
1058
+
1059
+ Assumes value may be stored in a SQLite column. Most builds that target
1060
+ machines with 64-bit pointer widths will support 64-bit signed
1061
+ integers.
1062
+
1063
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1064
+ `False` (default).
1065
+
1066
+ :param key: key for item
1067
+ :param int delta: amount to increment (default 1)
1068
+ :param int default: value if key is missing (default 0)
1069
+ :param bool retry: retry if database timeout occurs (default False)
1070
+ :return: new value for item
1071
+ :raises KeyError: if key is not found and default is None
1072
+ :raises Timeout: if database timeout occurs
1073
+
1074
+ """
1075
+ now = time.time()
1076
+ db_key, raw = self._disk.put(key)
1077
+ select = (
1078
+ 'SELECT rowid, expire_time, filename, value FROM Cache'
1079
+ ' WHERE key = ? AND raw = ?'
1080
+ )
1081
+
1082
+ with self._transact(retry) as (sql, cleanup):
1083
+ rows = sql(select, (db_key, raw)).fetchall()
1084
+
1085
+ if not rows:
1086
+ if default is None:
1087
+ raise KeyError(key)
1088
+
1089
+ value = default + delta
1090
+ columns = (None, None) + self._disk.store(
1091
+ value, False, key=key
1092
+ )
1093
+ self._row_insert(db_key, raw, now, columns)
1094
+ self._cull(now, sql, cleanup)
1095
+ return value
1096
+
1097
+ ((rowid, expire_time, filename, value),) = rows
1098
+
1099
+ if expire_time is not None and expire_time < now:
1100
+ if default is None:
1101
+ raise KeyError(key)
1102
+
1103
+ value = default + delta
1104
+ columns = (None, None) + self._disk.store(
1105
+ value, False, key=key
1106
+ )
1107
+ self._row_update(rowid, now, columns)
1108
+ self._cull(now, sql, cleanup)
1109
+ cleanup(filename)
1110
+ return value
1111
+
1112
+ value += delta
1113
+
1114
+ columns = 'store_time = ?, value = ?'
1115
+ update_column = EVICTION_POLICY[self.eviction_policy]['get']
1116
+
1117
+ if update_column is not None:
1118
+ columns += ', ' + update_column.format(now=now)
1119
+
1120
+ update = 'UPDATE Cache SET %s WHERE rowid = ?' % columns
1121
+ sql(update, (now, value, rowid))
1122
+
1123
+ return value
1124
+
1125
+ def decr(self, key, delta=1, default=0, retry=False):
1126
+ """Decrement value by delta for item with key.
1127
+
1128
+ If key is missing and default is None then raise KeyError. Else if key
1129
+ is missing and default is not None then use default for value.
1130
+
1131
+ Operation is atomic. All concurrent decrement operations will be
1132
+ counted individually.
1133
+
1134
+ Unlike Memcached, negative values are supported. Value may be
1135
+ decremented below zero.
1136
+
1137
+ Assumes value may be stored in a SQLite column. Most builds that target
1138
+ machines with 64-bit pointer widths will support 64-bit signed
1139
+ integers.
1140
+
1141
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1142
+ `False` (default).
1143
+
1144
+ :param key: key for item
1145
+ :param int delta: amount to decrement (default 1)
1146
+ :param int default: value if key is missing (default 0)
1147
+ :param bool retry: retry if database timeout occurs (default False)
1148
+ :return: new value for item
1149
+ :raises KeyError: if key is not found and default is None
1150
+ :raises Timeout: if database timeout occurs
1151
+
1152
+ """
1153
+ return self.incr(key, -delta, default, retry)
1154
+
1155
+ def get(
1156
+ self,
1157
+ key,
1158
+ default=None,
1159
+ read=False,
1160
+ expire_time=False,
1161
+ tag=False,
1162
+ retry=False,
1163
+ ):
1164
+ """Retrieve value from cache. If `key` is missing, return `default`.
1165
+
1166
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1167
+ `False` (default).
1168
+
1169
+ :param key: key for item
1170
+ :param default: value to return if key is missing (default None)
1171
+ :param bool read: if True, return file handle to value
1172
+ (default False)
1173
+ :param bool expire_time: if True, return expire_time in tuple
1174
+ (default False)
1175
+ :param bool tag: if True, return tag in tuple (default False)
1176
+ :param bool retry: retry if database timeout occurs (default False)
1177
+ :return: value for item or default if key not found
1178
+ :raises Timeout: if database timeout occurs
1179
+
1180
+ """
1181
+ db_key, raw = self._disk.put(key)
1182
+ update_column = EVICTION_POLICY[self.eviction_policy]['get']
1183
+ select = (
1184
+ 'SELECT rowid, expire_time, tag, mode, filename, value'
1185
+ ' FROM Cache WHERE key = ? AND raw = ?'
1186
+ ' AND (expire_time IS NULL OR expire_time > ?)'
1187
+ )
1188
+
1189
+ if expire_time and tag:
1190
+ default = (default, None, None)
1191
+ elif expire_time or tag:
1192
+ default = (default, None)
1193
+
1194
+ if not self.statistics and update_column is None:
1195
+ # Fast path, no transaction necessary.
1196
+
1197
+ rows = self._sql(select, (db_key, raw, time.time())).fetchall()
1198
+
1199
+ if not rows:
1200
+ return default
1201
+
1202
+ ((rowid, db_expire_time, db_tag, mode, filename, db_value),) = rows
1203
+
1204
+ try:
1205
+ value = self._disk.fetch(mode, filename, db_value, read)
1206
+ except IOError:
1207
+ # Key was deleted before we could retrieve result.
1208
+ return default
1209
+
1210
+ else: # Slow path, transaction required.
1211
+ cache_hit = (
1212
+ 'UPDATE Settings SET value = value + 1 WHERE key = "hits"'
1213
+ )
1214
+ cache_miss = (
1215
+ 'UPDATE Settings SET value = value + 1 WHERE key = "misses"'
1216
+ )
1217
+
1218
+ with self._transact(retry) as (sql, _):
1219
+ rows = sql(select, (db_key, raw, time.time())).fetchall()
1220
+
1221
+ if not rows:
1222
+ if self.statistics:
1223
+ sql(cache_miss)
1224
+ return default
1225
+
1226
+ (
1227
+ (rowid, db_expire_time, db_tag, mode, filename, db_value),
1228
+ ) = rows # noqa: E127
1229
+
1230
+ try:
1231
+ value = self._disk.fetch(mode, filename, db_value, read)
1232
+ except IOError:
1233
+ # Key was deleted before we could retrieve result.
1234
+ if self.statistics:
1235
+ sql(cache_miss)
1236
+ return default
1237
+
1238
+ if self.statistics:
1239
+ sql(cache_hit)
1240
+
1241
+ now = time.time()
1242
+ update = 'UPDATE Cache SET %s WHERE rowid = ?'
1243
+
1244
+ if update_column is not None:
1245
+ sql(update % update_column.format(now=now), (rowid,))
1246
+
1247
+ if expire_time and tag:
1248
+ return (value, db_expire_time, db_tag)
1249
+ elif expire_time:
1250
+ return (value, db_expire_time)
1251
+ elif tag:
1252
+ return (value, db_tag)
1253
+ else:
1254
+ return value
1255
+
1256
+ def __getitem__(self, key):
1257
+ """Return corresponding value for `key` from cache.
1258
+
1259
+ :param key: key matching item
1260
+ :return: corresponding value
1261
+ :raises KeyError: if key is not found
1262
+
1263
+ """
1264
+ value = self.get(key, default=ENOVAL, retry=True)
1265
+ if value is ENOVAL:
1266
+ raise KeyError(key)
1267
+ return value
1268
+
1269
+ def read(self, key, retry=False):
1270
+ """Return file handle value corresponding to `key` from cache.
1271
+
1272
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1273
+ `False` (default).
1274
+
1275
+ :param key: key matching item
1276
+ :param bool retry: retry if database timeout occurs (default False)
1277
+ :return: file open for reading in binary mode
1278
+ :raises KeyError: if key is not found
1279
+ :raises Timeout: if database timeout occurs
1280
+
1281
+ """
1282
+ handle = self.get(key, default=ENOVAL, read=True, retry=retry)
1283
+ if handle is ENOVAL:
1284
+ raise KeyError(key)
1285
+ return handle
1286
+
1287
+ def __contains__(self, key):
1288
+ """Return `True` if `key` matching item is found in cache.
1289
+
1290
+ :param key: key matching item
1291
+ :return: True if key matching item
1292
+
1293
+ """
1294
+ sql = self._sql
1295
+ db_key, raw = self._disk.put(key)
1296
+ select = (
1297
+ 'SELECT rowid FROM Cache'
1298
+ ' WHERE key = ? AND raw = ?'
1299
+ ' AND (expire_time IS NULL OR expire_time > ?)'
1300
+ )
1301
+
1302
+ rows = sql(select, (db_key, raw, time.time())).fetchall()
1303
+
1304
+ return bool(rows)
1305
+
1306
+ def pop(
1307
+ self, key, default=None, expire_time=False, tag=False, retry=False
1308
+ ): # noqa: E501
1309
+ """Remove corresponding item for `key` from cache and return value.
1310
+
1311
+ If `key` is missing, return `default`.
1312
+
1313
+ Operation is atomic. Concurrent operations will be serialized.
1314
+
1315
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1316
+ `False` (default).
1317
+
1318
+ :param key: key for item
1319
+ :param default: value to return if key is missing (default None)
1320
+ :param bool expire_time: if True, return expire_time in tuple
1321
+ (default False)
1322
+ :param bool tag: if True, return tag in tuple (default False)
1323
+ :param bool retry: retry if database timeout occurs (default False)
1324
+ :return: value for item or default if key not found
1325
+ :raises Timeout: if database timeout occurs
1326
+
1327
+ """
1328
+ db_key, raw = self._disk.put(key)
1329
+ select = (
1330
+ 'SELECT rowid, expire_time, tag, mode, filename, value'
1331
+ ' FROM Cache WHERE key = ? AND raw = ?'
1332
+ ' AND (expire_time IS NULL OR expire_time > ?)'
1333
+ )
1334
+
1335
+ if expire_time and tag:
1336
+ default = default, None, None
1337
+ elif expire_time or tag:
1338
+ default = default, None
1339
+
1340
+ with self._transact(retry) as (sql, _):
1341
+ rows = sql(select, (db_key, raw, time.time())).fetchall()
1342
+
1343
+ if not rows:
1344
+ return default
1345
+
1346
+ ((rowid, db_expire_time, db_tag, mode, filename, db_value),) = rows
1347
+
1348
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
1349
+
1350
+ try:
1351
+ value = self._disk.fetch(mode, filename, db_value, False)
1352
+ except IOError:
1353
+ # Key was deleted before we could retrieve result.
1354
+ return default
1355
+ finally:
1356
+ if filename is not None:
1357
+ self._disk.remove(filename)
1358
+
1359
+ if expire_time and tag:
1360
+ return value, db_expire_time, db_tag
1361
+ elif expire_time:
1362
+ return value, db_expire_time
1363
+ elif tag:
1364
+ return value, db_tag
1365
+ else:
1366
+ return value
1367
+
1368
+ def __delitem__(self, key, retry=True):
1369
+ """Delete corresponding item for `key` from cache.
1370
+
1371
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1372
+ `False` (default `True`).
1373
+
1374
+ :param key: key matching item
1375
+ :param bool retry: retry if database timeout occurs (default True)
1376
+ :raises KeyError: if key is not found
1377
+ :raises Timeout: if database timeout occurs
1378
+
1379
+ """
1380
+ db_key, raw = self._disk.put(key)
1381
+
1382
+ with self._transact(retry) as (sql, cleanup):
1383
+ rows = sql(
1384
+ 'SELECT rowid, filename FROM Cache'
1385
+ ' WHERE key = ? AND raw = ?'
1386
+ ' AND (expire_time IS NULL OR expire_time > ?)',
1387
+ (db_key, raw, time.time()),
1388
+ ).fetchall()
1389
+
1390
+ if not rows:
1391
+ raise KeyError(key)
1392
+
1393
+ ((rowid, filename),) = rows
1394
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
1395
+ cleanup(filename)
1396
+
1397
+ return True
1398
+
1399
+ def delete(self, key, retry=False):
1400
+ """Delete corresponding item for `key` from cache.
1401
+
1402
+ Missing keys are ignored.
1403
+
1404
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1405
+ `False` (default).
1406
+
1407
+ :param key: key matching item
1408
+ :param bool retry: retry if database timeout occurs (default False)
1409
+ :return: True if item was deleted
1410
+ :raises Timeout: if database timeout occurs
1411
+
1412
+ """
1413
+ # pylint: disable=unnecessary-dunder-call
1414
+ try:
1415
+ return self.__delitem__(key, retry=retry)
1416
+ except KeyError:
1417
+ return False
1418
+
1419
+ def push(
1420
+ self,
1421
+ value,
1422
+ prefix=None,
1423
+ side='back',
1424
+ expire=None,
1425
+ read=False,
1426
+ tag=None,
1427
+ retry=False,
1428
+ ):
1429
+ """Push `value` onto `side` of queue identified by `prefix` in cache.
1430
+
1431
+ When prefix is None, integer keys are used. Otherwise, string keys are
1432
+ used in the format "prefix-integer". Integer starts at 500 trillion.
1433
+
1434
+ Defaults to pushing value on back of queue. Set side to 'front' to push
1435
+ value on front of queue. Side must be one of 'back' or 'front'.
1436
+
1437
+ Operation is atomic. Concurrent operations will be serialized.
1438
+
1439
+ When `read` is `True`, `value` should be a file-like object opened
1440
+ for reading in binary mode.
1441
+
1442
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1443
+ `False` (default).
1444
+
1445
+ See also `Cache.pull`.
1446
+
1447
+ >>> cache = Cache()
1448
+ >>> print(cache.push('first value'))
1449
+ 500000000000000
1450
+ >>> cache.get(500000000000000)
1451
+ 'first value'
1452
+ >>> print(cache.push('second value'))
1453
+ 500000000000001
1454
+ >>> print(cache.push('third value', side='front'))
1455
+ 499999999999999
1456
+ >>> cache.push(1234, prefix='userids')
1457
+ 'userids-500000000000000'
1458
+
1459
+ :param value: value for item
1460
+ :param str prefix: key prefix (default None, key is integer)
1461
+ :param str side: either 'back' or 'front' (default 'back')
1462
+ :param float expire: seconds until the key expires
1463
+ (default None, no expiry)
1464
+ :param bool read: read value as bytes from file (default False)
1465
+ :param str tag: text to associate with key (default None)
1466
+ :param bool retry: retry if database timeout occurs (default False)
1467
+ :return: key for item in cache
1468
+ :raises Timeout: if database timeout occurs
1469
+
1470
+ """
1471
+ if prefix is None:
1472
+ min_key = 0
1473
+ max_key = 999999999999999
1474
+ else:
1475
+ min_key = prefix + '-000000000000000'
1476
+ max_key = prefix + '-999999999999999'
1477
+
1478
+ now = time.time()
1479
+ raw = True
1480
+ expire_time = None if expire is None else now + expire
1481
+ size, mode, filename, db_value = self._disk.store(value, read)
1482
+ columns = (expire_time, tag, size, mode, filename, db_value)
1483
+ order = {'back': 'DESC', 'front': 'ASC'}
1484
+ select = (
1485
+ 'SELECT key FROM Cache'
1486
+ ' WHERE ? < key AND key < ? AND raw = ?'
1487
+ ' ORDER BY key %s LIMIT 1'
1488
+ ) % order[side]
1489
+
1490
+ with self._transact(retry, filename) as (sql, cleanup):
1491
+ rows = sql(select, (min_key, max_key, raw)).fetchall()
1492
+
1493
+ if rows:
1494
+ ((key,),) = rows
1495
+
1496
+ if prefix is not None:
1497
+ num = int(key[(key.rfind('-') + 1) :])
1498
+ else:
1499
+ num = key
1500
+
1501
+ if side == 'back':
1502
+ num += 1
1503
+ else:
1504
+ assert side == 'front'
1505
+ num -= 1
1506
+ else:
1507
+ num = 500000000000000
1508
+
1509
+ if prefix is not None:
1510
+ db_key = '{0}-{1:015d}'.format(prefix, num)
1511
+ else:
1512
+ db_key = num
1513
+
1514
+ self._row_insert(db_key, raw, now, columns)
1515
+ self._cull(now, sql, cleanup)
1516
+
1517
+ return db_key
1518
+
1519
+ def pull(
1520
+ self,
1521
+ prefix=None,
1522
+ default=(None, None),
1523
+ side='front',
1524
+ expire_time=False,
1525
+ tag=False,
1526
+ retry=False,
1527
+ ):
1528
+ """Pull key and value item pair from `side` of queue in cache.
1529
+
1530
+ When prefix is None, integer keys are used. Otherwise, string keys are
1531
+ used in the format "prefix-integer". Integer starts at 500 trillion.
1532
+
1533
+ If queue is empty, return default.
1534
+
1535
+ Defaults to pulling key and value item pairs from front of queue. Set
1536
+ side to 'back' to pull from back of queue. Side must be one of 'front'
1537
+ or 'back'.
1538
+
1539
+ Operation is atomic. Concurrent operations will be serialized.
1540
+
1541
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1542
+ `False` (default).
1543
+
1544
+ See also `Cache.push` and `Cache.get`.
1545
+
1546
+ >>> cache = Cache()
1547
+ >>> cache.pull()
1548
+ (None, None)
1549
+ >>> for letter in 'abc':
1550
+ ... print(cache.push(letter))
1551
+ 500000000000000
1552
+ 500000000000001
1553
+ 500000000000002
1554
+ >>> key, value = cache.pull()
1555
+ >>> print(key)
1556
+ 500000000000000
1557
+ >>> value
1558
+ 'a'
1559
+ >>> _, value = cache.pull(side='back')
1560
+ >>> value
1561
+ 'c'
1562
+ >>> cache.push(1234, 'userids')
1563
+ 'userids-500000000000000'
1564
+ >>> _, value = cache.pull('userids')
1565
+ >>> value
1566
+ 1234
1567
+
1568
+ :param str prefix: key prefix (default None, key is integer)
1569
+ :param default: value to return if key is missing
1570
+ (default (None, None))
1571
+ :param str side: either 'front' or 'back' (default 'front')
1572
+ :param bool expire_time: if True, return expire_time in tuple
1573
+ (default False)
1574
+ :param bool tag: if True, return tag in tuple (default False)
1575
+ :param bool retry: retry if database timeout occurs (default False)
1576
+ :return: key and value item pair or default if queue is empty
1577
+ :raises Timeout: if database timeout occurs
1578
+
1579
+ """
1580
+ # Caution: Nearly identical code exists in Cache.peek
1581
+ if prefix is None:
1582
+ min_key = 0
1583
+ max_key = 999999999999999
1584
+ else:
1585
+ min_key = prefix + '-000000000000000'
1586
+ max_key = prefix + '-999999999999999'
1587
+
1588
+ order = {'front': 'ASC', 'back': 'DESC'}
1589
+ select = (
1590
+ 'SELECT rowid, key, expire_time, tag, mode, filename, value'
1591
+ ' FROM Cache WHERE ? < key AND key < ? AND raw = 1'
1592
+ ' ORDER BY key %s LIMIT 1'
1593
+ ) % order[side]
1594
+
1595
+ if expire_time and tag:
1596
+ default = default, None, None
1597
+ elif expire_time or tag:
1598
+ default = default, None
1599
+
1600
+ while True:
1601
+ while True:
1602
+ with self._transact(retry) as (sql, cleanup):
1603
+ rows = sql(select, (min_key, max_key)).fetchall()
1604
+
1605
+ if not rows:
1606
+ return default
1607
+
1608
+ (
1609
+ (rowid, key, db_expire, db_tag, mode, name, db_value),
1610
+ ) = rows
1611
+
1612
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
1613
+
1614
+ if db_expire is not None and db_expire < time.time():
1615
+ cleanup(name)
1616
+ else:
1617
+ break
1618
+
1619
+ try:
1620
+ value = self._disk.fetch(mode, name, db_value, False)
1621
+ except IOError:
1622
+ # Key was deleted before we could retrieve result.
1623
+ continue
1624
+ finally:
1625
+ if name is not None:
1626
+ self._disk.remove(name)
1627
+ break
1628
+
1629
+ if expire_time and tag:
1630
+ return (key, value), db_expire, db_tag
1631
+ elif expire_time:
1632
+ return (key, value), db_expire
1633
+ elif tag:
1634
+ return (key, value), db_tag
1635
+ else:
1636
+ return key, value
1637
+
1638
+ def peek(
1639
+ self,
1640
+ prefix=None,
1641
+ default=(None, None),
1642
+ side='front',
1643
+ expire_time=False,
1644
+ tag=False,
1645
+ retry=False,
1646
+ ):
1647
+ """Peek at key and value item pair from `side` of queue in cache.
1648
+
1649
+ When prefix is None, integer keys are used. Otherwise, string keys are
1650
+ used in the format "prefix-integer". Integer starts at 500 trillion.
1651
+
1652
+ If queue is empty, return default.
1653
+
1654
+ Defaults to peeking at key and value item pairs from front of queue.
1655
+ Set side to 'back' to pull from back of queue. Side must be one of
1656
+ 'front' or 'back'.
1657
+
1658
+ Expired items are deleted from cache. Operation is atomic. Concurrent
1659
+ operations will be serialized.
1660
+
1661
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1662
+ `False` (default).
1663
+
1664
+ See also `Cache.pull` and `Cache.push`.
1665
+
1666
+ >>> cache = Cache()
1667
+ >>> for letter in 'abc':
1668
+ ... print(cache.push(letter))
1669
+ 500000000000000
1670
+ 500000000000001
1671
+ 500000000000002
1672
+ >>> key, value = cache.peek()
1673
+ >>> print(key)
1674
+ 500000000000000
1675
+ >>> value
1676
+ 'a'
1677
+ >>> key, value = cache.peek(side='back')
1678
+ >>> print(key)
1679
+ 500000000000002
1680
+ >>> value
1681
+ 'c'
1682
+
1683
+ :param str prefix: key prefix (default None, key is integer)
1684
+ :param default: value to return if key is missing
1685
+ (default (None, None))
1686
+ :param str side: either 'front' or 'back' (default 'front')
1687
+ :param bool expire_time: if True, return expire_time in tuple
1688
+ (default False)
1689
+ :param bool tag: if True, return tag in tuple (default False)
1690
+ :param bool retry: retry if database timeout occurs (default False)
1691
+ :return: key and value item pair or default if queue is empty
1692
+ :raises Timeout: if database timeout occurs
1693
+
1694
+ """
1695
+ # Caution: Nearly identical code exists in Cache.pull
1696
+ if prefix is None:
1697
+ min_key = 0
1698
+ max_key = 999999999999999
1699
+ else:
1700
+ min_key = prefix + '-000000000000000'
1701
+ max_key = prefix + '-999999999999999'
1702
+
1703
+ order = {'front': 'ASC', 'back': 'DESC'}
1704
+ select = (
1705
+ 'SELECT rowid, key, expire_time, tag, mode, filename, value'
1706
+ ' FROM Cache WHERE ? < key AND key < ? AND raw = 1'
1707
+ ' ORDER BY key %s LIMIT 1'
1708
+ ) % order[side]
1709
+
1710
+ if expire_time and tag:
1711
+ default = default, None, None
1712
+ elif expire_time or tag:
1713
+ default = default, None
1714
+
1715
+ while True:
1716
+ while True:
1717
+ with self._transact(retry) as (sql, cleanup):
1718
+ rows = sql(select, (min_key, max_key)).fetchall()
1719
+
1720
+ if not rows:
1721
+ return default
1722
+
1723
+ (
1724
+ (rowid, key, db_expire, db_tag, mode, name, db_value),
1725
+ ) = rows
1726
+
1727
+ if db_expire is not None and db_expire < time.time():
1728
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
1729
+ cleanup(name)
1730
+ else:
1731
+ break
1732
+
1733
+ try:
1734
+ value = self._disk.fetch(mode, name, db_value, False)
1735
+ except IOError:
1736
+ # Key was deleted before we could retrieve result.
1737
+ continue
1738
+ break
1739
+
1740
+ if expire_time and tag:
1741
+ return (key, value), db_expire, db_tag
1742
+ elif expire_time:
1743
+ return (key, value), db_expire
1744
+ elif tag:
1745
+ return (key, value), db_tag
1746
+ else:
1747
+ return key, value
1748
+
1749
+ def peekitem(self, last=True, expire_time=False, tag=False, retry=False):
1750
+ """Peek at key and value item pair in cache based on iteration order.
1751
+
1752
+ Expired items are deleted from cache. Operation is atomic. Concurrent
1753
+ operations will be serialized.
1754
+
1755
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1756
+ `False` (default).
1757
+
1758
+ >>> cache = Cache()
1759
+ >>> for num, letter in enumerate('abc'):
1760
+ ... cache[letter] = num
1761
+ >>> cache.peekitem()
1762
+ ('c', 2)
1763
+ >>> cache.peekitem(last=False)
1764
+ ('a', 0)
1765
+
1766
+ :param bool last: last item in iteration order (default True)
1767
+ :param bool expire_time: if True, return expire_time in tuple
1768
+ (default False)
1769
+ :param bool tag: if True, return tag in tuple (default False)
1770
+ :param bool retry: retry if database timeout occurs (default False)
1771
+ :return: key and value item pair
1772
+ :raises KeyError: if cache is empty
1773
+ :raises Timeout: if database timeout occurs
1774
+
1775
+ """
1776
+ order = ('ASC', 'DESC')
1777
+ select = (
1778
+ 'SELECT rowid, key, raw, expire_time, tag, mode, filename, value'
1779
+ ' FROM Cache ORDER BY rowid %s LIMIT 1'
1780
+ ) % order[last]
1781
+
1782
+ while True:
1783
+ while True:
1784
+ with self._transact(retry) as (sql, cleanup):
1785
+ rows = sql(select).fetchall()
1786
+
1787
+ if not rows:
1788
+ raise KeyError('dictionary is empty')
1789
+
1790
+ (
1791
+ (
1792
+ rowid,
1793
+ db_key,
1794
+ raw,
1795
+ db_expire,
1796
+ db_tag,
1797
+ mode,
1798
+ name,
1799
+ db_value,
1800
+ ),
1801
+ ) = rows
1802
+
1803
+ if db_expire is not None and db_expire < time.time():
1804
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
1805
+ cleanup(name)
1806
+ else:
1807
+ break
1808
+
1809
+ key = self._disk.get(db_key, raw)
1810
+
1811
+ try:
1812
+ value = self._disk.fetch(mode, name, db_value, False)
1813
+ except IOError:
1814
+ # Key was deleted before we could retrieve result.
1815
+ continue
1816
+ break
1817
+
1818
+ if expire_time and tag:
1819
+ return (key, value), db_expire, db_tag
1820
+ elif expire_time:
1821
+ return (key, value), db_expire
1822
+ elif tag:
1823
+ return (key, value), db_tag
1824
+ else:
1825
+ return key, value
1826
+
1827
+ def memoize(
1828
+ self, name=None, typed=False, expire=None, tag=None, ignore=()
1829
+ ):
1830
+ """Memoizing cache decorator.
1831
+
1832
+ Decorator to wrap callable with memoizing function using cache.
1833
+ Repeated calls with the same arguments will lookup result in cache and
1834
+ avoid function evaluation.
1835
+
1836
+ If name is set to None (default), the callable name will be determined
1837
+ automatically.
1838
+
1839
+ When expire is set to zero, function results will not be set in the
1840
+ cache. Cache lookups still occur, however. Read
1841
+ :doc:`case-study-landing-page-caching` for example usage.
1842
+
1843
+ If typed is set to True, function arguments of different types will be
1844
+ cached separately. For example, f(3) and f(3.0) will be treated as
1845
+ distinct calls with distinct results.
1846
+
1847
+ The original underlying function is accessible through the __wrapped__
1848
+ attribute. This is useful for introspection, for bypassing the cache,
1849
+ or for rewrapping the function with a different cache.
1850
+
1851
+ >>> from diskcache import Cache
1852
+ >>> cache = Cache()
1853
+ >>> @cache.memoize(expire=1, tag='fib')
1854
+ ... def fibonacci(number):
1855
+ ... if number == 0:
1856
+ ... return 0
1857
+ ... elif number == 1:
1858
+ ... return 1
1859
+ ... else:
1860
+ ... return fibonacci(number - 1) + fibonacci(number - 2)
1861
+ >>> print(fibonacci(100))
1862
+ 354224848179261915075
1863
+
1864
+ An additional `__cache_key__` attribute can be used to generate the
1865
+ cache key used for the given arguments.
1866
+
1867
+ >>> key = fibonacci.__cache_key__(100)
1868
+ >>> print(cache[key])
1869
+ 354224848179261915075
1870
+
1871
+ Remember to call memoize when decorating a callable. If you forget,
1872
+ then a TypeError will occur. Note the lack of parenthenses after
1873
+ memoize below:
1874
+
1875
+ >>> @cache.memoize
1876
+ ... def test():
1877
+ ... pass
1878
+ Traceback (most recent call last):
1879
+ ...
1880
+ TypeError: name cannot be callable
1881
+
1882
+ :param cache: cache to store callable arguments and return values
1883
+ :param str name: name given for callable (default None, automatic)
1884
+ :param bool typed: cache different types separately (default False)
1885
+ :param float expire: seconds until arguments expire
1886
+ (default None, no expiry)
1887
+ :param str tag: text to associate with arguments (default None)
1888
+ :param set ignore: positional or keyword args to ignore (default ())
1889
+ :return: callable decorator
1890
+
1891
+ """
1892
+ # Caution: Nearly identical code exists in DjangoCache.memoize
1893
+ if callable(name):
1894
+ raise TypeError('name cannot be callable')
1895
+
1896
+ def decorator(func):
1897
+ """Decorator created by memoize() for callable `func`."""
1898
+ base = (full_name(func),) if name is None else (name,)
1899
+
1900
+ @ft.wraps(func)
1901
+ def wrapper(*args, **kwargs):
1902
+ """Wrapper for callable to cache arguments and return values."""
1903
+ key = wrapper.__cache_key__(*args, **kwargs)
1904
+ result = self.get(key, default=ENOVAL, retry=True)
1905
+
1906
+ if result is ENOVAL:
1907
+ result = func(*args, **kwargs)
1908
+ if expire is None or expire > 0:
1909
+ self.set(key, result, expire, tag=tag, retry=True)
1910
+
1911
+ return result
1912
+
1913
+ def __cache_key__(*args, **kwargs):
1914
+ """Make key for cache given function arguments."""
1915
+ return args_to_key(base, args, kwargs, typed, ignore)
1916
+
1917
+ wrapper.__cache_key__ = __cache_key__
1918
+ return wrapper
1919
+
1920
+ return decorator
1921
+
1922
+ def check(self, fix=False, retry=False):
1923
+ """Check database and file system consistency.
1924
+
1925
+ Intended for use in testing and post-mortem error analysis.
1926
+
1927
+ While checking the Cache table for consistency, a writer lock is held
1928
+ on the database. The lock blocks other cache clients from writing to
1929
+ the database. For caches with many file references, the lock may be
1930
+ held for a long time. For example, local benchmarking shows that a
1931
+ cache with 1,000 file references takes ~60ms to check.
1932
+
1933
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1934
+ `False` (default).
1935
+
1936
+ :param bool fix: correct inconsistencies
1937
+ :param bool retry: retry if database timeout occurs (default False)
1938
+ :return: list of warnings
1939
+ :raises Timeout: if database timeout occurs
1940
+
1941
+ """
1942
+ # pylint: disable=access-member-before-definition,W0201
1943
+ with warnings.catch_warnings(record=True) as warns:
1944
+ sql = self._sql
1945
+
1946
+ # Check integrity of database.
1947
+
1948
+ rows = sql('PRAGMA integrity_check').fetchall()
1949
+
1950
+ if len(rows) != 1 or rows[0][0] != 'ok':
1951
+ for (message,) in rows:
1952
+ warnings.warn(message)
1953
+
1954
+ if fix:
1955
+ sql('VACUUM')
1956
+
1957
+ with self._transact(retry) as (sql, _):
1958
+
1959
+ # Check Cache.filename against file system.
1960
+
1961
+ filenames = set()
1962
+ select = (
1963
+ 'SELECT rowid, size, filename FROM Cache'
1964
+ ' WHERE filename IS NOT NULL'
1965
+ )
1966
+
1967
+ rows = sql(select).fetchall()
1968
+
1969
+ for rowid, size, filename in rows:
1970
+ full_path = op.join(self._directory, filename)
1971
+ filenames.add(full_path)
1972
+
1973
+ if op.exists(full_path):
1974
+ real_size = op.getsize(full_path)
1975
+
1976
+ if size != real_size:
1977
+ message = 'wrong file size: %s, %d != %d'
1978
+ args = full_path, real_size, size
1979
+ warnings.warn(message % args)
1980
+
1981
+ if fix:
1982
+ sql(
1983
+ 'UPDATE Cache SET size = ?'
1984
+ ' WHERE rowid = ?',
1985
+ (real_size, rowid),
1986
+ )
1987
+
1988
+ continue
1989
+
1990
+ warnings.warn('file not found: %s' % full_path)
1991
+
1992
+ if fix:
1993
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
1994
+
1995
+ # Check file system against Cache.filename.
1996
+
1997
+ for dirpath, _, files in os.walk(self._directory):
1998
+ paths = [op.join(dirpath, filename) for filename in files]
1999
+ error = set(paths) - filenames
2000
+
2001
+ for full_path in error:
2002
+ if DBNAME in full_path:
2003
+ continue
2004
+
2005
+ message = 'unknown file: %s' % full_path
2006
+ warnings.warn(message, UnknownFileWarning)
2007
+
2008
+ if fix:
2009
+ os.remove(full_path)
2010
+
2011
+ # Check for empty directories.
2012
+
2013
+ for dirpath, dirs, files in os.walk(self._directory):
2014
+ if not (dirs or files):
2015
+ message = 'empty directory: %s' % dirpath
2016
+ warnings.warn(message, EmptyDirWarning)
2017
+
2018
+ if fix:
2019
+ os.rmdir(dirpath)
2020
+
2021
+ # Check Settings.count against count of Cache rows.
2022
+
2023
+ self.reset('count')
2024
+ ((count,),) = sql('SELECT COUNT(key) FROM Cache').fetchall()
2025
+
2026
+ if self.count != count:
2027
+ message = 'Settings.count != COUNT(Cache.key); %d != %d'
2028
+ warnings.warn(message % (self.count, count))
2029
+
2030
+ if fix:
2031
+ sql(
2032
+ 'UPDATE Settings SET value = ? WHERE key = ?',
2033
+ (count, 'count'),
2034
+ )
2035
+
2036
+ # Check Settings.size against sum of Cache.size column.
2037
+
2038
+ self.reset('size')
2039
+ select_size = 'SELECT COALESCE(SUM(size), 0) FROM Cache'
2040
+ ((size,),) = sql(select_size).fetchall()
2041
+
2042
+ if self.size != size:
2043
+ message = 'Settings.size != SUM(Cache.size); %d != %d'
2044
+ warnings.warn(message % (self.size, size))
2045
+
2046
+ if fix:
2047
+ sql(
2048
+ 'UPDATE Settings SET value = ? WHERE key =?',
2049
+ (size, 'size'),
2050
+ )
2051
+
2052
+ return warns
2053
+
2054
+ def create_tag_index(self):
2055
+ """Create tag index on cache database.
2056
+
2057
+ It is better to initialize cache with `tag_index=True` than use this.
2058
+
2059
+ :raises Timeout: if database timeout occurs
2060
+
2061
+ """
2062
+ sql = self._sql
2063
+ sql(
2064
+ 'CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON Cache(tag, rowid) '
2065
+ 'WHERE tag IS NOT NULL'
2066
+ )
2067
+ self.reset('tag_index', 1)
2068
+
2069
+ def drop_tag_index(self):
2070
+ """Drop tag index on cache database.
2071
+
2072
+ :raises Timeout: if database timeout occurs
2073
+
2074
+ """
2075
+ sql = self._sql
2076
+ sql('DROP INDEX IF EXISTS Cache_tag_rowid')
2077
+ self.reset('tag_index', 0)
2078
+
2079
+ def evict(self, tag, retry=False):
2080
+ """Remove items with matching `tag` from cache.
2081
+
2082
+ Removing items is an iterative process. In each iteration, a subset of
2083
+ items is removed. Concurrent writes may occur between iterations.
2084
+
2085
+ If a :exc:`Timeout` occurs, the first element of the exception's
2086
+ `args` attribute will be the number of items removed before the
2087
+ exception occurred.
2088
+
2089
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2090
+ `False` (default).
2091
+
2092
+ :param str tag: tag identifying items
2093
+ :param bool retry: retry if database timeout occurs (default False)
2094
+ :return: count of rows removed
2095
+ :raises Timeout: if database timeout occurs
2096
+
2097
+ """
2098
+ select = (
2099
+ 'SELECT rowid, filename FROM Cache'
2100
+ ' WHERE tag = ? AND rowid > ?'
2101
+ ' ORDER BY rowid LIMIT ?'
2102
+ )
2103
+ args = [tag, 0, 100]
2104
+ return self._select_delete(select, args, arg_index=1, retry=retry)
2105
+
2106
+ def expire(self, now=None, retry=False):
2107
+ """Remove expired items from cache.
2108
+
2109
+ Removing items is an iterative process. In each iteration, a subset of
2110
+ items is removed. Concurrent writes may occur between iterations.
2111
+
2112
+ If a :exc:`Timeout` occurs, the first element of the exception's
2113
+ `args` attribute will be the number of items removed before the
2114
+ exception occurred.
2115
+
2116
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2117
+ `False` (default).
2118
+
2119
+ :param float now: current time (default None, ``time.time()`` used)
2120
+ :param bool retry: retry if database timeout occurs (default False)
2121
+ :return: count of items removed
2122
+ :raises Timeout: if database timeout occurs
2123
+
2124
+ """
2125
+ select = (
2126
+ 'SELECT rowid, expire_time, filename FROM Cache'
2127
+ ' WHERE ? < expire_time AND expire_time < ?'
2128
+ ' ORDER BY expire_time LIMIT ?'
2129
+ )
2130
+ args = [0, now or time.time(), 100]
2131
+ return self._select_delete(select, args, row_index=1, retry=retry)
2132
+
2133
+ def cull(self, retry=False):
2134
+ """Cull items from cache until volume is less than size limit.
2135
+
2136
+ Removing items is an iterative process. In each iteration, a subset of
2137
+ items is removed. Concurrent writes may occur between iterations.
2138
+
2139
+ If a :exc:`Timeout` occurs, the first element of the exception's
2140
+ `args` attribute will be the number of items removed before the
2141
+ exception occurred.
2142
+
2143
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2144
+ `False` (default).
2145
+
2146
+ :param bool retry: retry if database timeout occurs (default False)
2147
+ :return: count of items removed
2148
+ :raises Timeout: if database timeout occurs
2149
+
2150
+ """
2151
+ now = time.time()
2152
+
2153
+ # Remove expired items.
2154
+
2155
+ count = self.expire(now)
2156
+
2157
+ # Remove items by policy.
2158
+
2159
+ select_policy = EVICTION_POLICY[self.eviction_policy]['cull']
2160
+
2161
+ if select_policy is None:
2162
+ return 0
2163
+
2164
+ select_filename = select_policy.format(fields='filename', now=now)
2165
+
2166
+ try:
2167
+ while self.volume() > self.size_limit:
2168
+ with self._transact(retry) as (sql, cleanup):
2169
+ rows = sql(select_filename, (10,)).fetchall()
2170
+
2171
+ if not rows:
2172
+ break
2173
+
2174
+ count += len(rows)
2175
+ delete = (
2176
+ 'DELETE FROM Cache WHERE rowid IN (%s)'
2177
+ % select_policy.format(fields='rowid', now=now)
2178
+ )
2179
+ sql(delete, (10,))
2180
+
2181
+ for (filename,) in rows:
2182
+ cleanup(filename)
2183
+ except Timeout:
2184
+ raise Timeout(count) from None
2185
+
2186
+ return count
2187
+
2188
+ def clear(self, retry=False):
2189
+ """Remove all items from cache.
2190
+
2191
+ Removing items is an iterative process. In each iteration, a subset of
2192
+ items is removed. Concurrent writes may occur between iterations.
2193
+
2194
+ If a :exc:`Timeout` occurs, the first element of the exception's
2195
+ `args` attribute will be the number of items removed before the
2196
+ exception occurred.
2197
+
2198
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2199
+ `False` (default).
2200
+
2201
+ :param bool retry: retry if database timeout occurs (default False)
2202
+ :return: count of rows removed
2203
+ :raises Timeout: if database timeout occurs
2204
+
2205
+ """
2206
+ select = (
2207
+ 'SELECT rowid, filename FROM Cache'
2208
+ ' WHERE rowid > ?'
2209
+ ' ORDER BY rowid LIMIT ?'
2210
+ )
2211
+ args = [0, 100]
2212
+ return self._select_delete(select, args, retry=retry)
2213
+
2214
+ def _select_delete(
2215
+ self, select, args, row_index=0, arg_index=0, retry=False
2216
+ ):
2217
+ count = 0
2218
+ delete = 'DELETE FROM Cache WHERE rowid IN (%s)'
2219
+
2220
+ try:
2221
+ while True:
2222
+ with self._transact(retry) as (sql, cleanup):
2223
+ rows = sql(select, args).fetchall()
2224
+
2225
+ if not rows:
2226
+ break
2227
+
2228
+ count += len(rows)
2229
+ sql(delete % ','.join(str(row[0]) for row in rows))
2230
+
2231
+ for row in rows:
2232
+ args[arg_index] = row[row_index]
2233
+ cleanup(row[-1])
2234
+
2235
+ except Timeout:
2236
+ raise Timeout(count) from None
2237
+
2238
+ return count
2239
+
2240
+ def iterkeys(self, reverse=False, *, prefix=None):
2241
+ """Iterate Cache keys in database sort order.
2242
+
2243
+ >>> cache = Cache()
2244
+ >>> for key in [4, 1, 3, 0, 2]:
2245
+ ... cache[key] = key
2246
+ >>> list(cache.iterkeys())
2247
+ [0, 1, 2, 3, 4]
2248
+ >>> list(cache.iterkeys(reverse=True))
2249
+ [4, 3, 2, 1, 0]
2250
+ >>> cache.clear() > 0
2251
+ True
2252
+ >>> for key in (b'a-1', b'a-2', b'b-1'):
2253
+ ... cache[key] = 1
2254
+ >>> list(cache.iterkeys(prefix=b'a'))
2255
+ [b'a-1', b'a-2']
2256
+ >>> list(cache.iterkeys(prefix=b'a', reverse=True))
2257
+ [b'a-2', b'a-1']
2258
+
2259
+ :param bool reverse: reverse sort order (default False)
2260
+ :param prefix: when given (str or bytes), only yield keys that start
2261
+ with this prefix. Keys of other types (int, float, pickled
2262
+ objects) are excluded by the database range query. (default None)
2263
+ :return: iterator of Cache keys
2264
+
2265
+ """
2266
+ sql = self._sql
2267
+ limit = 100
2268
+ _disk_get = self._disk.get
2269
+
2270
+ if prefix is None:
2271
+ if reverse:
2272
+ select = (
2273
+ 'SELECT key, raw FROM Cache'
2274
+ ' ORDER BY key DESC, raw DESC LIMIT 1'
2275
+ )
2276
+ iterate = (
2277
+ 'SELECT key, raw FROM Cache'
2278
+ ' WHERE key = ? AND raw < ? OR key < ?'
2279
+ ' ORDER BY key DESC, raw DESC LIMIT ?'
2280
+ )
2281
+ else:
2282
+ select = (
2283
+ 'SELECT key, raw FROM Cache'
2284
+ ' ORDER BY key ASC, raw ASC LIMIT 1'
2285
+ )
2286
+ iterate = (
2287
+ 'SELECT key, raw FROM Cache'
2288
+ ' WHERE key = ? AND raw > ? OR key > ?'
2289
+ ' ORDER BY key ASC, raw ASC LIMIT ?'
2290
+ )
2291
+ init_args = ()
2292
+ iter_tail = (limit,)
2293
+ else:
2294
+ lower, upper = _prefix_bounds(prefix)
2295
+ upper_clause = '' if upper is None else ' AND key < ?'
2296
+ if reverse:
2297
+ select = (
2298
+ 'SELECT key, raw FROM Cache'
2299
+ ' WHERE key >= ?'
2300
+ + upper_clause
2301
+ + ' ORDER BY key DESC, raw DESC LIMIT 1'
2302
+ )
2303
+ iterate = (
2304
+ 'SELECT key, raw FROM Cache'
2305
+ ' WHERE (key = ? AND raw < ? OR key < ?) AND key >= ?'
2306
+ ' ORDER BY key DESC, raw DESC LIMIT ?'
2307
+ )
2308
+ iter_tail = (lower, limit)
2309
+ else:
2310
+ select = (
2311
+ 'SELECT key, raw FROM Cache'
2312
+ ' WHERE key >= ?'
2313
+ + upper_clause
2314
+ + ' ORDER BY key ASC, raw ASC LIMIT 1'
2315
+ )
2316
+ iterate = (
2317
+ 'SELECT key, raw FROM Cache'
2318
+ ' WHERE (key = ? AND raw > ? OR key > ?)'
2319
+ + upper_clause
2320
+ + ' ORDER BY key ASC, raw ASC LIMIT ?'
2321
+ )
2322
+ iter_tail = (limit,) if upper is None else (upper, limit)
2323
+ init_args = (lower,) if upper is None else (lower, upper)
2324
+
2325
+ row = sql(select, init_args).fetchall()
2326
+
2327
+ if not row:
2328
+ return
2329
+
2330
+ ((key, raw),) = row
2331
+
2332
+ value = _disk_get(key, raw)
2333
+ if prefix is None or (
2334
+ isinstance(value, (str, bytes)) and value.startswith(prefix)
2335
+ ):
2336
+ yield value
2337
+
2338
+ while True:
2339
+ rows = sql(iterate, (key, raw, key) + iter_tail).fetchall()
2340
+
2341
+ if not rows:
2342
+ break
2343
+
2344
+ for key, raw in rows:
2345
+ value = _disk_get(key, raw)
2346
+ if prefix is None or (
2347
+ isinstance(value, (str, bytes))
2348
+ and value.startswith(prefix)
2349
+ ):
2350
+ yield value
2351
+
2352
+ def _iter(self, ascending=True):
2353
+ sql = self._sql
2354
+ rows = sql('SELECT MAX(rowid) FROM Cache').fetchall()
2355
+ ((max_rowid,),) = rows
2356
+ yield # Signal ready.
2357
+
2358
+ if max_rowid is None:
2359
+ return
2360
+
2361
+ bound = max_rowid + 1
2362
+ limit = 100
2363
+ _disk_get = self._disk.get
2364
+ rowid = 0 if ascending else bound
2365
+ select = (
2366
+ 'SELECT rowid, key, raw FROM Cache'
2367
+ ' WHERE ? < rowid AND rowid < ?'
2368
+ ' ORDER BY rowid %s LIMIT ?'
2369
+ ) % ('ASC' if ascending else 'DESC')
2370
+
2371
+ while True:
2372
+ if ascending:
2373
+ args = (rowid, bound, limit)
2374
+ else:
2375
+ args = (0, rowid, limit)
2376
+
2377
+ rows = sql(select, args).fetchall()
2378
+
2379
+ if not rows:
2380
+ break
2381
+
2382
+ for rowid, key, raw in rows:
2383
+ yield _disk_get(key, raw)
2384
+
2385
+ def __iter__(self):
2386
+ """Iterate keys in cache including expired items."""
2387
+ iterator = self._iter()
2388
+ next(iterator)
2389
+ return iterator
2390
+
2391
+ def __reversed__(self):
2392
+ """Reverse iterate keys in cache including expired items."""
2393
+ iterator = self._iter(ascending=False)
2394
+ next(iterator)
2395
+ return iterator
2396
+
2397
+ def stats(self, enable=True, reset=False):
2398
+ """Return cache statistics hits and misses.
2399
+
2400
+ :param bool enable: enable collecting statistics (default True)
2401
+ :param bool reset: reset hits and misses to 0 (default False)
2402
+ :return: (hits, misses)
2403
+
2404
+ """
2405
+ # pylint: disable=E0203,W0201
2406
+ result = (self.reset('hits'), self.reset('misses'))
2407
+
2408
+ if reset:
2409
+ self.reset('hits', 0)
2410
+ self.reset('misses', 0)
2411
+
2412
+ self.reset('statistics', enable)
2413
+
2414
+ return result
2415
+
2416
+ def volume(self):
2417
+ """Return estimated total size of cache on disk.
2418
+
2419
+ :return: size in bytes
2420
+
2421
+ """
2422
+ ((page_count,),) = self._sql('PRAGMA page_count').fetchall()
2423
+ total_size = self._page_size * page_count + self.reset('size')
2424
+ return total_size
2425
+
2426
+ def close(self):
2427
+ """Close database connection."""
2428
+ con = getattr(self._local, 'con', None)
2429
+
2430
+ if con is None:
2431
+ return
2432
+
2433
+ con.close()
2434
+
2435
+ try:
2436
+ delattr(self._local, 'con')
2437
+ except AttributeError:
2438
+ pass
2439
+
2440
+ def __enter__(self):
2441
+ # Create connection in thread.
2442
+ # pylint: disable=unused-variable
2443
+ connection = self._con # noqa
2444
+ return self
2445
+
2446
+ def __exit__(self, *exception):
2447
+ self.close()
2448
+
2449
+ def __len__(self):
2450
+ """Count of items in cache including expired items."""
2451
+ return self.reset('count')
2452
+
2453
+ def __getstate__(self):
2454
+ return (self.directory, self.timeout, type(self.disk))
2455
+
2456
+ def __setstate__(self, state):
2457
+ self.__init__(*state)
2458
+
2459
+ def reset(self, key, value=ENOVAL, update=True):
2460
+ """Reset `key` and `value` item from Settings table.
2461
+
2462
+ Use `reset` to update the value of Cache settings correctly. Cache
2463
+ settings are stored in the Settings table of the SQLite database. If
2464
+ `update` is ``False`` then no attempt is made to update the database.
2465
+
2466
+ If `value` is not given, it is reloaded from the Settings
2467
+ table. Otherwise, the Settings table is updated.
2468
+
2469
+ Settings with the ``disk_`` prefix correspond to Disk
2470
+ attributes. Updating the value will change the unprefixed attribute on
2471
+ the associated Disk instance.
2472
+
2473
+ Settings with the ``sqlite_`` prefix correspond to SQLite
2474
+ pragmas. Updating the value will execute the corresponding PRAGMA
2475
+ statement.
2476
+
2477
+ SQLite PRAGMA statements may be executed before the Settings table
2478
+ exists in the database by setting `update` to ``False``.
2479
+
2480
+ :param str key: Settings key for item
2481
+ :param value: value for item (optional)
2482
+ :param bool update: update database Settings table (default True)
2483
+ :return: updated value for item
2484
+ :raises Timeout: if database timeout occurs
2485
+
2486
+ """
2487
+ sql = self._sql
2488
+ sql_retry = self._sql_retry
2489
+
2490
+ if value is ENOVAL:
2491
+ select = 'SELECT value FROM Settings WHERE key = ?'
2492
+ ((value,),) = sql_retry(select, (key,)).fetchall()
2493
+ setattr(self, key, value)
2494
+ return value
2495
+
2496
+ if update:
2497
+ statement = 'UPDATE Settings SET value = ? WHERE key = ?'
2498
+ sql_retry(statement, (value, key))
2499
+
2500
+ if key.startswith('sqlite_'):
2501
+ pragma = key[7:]
2502
+
2503
+ # 2016-02-17 GrantJ - PRAGMA and isolation_level=None
2504
+ # don't always play nicely together. Retry setting the
2505
+ # PRAGMA. I think some PRAGMA statements expect to
2506
+ # immediately take an EXCLUSIVE lock on the database. I
2507
+ # can't find any documentation for this but without the
2508
+ # retry, stress will intermittently fail with multiple
2509
+ # processes.
2510
+
2511
+ # 2018-11-05 GrantJ - Avoid setting pragma values that
2512
+ # are already set. Pragma settings like auto_vacuum and
2513
+ # journal_mode can take a long time or may not work after
2514
+ # tables have been created.
2515
+
2516
+ start = time.time()
2517
+ while True:
2518
+ try:
2519
+ try:
2520
+ ((old_value,),) = sql(
2521
+ 'PRAGMA %s' % (pragma)
2522
+ ).fetchall()
2523
+ update = old_value != value
2524
+ except ValueError:
2525
+ update = True
2526
+ if update:
2527
+ sql('PRAGMA %s = %s' % (pragma, value)).fetchall()
2528
+ break
2529
+ except sqlite3.OperationalError as exc:
2530
+ if str(exc) != 'database is locked':
2531
+ raise
2532
+ diff = time.time() - start
2533
+ if diff > 60:
2534
+ raise
2535
+ time.sleep(0.001)
2536
+ elif key.startswith('disk_'):
2537
+ attr = key[5:]
2538
+ setattr(self._disk, attr, value)
2539
+
2540
+ setattr(self, key, value)
2541
+ return value