if type_key is bytes: return sqlite3.Binary(key), True elif (
(type_key is str) or (
type_key is int and -9223372036854775808 <= key <= 9223372036854775807
) or (type_key is float)
): return key, True else:
data = pickle.dumps(key, protocol=self.pickle_protocol)
result = pickletools.optimize(data) return sqlite3.Binary(result), False
def get(self, key, raw): """Convert fields `key` and `raw` from Cache table to key.
:param key: database key to convert
:param bool raw: flag indicating raw database storage
:return: corresponding Python key
""" # pylint: disable=unidiomatic-typecheck if raw: return bytes(key) if type(key) is sqlite3.Binary else key else: return pickle.load(io.BytesIO(key))
def store(self, value, read, key=UNKNOWN): """Convert `value` to fields size, mode, filename, and value for Cache
table.
:param value: value to convert
:param bool read: True when value is file-like object
:param key: key for item (default UNKNOWN)
:return: (size, mode, filename, value) tuple for Cache table
for count in range(1, 11): with cl.suppress(OSError):
os.makedirs(full_dir)
try: # Another cache may have deleted the directory before # the file could be opened.
writer = open(full_path, mode, encoding=encoding) except OSError: if count == 10: # Give up after 10 tries to open the file. raise continue
with writer:
size = 0 for chunk in iterator:
size += len(chunk)
writer.write(chunk) return size
def fetch(self, mode, filename, value, read): """Convert fields `mode`, `filename`, and `value` from Cache table to
value.
:param int mode: value mode raw, binary, text, or pickle
:param str filename: filename of corresponding value
:param value: database value
:param bool read: when True, return an open file handle
:return: corresponding Python value
:raises: IOError if the value cannot be read
""" # pylint: disable=unidiomatic-typecheck,consider-using-with if mode == MODE_RAW: return bytes(value) if type(value) is sqlite3.Binary else value elif mode == MODE_BINARY: if read: return open(op.join(self._directory, filename), 'rb') else: with open(op.join(self._directory, filename), 'rb') as reader: return reader.read() elif mode == MODE_TEXT:
full_path = op.join(self._directory, filename) with open(full_path, 'r', encoding='UTF-8') as reader: return reader.read() elif mode == MODE_PICKLE: if value isNone: with open(op.join(self._directory, filename), 'rb') as reader: return pickle.load(reader) else: return pickle.load(io.BytesIO(value))
def filename(self, key=UNKNOWN, value=UNKNOWN): """Return filename and full-path tuple for file storage.
Filename will be a randomly generated 28 character hexadecimal string with".val" suffixed. Two levels of sub-directories will be used to
reduce the size of directories. On older filesystems, lookups in
directories with many files may be slow.
The default implementation ignores the `key` and `value` parameters.
In some scenarios, for example :meth:`Cache.push
<diskcache.Cache.push>`, the `key` or `value` may not be known when the
item is stored in the cache.
:param key: key for item (default UNKNOWN)
:param value: value for item (default UNKNOWN)
# Suppress OSError that may occur if two caches attempt to delete the # same file or directory at the same time.
with cl.suppress(OSError):
os.remove(full_path)
with cl.suppress(OSError):
os.removedirs(full_dir)
class JSONDisk(Disk): """Cache key and value using JSON serialization with zlib compression."""
def __init__(self, directory, compress_level=1, **kwargs): """Initialize JSON disk instance.
Keys and values are compressed using the zlib library. The
`compress_level` is an integer from 0 to 9 controlling the level of
compression; 1 is fastest and produces the least compression, 9 is
slowest and produces the most compression, and 0 is no compression.
:param str directory: directory path
:param int compress_level: zlib compression level (default 1)
:param kwargs: super class arguments
def fetch(self, mode, filename, value, read):
data = super().fetch(mode, filename, value, read) ifnot read:
data = json.loads(zlib.decompress(data).decode('utf-8')) return data
class Timeout(Exception): """Database timeout expired."""
class UnknownFileWarning(UserWarning): """Warning used by Cache.check for unknown files."""
class EmptyDirWarning(UserWarning): """Warning used by Cache.check for empty directories."""
def args_to_key(base, args, kwargs, typed, ignore): """Create cache key out of function arguments.
:param tuple base: base of key
:param tuple args: function arguments
:param dict kwargs: function keyword arguments
:param bool typed: include types in cache key
:param set ignore: positional or keyword args to ignore
:return: cache key tuple
"""
args = tuple(arg for index, arg in enumerate(args) if index notin ignore)
key = base + args + (None,)
if kwargs:
kwargs = {key: val for key, val in kwargs.items() if key notin ignore}
sorted_items = sorted(kwargs.items())
for item in sorted_items:
key += item
if typed:
key += tuple(type(arg) for arg in args)
if kwargs:
key += tuple(type(value) for _, value in sorted_items)
:param str directory: cache directory
:param float timeout: SQLite connection timeout
:param disk: Disk type or subclass for serialization
:param settings: any of DEFAULT_SETTINGS
ifnot op.isdir(directory): try:
os.makedirs(directory, 0o755) except OSError as error: if error.errno != errno.EEXIST: raise EnvironmentError(
error.errno, 'Cache directory "%s" does not exist' ' and could not be created' % self._directory,
) fromNone
sql = self._sql_retry
# Setup Settings table.
try:
current_settings = dict(
sql('SELECT key, value FROM Settings').fetchall()
) except sqlite3.OperationalError:
current_settings = {}
sql( 'CREATE TRIGGER IF NOT EXISTS Settings_count_insert' ' AFTER INSERT ON Cache FOR EACH ROW BEGIN' ' UPDATE Settings SET value = value + 1' ' WHERE key = "count"; END'
)
sql( 'CREATE TRIGGER IF NOT EXISTS Settings_count_delete' ' AFTER DELETE ON Cache FOR EACH ROW BEGIN' ' UPDATE Settings SET value = value - 1' ' WHERE key = "count"; END'
)
sql( 'CREATE TRIGGER IF NOT EXISTS Settings_size_insert' ' AFTER INSERT ON Cache FOR EACH ROW BEGIN' ' UPDATE Settings SET value = value + NEW.size' ' WHERE key = "size"; END'
)
sql( 'CREATE TRIGGER IF NOT EXISTS Settings_size_update' ' AFTER UPDATE ON Cache FOR EACH ROW BEGIN' ' UPDATE Settings' ' SET value = value + NEW.size - OLD.size' ' WHERE key = "size"; END'
)
sql( 'CREATE TRIGGER IF NOT EXISTS Settings_size_delete' ' AFTER DELETE ON Cache FOR EACH ROW BEGIN' ' UPDATE Settings SET value = value - OLD.size' ' WHERE key = "size"; END'
)
# Create tag index if requested.
if self.tag_index: # pylint: disable=no-member
self.create_tag_index() else:
self.drop_tag_index()
# Close and re-open database connection with given timeout.
if local_pid != pid:
self.close()
self._local.pid = pid
con = getattr(self._local, 'con', None)
if con isNone:
con = self._local.con = sqlite3.connect(
op.join(self._directory, DBNAME),
timeout=self._timeout,
isolation_level=None,
)
# Some SQLite pragmas work on a per-connection basis so # query the Settings table and reset the pragmas. The # Settings table may not exist so catch and ignore the # OperationalError that may occur.
try:
select = 'SELECT key, value FROM Settings'
settings = con.execute(select).fetchall() except sqlite3.OperationalError: pass else: for key, value in settings: if key.startswith('sqlite_'):
self.reset(key, value, update=False)
# 2018-11-01 GrantJ - Some SQLite builds/versions handle # the SQLITE_BUSY return value and connection parameter # "timeout" differently. For a more reliable duration, # manually retry the statement for 60 seconds. Only used # by statements which modify the database and do not use # a transaction (like those in ``__init__`` or ``reset``). # See Issue #85 for and tests/issue_85.py for more details.
def _execute_with_retry(statement, *args, **kwargs):
start = time.time() whileTrue: try: return sql(statement, *args, **kwargs) except sqlite3.OperationalError as exc: if str(exc) != 'database is locked': raise
diff = time.time() - start if diff > 60: raise
time.sleep(0.001)
return _execute_with_retry
@cl.contextmanager def transact(self, retry=False): """Context manager to perform a transaction by locking the cache.
While the cache is locked, no other write operation is permitted.
Transactions should therefore be as short as possible. Read and write
operations performed in a transaction are atomic. Read operations may
occur concurrent to a transaction.
Transactions may be nested and may not be shared between threads.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
>>> cache = Cache()
>>> with cache.transact(): # Atomically increment two keys.
... _ = cache.incr('total', 123.4)
... _ = cache.incr('count', 1)
>>> with cache.transact(): # Atomically calculate average.
... average = cache['total'] / cache['count']
>>> average
123.4
:param bool retry: retry if database timeout occurs (default False)
:return: context manager for use in `with` statement
:raises Timeout: if database timeout occurs
if tid == txn_id:
begin = False else: whileTrue: try:
sql('BEGIN IMMEDIATE')
begin = True
self._txn_id = tid break except sqlite3.OperationalError: if retry: continue if filename isnotNone:
_disk_remove(filename) raise Timeout fromNone
try: yield sql, filenames.append except BaseException: if begin: assert self._txn_id == tid
self._txn_id = None
sql('ROLLBACK') raise else: if begin: assert self._txn_id == tid
self._txn_id = None
sql('COMMIT') for name in filenames: if name isnotNone:
_disk_remove(name)
def set(self, key, value, expire=None, read=False, tag=None, retry=False): """Set `key` and `value` item in cache.
When `read` is `True`, `value` should be a file-like object opened for reading in binary mode.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key for item
:param value: value for item
:param float expire: seconds until item expires
(default None, no expiry)
:param bool read: read value as bytes from file (default False)
:param str tag: text to associate with key (default None)
:param bool retry: retry if database timeout occurs (default False)
:return: Trueif item was set
:raises Timeout: if database timeout occurs
# The order of SELECT, UPDATE, and INSERT is important below. # # Typical cache usage pattern is: # # value = cache.get(key) # if value is None: # value = expensive_calculation() # cache.set(key, value) # # Cache.get does not evict expired keys to avoid writes during lookups. # Commonly used/expired keys will therefore remain in the cache making # an UPDATE the preferred path. # # The alternative is to assume the key is not present by first trying # to INSERT and then handling the IntegrityError that occurs from # violating the UNIQUE constraint. This optimistic approach was # rejected based on the common cache usage pattern. # # INSERT OR REPLACE aka UPSERT is not used because the old filename may # need cleanup.
with self._transact(retry, filename) as (sql, cleanup):
rows = sql( 'SELECT rowid, filename FROM Cache' ' WHERE key = ? AND raw = ?',
(db_key, raw),
).fetchall()
if rows:
delete = 'DELETE FROM Cache WHERE rowid IN (%s)' % (
select_policy.format(fields='rowid', now=now)
)
sql(delete, (cull_limit,))
for (filename,) in rows:
cleanup(filename)
def touch(self, key, expire=None, retry=False): """Touch `key` in cache and update `expire` time.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key for item
:param float expire: seconds until item expires
(default None, no expiry)
:param bool retry: retry if database timeout occurs (default False)
:return: Trueif key was touched
:raises Timeout: if database timeout occurs
"""
now = time.time()
db_key, raw = self._disk.put(key)
expire_time = Noneif expire isNoneelse now + expire
with self._transact(retry) as (sql, _):
rows = sql( 'SELECT rowid, expire_time FROM Cache' ' WHERE key = ? AND raw = ?',
(db_key, raw),
).fetchall()
if rows:
((rowid, old_expire_time),) = rows
if old_expire_time isNoneor old_expire_time > now:
sql( 'UPDATE Cache SET expire_time = ? WHERE rowid = ?',
(expire_time, rowid),
) returnTrue
returnFalse
def add(self, key, value, expire=None, read=False, tag=None, retry=False): """Add `key` and `value` item to cache.
Similar to `set`, but only add to cache if key not present.
Operation is atomic. Only one concurrent add operation for a given key
will succeed.
When `read` is `True`, `value` should be a file-like object opened for reading in binary mode.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key for item
:param value: value for item
:param float expire: seconds until the key expires
(default None, no expiry)
:param bool read: read value as bytes from file (default False)
:param str tag: text to associate with key (default None)
:param bool retry: retry if database timeout occurs (default False)
:return: Trueif item was added
:raises Timeout: if database timeout occurs
with self._transact(retry, filename) as (sql, cleanup):
rows = sql( 'SELECT rowid, filename, expire_time FROM Cache' ' WHERE key = ? AND raw = ?',
(db_key, raw),
).fetchall()
if rows:
((rowid, old_filename, old_expire_time),) = rows
if old_expire_time isNoneor old_expire_time > now:
cleanup(filename) returnFalse
def incr(self, key, delta=1, default=0, retry=False): """Increment value by delta for item with key.
If key is missing and default isNone then raise KeyError. Elseif key is missing and default isnotNone then use default for value.
Operation is atomic. All concurrent increment operations will be
counted individually.
Assumes value may be stored in a SQLite column. Most builds that target
machines with 64-bit pointer widths will support 64-bit signed
integers.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key for item
:param int delta: amount to increment (default 1)
:param int default: value if key is missing (default 0)
:param bool retry: retry if database timeout occurs (default False)
:return: new value for item
:raises KeyError: if key isnot found and default isNone
:raises Timeout: if database timeout occurs
"""
now = time.time()
db_key, raw = self._disk.put(key)
select = ( 'SELECT rowid, expire_time, filename, value FROM Cache' ' WHERE key = ? AND raw = ?'
)
with self._transact(retry) as (sql, cleanup):
rows = sql(select, (db_key, raw)).fetchall()
ifnot rows: if default isNone: raise KeyError(key)
if update_column isnotNone:
columns += ', ' + update_column.format(now=now)
update = 'UPDATE Cache SET %s WHERE rowid = ?' % columns
sql(update, (now, value, rowid))
return value
def decr(self, key, delta=1, default=0, retry=False): """Decrement value by delta for item with key.
If key is missing and default isNone then raise KeyError. Elseif key is missing and default isnotNone then use default for value.
Operation is atomic. All concurrent decrement operations will be
counted individually.
Unlike Memcached, negative values are supported. Value may be
decremented below zero.
Assumes value may be stored in a SQLite column. Most builds that target
machines with 64-bit pointer widths will support 64-bit signed
integers.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key for item
:param int delta: amount to decrement (default 1)
:param int default: value if key is missing (default 0)
:param bool retry: retry if database timeout occurs (default False)
:return: new value for item
:raises KeyError: if key isnot found and default isNone
:raises Timeout: if database timeout occurs
""" return self.incr(key, -delta, default, retry)
def get(
self,
key,
default=None,
read=False,
expire_time=False,
tag=False,
retry=False,
): """Retrieve value from cache. If `key` is missing, return `default`.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key for item
:param default: value to returnif key is missing (default None)
:param bool read: ifTrue, return file handle to value
(default False)
:param bool expire_time: ifTrue, return expire_time in tuple
(default False)
:param bool tag: ifTrue, return tag in tuple (default False)
:param bool retry: retry if database timeout occurs (default False)
:return: value for item or default if key not found
:raises Timeout: if database timeout occurs
"""
db_key, raw = self._disk.put(key)
update_column = EVICTION_POLICY[self.eviction_policy]['get']
select = ( 'SELECT rowid, expire_time, tag, mode, filename, value' ' FROM Cache WHERE key = ? AND raw = ?' ' AND (expire_time IS NULL OR expire_time > ?)'
)
if expire_time and tag:
default = (default, None, None) elif expire_time or tag:
default = (default, None)
ifnot self.statistics and update_column isNone: # Fast path, no transaction necessary.
try:
value = self._disk.fetch(mode, filename, db_value, read) except IOError: # Key was deleted before we could retrieve result. return default
else: # Slow path, transaction required.
cache_hit = ( 'UPDATE Settings SET value = value + 1 WHERE key = "hits"'
)
cache_miss = ( 'UPDATE Settings SET value = value + 1 WHERE key = "misses"'
)
with self._transact(retry) as (sql, _):
rows = sql(select, (db_key, raw, time.time())).fetchall()
ifnot rows: if self.statistics:
sql(cache_miss) return default
try:
value = self._disk.fetch(mode, filename, db_value, read) except IOError: # Key was deleted before we could retrieve result. if self.statistics:
sql(cache_miss) return default
if self.statistics:
sql(cache_hit)
now = time.time()
update = 'UPDATE Cache SET %s WHERE rowid = ?'
if update_column isnotNone:
sql(update % update_column.format(now=now), (rowid,))
if expire_time and tag: return (value, db_expire_time, db_tag) elif expire_time: return (value, db_expire_time) elif tag: return (value, db_tag) else: return value
def __getitem__(self, key): """Return corresponding value for `key` from cache.
:param key: key matching item
:return: corresponding value
:raises KeyError: if key isnot found
"""
value = self.get(key, default=ENOVAL, retry=True) if value is ENOVAL: raise KeyError(key) return value
def read(self, key, retry=False): """Return file handle value corresponding to `key` from cache.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key matching item
:param bool retry: retry if database timeout occurs (default False)
:return: file open for reading in binary mode
:raises KeyError: if key isnot found
:raises Timeout: if database timeout occurs
"""
handle = self.get(key, default=ENOVAL, read=True, retry=retry) if handle is ENOVAL: raise KeyError(key) return handle
def __contains__(self, key): """Return `True` if `key` matching item is found in cache.
"""
sql = self._sql
db_key, raw = self._disk.put(key)
select = ( 'SELECT rowid FROM Cache' ' WHERE key = ? AND raw = ?' ' AND (expire_time IS NULL OR expire_time > ?)'
)
def pop(
self, key, default=None, expire_time=False, tag=False, retry=False
): # noqa: E501 """Remove corresponding item for `key` from cache and return value.
If `key` is missing, return `default`.
Operation is atomic. Concurrent operations will be serialized.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key for item
:param default: value to returnif key is missing (default None)
:param bool expire_time: ifTrue, return expire_time in tuple
(default False)
:param bool tag: ifTrue, return tag in tuple (default False)
:param bool retry: retry if database timeout occurs (default False)
:return: value for item or default if key not found
:raises Timeout: if database timeout occurs
"""
db_key, raw = self._disk.put(key)
select = ( 'SELECT rowid, expire_time, tag, mode, filename, value' ' FROM Cache WHERE key = ? AND raw = ?' ' AND (expire_time IS NULL OR expire_time > ?)'
)
if expire_time and tag:
default = default, None, None elif expire_time or tag:
default = default, None
with self._transact(retry) as (sql, _):
rows = sql(select, (db_key, raw, time.time())).fetchall()
sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
try:
value = self._disk.fetch(mode, filename, db_value, False) except IOError: # Key was deleted before we could retrieve result. return default finally: if filename isnotNone:
self._disk.remove(filename)
if expire_time and tag: return value, db_expire_time, db_tag elif expire_time: return value, db_expire_time elif tag: return value, db_tag else: return value
def __delitem__(self, key, retry=True): """Delete corresponding item for `key` from cache.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default `True`).
:param key: key matching item
:param bool retry: retry if database timeout occurs (default True)
:raises KeyError: if key isnot found
:raises Timeout: if database timeout occurs
"""
db_key, raw = self._disk.put(key)
with self._transact(retry) as (sql, cleanup):
rows = sql( 'SELECT rowid, filename FROM Cache' ' WHERE key = ? AND raw = ?' ' AND (expire_time IS NULL OR expire_time > ?)',
(db_key, raw, time.time()),
).fetchall()
ifnot rows: raise KeyError(key)
((rowid, filename),) = rows
sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
cleanup(filename)
returnTrue
def delete(self, key, retry=False): """Delete corresponding item for `key` from cache.
Missing keys are ignored.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param key: key matching item
:param bool retry: retry if database timeout occurs (default False)
:return: Trueif item was deleted
:raises Timeout: if database timeout occurs
:param value: value for item
:param str prefix: key prefix (default None, key is integer)
:param str side: either 'back'or'front' (default 'back')
:param float expire: seconds until the key expires
(default None, no expiry)
:param bool read: read value as bytes from file (default False)
:param str tag: text to associate with key (default None)
:param bool retry: retry if database timeout occurs (default False)
:return: key for item in cache
:raises Timeout: if database timeout occurs
def pull(
self,
prefix=None,
default=(None, None),
side='front',
expire_time=False,
tag=False,
retry=False,
): """Pull key and value item pair from `side` of queue in cache.
When prefix isNone, integer keys are used. Otherwise, string keys are
used in the format "prefix-integer". Integer starts at 500 trillion.
If queue is empty, return default.
Defaults to pulling key and value item pairs from front of queue. Set
side to 'back' to pull from back of queue. Side must be one of 'front' or'back'.
Operation is atomic. Concurrent operations will be serialized.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
See also `Cache.push` and `Cache.get`.
>>> cache = Cache()
>>> cache.pull()
(None, None)
>>> for letter in'abc':
... print(cache.push(letter))
500000000000000
500000000000001
500000000000002
>>> key, value = cache.pull()
>>> print(key)
500000000000000
>>> value 'a'
>>> _, value = cache.pull(side='back')
>>> value 'c'
>>> cache.push(1234, 'userids') 'userids-500000000000000'
>>> _, value = cache.pull('userids')
>>> value
1234
:param str prefix: key prefix (default None, key is integer)
:param default: value to returnif key is missing
(default (None, None))
:param str side: either 'front'or'back' (default 'front')
:param bool expire_time: ifTrue, return expire_time in tuple
(default False)
:param bool tag: ifTrue, return tag in tuple (default False)
:param bool retry: retry if database timeout occurs (default False)
:return: key and value item pair or default if queue is empty
:raises Timeout: if database timeout occurs
sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
if db_expire isnotNoneand db_expire < time.time():
cleanup(name) else: break
try:
value = self._disk.fetch(mode, name, db_value, False) except IOError: # Key was deleted before we could retrieve result. continue finally: if name isnotNone:
self._disk.remove(name) break
if expire_time and tag: return (key, value), db_expire, db_tag elif expire_time: return (key, value), db_expire elif tag: return (key, value), db_tag else: return key, value
def peek(
self,
prefix=None,
default=(None, None),
side='front',
expire_time=False,
tag=False,
retry=False,
): """Peek at key and value item pair from `side` of queue in cache.
When prefix isNone, integer keys are used. Otherwise, string keys are
used in the format "prefix-integer". Integer starts at 500 trillion.
If queue is empty, return default.
Defaults to peeking at key and value item pairs from front of queue.
Set side to 'back' to pull from back of queue. Side must be one of 'front'or'back'.
Expired items are deleted from cache. Operation is atomic. Concurrent
operations will be serialized.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
See also `Cache.pull` and `Cache.push`.
>>> cache = Cache()
>>> for letter in'abc':
... print(cache.push(letter))
500000000000000
500000000000001
500000000000002
>>> key, value = cache.peek()
>>> print(key)
500000000000000
>>> value 'a'
>>> key, value = cache.peek(side='back')
>>> print(key)
500000000000002
>>> value 'c'
:param str prefix: key prefix (default None, key is integer)
:param default: value to returnif key is missing
(default (None, None))
:param str side: either 'front'or'back' (default 'front')
:param bool expire_time: ifTrue, return expire_time in tuple
(default False)
:param bool tag: ifTrue, return tag in tuple (default False)
:param bool retry: retry if database timeout occurs (default False)
:return: key and value item pair or default if queue is empty
:raises Timeout: if database timeout occurs
if db_expire isnotNoneand db_expire < time.time():
sql('DELETE FROM Cache WHERE rowid = ?', (rowid,))
cleanup(name) else: break
try:
value = self._disk.fetch(mode, name, db_value, False) except IOError: # Key was deleted before we could retrieve result. continue break
if expire_time and tag: return (key, value), db_expire, db_tag elif expire_time: return (key, value), db_expire elif tag: return (key, value), db_tag else: return key, value
def peekitem(self, last=True, expire_time=False, tag=False, retry=False): """Peek at key and value item pair in cache based on iteration order.
Expired items are deleted from cache. Operation is atomic. Concurrent
operations will be serialized.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
>>> cache = Cache()
>>> for num, letter in enumerate('abc'):
... cache[letter] = num
>>> cache.peekitem()
('c', 2)
>>> cache.peekitem(last=False)
('a', 0)
:param bool last: last item in iteration order (default True)
:param bool expire_time: ifTrue, return expire_time in tuple
(default False)
:param bool tag: ifTrue, return tag in tuple (default False)
:param bool retry: retry if database timeout occurs (default False)
:return: key and value item pair
:raises KeyError: if cache is empty
:raises Timeout: if database timeout occurs
"""
order = ('ASC', 'DESC')
select = ( 'SELECT rowid, key, raw, expire_time, tag, mode, filename, value' ' FROM Cache ORDER BY rowid %s LIMIT 1'
) % order[last]
whileTrue: whileTrue: with self._transact(retry) as (sql, cleanup):
rows = sql(select).fetchall()
Decorator to wrap callable with memoizing function using cache.
Repeated calls with the same arguments will lookup result in cache and
avoid function evaluation.
If name is set to None (default), the callable name will be determined
automatically.
When expire is set to zero, function results will not be set in the
cache. Cache lookups still occur, however. Read
:doc:`case-study-landing-page-caching` for example usage.
If typed is set to True, function arguments of different types will be
cached separately. For example, f(3) and f(3.0) will be treated as
distinct calls with distinct results.
The original underlying function is accessible through the __wrapped__
attribute. This is useful for introspection, for bypassing the cache, orfor rewrapping the function with a different cache.
Remember to call memoize when decorating a callable. If you forget,
then a TypeError will occur. Note the lack of parenthenses after
memoize below:
>>> @cache.memoize
... def test():
... pass
Traceback (most recent call last):
...
TypeError: name cannot be callable
:param cache: cache to store callable arguments andreturn values
:param str name: name given for callable (default None, automatic)
:param bool typed: cache different types separately (default False)
:param float expire: seconds until arguments expire
(default None, no expiry)
:param str tag: text to associate with arguments (default None)
:param set ignore: positional or keyword args to ignore (default ())
:return: callable decorator
""" # Caution: Nearly identical code exists in DjangoCache.memoize if callable(name): raise TypeError('name cannot be callable')
def decorator(func): """Decorator created by memoize() for callable `func`."""
base = (full_name(func),) if name isNoneelse (name,)
@ft.wraps(func) def wrapper(*args, **kwargs): """Wrapper for callable to cache arguments and return values."""
key = wrapper.__cache_key__(*args, **kwargs)
result = self.get(key, default=ENOVAL, retry=True)
if result is ENOVAL:
result = func(*args, **kwargs) if expire isNoneor expire > 0:
self.set(key, result, expire, tag=tag, retry=True)
return result
def __cache_key__(*args, **kwargs): """Make key for cache given function arguments.""" return args_to_key(base, args, kwargs, typed, ignore)
def check(self, fix=False, retry=False): """Check database and file system consistency.
Intended for use in testing and post-mortem error analysis.
While checking the Cache table for consistency, a writer lock is held
on the database. The lock blocks other cache clients from writing to
the database. For caches with many file references, the lock may be
held for a long time. For example, local benchmarking shows that a
cache with 1,000 file references takes ~60ms to check.
Raises :exc:`Timeout` error when database timeout occurs and `retry` is
`False` (default).
:param bool fix: correct inconsistencies
:param bool retry: retry if database timeout occurs (default False)
:return: list of warnings
:raises Timeout: if database timeout occurs
""" # pylint: disable=access-member-before-definition,W0201 with warnings.catch_warnings(record=True) as warns:
sql = self._sql
# Check integrity of database.
rows = sql('PRAGMA integrity_check').fetchall()
if len(rows) != 1 or rows[0][0] != 'ok': for (message,) in rows:
warnings.warn(message)
if fix:
sql('VACUUM')
with self._transact(retry) as (sql, _):
# Check Cache.filename against file system.
filenames = set()
select = ( 'SELECT rowid, size, filename FROM Cache' ' WHERE filename IS NOT NULL'
)
rows = sql(select).fetchall()
for rowid, size, filename in rows:
full_path = op.join(self._directory, filename)
filenames.add(full_path)
if op.exists(full_path):
real_size = op.getsize(full_path)
if fix:
sql( 'UPDATE Cache SET size = ?' ' WHERE rowid = ?',
(real_size, rowid),
--> --------------------
--> maximum size reached
--> --------------------
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.32Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.