diskcache-community 5.7.0__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,3328 @@
1
+ """Core disk and file backed cache API.
2
+ """
3
+
4
+ import codecs
5
+ import collections as co
6
+ import contextlib as cl
7
+ import datetime as dt
8
+ import decimal
9
+ import errno
10
+ import fractions
11
+ import functools as ft
12
+ import inspect
13
+ import io
14
+ import json
15
+ import os
16
+ import os.path as op
17
+ import pathlib
18
+ import pickle
19
+ import pickletools
20
+ import sqlite3
21
+ import struct
22
+ import tempfile
23
+ import threading
24
+ import time
25
+ import uuid
26
+ import warnings
27
+ import weakref
28
+ import zlib
29
+
30
+
31
+ def full_name(func):
32
+ """Return full name of `func` by adding the module and function name."""
33
+ return func.__module__ + '.' + func.__qualname__
34
+
35
+
36
+ class Constant(tuple):
37
+ """Pretty display of immutable constant."""
38
+
39
+ def __new__(cls, name):
40
+ return tuple.__new__(cls, (name,))
41
+
42
+ def __repr__(self):
43
+ return '%s' % self[0]
44
+
45
+
46
+ DBNAME = 'cache.db'
47
+ ENOVAL = Constant('ENOVAL')
48
+ UNKNOWN = Constant('UNKNOWN')
49
+
50
+ MODE_NONE = 0
51
+ MODE_RAW = 1
52
+ MODE_BINARY = 2
53
+ MODE_TEXT = 3
54
+ MODE_PICKLE = 4
55
+
56
+ DEFAULT_SETTINGS = {
57
+ 'statistics': 0, # False
58
+ 'tag_index': 0, # False
59
+ 'eviction_policy': 'least-recently-stored',
60
+ 'size_limit': 2**30, # 1gb
61
+ 'cull_limit': 10,
62
+ 'sqlite_auto_vacuum': 1, # FULL
63
+ 'sqlite_cache_size': 2**13, # 8,192 pages
64
+ 'sqlite_journal_mode': 'wal',
65
+ 'sqlite_mmap_size': 2**26, # 64mb
66
+ 'sqlite_synchronous': 1, # NORMAL
67
+ 'disk_min_file_size': 2**15, # 32kb
68
+ 'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL,
69
+ }
70
+
71
+ METADATA = {
72
+ 'count': 0,
73
+ 'size': 0,
74
+ 'hits': 0,
75
+ 'misses': 0,
76
+ }
77
+
78
+ # Triggers that keep the "count" and "size" metadata in the Settings table in
79
+ # step with the Cache table, as (name, definition) pairs where the definition
80
+ # is the text that follows "CREATE TRIGGER <name>". Caches written by
81
+ # versions before 5.7.0 store these triggers with double-quoted literals,
82
+ # which SQLite builds compiled with SQLITE_DQS=0 reject (issue #343), so
83
+ # Cache.__init__ compares the stored trigger text with these definitions and
84
+ # recreates the triggers when they differ.
85
+
86
+ SETTINGS_TRIGGERS = (
87
+ (
88
+ 'Settings_count_insert',
89
+ 'AFTER INSERT ON Cache FOR EACH ROW BEGIN'
90
+ ' UPDATE Settings SET value = value + 1'
91
+ " WHERE key = 'count'; END",
92
+ ),
93
+ (
94
+ 'Settings_count_delete',
95
+ 'AFTER DELETE ON Cache FOR EACH ROW BEGIN'
96
+ ' UPDATE Settings SET value = value - 1'
97
+ " WHERE key = 'count'; END",
98
+ ),
99
+ (
100
+ 'Settings_size_insert',
101
+ 'AFTER INSERT ON Cache FOR EACH ROW BEGIN'
102
+ ' UPDATE Settings SET value = value + NEW.size'
103
+ " WHERE key = 'size'; END",
104
+ ),
105
+ (
106
+ 'Settings_size_update',
107
+ 'AFTER UPDATE ON Cache FOR EACH ROW BEGIN'
108
+ ' UPDATE Settings'
109
+ ' SET value = value + NEW.size - OLD.size'
110
+ " WHERE key = 'size'; END",
111
+ ),
112
+ (
113
+ 'Settings_size_delete',
114
+ 'AFTER DELETE ON Cache FOR EACH ROW BEGIN'
115
+ ' UPDATE Settings SET value = value - OLD.size'
116
+ " WHERE key = 'size'; END",
117
+ ),
118
+ )
119
+
120
+ EVICTION_POLICY = {
121
+ 'none': {
122
+ 'init': None,
123
+ 'get': None,
124
+ 'cull': None,
125
+ },
126
+ 'least-recently-stored': {
127
+ 'init': (
128
+ 'CREATE INDEX IF NOT EXISTS Cache_store_time ON'
129
+ ' Cache (store_time)'
130
+ ),
131
+ 'get': None,
132
+ 'cull': 'SELECT {fields} FROM Cache ORDER BY store_time LIMIT ?',
133
+ },
134
+ 'least-recently-used': {
135
+ 'init': (
136
+ 'CREATE INDEX IF NOT EXISTS Cache_access_time ON'
137
+ ' Cache (access_time)'
138
+ ),
139
+ 'get': 'access_time = {now}',
140
+ 'cull': 'SELECT {fields} FROM Cache ORDER BY access_time LIMIT ?',
141
+ },
142
+ 'least-frequently-used': {
143
+ 'init': (
144
+ 'CREATE INDEX IF NOT EXISTS Cache_access_count ON'
145
+ ' Cache (access_count)'
146
+ ),
147
+ 'get': 'access_count = access_count + 1',
148
+ 'cull': 'SELECT {fields} FROM Cache ORDER BY access_count LIMIT ?',
149
+ },
150
+ }
151
+
152
+
153
+ def native_filename(filename):
154
+ """Return `filename` with the path separators of the current platform.
155
+
156
+ The Cache table's `filename` column stores a path relative to the cache
157
+ directory with the separator of the operating system that wrote the
158
+ value, so a cache written on Windows holds backslashes and a cache written
159
+ anywhere else holds slashes (upstream issue 304). Both forms are accepted:
160
+ on Windows, slashes become backslashes so paths compare equal to those
161
+ returned by `os.walk`; elsewhere, backslashes become slashes. Filenames are
162
+ generated by :meth:`Disk.filename`, which never uses either character
163
+ except as a separator, so no stored name is misread.
164
+
165
+ :param str filename: filename relative to the cache directory
166
+ :return: filename with native separators
167
+
168
+ """
169
+ if os.sep == '/':
170
+ return filename.replace('\\', '/')
171
+ return filename.replace('/', os.sep)
172
+
173
+
174
+ def _makedirs_private(directory):
175
+ """Create `directory` and its missing parents with mode ``0o700``.
176
+
177
+ Unlike :func:`os.makedirs`, the mode applies to every directory created,
178
+ not only to the last one, so no directory the cache creates is readable
179
+ by other users. Directories that already exist keep their mode. Windows
180
+ ignores the mode bits.
181
+
182
+ """
183
+ parent = op.dirname(directory)
184
+
185
+ if parent and parent != directory and not op.isdir(parent):
186
+ _makedirs_private(parent)
187
+
188
+ with cl.suppress(FileExistsError):
189
+ os.mkdir(directory, 0o700)
190
+
191
+
192
+ class Disk:
193
+ """Cache key and value serialization for SQLite database and files."""
194
+
195
+ def __init__(self, directory, min_file_size=0, pickle_protocol=0):
196
+ """Initialize disk instance.
197
+
198
+ :param str directory: directory path
199
+ :param int min_file_size: minimum size for file use
200
+ :param int pickle_protocol: pickle protocol for serialization
201
+
202
+ """
203
+ self._directory = directory
204
+ self.min_file_size = min_file_size
205
+ self.pickle_protocol = pickle_protocol
206
+
207
+ def hash(self, key):
208
+ """Compute portable hash for `key`.
209
+
210
+ :param key: key to hash
211
+ :return: hash value
212
+
213
+ """
214
+ mask = 0xFFFFFFFF
215
+ disk_key, _ = self.put(key)
216
+ type_disk_key = type(disk_key)
217
+
218
+ if type_disk_key is sqlite3.Binary:
219
+ return zlib.adler32(disk_key) & mask
220
+ elif type_disk_key is str:
221
+ return zlib.adler32(disk_key.encode('utf-8')) & mask # noqa
222
+ elif type_disk_key is int:
223
+ return disk_key % mask
224
+ else:
225
+ assert type_disk_key is float
226
+ return zlib.adler32(struct.pack('!d', disk_key)) & mask
227
+
228
+ def put(self, key):
229
+ """Convert `key` to fields key and raw for Cache table.
230
+
231
+ :param key: key to convert
232
+ :return: (database key, raw boolean) pair
233
+
234
+ """
235
+ # pylint: disable=unidiomatic-typecheck
236
+ type_key = type(key)
237
+
238
+ if type_key is bytes:
239
+ return sqlite3.Binary(key), True
240
+ elif (
241
+ (type_key is str)
242
+ or (
243
+ type_key is int
244
+ and -9223372036854775808 <= key <= 9223372036854775807
245
+ )
246
+ or (type_key is float)
247
+ ):
248
+ return key, True
249
+ else:
250
+ data = pickle.dumps(key, protocol=self.pickle_protocol)
251
+ result = pickletools.optimize(data)
252
+ return sqlite3.Binary(result), False
253
+
254
+ def get(self, key, raw):
255
+ """Convert fields `key` and `raw` from Cache table to key.
256
+
257
+ :param key: database key to convert
258
+ :param bool raw: flag indicating raw database storage
259
+ :return: corresponding Python key
260
+
261
+ """
262
+ # pylint: disable=unidiomatic-typecheck
263
+ if raw:
264
+ return bytes(key) if type(key) is sqlite3.Binary else key
265
+ else:
266
+ return pickle.load(io.BytesIO(key))
267
+
268
+ def store(self, value, read, key=UNKNOWN):
269
+ """Convert `value` to fields size, mode, filename, and value for Cache
270
+ table.
271
+
272
+ :param value: value to convert
273
+ :param bool read: True when value is file-like object
274
+ :param key: key for item (default UNKNOWN)
275
+ :return: (size, mode, filename, value) tuple for Cache table
276
+
277
+ """
278
+ # pylint: disable=unidiomatic-typecheck
279
+ type_value = type(value)
280
+ min_file_size = self.min_file_size
281
+
282
+ if (
283
+ (type_value is str and len(value) < min_file_size)
284
+ or (
285
+ type_value is int
286
+ and -9223372036854775808 <= value <= 9223372036854775807
287
+ )
288
+ or (type_value is float)
289
+ ):
290
+ return 0, MODE_RAW, None, value
291
+ elif type_value is bytes:
292
+ if len(value) < min_file_size:
293
+ return 0, MODE_RAW, None, sqlite3.Binary(value)
294
+ else:
295
+ filename, full_path = self.filename(key, value)
296
+ self._write(full_path, value, 'xb')
297
+ return len(value), MODE_BINARY, filename, None
298
+ elif type_value is str:
299
+ filename, full_path = self.filename(key, value)
300
+ self._write(full_path, io.StringIO(value), 'x', 'UTF-8')
301
+ size = op.getsize(full_path)
302
+ return size, MODE_TEXT, filename, None
303
+ elif read:
304
+ reader = ft.partial(value.read, 2**22)
305
+ filename, full_path = self.filename(key, value)
306
+ iterator = iter(reader, b'')
307
+ size = self._write(full_path, iterator, 'xb')
308
+ return size, MODE_BINARY, filename, None
309
+ else:
310
+ result = pickle.dumps(value, protocol=self.pickle_protocol)
311
+
312
+ if len(result) < min_file_size:
313
+ return 0, MODE_PICKLE, None, sqlite3.Binary(result)
314
+ else:
315
+ filename, full_path = self.filename(key, value)
316
+ self._write(full_path, result, 'xb')
317
+ return len(result), MODE_PICKLE, filename, None
318
+
319
+ def _write(self, full_path, iterator_or_bytes, mode, encoding=None):
320
+ full_dir, _ = op.split(full_path)
321
+
322
+ for count in range(1, 11):
323
+ with cl.suppress(OSError):
324
+ _makedirs_private(full_dir)
325
+
326
+ try:
327
+ # Another cache may have deleted the directory before
328
+ # the file could be opened.
329
+ writer = open(full_path, mode, encoding=encoding)
330
+ except OSError:
331
+ if count == 10:
332
+ # Give up after 10 tries to open the file.
333
+ raise
334
+ continue
335
+
336
+ with writer:
337
+ if isinstance(iterator_or_bytes, bytes):
338
+ writer.write(iterator_or_bytes)
339
+ return len(iterator_or_bytes)
340
+ else:
341
+ size = 0
342
+ for chunk in iterator_or_bytes:
343
+ size += len(chunk)
344
+ writer.write(chunk)
345
+ return size
346
+
347
+ def fetch(self, mode, filename, value, read):
348
+ """Convert fields `mode`, `filename`, and `value` from Cache table to
349
+ value.
350
+
351
+ :param int mode: value mode raw, binary, text, or pickle
352
+ :param str filename: filename of corresponding value, with the path
353
+ separators of the operating system that stored it
354
+ :param value: database value
355
+ :param bool read: when True, return an open file handle
356
+ :return: corresponding Python value
357
+ :raises: IOError if the value cannot be read
358
+
359
+ """
360
+ # pylint: disable=unidiomatic-typecheck,consider-using-with
361
+ if mode == MODE_RAW:
362
+ return bytes(value) if type(value) is sqlite3.Binary else value
363
+ elif mode == MODE_BINARY:
364
+ full_path = op.join(self._directory, native_filename(filename))
365
+ if read:
366
+ return open(full_path, 'rb')
367
+ else:
368
+ with open(full_path, 'rb') as reader:
369
+ return reader.read()
370
+ elif mode == MODE_TEXT:
371
+ full_path = op.join(self._directory, native_filename(filename))
372
+ with open(full_path, 'r', encoding='UTF-8') as reader:
373
+ return reader.read()
374
+ elif mode == MODE_PICKLE:
375
+ if value is None:
376
+ full_path = op.join(self._directory, native_filename(filename))
377
+ with open(full_path, 'rb') as reader:
378
+ return pickle.load(reader)
379
+ else:
380
+ return pickle.load(io.BytesIO(value))
381
+
382
+ def filename(self, key=UNKNOWN, value=UNKNOWN):
383
+ """Return filename and full-path tuple for file storage.
384
+
385
+ Filename will be a randomly generated 28 character hexadecimal string
386
+ with ".val" suffixed. Two levels of sub-directories will be used to
387
+ reduce the size of directories. On older filesystems, lookups in
388
+ directories with many files may be slow.
389
+
390
+ The default implementation ignores the `key` and `value` parameters.
391
+
392
+ In some scenarios, for example :meth:`Cache.push
393
+ <diskcache.Cache.push>`, the `key` or `value` may not be known when the
394
+ item is stored in the cache.
395
+
396
+ :param key: key for item (default UNKNOWN)
397
+ :param value: value for item (default UNKNOWN)
398
+
399
+ """
400
+ # pylint: disable=unused-argument
401
+ hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8')
402
+ sub_dir = op.join(hex_name[:2], hex_name[2:4])
403
+ name = hex_name[4:] + '.val'
404
+ filename = op.join(sub_dir, name)
405
+ full_path = op.join(self._directory, filename)
406
+ return filename, full_path
407
+
408
+ def remove(self, file_path):
409
+ """Remove a file given by `file_path`.
410
+
411
+ This method is cross-thread and cross-process safe. If an OSError
412
+ occurs, it is suppressed.
413
+
414
+ :param str file_path: relative path to file, with the path separators
415
+ of the operating system that stored it
416
+
417
+ """
418
+ full_path = op.join(self._directory, native_filename(file_path))
419
+ full_dir, _ = op.split(full_path)
420
+
421
+ # Suppress OSError that may occur if two caches attempt to delete the
422
+ # same file or directory at the same time.
423
+
424
+ with cl.suppress(OSError):
425
+ os.remove(full_path)
426
+
427
+ with cl.suppress(OSError):
428
+ os.removedirs(full_dir)
429
+
430
+
431
+ class JSONDisk(Disk):
432
+ """Cache key and value using JSON serialization with zlib compression."""
433
+
434
+ def __init__(self, directory, compress_level=1, **kwargs):
435
+ """Initialize JSON disk instance.
436
+
437
+ Keys and values are compressed using the zlib library. The
438
+ `compress_level` is an integer from 0 to 9 controlling the level of
439
+ compression; 1 is fastest and produces the least compression, 9 is
440
+ slowest and produces the most compression, and 0 is no compression.
441
+
442
+ :param str directory: directory path
443
+ :param int compress_level: zlib compression level (default 1)
444
+ :param kwargs: super class arguments
445
+
446
+ """
447
+ self.compress_level = compress_level
448
+ super().__init__(directory, **kwargs)
449
+
450
+ def put(self, key):
451
+ json_bytes = json.dumps(key).encode('utf-8')
452
+ data = zlib.compress(json_bytes, self.compress_level)
453
+ return super().put(data)
454
+
455
+ def get(self, key, raw):
456
+ data = super().get(key, raw)
457
+ return json.loads(zlib.decompress(data).decode('utf-8'))
458
+
459
+ def store(self, value, read, key=UNKNOWN):
460
+ if not read:
461
+ json_bytes = json.dumps(value).encode('utf-8')
462
+ value = zlib.compress(json_bytes, self.compress_level)
463
+ return super().store(value, read, key=key)
464
+
465
+ def fetch(self, mode, filename, value, read):
466
+ data = super().fetch(mode, filename, value, read)
467
+ if not read:
468
+ data = json.loads(zlib.decompress(data).decode('utf-8'))
469
+ return data
470
+
471
+
472
+ # Names that SafeDisk lets pickle resolve while reading, keyed by the module
473
+ # recorded in the pickle. Protocols 0 to 2 record the Python 2 module names
474
+ # and use copyreg._reconstructor and _codecs.encode to rebuild objects, so
475
+ # those spellings and helpers are included. Every entry is a standard-library
476
+ # constructor whose call cannot run code beyond building the object itself.
477
+ # Helpers that reach attributes, such as builtins.getattr, which pickle uses
478
+ # for zoneinfo.ZoneInfo, are deliberately absent.
479
+
480
+ _SAFE_BUILTINS = frozenset(
481
+ {
482
+ 'bytearray',
483
+ 'bytes',
484
+ 'complex',
485
+ 'dict',
486
+ 'float',
487
+ 'frozenset',
488
+ 'int',
489
+ 'list',
490
+ 'object',
491
+ 'range',
492
+ 'set',
493
+ 'slice',
494
+ 'str',
495
+ 'tuple',
496
+ }
497
+ )
498
+
499
+ SAFE_PICKLE_NAMES = {
500
+ 'builtins': _SAFE_BUILTINS,
501
+ '__builtin__': _SAFE_BUILTINS | {'long', 'unicode', 'xrange'},
502
+ 'collections': frozenset(
503
+ {'Counter', 'OrderedDict', 'defaultdict', 'deque'}
504
+ ),
505
+ 'copyreg': frozenset({'_reconstructor'}),
506
+ 'copy_reg': frozenset({'_reconstructor'}),
507
+ '_codecs': frozenset({'encode'}),
508
+ 'datetime': frozenset(
509
+ {'date', 'datetime', 'time', 'timedelta', 'timezone'}
510
+ ),
511
+ 'decimal': frozenset({'Decimal'}),
512
+ 'fractions': frozenset({'Fraction'}),
513
+ 'uuid': frozenset({'UUID'}),
514
+ }
515
+
516
+ # Python types that SafeDisk accepts for writing: exactly the types that the
517
+ # names above read back. Subclasses are rejected because pickle records the
518
+ # subclass, which the reader would refuse.
519
+
520
+ SAFE_TYPES = frozenset(
521
+ {
522
+ type(None),
523
+ bool,
524
+ int,
525
+ float,
526
+ complex,
527
+ str,
528
+ bytes,
529
+ bytearray,
530
+ list,
531
+ tuple,
532
+ dict,
533
+ set,
534
+ frozenset,
535
+ range,
536
+ slice,
537
+ co.Counter,
538
+ co.OrderedDict,
539
+ co.defaultdict,
540
+ co.deque,
541
+ dt.date,
542
+ dt.datetime,
543
+ dt.time,
544
+ dt.timedelta,
545
+ dt.timezone,
546
+ decimal.Decimal,
547
+ fractions.Fraction,
548
+ uuid.UUID,
549
+ }
550
+ )
551
+
552
+ _SAFE_MAPPINGS = (dict, co.Counter, co.OrderedDict, co.defaultdict)
553
+ _SAFE_SEQUENCES = (list, tuple, set, frozenset, co.deque)
554
+ _SAFE_TZ_TYPES = (type(None), dt.timezone)
555
+
556
+
557
+ class SafeUnpickler(pickle.Unpickler):
558
+ """Unpickler that resolves only the names in SAFE_PICKLE_NAMES.
559
+
560
+ Every pickle opcode that can call code (GLOBAL, STACK_GLOBAL, INST, OBJ,
561
+ NEWOBJ, NEWOBJ_EX and REDUCE) obtains its callable through `find_class`,
562
+ so restricting `find_class` to a fixed set of constructors is enough to
563
+ keep a crafted pickle from running code.
564
+
565
+ """
566
+
567
+ def find_class(self, module, name):
568
+ if name in SAFE_PICKLE_NAMES.get(module, ()):
569
+ return super().find_class(module, name)
570
+ raise pickle.UnpicklingError(
571
+ 'SafeDisk refuses to unpickle %s.%s' % (module, name)
572
+ )
573
+
574
+
575
+ class SafeDisk(Disk):
576
+ """Disk that reads back only an allowlist of standard-library types.
577
+
578
+ The default :class:`Disk` stores keys and values that SQLite cannot hold
579
+ natively as pickles and reads them back with :func:`pickle.load`, so
580
+ anyone who can write into the cache directory can run code in the process
581
+ that reads from it (CVE-2025-69872). :class:`SafeDisk` closes that path:
582
+
583
+ * Pickled keys and values are read with an unpickler that resolves only
584
+ the standard-library constructors in ``SAFE_PICKLE_NAMES`` and raises
585
+ :exc:`pickle.UnpicklingError` for anything else.
586
+ * Keys and values are checked before they are written, and a key or value
587
+ whose type the reader would refuse raises :exc:`TypeError` at write
588
+ time instead of failing later at read time.
589
+ * Paths read from the ``filename`` column are confined to the cache
590
+ directory, and a path that escapes it raises :exc:`ValueError`.
591
+
592
+ The on-disk format is the one :class:`Disk` writes, so a directory
593
+ written with :class:`SafeDisk` opens with :class:`Disk` and the reverse,
594
+ as long as the values are within the allowlist. Nothing about the choice
595
+ of disk is persisted; select it with ``Cache(directory, disk=SafeDisk)``,
596
+ ``FanoutCache(directory, disk=SafeDisk)``, ``Deque(disk=SafeDisk)``,
597
+ ``Index(disk=SafeDisk)`` or the ``DISK`` option of the Django backend.
598
+
599
+ Objects outside the allowlist, such as instances of your own classes,
600
+ Django model instances and responses, and the ``type`` objects that
601
+ ``memoize(typed=True)`` puts into keys, need the default :class:`Disk`.
602
+
603
+ """
604
+
605
+ def __init__(self, directory, min_file_size=0, pickle_protocol=0):
606
+ super().__init__(directory, min_file_size, pickle_protocol)
607
+ self._real_directory = op.realpath(directory)
608
+
609
+ def _check(self, obj, what):
610
+ """Raise TypeError unless every object in `obj` is in SAFE_TYPES."""
611
+ seen = set()
612
+ stack = [obj]
613
+
614
+ while stack:
615
+ item = stack.pop()
616
+ type_item = type(item)
617
+
618
+ if type_item not in SAFE_TYPES:
619
+ raise TypeError(
620
+ 'SafeDisk cannot store %s of type %s.%s'
621
+ % (what, type_item.__module__, type_item.__qualname__)
622
+ )
623
+
624
+ if type_item in _SAFE_MAPPINGS:
625
+ if id(item) in seen:
626
+ continue
627
+ seen.add(id(item))
628
+ if type_item is co.defaultdict:
629
+ factory = item.default_factory
630
+ if factory is not None and factory not in SAFE_TYPES:
631
+ raise TypeError(
632
+ 'SafeDisk cannot store %s with defaultdict'
633
+ ' factory %r' % (what, factory)
634
+ )
635
+ stack.extend(item.keys())
636
+ stack.extend(item.values())
637
+ elif type_item in _SAFE_SEQUENCES:
638
+ if id(item) in seen:
639
+ continue
640
+ seen.add(id(item))
641
+ stack.extend(item)
642
+ elif type_item in (dt.datetime, dt.time):
643
+ if type(item.tzinfo) not in _SAFE_TZ_TYPES:
644
+ raise TypeError(
645
+ 'SafeDisk cannot store %s with tzinfo of type %s.%s'
646
+ % (
647
+ what,
648
+ type(item.tzinfo).__module__,
649
+ type(item.tzinfo).__qualname__,
650
+ )
651
+ )
652
+
653
+ def _confine(self, filename):
654
+ """Raise ValueError unless `filename` stays in the cache directory."""
655
+ normalized = str(filename).replace('\\', '/')
656
+
657
+ if (
658
+ normalized.startswith('/')
659
+ or op.isabs(normalized)
660
+ or op.splitdrive(normalized)[0]
661
+ or '..' in normalized.split('/')
662
+ ):
663
+ raise ValueError(
664
+ 'SafeDisk refuses the path %r outside the cache directory'
665
+ % (filename,)
666
+ )
667
+
668
+ directory = self._real_directory
669
+ full_path = op.realpath(op.join(directory, filename))
670
+
671
+ try:
672
+ inside = op.commonpath((directory, full_path)) == directory
673
+ except ValueError: # Different drives on Windows.
674
+ inside = False
675
+
676
+ if not inside:
677
+ raise ValueError(
678
+ 'SafeDisk refuses the path %r outside the cache directory'
679
+ % (filename,)
680
+ )
681
+
682
+ def put(self, key):
683
+ # pylint: disable=unidiomatic-typecheck
684
+ if type(key) not in (bytes, str, int, float):
685
+ self._check(key, 'key')
686
+ return super().put(key)
687
+
688
+ def get(self, key, raw):
689
+ if raw:
690
+ return super().get(key, raw)
691
+ return SafeUnpickler(io.BytesIO(key)).load()
692
+
693
+ def store(self, value, read, key=UNKNOWN):
694
+ # pylint: disable=unidiomatic-typecheck
695
+ if not read and type(value) not in (bytes, str, int, float):
696
+ self._check(value, 'value')
697
+ return super().store(value, read, key=key)
698
+
699
+ def fetch(self, mode, filename, value, read):
700
+ if filename is not None:
701
+ self._confine(filename)
702
+
703
+ if mode == MODE_PICKLE:
704
+ if value is None:
705
+ value = super().fetch(MODE_BINARY, filename, None, False)
706
+ return SafeUnpickler(io.BytesIO(value)).load()
707
+
708
+ return super().fetch(mode, filename, value, read)
709
+
710
+ def remove(self, file_path):
711
+ self._confine(file_path)
712
+ super().remove(file_path)
713
+
714
+
715
+ def _meta_result(value, expire_time, tag, store_time, meta=(None, None, None)):
716
+ """Return `value`, or a tuple of `value` and the requested metadata.
717
+
718
+ The tuple holds, after the value, the expire time when `expire_time` is
719
+ true, the tag when `tag` is true and the store time when `store_time` is
720
+ true, in that order. `meta` supplies the three database values; the
721
+ default is used when the key is missing.
722
+
723
+ """
724
+ if not (expire_time or tag or store_time):
725
+ return value
726
+ db_expire_time, db_tag, db_store_time = meta
727
+ result = (value,)
728
+ if expire_time:
729
+ result += (db_expire_time,)
730
+ if tag:
731
+ result += (db_tag,)
732
+ if store_time:
733
+ result += (db_store_time,)
734
+ return result
735
+
736
+
737
+ class Timeout(Exception):
738
+ """Database timeout expired."""
739
+
740
+
741
+ class ReadOnlyError(sqlite3.OperationalError): # pylint: disable=R0903
742
+ """Write attempted on a read-only cache.
743
+
744
+ Raised by every method that would modify a cache opened read-only, either
745
+ explicitly with ``Cache(directory, read_only=True)`` or because the cache
746
+ database or its directory is not writable. A subclass of
747
+ :exc:`sqlite3.OperationalError`, the error SQLite itself raises for
748
+ writes to a read-only database, so existing handlers keep working.
749
+ """
750
+
751
+
752
+ # Pragmas that are stored in the database file rather than set per
753
+ # connection; a read-only cache leaves them as the file has them.
754
+ _PERSISTENT_PRAGMAS = frozenset({'auto_vacuum', 'journal_mode'})
755
+
756
+ # SQL condition selecting rows that have not expired, evaluated by SQLite
757
+ # itself so it can be embedded in a statement without a parameter. Used by
758
+ # read-only caches, which cannot delete expired rows while reading.
759
+ _LIVE_CONDITION = (
760
+ '(expire_time IS NULL'
761
+ " OR expire_time > (julianday('now') - 2440587.5) * 86400.0)"
762
+ )
763
+
764
+
765
+ class UnknownFileWarning(UserWarning):
766
+ """Warning used by Cache.check for unknown files."""
767
+
768
+
769
+ class EmptyDirWarning(UserWarning):
770
+ """Warning used by Cache.check for empty directories."""
771
+
772
+
773
+ def args_to_key(base, args, kwargs, typed, ignore):
774
+ """Create cache key out of function arguments.
775
+
776
+ :param tuple base: base of key
777
+ :param tuple args: function arguments
778
+ :param dict kwargs: function keyword arguments
779
+ :param bool typed: include types in cache key
780
+ :param set ignore: positional or keyword args to ignore
781
+ :return: cache key tuple
782
+
783
+ """
784
+ args = tuple(arg for index, arg in enumerate(args) if index not in ignore)
785
+ key = base + args + (None,)
786
+
787
+ if kwargs:
788
+ kwargs = {key: val for key, val in kwargs.items() if key not in ignore}
789
+ sorted_items = sorted(kwargs.items())
790
+
791
+ for item in sorted_items:
792
+ key += item
793
+
794
+ if typed:
795
+ key += tuple(type(arg) for arg in args)
796
+
797
+ if kwargs:
798
+ key += tuple(type(value) for _, value in sorted_items)
799
+
800
+ return key
801
+
802
+
803
+ _POSITIONAL_KINDS = (
804
+ inspect.Parameter.POSITIONAL_ONLY,
805
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
806
+ )
807
+
808
+
809
+ def resolve_ignore(func, ignore):
810
+ """Expand `ignore` with the positions of the parameters it names.
811
+
812
+ `args_to_key` drops positional arguments by index and keyword arguments by
813
+ name, so a name in `ignore` used to match an argument only when the caller
814
+ passed it by keyword (upstream issue 240). Adding the position of each
815
+ positional parameter with that name makes the name match the argument
816
+ however it is passed. Keys of calls that do not pass an ignored parameter
817
+ positionally are unchanged.
818
+
819
+ :param func: callable being memoized
820
+ :param set ignore: positional or keyword args to ignore
821
+ :return: `ignore`, or a frozenset of `ignore` plus the resolved positions
822
+
823
+ """
824
+ if not ignore:
825
+ return ignore
826
+
827
+ try:
828
+ parameters = inspect.signature(func).parameters.values()
829
+ except (TypeError, ValueError): # No signature, like some builtins.
830
+ return ignore
831
+
832
+ positions = set()
833
+
834
+ for index, parameter in enumerate(parameters):
835
+ if parameter.kind not in _POSITIONAL_KINDS:
836
+ break
837
+ if parameter.name in ignore:
838
+ positions.add(index)
839
+
840
+ if not positions:
841
+ return ignore
842
+
843
+ return frozenset(ignore) | positions
844
+
845
+
846
+ class _ThreadSentinel: # pylint: disable=too-few-public-methods
847
+ """Marker kept in a Cache's thread-local state next to its connection.
848
+
849
+ Thread-local state is destroyed when its thread exits, so once a weak
850
+ reference to the sentinel is dead the connection's thread is gone and
851
+ the connection may be closed from another thread.
852
+ """
853
+
854
+ __slots__ = ('__weakref__',)
855
+
856
+
857
+ class _Connections:
858
+ """Registry of the SQLite connections one Cache opened in this process.
859
+
860
+ Connections are opened per thread (see ``Cache._con``). The registry
861
+ lets ``Cache.close`` and garbage collection close the connections of
862
+ threads that have already exited, and lets a forked child process
863
+ forget the connections it inherited without closing them.
864
+ """
865
+
866
+ def __init__(self):
867
+ self._lock = threading.Lock()
868
+ self._items = {}
869
+ self.pid = os.getpid()
870
+
871
+ def add(self, ident, sentinel, con):
872
+ """Register `con` for thread `ident`; return connections to close.
873
+
874
+ The returned connections belong to threads that have exited, or are
875
+ an earlier connection of the same thread whose thread-local state
876
+ was lost (a foreign thread that got a new interpreter thread state).
877
+ """
878
+ with self._lock:
879
+ stale = self._pop_stale()
880
+ previous = self._items.pop(ident, None)
881
+ self._items[ident] = (weakref.ref(sentinel), con)
882
+ if previous is not None and previous[1] is not con:
883
+ stale.append(previous[1])
884
+ return stale
885
+
886
+ def pop(self, ident):
887
+ """Remove the connection of thread `ident` and return it with the
888
+ connections of threads that have exited, all to be closed."""
889
+ with self._lock:
890
+ stale = self._pop_stale()
891
+ item = self._items.pop(ident, None)
892
+ if item is not None:
893
+ stale.append(item[1])
894
+ return stale
895
+
896
+ def _pop_stale(self):
897
+ # The caller holds the lock.
898
+ stale = []
899
+ for ident, (ref, con) in list(self._items.items()):
900
+ if ref() is None:
901
+ del self._items[ident]
902
+ stale.append(con)
903
+ return stale
904
+
905
+ def close_all(self):
906
+ """Close every registered connection.
907
+
908
+ Runs when the Cache is garbage collected. In a child process the
909
+ connections were inherited and are discarded instead.
910
+ """
911
+ if os.getpid() != self.pid:
912
+ self.discard()
913
+ return
914
+ with self._lock:
915
+ cons = [con for _, con in self._items.values()]
916
+ self._items.clear()
917
+ for con in cons:
918
+ con.close()
919
+
920
+ def discard(self):
921
+ """Forget the connections after a fork without closing them.
922
+
923
+ SQLite forbids a child process from using, or even closing, a
924
+ connection the parent opened: the child inherits the parent's lock
925
+ state, and closing may roll back or unlock underneath the parent
926
+ and corrupt the database. The connections stay referenced for the
927
+ life of the child so garbage collection never closes them either;
928
+ their file descriptors leak on purpose, as SQLite recommends.
929
+ """
930
+ _INHERITED_CONNECTIONS.extend(con for _, con in self._items.values())
931
+ self._items = {}
932
+ self._lock = threading.Lock() # The parent's lock may be held.
933
+ self.pid = os.getpid()
934
+
935
+
936
+ class _ForkGate:
937
+ """Delay ``os.fork`` while another thread is inside a transaction.
938
+
939
+ A child process inherits the parent's SQLite state as of the fork. A
940
+ transaction in flight in another thread leaves the child's copy of that
941
+ state holding a write lock that nothing in the child ever releases, so
942
+ the child's own writes to that database would fail with ``Timeout``.
943
+ The gate holds new transactions back and makes fork wait, up to
944
+ `timeout` seconds, until the transactions in flight in other threads
945
+ finish; the held-back transactions proceed once the fork is done. The
946
+ forking thread's own transaction cannot be waited for and is not
947
+ counted; forking inside a transaction is unsupported.
948
+ """
949
+
950
+ timeout = 60
951
+
952
+ def __init__(self):
953
+ self._cond = threading.Condition(threading.Lock())
954
+ self._active = 0
955
+ self._forking = False
956
+ self._local = threading.local()
957
+
958
+ def __enter__(self):
959
+ depth = getattr(self._local, 'depth', 0)
960
+ if not depth:
961
+ with self._cond:
962
+ while self._forking:
963
+ self._cond.wait()
964
+ self._active += 1
965
+ self._local.depth = depth + 1
966
+
967
+ def __exit__(self, *exception):
968
+ depth = getattr(self._local, 'depth', 0) - 1
969
+ if depth < 0:
970
+ return # A child process reset the gate during a transaction.
971
+ self._local.depth = depth
972
+ if not depth:
973
+ with self._cond:
974
+ self._active -= 1
975
+ if not self._active:
976
+ self._cond.notify_all()
977
+
978
+ def before_fork(self):
979
+ """Hold new transactions back, then wait for the active ones."""
980
+ own = 1 if getattr(self._local, 'depth', 0) else 0
981
+ deadline = time.monotonic() + self.timeout
982
+ with self._cond:
983
+ while self._forking: # Another thread is forking.
984
+ self._cond.wait()
985
+ self._forking = True
986
+ while self._active > own:
987
+ remaining = deadline - time.monotonic()
988
+ if remaining <= 0:
989
+ warnings.warn(
990
+ 'forking while a Cache transaction is in progress in'
991
+ ' another thread; the child process may be unable'
992
+ ' to write to that cache',
993
+ RuntimeWarning,
994
+ stacklevel=2,
995
+ )
996
+ break
997
+ self._cond.wait(remaining)
998
+
999
+ def after_fork_parent(self):
1000
+ """Let transactions proceed again."""
1001
+ with self._cond:
1002
+ self._forking = False
1003
+ self._cond.notify_all()
1004
+
1005
+ def after_fork_child(self):
1006
+ """Start over: the child has one thread and no transactions."""
1007
+ self._cond = threading.Condition(threading.Lock())
1008
+ self._active = 0
1009
+ self._forking = False
1010
+ self._local = threading.local()
1011
+
1012
+
1013
+ _FORK_GATE = _ForkGate()
1014
+
1015
+ # Every live Cache, so a child process can forget inherited connections.
1016
+ _CACHES: 'weakref.WeakSet[Cache]' = weakref.WeakSet()
1017
+
1018
+ # Connections inherited from a parent process, kept open on purpose.
1019
+ _INHERITED_CONNECTIONS: list[sqlite3.Connection] = []
1020
+
1021
+
1022
+ def _before_fork():
1023
+ # SQLite advises closing every connection before fork. The gate waits
1024
+ # for other threads' transactions; then each cache closes the forking
1025
+ # thread's own connection and those of exited threads, which the
1026
+ # parent reopens on next use. Connections held by other live threads
1027
+ # cannot be closed here and are discarded in the child.
1028
+ _FORK_GATE.before_fork()
1029
+ for cache in list(_CACHES):
1030
+ cache._close_before_fork() # pylint: disable=protected-access
1031
+
1032
+
1033
+ def _after_fork_in_child():
1034
+ _FORK_GATE.after_fork_child()
1035
+ for cache in list(_CACHES):
1036
+ cache._discard_connections() # pylint: disable=protected-access
1037
+
1038
+
1039
+ if hasattr(os, 'register_at_fork'):
1040
+ os.register_at_fork(
1041
+ before=_before_fork,
1042
+ after_in_parent=_FORK_GATE.after_fork_parent,
1043
+ after_in_child=_after_fork_in_child,
1044
+ )
1045
+
1046
+
1047
+ class Cache:
1048
+ """Disk and file backed cache."""
1049
+
1050
+ def __init__(
1051
+ self,
1052
+ directory=None,
1053
+ timeout=60,
1054
+ disk=Disk,
1055
+ *,
1056
+ read_only=False,
1057
+ **settings
1058
+ ):
1059
+ """Initialize cache instance.
1060
+
1061
+ :param str directory: cache directory
1062
+ :param float timeout: SQLite connection timeout
1063
+ :param disk: Disk type or subclass for serialization
1064
+ :param bool read_only: open the cache read-only (default False)
1065
+ :param settings: any of DEFAULT_SETTINGS
1066
+
1067
+ The cache directory, any missing parents and the subdirectories that
1068
+ hold large values are created with mode ``0o700``, so only the owner
1069
+ can read them. A directory that already exists keeps its mode; the
1070
+ cache never changes permissions. Windows ignores the mode bits.
1071
+
1072
+ Opening an existing cache writes nothing unless a setting differs
1073
+ from the stored one. A cache whose database file or directory is not
1074
+ writable opens read-only, as does a cache opened with `read_only`;
1075
+ reads work and every method that would write raises
1076
+ :exc:`ReadOnlyError`. The read-only state is not persisted and
1077
+ `read_only` is not a setting. See the :ref:`tutorial
1078
+ <tutorial-read-only>` for details.
1079
+
1080
+ """
1081
+ try:
1082
+ assert issubclass(disk, Disk)
1083
+ except (TypeError, AssertionError):
1084
+ raise ValueError('disk must subclass diskcache.Disk') from None
1085
+
1086
+ if directory is None:
1087
+ directory = tempfile.mkdtemp(prefix='diskcache-')
1088
+ directory = str(directory)
1089
+ directory = op.expanduser(directory)
1090
+ directory = op.expandvars(directory)
1091
+
1092
+ self._directory = directory
1093
+ self._timeout = 0 # Manually handle retries during initialization.
1094
+ self._local = threading.local()
1095
+ self._cons = _Connections()
1096
+ self._txn_id = None
1097
+ _CACHES.add(self)
1098
+ finalizer = weakref.finalize(self, self._cons.close_all)
1099
+ finalizer.atexit = False
1100
+
1101
+ self._read_only = self._resolve_read_only(directory, read_only)
1102
+
1103
+ if not op.isdir(directory):
1104
+ try:
1105
+ _makedirs_private(op.abspath(directory))
1106
+ except OSError as error:
1107
+ if error.errno != errno.EEXIST:
1108
+ raise EnvironmentError(
1109
+ error.errno,
1110
+ 'Cache directory "%s" does not exist'
1111
+ ' and could not be created' % self._directory,
1112
+ ) from None
1113
+
1114
+ sql = self._sql_retry
1115
+
1116
+ # Setup Settings table.
1117
+
1118
+ try:
1119
+ current_settings = dict(
1120
+ sql('SELECT key, value FROM Settings').fetchall()
1121
+ )
1122
+ except sqlite3.OperationalError:
1123
+ if self._read_only:
1124
+ raise ReadOnlyError(
1125
+ 'read-only cache in %r has no Settings table' % directory
1126
+ ) from None
1127
+ current_settings = {}
1128
+
1129
+ sets = DEFAULT_SETTINGS.copy()
1130
+ sets.update(current_settings)
1131
+ sets.update(settings)
1132
+
1133
+ for key in METADATA:
1134
+ sets.pop(key, None)
1135
+
1136
+ # Chance to set pragmas before any tables are created.
1137
+
1138
+ for key, value in sorted(sets.items()):
1139
+ if key.startswith('sqlite_'):
1140
+ self.reset(key, value, update=False)
1141
+
1142
+ if not self._read_only:
1143
+ sql(
1144
+ 'CREATE TABLE IF NOT EXISTS Settings ('
1145
+ ' key TEXT NOT NULL UNIQUE,'
1146
+ ' value)'
1147
+ )
1148
+
1149
+ # Setup Disk object (must happen after settings initialized).
1150
+
1151
+ kwargs = {
1152
+ key[5:]: value
1153
+ for key, value in sets.items()
1154
+ if key.startswith('disk_')
1155
+ }
1156
+ self._disk = disk(directory, **kwargs)
1157
+
1158
+ self._init_settings(sql, sets, current_settings)
1159
+
1160
+ ((self._page_size,),) = sql('PRAGMA page_size').fetchall()
1161
+
1162
+ if self._read_only:
1163
+ # The schema, triggers and tag index are whatever the writer
1164
+ # left; reads need none of them to change.
1165
+ self.close()
1166
+ self._timeout = timeout
1167
+ self._sql # pylint: disable=pointless-statement
1168
+ return
1169
+
1170
+ self._init_schema(sql)
1171
+
1172
+ # Close and re-open database connection with given timeout.
1173
+
1174
+ self.close()
1175
+ self._timeout = timeout
1176
+ self._sql # pylint: disable=pointless-statement
1177
+
1178
+ @staticmethod
1179
+ def _resolve_read_only(directory, read_only):
1180
+ # A database that exists but cannot be written, or whose directory
1181
+ # cannot be written, is opened read-only. Opening read-only on
1182
+ # purpose needs an existing database, since nothing is created.
1183
+ db_path = op.join(directory, DBNAME)
1184
+ db_exists = op.isfile(db_path)
1185
+
1186
+ if read_only and not db_exists:
1187
+ raise ReadOnlyError(
1188
+ 'no cache database to open read-only in %r' % directory
1189
+ )
1190
+
1191
+ writable = os.access(directory, os.W_OK) and os.access(
1192
+ db_path, os.W_OK
1193
+ )
1194
+ return bool(read_only) or (db_exists and not writable)
1195
+
1196
+ def _init_settings(self, sql, sets, current_settings):
1197
+ # Set cached attributes: updates settings and sets pragmas. Only a
1198
+ # setting that is missing or differs from the stored value is
1199
+ # written, so opening an existing cache leaves the database alone.
1200
+
1201
+ for key, value in sets.items():
1202
+ if (
1203
+ not self._read_only
1204
+ and current_settings.get(key, ENOVAL) != value
1205
+ ):
1206
+ query = 'INSERT OR REPLACE INTO Settings VALUES (?, ?)'
1207
+ sql(query, (key, value))
1208
+ self.reset(key, value, update=False)
1209
+
1210
+ for key, value in METADATA.items():
1211
+ if key not in current_settings and not self._read_only:
1212
+ query = 'INSERT OR IGNORE INTO Settings VALUES (?, ?)'
1213
+ sql(query, (key, value))
1214
+ self.reset(key)
1215
+
1216
+ def _init_schema(self, sql):
1217
+ # Create the Cache table, its indexes and the triggers when they are
1218
+ # missing. Every statement is a no-op on an existing database.
1219
+
1220
+ sql(
1221
+ 'CREATE TABLE IF NOT EXISTS Cache ('
1222
+ ' rowid INTEGER PRIMARY KEY,'
1223
+ ' key BLOB,'
1224
+ ' raw INTEGER,'
1225
+ ' store_time REAL,'
1226
+ ' expire_time REAL,'
1227
+ ' access_time REAL,'
1228
+ ' access_count INTEGER DEFAULT 0,'
1229
+ ' tag BLOB,'
1230
+ ' size INTEGER DEFAULT 0,'
1231
+ ' mode INTEGER DEFAULT 0,'
1232
+ ' filename TEXT,'
1233
+ ' value BLOB)'
1234
+ )
1235
+
1236
+ sql(
1237
+ 'CREATE UNIQUE INDEX IF NOT EXISTS Cache_key_raw ON'
1238
+ ' Cache(key, raw)'
1239
+ )
1240
+
1241
+ sql(
1242
+ 'CREATE INDEX IF NOT EXISTS Cache_expire_time ON'
1243
+ ' Cache (expire_time) WHERE expire_time IS NOT NULL'
1244
+ )
1245
+
1246
+ query = EVICTION_POLICY[self.eviction_policy]['init']
1247
+
1248
+ if query is not None:
1249
+ sql(query)
1250
+
1251
+ # Use triggers to keep Metadata updated.
1252
+
1253
+ for name, definition in SETTINGS_TRIGGERS:
1254
+ sql('CREATE TRIGGER IF NOT EXISTS %s %s' % (name, definition))
1255
+
1256
+ self._update_triggers(sql)
1257
+
1258
+ # Create or drop the tag index only when the database disagrees
1259
+ # with the setting, so a reopen does not write.
1260
+
1261
+ ((has_tag_index,),) = sql(
1262
+ "SELECT COUNT(*) FROM sqlite_master WHERE type = 'index'"
1263
+ " AND name = 'Cache_tag_rowid'"
1264
+ ).fetchall()
1265
+
1266
+ if self.tag_index and not has_tag_index: # pylint: disable=no-member
1267
+ self.create_tag_index()
1268
+ elif has_tag_index and not self.tag_index: # pylint: disable=no-member
1269
+ self.drop_tag_index()
1270
+
1271
+ @property
1272
+ def directory(self):
1273
+ """Cache directory."""
1274
+ return self._directory
1275
+
1276
+ @property
1277
+ def timeout(self):
1278
+ """SQLite connection timeout value in seconds."""
1279
+ return self._timeout
1280
+
1281
+ @property
1282
+ def disk(self):
1283
+ """Disk used for serialization."""
1284
+ return self._disk
1285
+
1286
+ @property
1287
+ def read_only(self):
1288
+ """True when the cache was opened read-only."""
1289
+ return self._read_only
1290
+
1291
+ def _check_writable(self):
1292
+ if self._read_only:
1293
+ raise ReadOnlyError('cache in %r is read-only' % self._directory)
1294
+
1295
+ @property
1296
+ def _con(self):
1297
+ # Check the process ID to support forking. If it changed, the
1298
+ # connections were inherited from the parent process: forget them
1299
+ # without closing them (see _Connections.discard) and open new ones.
1300
+ # The at-fork handler normally does this before the cache is used.
1301
+
1302
+ if self._cons.pid != os.getpid():
1303
+ self._discard_connections()
1304
+
1305
+ con = getattr(self._local, 'con', None)
1306
+
1307
+ if con is None:
1308
+ con = self._connect()
1309
+
1310
+ return con
1311
+
1312
+ def _connect(self):
1313
+ # check_same_thread=False lets close() and the connection registry
1314
+ # close a connection from a thread other than the one that opened
1315
+ # it, once that thread has exited. Each connection is still used by
1316
+ # one thread at a time, which is all SQLite's multi-thread mode asks.
1317
+
1318
+ path = op.join(self._directory, DBNAME)
1319
+
1320
+ if self._read_only:
1321
+ # mode=ro refuses writes at the SQLite level. A reader of a WAL
1322
+ # database needs to create the -shm file next to it, so when the
1323
+ # directory is not writable immutable=1 skips that file and all
1324
+ # locking, which is safe only because nothing can write there.
1325
+ path = pathlib.Path(op.abspath(path)).as_uri() + '?mode=ro'
1326
+ if not os.access(self._directory, os.W_OK):
1327
+ path += '&immutable=1'
1328
+
1329
+ con = sqlite3.connect(
1330
+ path,
1331
+ timeout=self._timeout,
1332
+ isolation_level=None,
1333
+ check_same_thread=False,
1334
+ uri=self._read_only,
1335
+ )
1336
+ sentinel = _ThreadSentinel()
1337
+ stale = self._cons.add(threading.get_ident(), sentinel, con)
1338
+ self._local.con = con
1339
+ self._local.sentinel = sentinel
1340
+
1341
+ for old in stale:
1342
+ old.close()
1343
+
1344
+ # Some SQLite pragmas work on a per-connection basis so
1345
+ # query the Settings table and reset the pragmas. The
1346
+ # Settings table may not exist so catch and ignore the
1347
+ # OperationalError that may occur.
1348
+
1349
+ try:
1350
+ select = 'SELECT key, value FROM Settings'
1351
+ settings = con.execute(select).fetchall()
1352
+ except sqlite3.OperationalError:
1353
+ pass
1354
+ else:
1355
+ for key, value in settings:
1356
+ if key.startswith('sqlite_'):
1357
+ self.reset(key, value, update=False)
1358
+
1359
+ return con
1360
+
1361
+ def _close_before_fork(self):
1362
+ # Called in the parent right before fork. Inside a transaction the
1363
+ # connection cannot be closed; forking there is unsupported.
1364
+ if self._txn_id != threading.get_ident():
1365
+ self.close()
1366
+
1367
+ def _discard_connections(self):
1368
+ # In a child process: forget the connections inherited from the
1369
+ # parent without closing them, and start with fresh per-thread state.
1370
+ self._cons.discard()
1371
+ self._local = threading.local()
1372
+ self._txn_id = None
1373
+
1374
+ @property
1375
+ def _sql(self):
1376
+ return self._con.execute
1377
+
1378
+ @property
1379
+ def _sql_retry(self):
1380
+ sql = self._sql
1381
+
1382
+ # 2018-11-01 GrantJ - Some SQLite builds/versions handle
1383
+ # the SQLITE_BUSY return value and connection parameter
1384
+ # "timeout" differently. For a more reliable duration,
1385
+ # manually retry the statement for 60 seconds. Only used
1386
+ # by statements which modify the database and do not use
1387
+ # a transaction (like those in ``__init__`` or ``reset``).
1388
+ # See Issue #85 for and tests/issue_85.py for more details.
1389
+
1390
+ def _execute_with_retry(statement, *args, **kwargs):
1391
+ start = time.time()
1392
+ while True:
1393
+ try:
1394
+ return sql(statement, *args, **kwargs)
1395
+ except sqlite3.OperationalError as exc:
1396
+ if str(exc) != 'database is locked':
1397
+ raise
1398
+ diff = time.time() - start
1399
+ if diff > 60:
1400
+ raise
1401
+ time.sleep(0.001)
1402
+
1403
+ return _execute_with_retry
1404
+
1405
+ def _triggers_stale(self, sql):
1406
+ # Compare the trigger text stored in sqlite_master with the current
1407
+ # definitions. SQLite stores the statement without IF NOT EXISTS,
1408
+ # with the leading keywords in upper case and the whitespace after
1409
+ # them collapsed, so compare whitespace-normalized text.
1410
+
1411
+ names = tuple(name for name, _ in SETTINGS_TRIGGERS)
1412
+ select = (
1413
+ 'SELECT name, sql FROM sqlite_master'
1414
+ " WHERE type = 'trigger' AND name IN (%s)"
1415
+ % ', '.join('?' * len(names))
1416
+ )
1417
+ stored = dict(sql(select, names).fetchall())
1418
+
1419
+ for name, definition in SETTINGS_TRIGGERS:
1420
+ expected = 'CREATE TRIGGER %s %s' % (name, definition)
1421
+ current = ' '.join(stored.get(name, '').split())
1422
+ if current != ' '.join(expected.split()):
1423
+ return True
1424
+
1425
+ return False
1426
+
1427
+ def _update_triggers(self, sql):
1428
+ # Recreate the Settings triggers only when their stored text differs
1429
+ # from SETTINGS_TRIGGERS, as it does in caches written by versions
1430
+ # before 5.7.0 (double-quoted literals, PR #311 and issue #343).
1431
+ # The comparison keeps DDL off the common path: dropping or creating
1432
+ # a trigger bumps the schema cookie, which makes every other
1433
+ # connection re-prepare its statements, and it needs an exclusive
1434
+ # lock on the database. All five triggers are replaced inside one
1435
+ # IMMEDIATE transaction so no writer can observe a missing trigger.
1436
+
1437
+ if not self._triggers_stale(sql):
1438
+ return
1439
+
1440
+ sql('BEGIN IMMEDIATE')
1441
+
1442
+ try:
1443
+ if self._triggers_stale(sql): # Another opener may have won.
1444
+ for name, definition in SETTINGS_TRIGGERS:
1445
+ sql('DROP TRIGGER IF EXISTS %s' % name)
1446
+ sql('CREATE TRIGGER %s %s' % (name, definition))
1447
+ sql('COMMIT')
1448
+ except BaseException:
1449
+ with cl.suppress(sqlite3.OperationalError):
1450
+ sql('ROLLBACK')
1451
+ raise
1452
+
1453
+ @cl.contextmanager
1454
+ def transact(self, retry=False):
1455
+ """Context manager to perform a transaction by locking the cache.
1456
+
1457
+ While the cache is locked, no other write operation is permitted.
1458
+ Transactions should therefore be as short as possible. Read and write
1459
+ operations performed in a transaction are atomic. Read operations may
1460
+ occur concurrent to a transaction.
1461
+
1462
+ Transactions may be nested and may not be shared between threads.
1463
+
1464
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1465
+ `False` (default).
1466
+
1467
+ >>> cache = Cache()
1468
+ >>> with cache.transact(): # Atomically increment two keys.
1469
+ ... _ = cache.incr('total', 123.4)
1470
+ ... _ = cache.incr('count', 1)
1471
+ >>> with cache.transact(): # Atomically calculate average.
1472
+ ... average = cache['total'] / cache['count']
1473
+ >>> average
1474
+ 123.4
1475
+
1476
+ :param bool retry: retry if database timeout occurs (default False)
1477
+ :return: context manager for use in `with` statement
1478
+ :raises Timeout: if database timeout occurs
1479
+
1480
+ """
1481
+ with self._transact(retry=retry):
1482
+ yield
1483
+
1484
+ @cl.contextmanager
1485
+ def _transact(self, retry=False, filename=None):
1486
+ with _FORK_GATE:
1487
+ sql = self._sql
1488
+ filenames = []
1489
+ _disk_remove = self._disk.remove
1490
+ tid = threading.get_ident()
1491
+ txn_id = self._txn_id
1492
+
1493
+ if tid == txn_id:
1494
+ begin = False
1495
+ else:
1496
+ # A read-only cache cannot take the reserved lock that
1497
+ # BEGIN IMMEDIATE asks for; its transactions only read.
1498
+ begin_statement = (
1499
+ 'BEGIN' if self._read_only else 'BEGIN IMMEDIATE'
1500
+ )
1501
+
1502
+ while True:
1503
+ try:
1504
+ sql(begin_statement)
1505
+ begin = True
1506
+ self._txn_id = tid
1507
+ break
1508
+ except sqlite3.OperationalError:
1509
+ if retry:
1510
+ continue
1511
+ if filename is not None:
1512
+ _disk_remove(filename)
1513
+ raise Timeout from None
1514
+
1515
+ try:
1516
+ yield sql, filenames.append
1517
+ except BaseException as error:
1518
+ if begin:
1519
+ assert self._txn_id == tid
1520
+ self._txn_id = None
1521
+ sql('ROLLBACK')
1522
+ if (
1523
+ self._read_only
1524
+ and isinstance(error, sqlite3.OperationalError)
1525
+ and 'readonly' in str(error)
1526
+ ):
1527
+ raise ReadOnlyError(
1528
+ 'cache in %r is read-only' % self._directory
1529
+ ) from error
1530
+ raise
1531
+ else:
1532
+ if begin:
1533
+ assert self._txn_id == tid
1534
+ self._txn_id = None
1535
+ sql('COMMIT')
1536
+ for name in filenames:
1537
+ if name is not None:
1538
+ _disk_remove(name)
1539
+
1540
+ def set(self, key, value, expire=None, read=False, tag=None, retry=False):
1541
+ """Set `key` and `value` item in cache.
1542
+
1543
+ When `read` is `True`, `value` should be a file-like object opened
1544
+ for reading in binary mode.
1545
+
1546
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1547
+ `False` (default).
1548
+
1549
+ :param key: key for item
1550
+ :param value: value for item
1551
+ :param float expire: seconds until item expires
1552
+ (default None, no expiry)
1553
+ :param bool read: read value as bytes from file (default False)
1554
+ :param str tag: text to associate with key (default None)
1555
+ :param bool retry: retry if database timeout occurs (default False)
1556
+ :return: True if item was set
1557
+ :raises Timeout: if database timeout occurs
1558
+
1559
+ """
1560
+ self._check_writable()
1561
+ now = time.time()
1562
+ db_key, raw = self._disk.put(key)
1563
+ expire_time = None if expire is None else now + expire
1564
+ size, mode, filename, db_value = self._disk.store(value, read, key=key)
1565
+ columns = (expire_time, tag, size, mode, filename, db_value)
1566
+
1567
+ # The order of SELECT, UPDATE, and INSERT is important below.
1568
+ #
1569
+ # Typical cache usage pattern is:
1570
+ #
1571
+ # value = cache.get(key)
1572
+ # if value is None:
1573
+ # value = expensive_calculation()
1574
+ # cache.set(key, value)
1575
+ #
1576
+ # Cache.get does not evict expired keys to avoid writes during lookups.
1577
+ # Commonly used/expired keys will therefore remain in the cache making
1578
+ # an UPDATE the preferred path.
1579
+ #
1580
+ # The alternative is to assume the key is not present by first trying
1581
+ # to INSERT and then handling the IntegrityError that occurs from
1582
+ # violating the UNIQUE constraint. This optimistic approach was
1583
+ # rejected based on the common cache usage pattern.
1584
+ #
1585
+ # INSERT OR REPLACE aka UPSERT is not used because the old filename may
1586
+ # need cleanup.
1587
+
1588
+ with self._transact(retry, filename) as (sql, cleanup):
1589
+ rows = sql(
1590
+ 'SELECT rowid, filename FROM Cache'
1591
+ ' WHERE key = ? AND raw = ?',
1592
+ (db_key, raw),
1593
+ ).fetchall()
1594
+
1595
+ if rows:
1596
+ ((rowid, old_filename),) = rows
1597
+ cleanup(old_filename)
1598
+ self._row_update(rowid, now, columns)
1599
+ else:
1600
+ self._row_insert(db_key, raw, now, columns)
1601
+
1602
+ self._cull(now, sql, cleanup)
1603
+
1604
+ return True
1605
+
1606
+ def __setitem__(self, key, value):
1607
+ """Set corresponding `value` for `key` in cache.
1608
+
1609
+ :param key: key for item
1610
+ :param value: value for item
1611
+ :return: corresponding value
1612
+ :raises KeyError: if key is not found
1613
+
1614
+ """
1615
+ self.set(key, value, retry=True)
1616
+
1617
+ def _row_update(self, rowid, now, columns):
1618
+ sql = self._sql
1619
+ expire_time, tag, size, mode, filename, value = columns
1620
+ sql(
1621
+ 'UPDATE Cache SET'
1622
+ ' store_time = ?,'
1623
+ ' expire_time = ?,'
1624
+ ' access_time = ?,'
1625
+ ' access_count = ?,'
1626
+ ' tag = ?,'
1627
+ ' size = ?,'
1628
+ ' mode = ?,'
1629
+ ' filename = ?,'
1630
+ ' value = ?'
1631
+ ' WHERE rowid = ?',
1632
+ (
1633
+ now, # store_time
1634
+ expire_time,
1635
+ now, # access_time
1636
+ 0, # access_count
1637
+ tag,
1638
+ size,
1639
+ mode,
1640
+ filename,
1641
+ value,
1642
+ rowid,
1643
+ ),
1644
+ )
1645
+
1646
+ def _row_insert(self, key, raw, now, columns):
1647
+ sql = self._sql
1648
+ expire_time, tag, size, mode, filename, value = columns
1649
+ sql(
1650
+ 'INSERT INTO Cache('
1651
+ ' key, raw, store_time, expire_time, access_time,'
1652
+ ' access_count, tag, size, mode, filename, value'
1653
+ ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
1654
+ (
1655
+ key,
1656
+ raw,
1657
+ now, # store_time
1658
+ expire_time,
1659
+ now, # access_time
1660
+ 0, # access_count
1661
+ tag,
1662
+ size,
1663
+ mode,
1664
+ filename,
1665
+ value,
1666
+ ),
1667
+ )
1668
+
1669
+ def _cull(self, now, sql, cleanup, limit=None):
1670
+ cull_limit = self.cull_limit if limit is None else limit
1671
+
1672
+ if cull_limit == 0:
1673
+ return
1674
+
1675
+ # Evict expired keys.
1676
+
1677
+ select_expired_template = (
1678
+ 'SELECT %s FROM Cache'
1679
+ ' WHERE expire_time IS NOT NULL AND expire_time < ?'
1680
+ ' ORDER BY expire_time LIMIT ?'
1681
+ )
1682
+
1683
+ select_expired = select_expired_template % 'filename'
1684
+ rows = sql(select_expired, (now, cull_limit)).fetchall()
1685
+
1686
+ if rows:
1687
+ delete_expired = 'DELETE FROM Cache WHERE rowid IN (%s)' % (
1688
+ select_expired_template % 'rowid'
1689
+ )
1690
+ sql(delete_expired, (now, cull_limit))
1691
+
1692
+ for (filename,) in rows:
1693
+ cleanup(filename)
1694
+
1695
+ cull_limit -= len(rows)
1696
+
1697
+ if cull_limit == 0:
1698
+ return
1699
+
1700
+ # Evict keys by policy.
1701
+
1702
+ select_policy = EVICTION_POLICY[self.eviction_policy]['cull']
1703
+
1704
+ if select_policy is None or self.volume() < self.size_limit:
1705
+ return
1706
+
1707
+ select_filename = select_policy.format(fields='filename', now=now)
1708
+ rows = sql(select_filename, (cull_limit,)).fetchall()
1709
+
1710
+ if rows:
1711
+ delete = 'DELETE FROM Cache WHERE rowid IN (%s)' % (
1712
+ select_policy.format(fields='rowid', now=now)
1713
+ )
1714
+ sql(delete, (cull_limit,))
1715
+
1716
+ for (filename,) in rows:
1717
+ cleanup(filename)
1718
+
1719
+ def touch(self, key, expire=None, retry=False):
1720
+ """Touch `key` in cache and update `expire` time.
1721
+
1722
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1723
+ `False` (default).
1724
+
1725
+ :param key: key for item
1726
+ :param float expire: seconds until item expires
1727
+ (default None, no expiry)
1728
+ :param bool retry: retry if database timeout occurs (default False)
1729
+ :return: True if key was touched
1730
+ :raises Timeout: if database timeout occurs
1731
+
1732
+ """
1733
+ self._check_writable()
1734
+ now = time.time()
1735
+ db_key, raw = self._disk.put(key)
1736
+ expire_time = None if expire is None else now + expire
1737
+
1738
+ with self._transact(retry) as (sql, _):
1739
+ rows = sql(
1740
+ 'SELECT rowid, expire_time FROM Cache'
1741
+ ' WHERE key = ? AND raw = ?',
1742
+ (db_key, raw),
1743
+ ).fetchall()
1744
+
1745
+ if rows:
1746
+ ((rowid, old_expire_time),) = rows
1747
+
1748
+ if old_expire_time is None or old_expire_time > now:
1749
+ sql(
1750
+ 'UPDATE Cache SET expire_time = ? WHERE rowid = ?',
1751
+ (expire_time, rowid),
1752
+ )
1753
+ return True
1754
+
1755
+ return False
1756
+
1757
+ def add(self, key, value, expire=None, read=False, tag=None, retry=False):
1758
+ """Add `key` and `value` item to cache.
1759
+
1760
+ Similar to `set`, but only add to cache if key not present.
1761
+
1762
+ Operation is atomic. Only one concurrent add operation for a given key
1763
+ will succeed.
1764
+
1765
+ When `read` is `True`, `value` should be a file-like object opened
1766
+ for reading in binary mode.
1767
+
1768
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1769
+ `False` (default).
1770
+
1771
+ :param key: key for item
1772
+ :param value: value for item
1773
+ :param float expire: seconds until the key expires
1774
+ (default None, no expiry)
1775
+ :param bool read: read value as bytes from file (default False)
1776
+ :param str tag: text to associate with key (default None)
1777
+ :param bool retry: retry if database timeout occurs (default False)
1778
+ :return: True if item was added
1779
+ :raises Timeout: if database timeout occurs
1780
+
1781
+ """
1782
+ self._check_writable()
1783
+ now = time.time()
1784
+ db_key, raw = self._disk.put(key)
1785
+ expire_time = None if expire is None else now + expire
1786
+ size, mode, filename, db_value = self._disk.store(value, read, key=key)
1787
+ columns = (expire_time, tag, size, mode, filename, db_value)
1788
+
1789
+ with self._transact(retry, filename) as (sql, cleanup):
1790
+ rows = sql(
1791
+ 'SELECT rowid, filename, expire_time FROM Cache'
1792
+ ' WHERE key = ? AND raw = ?',
1793
+ (db_key, raw),
1794
+ ).fetchall()
1795
+
1796
+ if rows:
1797
+ ((rowid, old_filename, old_expire_time),) = rows
1798
+
1799
+ if old_expire_time is None or old_expire_time > now:
1800
+ cleanup(filename)
1801
+ return False
1802
+
1803
+ cleanup(old_filename)
1804
+ self._row_update(rowid, now, columns)
1805
+ else:
1806
+ self._row_insert(db_key, raw, now, columns)
1807
+
1808
+ self._cull(now, sql, cleanup)
1809
+
1810
+ return True
1811
+
1812
+ def incr(self, key, delta=1, default=0, retry=False):
1813
+ """Increment value by delta for item with key.
1814
+
1815
+ If key is missing and default is None then raise KeyError. Else if key
1816
+ is missing and default is not None then use default for value.
1817
+
1818
+ Operation is atomic. All concurrent increment operations will be
1819
+ counted individually.
1820
+
1821
+ Assumes value may be stored in a SQLite column. Most builds that target
1822
+ machines with 64-bit pointer widths will support 64-bit signed
1823
+ integers.
1824
+
1825
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1826
+ `False` (default).
1827
+
1828
+ :param key: key for item
1829
+ :param int delta: amount to increment (default 1)
1830
+ :param int default: value if key is missing (default 0)
1831
+ :param bool retry: retry if database timeout occurs (default False)
1832
+ :return: new value for item
1833
+ :raises KeyError: if key is not found and default is None
1834
+ :raises Timeout: if database timeout occurs
1835
+
1836
+ """
1837
+ self._check_writable()
1838
+ now = time.time()
1839
+ db_key, raw = self._disk.put(key)
1840
+ select = (
1841
+ 'SELECT rowid, expire_time, filename, value FROM Cache'
1842
+ ' WHERE key = ? AND raw = ?'
1843
+ )
1844
+
1845
+ with self._transact(retry) as (sql, cleanup):
1846
+ rows = sql(select, (db_key, raw)).fetchall()
1847
+
1848
+ if not rows:
1849
+ if default is None:
1850
+ raise KeyError(key)
1851
+
1852
+ value = default + delta
1853
+ columns = (None, None) + self._disk.store(
1854
+ value, False, key=key
1855
+ )
1856
+ self._row_insert(db_key, raw, now, columns)
1857
+ self._cull(now, sql, cleanup)
1858
+ return value
1859
+
1860
+ ((rowid, expire_time, filename, value),) = rows
1861
+
1862
+ if expire_time is not None and expire_time < now:
1863
+ if default is None:
1864
+ raise KeyError(key)
1865
+
1866
+ value = default + delta
1867
+ columns = (None, None) + self._disk.store(
1868
+ value, False, key=key
1869
+ )
1870
+ self._row_update(rowid, now, columns)
1871
+ self._cull(now, sql, cleanup)
1872
+ cleanup(filename)
1873
+ return value
1874
+
1875
+ value += delta
1876
+
1877
+ columns = 'store_time = ?, value = ?'
1878
+ update_column = EVICTION_POLICY[self.eviction_policy]['get']
1879
+
1880
+ if update_column is not None:
1881
+ columns += ', ' + update_column.format(now=now)
1882
+
1883
+ update = 'UPDATE Cache SET %s WHERE rowid = ?' % columns
1884
+ sql(update, (now, value, rowid))
1885
+
1886
+ return value
1887
+
1888
+ def decr(self, key, delta=1, default=0, retry=False):
1889
+ """Decrement value by delta for item with key.
1890
+
1891
+ If key is missing and default is None then raise KeyError. Else if key
1892
+ is missing and default is not None then use default for value.
1893
+
1894
+ Operation is atomic. All concurrent decrement operations will be
1895
+ counted individually.
1896
+
1897
+ Unlike Memcached, negative values are supported. Value may be
1898
+ decremented below zero.
1899
+
1900
+ Assumes value may be stored in a SQLite column. Most builds that target
1901
+ machines with 64-bit pointer widths will support 64-bit signed
1902
+ integers.
1903
+
1904
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1905
+ `False` (default).
1906
+
1907
+ :param key: key for item
1908
+ :param int delta: amount to decrement (default 1)
1909
+ :param int default: value if key is missing (default 0)
1910
+ :param bool retry: retry if database timeout occurs (default False)
1911
+ :return: new value for item
1912
+ :raises KeyError: if key is not found and default is None
1913
+ :raises Timeout: if database timeout occurs
1914
+
1915
+ """
1916
+ return self.incr(key, -delta, default, retry)
1917
+
1918
+ def get(
1919
+ self,
1920
+ key,
1921
+ default=None,
1922
+ read=False,
1923
+ expire_time=False,
1924
+ tag=False,
1925
+ retry=False,
1926
+ store_time=False,
1927
+ ):
1928
+ """Retrieve value from cache. If `key` is missing, return `default`.
1929
+
1930
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
1931
+ `False` (default).
1932
+
1933
+ When `expire_time`, `tag` or `store_time` is true, the result is a
1934
+ tuple of the value followed by the requested metadata in that order:
1935
+ the expire time and store time as seconds since the epoch, the tag as
1936
+ stored. Metadata is `None` when the key is missing.
1937
+
1938
+ :param key: key for item
1939
+ :param default: value to return if key is missing (default None)
1940
+ :param bool read: if True, return file handle to value
1941
+ (default False)
1942
+ :param bool expire_time: if True, return expire_time in tuple
1943
+ (default False)
1944
+ :param bool tag: if True, return tag in tuple (default False)
1945
+ :param bool retry: retry if database timeout occurs (default False)
1946
+ :param bool store_time: if True, return store_time in tuple
1947
+ (default False)
1948
+ :return: value for item or default if key not found
1949
+ :raises Timeout: if database timeout occurs
1950
+
1951
+ """
1952
+ db_key, raw = self._disk.put(key)
1953
+ update_column = EVICTION_POLICY[self.eviction_policy]['get']
1954
+ statistics = self.statistics
1955
+
1956
+ if self._read_only:
1957
+ # Hits, misses and access times cannot be recorded, so take
1958
+ # the path that does not write.
1959
+ update_column = None
1960
+ statistics = False
1961
+ select = (
1962
+ 'SELECT rowid, expire_time, tag, mode, filename, value, store_time'
1963
+ ' FROM Cache WHERE key = ? AND raw = ?'
1964
+ ' AND (expire_time IS NULL OR expire_time > ?)'
1965
+ )
1966
+
1967
+ default = _meta_result(default, expire_time, tag, store_time)
1968
+
1969
+ if not statistics and update_column is None:
1970
+ # Fast path, no transaction necessary.
1971
+
1972
+ rows = self._sql(select, (db_key, raw, time.time())).fetchall()
1973
+
1974
+ if not rows:
1975
+ return default
1976
+
1977
+ (
1978
+ (
1979
+ rowid,
1980
+ db_expire_time,
1981
+ db_tag,
1982
+ mode,
1983
+ filename,
1984
+ db_value,
1985
+ db_store_time,
1986
+ ),
1987
+ ) = rows
1988
+
1989
+ try:
1990
+ value = self._disk.fetch(mode, filename, db_value, read)
1991
+ except IOError:
1992
+ # Key was deleted before we could retrieve result.
1993
+ return default
1994
+
1995
+ else: # Slow path, transaction required.
1996
+ cache_hit = (
1997
+ "UPDATE Settings SET value = value + 1 WHERE key = 'hits'"
1998
+ )
1999
+ cache_miss = (
2000
+ "UPDATE Settings SET value = value + 1 WHERE key = 'misses'"
2001
+ )
2002
+
2003
+ with self._transact(retry) as (sql, _):
2004
+ rows = sql(select, (db_key, raw, time.time())).fetchall()
2005
+
2006
+ if not rows:
2007
+ if statistics:
2008
+ sql(cache_miss)
2009
+ return default
2010
+
2011
+ (
2012
+ (
2013
+ rowid,
2014
+ db_expire_time,
2015
+ db_tag,
2016
+ mode,
2017
+ filename,
2018
+ db_value,
2019
+ db_store_time,
2020
+ ),
2021
+ ) = rows
2022
+
2023
+ try:
2024
+ value = self._disk.fetch(mode, filename, db_value, read)
2025
+ except IOError:
2026
+ # Key was deleted before we could retrieve result.
2027
+ if statistics:
2028
+ sql(cache_miss)
2029
+ return default
2030
+
2031
+ if statistics:
2032
+ sql(cache_hit)
2033
+
2034
+ now = time.time()
2035
+ update = 'UPDATE Cache SET %s WHERE rowid = ?'
2036
+
2037
+ if update_column is not None:
2038
+ sql(update % update_column.format(now=now), (rowid,))
2039
+
2040
+ meta = (db_expire_time, db_tag, db_store_time)
2041
+ return _meta_result(value, expire_time, tag, store_time, meta)
2042
+
2043
+ def __getitem__(self, key):
2044
+ """Return corresponding value for `key` from cache.
2045
+
2046
+ :param key: key matching item
2047
+ :return: corresponding value
2048
+ :raises KeyError: if key is not found
2049
+
2050
+ """
2051
+ value = self.get(key, default=ENOVAL, retry=True)
2052
+ if value is ENOVAL:
2053
+ raise KeyError(key)
2054
+ return value
2055
+
2056
+ def read(self, key, retry=False):
2057
+ """Return file handle value corresponding to `key` from cache.
2058
+
2059
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2060
+ `False` (default).
2061
+
2062
+ :param key: key matching item
2063
+ :param bool retry: retry if database timeout occurs (default False)
2064
+ :return: file open for reading in binary mode
2065
+ :raises KeyError: if key is not found
2066
+ :raises Timeout: if database timeout occurs
2067
+
2068
+ """
2069
+ handle = self.get(key, default=ENOVAL, read=True, retry=retry)
2070
+ if handle is ENOVAL:
2071
+ raise KeyError(key)
2072
+ return handle
2073
+
2074
+ def __contains__(self, key):
2075
+ """Return `True` if `key` matching item is found in cache.
2076
+
2077
+ :param key: key matching item
2078
+ :return: True if key matching item
2079
+
2080
+ """
2081
+ sql = self._sql
2082
+ db_key, raw = self._disk.put(key)
2083
+ select = (
2084
+ 'SELECT rowid FROM Cache'
2085
+ ' WHERE key = ? AND raw = ?'
2086
+ ' AND (expire_time IS NULL OR expire_time > ?)'
2087
+ )
2088
+
2089
+ rows = sql(select, (db_key, raw, time.time())).fetchall()
2090
+
2091
+ return bool(rows)
2092
+
2093
+ def pop(
2094
+ self, key, default=None, expire_time=False, tag=False, retry=False
2095
+ ): # noqa: E501
2096
+ """Remove corresponding item for `key` from cache and return value.
2097
+
2098
+ If `key` is missing, return `default`.
2099
+
2100
+ Operation is atomic. Concurrent operations will be serialized.
2101
+
2102
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2103
+ `False` (default).
2104
+
2105
+ :param key: key for item
2106
+ :param default: value to return if key is missing (default None)
2107
+ :param bool expire_time: if True, return expire_time in tuple
2108
+ (default False)
2109
+ :param bool tag: if True, return tag in tuple (default False)
2110
+ :param bool retry: retry if database timeout occurs (default False)
2111
+ :return: value for item or default if key not found
2112
+ :raises Timeout: if database timeout occurs
2113
+
2114
+ """
2115
+ self._check_writable()
2116
+ db_key, raw = self._disk.put(key)
2117
+ select = (
2118
+ 'SELECT rowid, expire_time, tag, mode, filename, value'
2119
+ ' FROM Cache WHERE key = ? AND raw = ?'
2120
+ ' AND (expire_time IS NULL OR expire_time > ?)'
2121
+ )
2122
+
2123
+ if expire_time and tag:
2124
+ default = default, None, None
2125
+ elif expire_time or tag:
2126
+ default = default, None
2127
+
2128
+ with self._transact(retry) as (sql, _):
2129
+ rows = sql(select, (db_key, raw, time.time())).fetchall()
2130
+
2131
+ if not rows:
2132
+ return default
2133
+
2134
+ ((rowid, db_expire_time, db_tag, mode, filename, db_value),) = rows
2135
+
2136
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
2137
+
2138
+ try:
2139
+ value = self._disk.fetch(mode, filename, db_value, False)
2140
+ except IOError:
2141
+ # Key was deleted before we could retrieve result.
2142
+ return default
2143
+ finally:
2144
+ if filename is not None:
2145
+ self._disk.remove(filename)
2146
+
2147
+ if expire_time and tag:
2148
+ return value, db_expire_time, db_tag
2149
+ elif expire_time:
2150
+ return value, db_expire_time
2151
+ elif tag:
2152
+ return value, db_tag
2153
+ else:
2154
+ return value
2155
+
2156
+ def __delitem__(self, key, retry=True):
2157
+ """Delete corresponding item for `key` from cache.
2158
+
2159
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2160
+ `False` (default `True`).
2161
+
2162
+ :param key: key matching item
2163
+ :param bool retry: retry if database timeout occurs (default True)
2164
+ :raises KeyError: if key is not found
2165
+ :raises Timeout: if database timeout occurs
2166
+
2167
+ """
2168
+ self._check_writable()
2169
+ db_key, raw = self._disk.put(key)
2170
+
2171
+ with self._transact(retry) as (sql, cleanup):
2172
+ rows = sql(
2173
+ 'SELECT rowid, filename FROM Cache'
2174
+ ' WHERE key = ? AND raw = ?'
2175
+ ' AND (expire_time IS NULL OR expire_time > ?)',
2176
+ (db_key, raw, time.time()),
2177
+ ).fetchall()
2178
+
2179
+ if not rows:
2180
+ raise KeyError(key)
2181
+
2182
+ ((rowid, filename),) = rows
2183
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
2184
+ cleanup(filename)
2185
+
2186
+ return True
2187
+
2188
+ def delete(self, key, retry=False):
2189
+ """Delete corresponding item for `key` from cache.
2190
+
2191
+ Missing keys are ignored.
2192
+
2193
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2194
+ `False` (default).
2195
+
2196
+ :param key: key matching item
2197
+ :param bool retry: retry if database timeout occurs (default False)
2198
+ :return: True if item was deleted
2199
+ :raises Timeout: if database timeout occurs
2200
+
2201
+ """
2202
+ # pylint: disable=unnecessary-dunder-call
2203
+ try:
2204
+ return self.__delitem__(key, retry=retry)
2205
+ except KeyError:
2206
+ return False
2207
+
2208
+ def push(
2209
+ self,
2210
+ value,
2211
+ prefix=None,
2212
+ side='back',
2213
+ expire=None,
2214
+ read=False,
2215
+ tag=None,
2216
+ retry=False,
2217
+ ):
2218
+ """Push `value` onto `side` of queue identified by `prefix` in cache.
2219
+
2220
+ When prefix is None, integer keys are used. Otherwise, string keys are
2221
+ used in the format "prefix-integer". Integer starts at 500 trillion.
2222
+
2223
+ Defaults to pushing value on back of queue. Set side to 'front' to push
2224
+ value on front of queue. Side must be one of 'back' or 'front'.
2225
+
2226
+ Operation is atomic. Concurrent operations will be serialized.
2227
+
2228
+ When `read` is `True`, `value` should be a file-like object opened
2229
+ for reading in binary mode.
2230
+
2231
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2232
+ `False` (default).
2233
+
2234
+ See also `Cache.pull`.
2235
+
2236
+ >>> cache = Cache()
2237
+ >>> print(cache.push('first value'))
2238
+ 500000000000000
2239
+ >>> cache.get(500000000000000)
2240
+ 'first value'
2241
+ >>> print(cache.push('second value'))
2242
+ 500000000000001
2243
+ >>> print(cache.push('third value', side='front'))
2244
+ 499999999999999
2245
+ >>> cache.push(1234, prefix='userids')
2246
+ 'userids-500000000000000'
2247
+
2248
+ :param value: value for item
2249
+ :param str prefix: key prefix (default None, key is integer)
2250
+ :param str side: either 'back' or 'front' (default 'back')
2251
+ :param float expire: seconds until the key expires
2252
+ (default None, no expiry)
2253
+ :param bool read: read value as bytes from file (default False)
2254
+ :param str tag: text to associate with key (default None)
2255
+ :param bool retry: retry if database timeout occurs (default False)
2256
+ :return: key for item in cache
2257
+ :raises Timeout: if database timeout occurs
2258
+
2259
+ """
2260
+ self._check_writable()
2261
+ if prefix is None:
2262
+ min_key = 0
2263
+ max_key = 999999999999999
2264
+ else:
2265
+ min_key = prefix + '-000000000000000'
2266
+ max_key = prefix + '-999999999999999'
2267
+
2268
+ now = time.time()
2269
+ raw = True
2270
+ expire_time = None if expire is None else now + expire
2271
+ size, mode, filename, db_value = self._disk.store(value, read)
2272
+ columns = (expire_time, tag, size, mode, filename, db_value)
2273
+ order = {'back': 'DESC', 'front': 'ASC'}
2274
+ select = (
2275
+ 'SELECT key FROM Cache'
2276
+ ' WHERE ? < key AND key < ? AND raw = ?'
2277
+ ' ORDER BY key %s LIMIT 1'
2278
+ ) % order[side]
2279
+
2280
+ with self._transact(retry, filename) as (sql, cleanup):
2281
+ rows = sql(select, (min_key, max_key, raw)).fetchall()
2282
+
2283
+ if rows:
2284
+ ((key,),) = rows
2285
+
2286
+ if prefix is not None:
2287
+ num = int(key[(key.rfind('-') + 1) :])
2288
+ else:
2289
+ num = key
2290
+
2291
+ if side == 'back':
2292
+ num += 1
2293
+ else:
2294
+ assert side == 'front'
2295
+ num -= 1
2296
+ else:
2297
+ num = 500000000000000
2298
+
2299
+ if prefix is not None:
2300
+ db_key = '{0}-{1:015d}'.format(prefix, num)
2301
+ else:
2302
+ db_key = num
2303
+
2304
+ self._row_insert(db_key, raw, now, columns)
2305
+ self._cull(now, sql, cleanup)
2306
+
2307
+ return db_key
2308
+
2309
+ def pull(
2310
+ self,
2311
+ prefix=None,
2312
+ default=(None, None),
2313
+ side='front',
2314
+ expire_time=False,
2315
+ tag=False,
2316
+ retry=False,
2317
+ ):
2318
+ """Pull key and value item pair from `side` of queue in cache.
2319
+
2320
+ When prefix is None, integer keys are used. Otherwise, string keys are
2321
+ used in the format "prefix-integer". Integer starts at 500 trillion.
2322
+
2323
+ If queue is empty, return default.
2324
+
2325
+ Defaults to pulling key and value item pairs from front of queue. Set
2326
+ side to 'back' to pull from back of queue. Side must be one of 'front'
2327
+ or 'back'.
2328
+
2329
+ Operation is atomic. Concurrent operations will be serialized.
2330
+
2331
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2332
+ `False` (default).
2333
+
2334
+ See also `Cache.push` and `Cache.get`.
2335
+
2336
+ >>> cache = Cache()
2337
+ >>> cache.pull()
2338
+ (None, None)
2339
+ >>> for letter in 'abc':
2340
+ ... print(cache.push(letter))
2341
+ 500000000000000
2342
+ 500000000000001
2343
+ 500000000000002
2344
+ >>> key, value = cache.pull()
2345
+ >>> print(key)
2346
+ 500000000000000
2347
+ >>> value
2348
+ 'a'
2349
+ >>> _, value = cache.pull(side='back')
2350
+ >>> value
2351
+ 'c'
2352
+ >>> cache.push(1234, 'userids')
2353
+ 'userids-500000000000000'
2354
+ >>> _, value = cache.pull('userids')
2355
+ >>> value
2356
+ 1234
2357
+
2358
+ :param str prefix: key prefix (default None, key is integer)
2359
+ :param default: value to return if key is missing
2360
+ (default (None, None))
2361
+ :param str side: either 'front' or 'back' (default 'front')
2362
+ :param bool expire_time: if True, return expire_time in tuple
2363
+ (default False)
2364
+ :param bool tag: if True, return tag in tuple (default False)
2365
+ :param bool retry: retry if database timeout occurs (default False)
2366
+ :return: key and value item pair or default if queue is empty
2367
+ :raises Timeout: if database timeout occurs
2368
+
2369
+ """
2370
+ self._check_writable()
2371
+ # Caution: Nearly identical code exists in Cache.peek
2372
+ if prefix is None:
2373
+ min_key = 0
2374
+ max_key = 999999999999999
2375
+ else:
2376
+ min_key = prefix + '-000000000000000'
2377
+ max_key = prefix + '-999999999999999'
2378
+
2379
+ order = {'front': 'ASC', 'back': 'DESC'}
2380
+ select = (
2381
+ 'SELECT rowid, key, expire_time, tag, mode, filename, value'
2382
+ ' FROM Cache WHERE ? < key AND key < ? AND raw = 1'
2383
+ ' ORDER BY key %s LIMIT 1'
2384
+ ) % order[side]
2385
+
2386
+ if expire_time and tag:
2387
+ default = default, None, None
2388
+ elif expire_time or tag:
2389
+ default = default, None
2390
+
2391
+ while True:
2392
+ while True:
2393
+ with self._transact(retry) as (sql, cleanup):
2394
+ rows = sql(select, (min_key, max_key)).fetchall()
2395
+
2396
+ if not rows:
2397
+ return default
2398
+
2399
+ (
2400
+ (rowid, key, db_expire, db_tag, mode, name, db_value),
2401
+ ) = rows
2402
+
2403
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
2404
+
2405
+ if db_expire is not None and db_expire < time.time():
2406
+ cleanup(name)
2407
+ else:
2408
+ break
2409
+
2410
+ try:
2411
+ value = self._disk.fetch(mode, name, db_value, False)
2412
+ except IOError:
2413
+ # Key was deleted before we could retrieve result.
2414
+ continue
2415
+ finally:
2416
+ if name is not None:
2417
+ self._disk.remove(name)
2418
+ break
2419
+
2420
+ if expire_time and tag:
2421
+ return (key, value), db_expire, db_tag
2422
+ elif expire_time:
2423
+ return (key, value), db_expire
2424
+ elif tag:
2425
+ return (key, value), db_tag
2426
+ else:
2427
+ return key, value
2428
+
2429
+ def peek(
2430
+ self,
2431
+ prefix=None,
2432
+ default=(None, None),
2433
+ side='front',
2434
+ expire_time=False,
2435
+ tag=False,
2436
+ retry=False,
2437
+ ):
2438
+ """Peek at key and value item pair from `side` of queue in cache.
2439
+
2440
+ When prefix is None, integer keys are used. Otherwise, string keys are
2441
+ used in the format "prefix-integer". Integer starts at 500 trillion.
2442
+
2443
+ If queue is empty, return default.
2444
+
2445
+ Defaults to peeking at key and value item pairs from front of queue.
2446
+ Set side to 'back' to pull from back of queue. Side must be one of
2447
+ 'front' or 'back'.
2448
+
2449
+ Expired items are deleted from cache. Operation is atomic. Concurrent
2450
+ operations will be serialized.
2451
+
2452
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2453
+ `False` (default).
2454
+
2455
+ See also `Cache.pull` and `Cache.push`.
2456
+
2457
+ >>> cache = Cache()
2458
+ >>> for letter in 'abc':
2459
+ ... print(cache.push(letter))
2460
+ 500000000000000
2461
+ 500000000000001
2462
+ 500000000000002
2463
+ >>> key, value = cache.peek()
2464
+ >>> print(key)
2465
+ 500000000000000
2466
+ >>> value
2467
+ 'a'
2468
+ >>> key, value = cache.peek(side='back')
2469
+ >>> print(key)
2470
+ 500000000000002
2471
+ >>> value
2472
+ 'c'
2473
+
2474
+ :param str prefix: key prefix (default None, key is integer)
2475
+ :param default: value to return if key is missing
2476
+ (default (None, None))
2477
+ :param str side: either 'front' or 'back' (default 'front')
2478
+ :param bool expire_time: if True, return expire_time in tuple
2479
+ (default False)
2480
+ :param bool tag: if True, return tag in tuple (default False)
2481
+ :param bool retry: retry if database timeout occurs (default False)
2482
+ :return: key and value item pair or default if queue is empty
2483
+ :raises Timeout: if database timeout occurs
2484
+
2485
+ """
2486
+ # Caution: Nearly identical code exists in Cache.pull
2487
+ if prefix is None:
2488
+ min_key = 0
2489
+ max_key = 999999999999999
2490
+ else:
2491
+ min_key = prefix + '-000000000000000'
2492
+ max_key = prefix + '-999999999999999'
2493
+
2494
+ order = {'front': 'ASC', 'back': 'DESC'}
2495
+ live = ' AND ' + _LIVE_CONDITION if self._read_only else ''
2496
+ select = (
2497
+ 'SELECT rowid, key, expire_time, tag, mode, filename, value'
2498
+ ' FROM Cache WHERE ? < key AND key < ? AND raw = 1%s'
2499
+ ' ORDER BY key %s LIMIT 1'
2500
+ ) % (live, order[side])
2501
+
2502
+ if expire_time and tag:
2503
+ default = default, None, None
2504
+ elif expire_time or tag:
2505
+ default = default, None
2506
+
2507
+ while True:
2508
+ while True:
2509
+ with self._transact(retry) as (sql, cleanup):
2510
+ rows = sql(select, (min_key, max_key)).fetchall()
2511
+
2512
+ if not rows:
2513
+ return default
2514
+
2515
+ (
2516
+ (rowid, key, db_expire, db_tag, mode, name, db_value),
2517
+ ) = rows
2518
+
2519
+ expired = db_expire is not None and db_expire < time.time()
2520
+
2521
+ if expired and not self._read_only:
2522
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
2523
+ cleanup(name)
2524
+ else:
2525
+ break
2526
+
2527
+ try:
2528
+ value = self._disk.fetch(mode, name, db_value, False)
2529
+ except IOError:
2530
+ # Key was deleted before we could retrieve result.
2531
+ continue
2532
+ break
2533
+
2534
+ if expire_time and tag:
2535
+ return (key, value), db_expire, db_tag
2536
+ elif expire_time:
2537
+ return (key, value), db_expire
2538
+ elif tag:
2539
+ return (key, value), db_tag
2540
+ else:
2541
+ return key, value
2542
+
2543
+ def peekitem(self, last=True, expire_time=False, tag=False, retry=False):
2544
+ """Peek at key and value item pair in cache based on iteration order.
2545
+
2546
+ Expired items are deleted from cache. Operation is atomic. Concurrent
2547
+ operations will be serialized.
2548
+
2549
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2550
+ `False` (default).
2551
+
2552
+ >>> cache = Cache()
2553
+ >>> for num, letter in enumerate('abc'):
2554
+ ... cache[letter] = num
2555
+ >>> cache.peekitem()
2556
+ ('c', 2)
2557
+ >>> cache.peekitem(last=False)
2558
+ ('a', 0)
2559
+
2560
+ :param bool last: last item in iteration order (default True)
2561
+ :param bool expire_time: if True, return expire_time in tuple
2562
+ (default False)
2563
+ :param bool tag: if True, return tag in tuple (default False)
2564
+ :param bool retry: retry if database timeout occurs (default False)
2565
+ :return: key and value item pair
2566
+ :raises KeyError: if cache is empty
2567
+ :raises Timeout: if database timeout occurs
2568
+
2569
+ """
2570
+ order = ('ASC', 'DESC')
2571
+ live = ' WHERE ' + _LIVE_CONDITION if self._read_only else ''
2572
+ select = (
2573
+ 'SELECT rowid, key, raw, expire_time, tag, mode, filename, value'
2574
+ ' FROM Cache%s ORDER BY rowid %s LIMIT 1'
2575
+ ) % (live, order[last])
2576
+
2577
+ while True:
2578
+ while True:
2579
+ with self._transact(retry) as (sql, cleanup):
2580
+ rows = sql(select).fetchall()
2581
+
2582
+ if not rows:
2583
+ raise KeyError('dictionary is empty')
2584
+
2585
+ (
2586
+ (
2587
+ rowid,
2588
+ db_key,
2589
+ raw,
2590
+ db_expire,
2591
+ db_tag,
2592
+ mode,
2593
+ name,
2594
+ db_value,
2595
+ ),
2596
+ ) = rows
2597
+
2598
+ expired = db_expire is not None and db_expire < time.time()
2599
+
2600
+ if expired and not self._read_only:
2601
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
2602
+ cleanup(name)
2603
+ else:
2604
+ break
2605
+
2606
+ key = self._disk.get(db_key, raw)
2607
+
2608
+ try:
2609
+ value = self._disk.fetch(mode, name, db_value, False)
2610
+ except IOError:
2611
+ # Key was deleted before we could retrieve result.
2612
+ continue
2613
+ break
2614
+
2615
+ if expire_time and tag:
2616
+ return (key, value), db_expire, db_tag
2617
+ elif expire_time:
2618
+ return (key, value), db_expire
2619
+ elif tag:
2620
+ return (key, value), db_tag
2621
+ else:
2622
+ return key, value
2623
+
2624
+ def memoize(
2625
+ self, name=None, typed=False, expire=None, tag=None, ignore=()
2626
+ ):
2627
+ """Memoizing cache decorator.
2628
+
2629
+ Decorator to wrap callable with memoizing function using cache.
2630
+ Repeated calls with the same arguments will lookup result in cache and
2631
+ avoid function evaluation.
2632
+
2633
+ If name is set to None (default), the callable name will be determined
2634
+ automatically.
2635
+
2636
+ When expire is set to zero, function results will not be set in the
2637
+ cache. Cache lookups still occur, however. Read
2638
+ :doc:`case-study-landing-page-caching` for example usage.
2639
+
2640
+ If typed is set to True, function arguments of different types will be
2641
+ cached separately. For example, f(3) and f(3.0) will be treated as
2642
+ distinct calls with distinct results.
2643
+
2644
+ The original underlying function is accessible through the __wrapped__
2645
+ attribute. This is useful for introspection, for bypassing the cache,
2646
+ or for rewrapping the function with a different cache.
2647
+
2648
+ >>> from diskcache import Cache
2649
+ >>> cache = Cache()
2650
+ >>> @cache.memoize(expire=1, tag='fib')
2651
+ ... def fibonacci(number):
2652
+ ... if number == 0:
2653
+ ... return 0
2654
+ ... elif number == 1:
2655
+ ... return 1
2656
+ ... else:
2657
+ ... return fibonacci(number - 1) + fibonacci(number - 2)
2658
+ >>> print(fibonacci(100))
2659
+ 354224848179261915075
2660
+
2661
+ An additional `__cache_key__` attribute can be used to generate the
2662
+ cache key used for the given arguments.
2663
+
2664
+ >>> key = fibonacci.__cache_key__(100)
2665
+ >>> print(cache[key])
2666
+ 354224848179261915075
2667
+
2668
+ Remember to call memoize when decorating a callable. If you forget,
2669
+ then a TypeError will occur. Note the lack of parenthenses after
2670
+ memoize below:
2671
+
2672
+ >>> @cache.memoize
2673
+ ... def test():
2674
+ ... pass
2675
+ Traceback (most recent call last):
2676
+ ...
2677
+ TypeError: name cannot be callable
2678
+
2679
+ :param cache: cache to store callable arguments and return values
2680
+ :param str name: name given for callable (default None, automatic)
2681
+ :param bool typed: cache different types separately (default False)
2682
+ :param float expire: seconds until arguments expire
2683
+ (default None, no expiry)
2684
+ :param str tag: text to associate with arguments (default None)
2685
+ :param set ignore: positional or keyword args to ignore (default ());
2686
+ an int ignores the argument at that position, a str ignores
2687
+ the parameter with that name however it is passed
2688
+ :return: callable decorator
2689
+
2690
+ """
2691
+ # Caution: Nearly identical code exists in DjangoCache.memoize
2692
+ if callable(name):
2693
+ raise TypeError('name cannot be callable')
2694
+
2695
+ def decorator(func):
2696
+ """Decorator created by memoize() for callable `func`."""
2697
+ base = (full_name(func),) if name is None else (name,)
2698
+ ignored = resolve_ignore(func, ignore)
2699
+
2700
+ @ft.wraps(func)
2701
+ def wrapper(*args, **kwargs):
2702
+ """Wrapper for callable to cache arguments and return values."""
2703
+ key = wrapper.__cache_key__(*args, **kwargs)
2704
+ result = self.get(key, default=ENOVAL, retry=True)
2705
+
2706
+ if result is ENOVAL:
2707
+ result = func(*args, **kwargs)
2708
+ if expire is None or expire > 0:
2709
+ self.set(key, result, expire, tag=tag, retry=True)
2710
+
2711
+ return result
2712
+
2713
+ def __cache_key__(*args, **kwargs):
2714
+ """Make key for cache given function arguments."""
2715
+ return args_to_key(base, args, kwargs, typed, ignored)
2716
+
2717
+ wrapper.__cache_key__ = __cache_key__
2718
+ return wrapper
2719
+
2720
+ return decorator
2721
+
2722
+ def check(self, fix=False, retry=False):
2723
+ """Check database and file system consistency.
2724
+
2725
+ Intended for use in testing and post-mortem error analysis.
2726
+
2727
+ While checking the Cache table for consistency, a writer lock is held
2728
+ on the database. The lock blocks other cache clients from writing to
2729
+ the database. For caches with many file references, the lock may be
2730
+ held for a long time. For example, local benchmarking shows that a
2731
+ cache with 1,000 file references takes ~60ms to check.
2732
+
2733
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2734
+ `False` (default).
2735
+
2736
+ :param bool fix: correct inconsistencies
2737
+ :param bool retry: retry if database timeout occurs (default False)
2738
+ :return: list of warnings
2739
+ :raises Timeout: if database timeout occurs
2740
+
2741
+ """
2742
+ if fix:
2743
+ self._check_writable()
2744
+ # pylint: disable=access-member-before-definition,W0201
2745
+ with warnings.catch_warnings(record=True) as warns:
2746
+ sql = self._sql
2747
+
2748
+ # Check integrity of database.
2749
+
2750
+ rows = sql('PRAGMA integrity_check').fetchall()
2751
+
2752
+ if len(rows) != 1 or rows[0][0] != 'ok':
2753
+ for (message,) in rows:
2754
+ warnings.warn(message)
2755
+
2756
+ if fix:
2757
+ sql('VACUUM')
2758
+
2759
+ with self._transact(retry) as (sql, _):
2760
+
2761
+ # Check Cache.filename against file system.
2762
+
2763
+ filenames = set()
2764
+ select = (
2765
+ 'SELECT rowid, size, filename FROM Cache'
2766
+ ' WHERE filename IS NOT NULL'
2767
+ )
2768
+
2769
+ rows = sql(select).fetchall()
2770
+
2771
+ for rowid, size, filename in rows:
2772
+ full_path = op.join(
2773
+ self._directory, native_filename(filename)
2774
+ )
2775
+ filenames.add(full_path)
2776
+
2777
+ if op.exists(full_path):
2778
+ real_size = op.getsize(full_path)
2779
+
2780
+ if size != real_size:
2781
+ message = 'wrong file size: %s, %d != %d'
2782
+ args = full_path, real_size, size
2783
+ warnings.warn(message % args)
2784
+
2785
+ if fix:
2786
+ sql(
2787
+ 'UPDATE Cache SET size = ?'
2788
+ ' WHERE rowid = ?',
2789
+ (real_size, rowid),
2790
+ )
2791
+
2792
+ continue
2793
+
2794
+ warnings.warn('file not found: %s' % full_path)
2795
+
2796
+ if fix:
2797
+ sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
2798
+
2799
+ # Check file system against Cache.filename.
2800
+
2801
+ for dirpath, _, files in os.walk(self._directory):
2802
+ paths = [op.join(dirpath, filename) for filename in files]
2803
+ error = set(paths) - filenames
2804
+
2805
+ for full_path in error:
2806
+ if DBNAME in full_path:
2807
+ continue
2808
+
2809
+ message = 'unknown file: %s' % full_path
2810
+ warnings.warn(message, UnknownFileWarning)
2811
+
2812
+ if fix:
2813
+ os.remove(full_path)
2814
+
2815
+ # Check for empty directories.
2816
+
2817
+ for dirpath, dirs, files in os.walk(self._directory):
2818
+ if not (dirs or files):
2819
+ message = 'empty directory: %s' % dirpath
2820
+ warnings.warn(message, EmptyDirWarning)
2821
+
2822
+ if fix:
2823
+ os.rmdir(dirpath)
2824
+
2825
+ # Check Settings.count against count of Cache rows.
2826
+
2827
+ self.reset('count')
2828
+ ((count,),) = sql('SELECT COUNT(key) FROM Cache').fetchall()
2829
+
2830
+ if self.count != count:
2831
+ message = 'Settings.count != COUNT(Cache.key); %d != %d'
2832
+ warnings.warn(message % (self.count, count))
2833
+
2834
+ if fix:
2835
+ sql(
2836
+ 'UPDATE Settings SET value = ? WHERE key = ?',
2837
+ (count, 'count'),
2838
+ )
2839
+
2840
+ # Check Settings.size against sum of Cache.size column.
2841
+
2842
+ self.reset('size')
2843
+ select_size = 'SELECT COALESCE(SUM(size), 0) FROM Cache'
2844
+ ((size,),) = sql(select_size).fetchall()
2845
+
2846
+ if self.size != size:
2847
+ message = 'Settings.size != SUM(Cache.size); %d != %d'
2848
+ warnings.warn(message % (self.size, size))
2849
+
2850
+ if fix:
2851
+ sql(
2852
+ 'UPDATE Settings SET value = ? WHERE key =?',
2853
+ (size, 'size'),
2854
+ )
2855
+
2856
+ return warns
2857
+
2858
+ def create_tag_index(self):
2859
+ """Create tag index on cache database.
2860
+
2861
+ It is better to initialize cache with `tag_index=True` than use this.
2862
+
2863
+ :raises Timeout: if database timeout occurs
2864
+
2865
+ """
2866
+ self._check_writable()
2867
+ sql = self._sql
2868
+ sql(
2869
+ 'CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON Cache(tag, rowid) '
2870
+ 'WHERE tag IS NOT NULL'
2871
+ )
2872
+ self.reset('tag_index', 1)
2873
+
2874
+ def drop_tag_index(self):
2875
+ """Drop tag index on cache database.
2876
+
2877
+ :raises Timeout: if database timeout occurs
2878
+
2879
+ """
2880
+ self._check_writable()
2881
+ sql = self._sql
2882
+ sql('DROP INDEX IF EXISTS Cache_tag_rowid')
2883
+ self.reset('tag_index', 0)
2884
+
2885
+ def evict(self, tag, retry=False):
2886
+ """Remove items with matching `tag` from cache.
2887
+
2888
+ Removing items is an iterative process. In each iteration, a subset of
2889
+ items is removed. Concurrent writes may occur between iterations.
2890
+
2891
+ If a :exc:`Timeout` occurs, the first element of the exception's
2892
+ `args` attribute will be the number of items removed before the
2893
+ exception occurred.
2894
+
2895
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2896
+ `False` (default).
2897
+
2898
+ :param str tag: tag identifying items
2899
+ :param bool retry: retry if database timeout occurs (default False)
2900
+ :return: count of rows removed
2901
+ :raises Timeout: if database timeout occurs
2902
+
2903
+ """
2904
+ self._check_writable()
2905
+ select = (
2906
+ 'SELECT rowid, filename FROM Cache'
2907
+ ' WHERE tag = ? AND rowid > ?'
2908
+ ' ORDER BY rowid LIMIT ?'
2909
+ )
2910
+ args = [tag, 0, 100]
2911
+ return self._select_delete(select, args, arg_index=1, retry=retry)
2912
+
2913
+ def expire(self, now=None, retry=False):
2914
+ """Remove expired items from cache.
2915
+
2916
+ Removing items is an iterative process. In each iteration, a subset of
2917
+ items is removed. Concurrent writes may occur between iterations.
2918
+
2919
+ If a :exc:`Timeout` occurs, the first element of the exception's
2920
+ `args` attribute will be the number of items removed before the
2921
+ exception occurred.
2922
+
2923
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2924
+ `False` (default).
2925
+
2926
+ :param float now: current time (default None, ``time.time()`` used)
2927
+ :param bool retry: retry if database timeout occurs (default False)
2928
+ :return: count of items removed
2929
+ :raises Timeout: if database timeout occurs
2930
+
2931
+ """
2932
+ self._check_writable()
2933
+ select = (
2934
+ 'SELECT rowid, expire_time, filename FROM Cache'
2935
+ ' WHERE ? < expire_time AND expire_time < ?'
2936
+ ' ORDER BY expire_time LIMIT ?'
2937
+ )
2938
+ args = [0, now or time.time(), 100]
2939
+ return self._select_delete(select, args, row_index=1, retry=retry)
2940
+
2941
+ def cull(self, retry=False):
2942
+ """Cull items from cache until volume is less than size limit.
2943
+
2944
+ Removing items is an iterative process. In each iteration, a subset of
2945
+ items is removed. Concurrent writes may occur between iterations.
2946
+
2947
+ If a :exc:`Timeout` occurs, the first element of the exception's
2948
+ `args` attribute will be the number of items removed before the
2949
+ exception occurred.
2950
+
2951
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
2952
+ `False` (default).
2953
+
2954
+ :param bool retry: retry if database timeout occurs (default False)
2955
+ :return: count of items removed
2956
+ :raises Timeout: if database timeout occurs
2957
+
2958
+ """
2959
+ self._check_writable()
2960
+ now = time.time()
2961
+
2962
+ # Remove expired items.
2963
+
2964
+ count = self.expire(now)
2965
+
2966
+ # Remove items by policy.
2967
+
2968
+ select_policy = EVICTION_POLICY[self.eviction_policy]['cull']
2969
+
2970
+ if select_policy is None:
2971
+ return 0
2972
+
2973
+ select_filename = select_policy.format(fields='filename', now=now)
2974
+
2975
+ try:
2976
+ while self.volume() > self.size_limit:
2977
+ with self._transact(retry) as (sql, cleanup):
2978
+ rows = sql(select_filename, (10,)).fetchall()
2979
+
2980
+ if not rows:
2981
+ break
2982
+
2983
+ count += len(rows)
2984
+ delete = (
2985
+ 'DELETE FROM Cache WHERE rowid IN (%s)'
2986
+ % select_policy.format(fields='rowid', now=now)
2987
+ )
2988
+ sql(delete, (10,))
2989
+
2990
+ for (filename,) in rows:
2991
+ cleanup(filename)
2992
+ except Timeout:
2993
+ raise Timeout(count) from None
2994
+
2995
+ return count
2996
+
2997
+ def clear(self, retry=False):
2998
+ """Remove all items from cache.
2999
+
3000
+ Removing items is an iterative process. In each iteration, a subset of
3001
+ items is removed. Concurrent writes may occur between iterations.
3002
+
3003
+ If a :exc:`Timeout` occurs, the first element of the exception's
3004
+ `args` attribute will be the number of items removed before the
3005
+ exception occurred.
3006
+
3007
+ Raises :exc:`Timeout` error when database timeout occurs and `retry` is
3008
+ `False` (default).
3009
+
3010
+ :param bool retry: retry if database timeout occurs (default False)
3011
+ :return: count of rows removed
3012
+ :raises Timeout: if database timeout occurs
3013
+
3014
+ """
3015
+ self._check_writable()
3016
+ select = (
3017
+ 'SELECT rowid, filename FROM Cache'
3018
+ ' WHERE rowid > ?'
3019
+ ' ORDER BY rowid LIMIT ?'
3020
+ )
3021
+ args = [0, 100]
3022
+ return self._select_delete(select, args, retry=retry)
3023
+
3024
+ def _select_delete(
3025
+ self, select, args, row_index=0, arg_index=0, retry=False
3026
+ ):
3027
+ count = 0
3028
+ delete = 'DELETE FROM Cache WHERE rowid IN (%s)'
3029
+
3030
+ try:
3031
+ while True:
3032
+ with self._transact(retry) as (sql, cleanup):
3033
+ rows = sql(select, args).fetchall()
3034
+
3035
+ if not rows:
3036
+ break
3037
+
3038
+ count += len(rows)
3039
+ sql(delete % ','.join(str(row[0]) for row in rows))
3040
+
3041
+ for row in rows:
3042
+ args[arg_index] = row[row_index]
3043
+ cleanup(row[-1])
3044
+
3045
+ except Timeout:
3046
+ raise Timeout(count) from None
3047
+
3048
+ return count
3049
+
3050
+ def iterkeys(self, reverse=False, tag=ENOVAL):
3051
+ """Iterate Cache keys in database sort order.
3052
+
3053
+ >>> cache = Cache()
3054
+ >>> for key in [4, 1, 3, 0, 2]:
3055
+ ... cache[key] = key
3056
+ >>> list(cache.iterkeys())
3057
+ [0, 1, 2, 3, 4]
3058
+ >>> list(cache.iterkeys(reverse=True))
3059
+ [4, 3, 2, 1, 0]
3060
+
3061
+ If `tag` is given, only keys with a matching tag are returned. Use
3062
+ ``tag=None`` to iterate untagged keys. Omitting `tag` returns all keys.
3063
+ Iteration includes expired items and does not read cached values.
3064
+
3065
+ :param bool reverse: reverse sort order (default False)
3066
+ :param tag: tag to match (default ENOVAL, matching all tags)
3067
+ :return: iterator of Cache keys
3068
+
3069
+ """
3070
+ sql = self._sql
3071
+ limit = 100
3072
+ _disk_get = self._disk.get
3073
+ select = 'SELECT key, raw FROM Cache'
3074
+ args = ()
3075
+
3076
+ if tag is None:
3077
+ select += ' WHERE tag IS NULL'
3078
+ elif tag is not ENOVAL:
3079
+ select += ' WHERE tag = ?'
3080
+ args = (tag,)
3081
+
3082
+ iterate = select + (' WHERE (' if tag is ENOVAL else ' AND (')
3083
+
3084
+ if reverse:
3085
+ iterate += 'key = ? AND raw < ? OR key < ?)'
3086
+ order = ' ORDER BY key DESC, raw DESC'
3087
+ else:
3088
+ iterate += 'key = ? AND raw > ? OR key > ?)'
3089
+ order = ' ORDER BY key ASC, raw ASC'
3090
+
3091
+ select += order + ' LIMIT 1'
3092
+ iterate += order + ' LIMIT ?'
3093
+ row = sql(select, args).fetchall()
3094
+
3095
+ if row:
3096
+ ((key, raw),) = row
3097
+ else:
3098
+ return
3099
+
3100
+ yield _disk_get(key, raw)
3101
+
3102
+ while True:
3103
+ rows = sql(iterate, args + (key, raw, key, limit)).fetchall()
3104
+
3105
+ if not rows:
3106
+ break
3107
+
3108
+ for key, raw in rows:
3109
+ yield _disk_get(key, raw)
3110
+
3111
+ def _iter(self, ascending=True):
3112
+ sql = self._sql
3113
+ rows = sql('SELECT MAX(rowid) FROM Cache').fetchall()
3114
+ ((max_rowid,),) = rows
3115
+ yield # Signal ready.
3116
+
3117
+ if max_rowid is None:
3118
+ return
3119
+
3120
+ bound = max_rowid + 1
3121
+ limit = 100
3122
+ _disk_get = self._disk.get
3123
+ rowid = 0 if ascending else bound
3124
+ select = (
3125
+ 'SELECT rowid, key, raw FROM Cache'
3126
+ ' WHERE ? < rowid AND rowid < ?'
3127
+ ' ORDER BY rowid %s LIMIT ?'
3128
+ ) % ('ASC' if ascending else 'DESC')
3129
+
3130
+ while True:
3131
+ if ascending:
3132
+ args = (rowid, bound, limit)
3133
+ else:
3134
+ args = (0, rowid, limit)
3135
+
3136
+ rows = sql(select, args).fetchall()
3137
+
3138
+ if not rows:
3139
+ break
3140
+
3141
+ for rowid, key, raw in rows:
3142
+ yield _disk_get(key, raw)
3143
+
3144
+ def __iter__(self):
3145
+ """Iterate keys in cache including expired items."""
3146
+ iterator = self._iter()
3147
+ next(iterator)
3148
+ return iterator
3149
+
3150
+ def __reversed__(self):
3151
+ """Reverse iterate keys in cache including expired items."""
3152
+ iterator = self._iter(ascending=False)
3153
+ next(iterator)
3154
+ return iterator
3155
+
3156
+ def stats(self, enable=True, reset=False):
3157
+ """Return cache statistics hits and misses.
3158
+
3159
+ :param bool enable: enable collecting statistics (default True)
3160
+ :param bool reset: reset hits and misses to 0 (default False)
3161
+ :return: (hits, misses)
3162
+
3163
+ """
3164
+ # pylint: disable=E0203,W0201
3165
+ result = (self.reset('hits'), self.reset('misses'))
3166
+
3167
+ if self._read_only:
3168
+ # Counters cannot be reset or enabled on a read-only cache.
3169
+ return result
3170
+
3171
+ if reset:
3172
+ self.reset('hits', 0)
3173
+ self.reset('misses', 0)
3174
+
3175
+ self.reset('statistics', enable)
3176
+
3177
+ return result
3178
+
3179
+ def volume(self):
3180
+ """Return estimated total size of cache on disk.
3181
+
3182
+ :return: size in bytes
3183
+
3184
+ """
3185
+ ((page_count,),) = self._sql('PRAGMA page_count').fetchall()
3186
+ total_size = self._page_size * page_count + self.reset('size')
3187
+ return total_size
3188
+
3189
+ def close(self):
3190
+ """Close database connection.
3191
+
3192
+ Closes the connection opened by the calling thread and the
3193
+ connections of threads that have exited. Connections that other
3194
+ live threads use stay open. The cache reconnects on next use.
3195
+
3196
+ """
3197
+ if self._cons.pid != os.getpid():
3198
+ self._discard_connections()
3199
+ return
3200
+
3201
+ local = self._local
3202
+ con = getattr(local, 'con', None)
3203
+ cons = self._cons.pop(threading.get_ident())
3204
+
3205
+ if con is not None and con not in cons:
3206
+ cons.append(con)
3207
+
3208
+ for name in ('con', 'sentinel'):
3209
+ try:
3210
+ delattr(local, name)
3211
+ except AttributeError:
3212
+ pass
3213
+
3214
+ for con in cons:
3215
+ con.close()
3216
+
3217
+ def __enter__(self):
3218
+ # Create connection in thread.
3219
+ # pylint: disable=unused-variable
3220
+ connection = self._con # noqa
3221
+ return self
3222
+
3223
+ def __exit__(self, *exception):
3224
+ self.close()
3225
+
3226
+ def __len__(self):
3227
+ """Count of items in cache including expired items."""
3228
+ return self.reset('count')
3229
+
3230
+ def __getstate__(self):
3231
+ return (self.directory, self.timeout, type(self.disk))
3232
+
3233
+ def __setstate__(self, state):
3234
+ self.__init__(*state)
3235
+
3236
+ def reset(self, key, value=ENOVAL, update=True):
3237
+ """Reset `key` and `value` item from Settings table.
3238
+
3239
+ Use `reset` to update the value of Cache settings correctly. Cache
3240
+ settings are stored in the Settings table of the SQLite database. If
3241
+ `update` is ``False`` then no attempt is made to update the database.
3242
+
3243
+ If `value` is not given, it is reloaded from the Settings
3244
+ table. Otherwise, the Settings table is updated.
3245
+
3246
+ Settings with the ``disk_`` prefix correspond to Disk
3247
+ attributes. Updating the value will change the unprefixed attribute on
3248
+ the associated Disk instance.
3249
+
3250
+ Settings with the ``sqlite_`` prefix correspond to SQLite
3251
+ pragmas. Updating the value will execute the corresponding PRAGMA
3252
+ statement.
3253
+
3254
+ SQLite PRAGMA statements may be executed before the Settings table
3255
+ exists in the database by setting `update` to ``False``.
3256
+
3257
+ :param str key: Settings key for item
3258
+ :param value: value for item (optional)
3259
+ :param bool update: update database Settings table (default True)
3260
+ :return: updated value for item
3261
+ :raises Timeout: if database timeout occurs
3262
+
3263
+ """
3264
+ sql = self._sql
3265
+ sql_retry = self._sql_retry
3266
+
3267
+ if value is ENOVAL:
3268
+ select = 'SELECT value FROM Settings WHERE key = ?'
3269
+ rows = sql_retry(select, (key,)).fetchall()
3270
+ if not rows and self._read_only and key in METADATA:
3271
+ value = METADATA[key] # A snapshot without counters.
3272
+ else:
3273
+ ((value,),) = rows
3274
+ setattr(self, key, value)
3275
+ return value
3276
+
3277
+ if update:
3278
+ self._check_writable()
3279
+ statement = 'UPDATE Settings SET value = ? WHERE key = ?'
3280
+ sql_retry(statement, (value, key))
3281
+
3282
+ if key.startswith('sqlite_'):
3283
+ pragma = key[7:]
3284
+
3285
+ if self._read_only and pragma in _PERSISTENT_PRAGMAS:
3286
+ # Stored in the file; a read-only cache takes what is there.
3287
+ setattr(self, key, value)
3288
+ return value
3289
+
3290
+ # 2016-02-17 GrantJ - PRAGMA and isolation_level=None
3291
+ # don't always play nicely together. Retry setting the
3292
+ # PRAGMA. I think some PRAGMA statements expect to
3293
+ # immediately take an EXCLUSIVE lock on the database. I
3294
+ # can't find any documentation for this but without the
3295
+ # retry, stress will intermittently fail with multiple
3296
+ # processes.
3297
+
3298
+ # 2018-11-05 GrantJ - Avoid setting pragma values that
3299
+ # are already set. Pragma settings like auto_vacuum and
3300
+ # journal_mode can take a long time or may not work after
3301
+ # tables have been created.
3302
+
3303
+ start = time.time()
3304
+ while True:
3305
+ try:
3306
+ try:
3307
+ ((old_value,),) = sql(
3308
+ 'PRAGMA %s' % (pragma)
3309
+ ).fetchall()
3310
+ update = old_value != value
3311
+ except ValueError:
3312
+ update = True
3313
+ if update:
3314
+ sql('PRAGMA %s = %s' % (pragma, value)).fetchall()
3315
+ break
3316
+ except sqlite3.OperationalError as exc:
3317
+ if str(exc) != 'database is locked':
3318
+ raise
3319
+ diff = time.time() - start
3320
+ if diff > 60:
3321
+ raise
3322
+ time.sleep(0.001)
3323
+ elif key.startswith('disk_'):
3324
+ attr = key[5:]
3325
+ setattr(self._disk, attr, value)
3326
+
3327
+ setattr(self, key, value)
3328
+ return value