From a743b0687891947c2bc11cdcae3bff5b5c9d8f00 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 9 Jul 2016 19:49:42 -0700 Subject: [PATCH 001/550] Read readme as UTF-8 for mu symbol --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 33827c1..74c520f 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import diskcache +import io from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand import sys @@ -15,7 +16,7 @@ def run_tests(self): sys.exit(errno) -with open('README.rst') as reader: +with io.open('README.rst', encoding='UTF-8') as reader: readme = reader.read() setup( From 4fc872da99506fc052ffa07137d16a6e7d0ac092 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 9 Jul 2016 19:50:23 -0700 Subject: [PATCH 002/550] Bump version to 1.6.7 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 4f92e99..2146206 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '1.6.6' -__build__ = 0x010606 +__version__ = '1.6.7' +__build__ = 0x010607 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 204762dc6e253cd8bb2bbd1b7d6d84a493de9804 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 11 Jul 2016 11:34:39 -0700 Subject: [PATCH 003/550] Add read method --- .pylintrc | 2 +- diskcache/core.py | 22 ++++++++++++++++++---- diskcache/djangocache.py | 12 ++++++++++++ diskcache/fanout.py | 18 ++++++++++++++++-- tests/test_core.py | 14 ++++++++++++++ tests/test_djangocache.py | 17 +++++++++++++++++ tests/test_fanout.py | 14 ++++++++++++++ 7 files changed, 92 insertions(+), 7 deletions(-) diff --git a/.pylintrc b/.pylintrc index d4be604..0c4a7d5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -229,7 +229,7 @@ notes=FIXME,XXX,TODO [SIMILARITIES] # Minimum lines number of a similarity. -min-similarity-lines=10 +min-similarity-lines=20 # Ignore comments when computing similarities. ignore-comments=yes diff --git a/diskcache/core.py b/diskcache/core.py index 2c67c9b..b286e23 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -607,16 +607,16 @@ def set(self, key, value, expire=None, read=False, tag=None): def get(self, key, default=None, read=False, expire_time=False, tag=False): - """Get key from cache. If key is missing, return default. + """Retrieve value from cache. If key is missing, return default. :param key: Python key to retrieve :param default: value to return if key is missing (default None) :param bool read: if True, return file handle to value (default False) - :param float expire_time: if True, return expire_time in tuple + :param bool expire_time: if True, return expire_time in tuple (default False) - :param tag: if True, return tag in tuple (default False) - :return: key or `default` if not found + :param bool tag: if True, return tag in tuple (default False) + :return: corresponding value or `default` if not found """ sql = self._sql @@ -697,6 +697,20 @@ def __getitem__(self, key): return value + def read(self, key): + """Return file handle corresponding to `key` from Cache. + + :param key: Python key to retrieve + :return: file open for reading in binary mode + :raises KeyError: if key is not found + + """ + handle = self.get(key, default=ENOVAL, read=True) + if handle is ENOVAL: + raise KeyError(key) + return handle + + def __contains__(self, key): "Return True if `key` in Cache." sql = self._sql diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 5da77e3..985c367 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -59,6 +59,18 @@ def get(self, key, default=None, version=None, read=False, ) + def read(self, key, version=None): + """Return file handle corresponding to `key` from Cache. + + :param key: Python key to retrieve + :return: file open for reading in binary mode + :raises KeyError: if key is not found + + """ + key = self.make_key(key, version=version) + return self._cache.read(key) + + def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, read=False, tag=None): """Set a value in the cache. If timeout is given, that timeout will be used diff --git a/diskcache/fanout.py b/diskcache/fanout.py index ff6a97b..da1a719 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -65,7 +65,7 @@ def set(self, key, value, read=False, expire=None, tag=None): def get(self, key, default=None, read=False, expire_time=False, tag=False): - """Get key from cache. If key is missing, return default. + """Retrieve value from cache. If key is missing, return default. :param key: Python key to retrieve :param default: value to return if key is missing (default None) @@ -74,7 +74,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): :param float expire_time: if True, return expire_time in tuple (default False) :param tag: if True, return tag in tuple (default False) - :return: key or `default` if not found + :return: corresponding value or `default` if not found """ try: @@ -95,6 +95,20 @@ def __getitem__(self, key): return value + def read(self, key): + """Return file handle corresponding to `key` from Cache. + + :param key: Python key to retrieve + :return: file open for reading in binary mode + :raises KeyError: if key is not found + + """ + handle = self.get(key, default=ENOVAL, read=True) + if handle is ENOVAL: + raise KeyError(key) + return handle + + def __contains__(self, key): "Return True if `key` in Cache." try: diff --git a/tests/test_core.py b/tests/test_core.py index 783999b..5f39cb1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -238,6 +238,20 @@ def test_get_keyerror5(cache): cache[0] +@setup_cache +def test_read(cache): + cache.set(0, b'abcd' * 2 ** 12) + with cache.read(0) as reader: + assert reader is not None + + +@nt.raises(KeyError) +@setup_cache +def test_read_keyerror(cache): + with cache.read(0) as reader: + pass + + @setup_cache def test_set_twice(cache): large_value = b'abcd' * 2 ** 12 diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 31eb99d..186f57d 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -858,3 +858,20 @@ def test_zero_cull(self): def test_invalid_keys(self): pass # DiskCache supports any Pickleable value as a key. + + def test_read(self): + value = b'abcd' * 2 ** 12 + result = cache.set(b'test-key', value) + + self.assertTrue(result) + + with cache.read(b'test-key') as reader: + self.assertEqual(reader.read(), value) + + try: + with cache.read(b'dne') as reader: + error = False + except KeyError: + error = True + + self.assertTrue(error) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index e43cc16..5a9a23c 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -89,6 +89,20 @@ def test_set_get_delete(cache): cache.check() +@setup_cache +def test_read(cache): + cache.set(0, b'abcd' * 2 ** 12) + with cache.read(0) as reader: + assert reader is not None + + +@nt.raises(KeyError) +@setup_cache +def test_read_keyerror(cache): + with cache.read(0) as reader: + pass + + def test_operationalerror(): cache = dc.FanoutCache('tmp', shards=1) From e48b94d3a6658129f2fa7a373ca434ee6841a4d0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Jul 2016 11:54:47 -0700 Subject: [PATCH 004/550] Implement atomic 'add' method on caches. --- diskcache/core.py | 189 +++++++++++++++++++++++++++++---------- diskcache/djangocache.py | 8 +- diskcache/fanout.py | 38 +++++++- tests/test_core.py | 90 +++++++++++++++++++ 4 files changed, 267 insertions(+), 58 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index b286e23..d8543c8 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -483,7 +483,7 @@ def _sql(self): def set(self, key, value, expire=None, read=False, tag=None): - """Store key, value pair in cache. + """Set key, value pair in cache. When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. @@ -493,7 +493,7 @@ def set(self, key, value, expire=None, read=False, tag=None): :param expire: seconds until the key expires (default None, no expiry) :param bool read: read value as raw bytes from file (default False) :param tag: text to associate with key (default None) - :return: True if item was successfully committed + :return: True if item was successfully set """ sql = self._sql @@ -508,17 +508,12 @@ def set(self, key, value, expire=None, read=False, tag=None): ).fetchall() if rows: - (version, filename), = rows + (old_version, old_filename), = rows else: sql('INSERT OR IGNORE INTO Cache(key, raw) VALUES (?, ?)', (db_key, raw) ) - version, filename = 0, None - - # Remove existing file if present. - - if filename is not None: - self._remove(filename) + old_version, old_filename = 0, None # Prepare value for disk storage. @@ -526,53 +521,71 @@ def set(self, key, value, expire=None, read=False, tag=None): value, read, self.large_value_threshold, self._prep_file ) - next_version = version + 1 + next_version = old_version + 1 now = time.time() expire_time = None if expire is None else now + expire # Update the row. Two step process so that all files remain tracked. - cursor = sql( - 'UPDATE Cache SET' - ' version = ?,' - ' store_time = ?,' - ' expire_time = ?,' - ' access_time = ?,' - ' access_count = ?,' - ' tag = ?,' - ' size = ?,' - ' mode = ?,' - ' filename = ?,' - ' value = ?' - ' WHERE key = ? AND raw = ? AND version = ?', ( - next_version, - now, # store_time - expire_time, - now, # access_time - 0, # access_count - tag, - size, - mode, - filename, - db_value, - db_key, - raw, - version, - ), - ) + try: + cursor = sql( + 'UPDATE Cache SET' + ' version = ?,' + ' store_time = ?,' + ' expire_time = ?,' + ' access_time = ?,' + ' access_count = ?,' + ' tag = ?,' + ' size = ?,' + ' mode = ?,' + ' filename = ?,' + ' value = ?' + ' WHERE key = ? AND raw = ? AND version = ?', ( + next_version, + now, # store_time + expire_time, + now, # access_time + 0, # access_count + tag, + size, + mode, + filename, + db_value, + db_key, + raw, + old_version, + ), + ) + except sqlite3.OperationalError: + # Most likely a connection timeout occurred. + self._remove(filename) + raise - if cursor.rowcount == 0: - # Another Cache wrote the value before us so abort. - if filename is not None: - self._remove(filename) + if cursor.rowcount == 1: + self._remove(old_filename) + else: + # Another client wrote the value before us so abort. + assert cursor.rowcount == 0 + self._remove(filename) return False - # Evict expired keys. + self._evict_expired_keys() + return True + + + __setitem__ = set + + + def _evict_expired_keys(self): + "Evict expired keys." cull_limit = self.cull_limit if cull_limit == 0: - return True + return + + sql = self._sql + now = time.time() rows = sql( 'SELECT rowid, version, filename FROM Cache' @@ -586,10 +599,10 @@ def set(self, key, value, expire=None, read=False, tag=None): cull_limit -= deleted if cull_limit == 0: - return True + return if self.volume() < self.size_limit: - return True + return # Evict keys by policy. @@ -601,9 +614,84 @@ def set(self, key, value, expire=None, read=False, tag=None): for rowid, version, filename in rows: self._delete(rowid, version, filename) - return True - __setitem__ = set + def add(self, key, value, expire=None, read=False, tag=None): + """Add key, value pair to cache. + + Similar to `set`, but only set in cache if key not present. + + This operation is atomic. Only one concurrent add operation for given + key from separate threads or processes will succeed. + + When `read` is `True`, `value` should be a file-like object opened + for reading in binary mode. + + :param key: Python key to store + :param value: Python value to store + :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) + :return: True if item was successfully added + + """ + sql = self._sql + now = time.time() + db_key, raw = self._disk.put(key) + expire_time = None if expire is None else now + expire + size, mode, filename, db_value = self._disk.store( + value, read, self.large_value_threshold, self._prep_file + ) + + try: + sql('BEGIN IMMEDIATE') + except sqlite3.OperationalError: + self._remove(filename) + raise + + rows = sql( + 'SELECT rowid, filename, expire_time FROM Cache' + ' WHERE key = ? AND raw = ?', + (db_key, raw), + ).fetchall() + + if rows: + (old_rowid, old_filename, old_expire_time), = rows + + if old_expire_time is not None and old_expire_time < now: + sql('DELETE FROM Cache WHERE rowid = ?', (old_rowid,)) + else: + sql('COMMIT') + self._remove(filename) + return False + else: + old_filename = None + + sql('INSERT INTO Cache(' + ' key, raw, version, store_time, expire_time, access_time,' + ' access_count, tag, size, mode, filename, value' + ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( + db_key, + raw, + 0, # version + now, # store_time + expire_time, + now, # access_time + 0, # access_count + tag, + size, + mode, + filename, + db_value, + ), + ) + + sql('COMMIT') + + self._remove(old_filename) + self._evict_expired_keys() + + return True def get(self, key, default=None, read=False, expire_time=False, tag=False): @@ -773,7 +861,7 @@ def _delete(self, rowid, version, filename): deleted = cursor.rowcount == 1 - if deleted and filename is not None: + if deleted: self._remove(filename) return deleted @@ -798,6 +886,9 @@ def _prep_file(self): def _remove(self, filename): + if filename is None: + return + full_path = op.join(self._dir, filename) try: diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 985c367..61a7fec 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -39,11 +39,9 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, """ # pylint: disable=arguments-differ - if self.has_key(key, version): - return False - return self.set( - key, value, timeout=timeout, version=version, read=read, tag=tag - ) + key = self.make_key(key, version=version) + timeout = self.get_backend_timeout(timeout=timeout) + return self._cache.add(key, value, expire=timeout, read=read, tag=tag) def get(self, key, default=None, version=None, read=False, diff --git a/diskcache/fanout.py b/diskcache/fanout.py index da1a719..e3e8b46 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -39,8 +39,8 @@ def __setattr__(self, name, value): setattr(shard, name, value) - def set(self, key, value, read=False, expire=None, tag=None): - """Store key, value pair in cache. + def set(self, key, value, expire=None, read=False, tag=None): + """Set key, value pair in cache. When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. @@ -50,20 +50,50 @@ def set(self, key, value, read=False, expire=None, tag=None): :param expire: seconds until the key expires (default None, no expiry) :param bool read: read value as raw bytes from file (default False) :param tag: text to associate with key (default None) - :return: True if item was successfully committed + :return: True if item was successfully set """ try: index = hash(key) % self._count return self._shards[index].set( - key, value, read=read, expire=expire, tag=tag, + key, value, expire=expire, read=read, tag=tag, ) except sqlite3.OperationalError: return False + __setitem__ = set + def add(self, key, value, expire=None, read=False, tag=None): + """Add key, value pair to cache. + + Similar to `set`, but only set in cache if key not present. + + This operation is atomic. Only one concurrent add operation for given + key from separate threads or processes will succeed. + + When `read` is `True`, `value` should be a file-like object opened + for reading in binary mode. + + :param key: Python key to store + :param value: Python value to store + :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) + :return: True if item was successfully added + + """ + try: + index = hash(key) % self._count + return self._shards[index].add( + key, value, expire=expire, read=read, tag=tag, + ) + except sqlite3.OperationalError: + return False + + def get(self, key, default=None, read=False, expire_time=False, tag=False): """Retrieve value from cache. If key is missing, return default. diff --git a/tests/test_core.py b/tests/test_core.py index 5f39cb1..e07ba3e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,6 @@ "Test diskcache.core.Cache." +import collections as co import errno import functools as ft import io @@ -298,6 +299,28 @@ def test_set_noupdate(cache): cache.check() +@setup_cache +@nt.raises(sqlite3.OperationalError) +def test_set_timeout(cache): + local = mock.Mock() + con = mock.Mock() + execute = mock.Mock() + cursor = mock.Mock() + fetchall = mock.Mock() + + local.con = con + con.execute = execute + execute.side_effect = [cursor, sqlite3.OperationalError] + cursor.fetchall = fetchall + fetchall.return_value = [(0, None)] + + try: + with mock.patch.object(cache, '_local', local): + cache.set(0, 0) + finally: + cache.check() + + @setup_cache def test_raw(cache): assert cache.set(0, io.BytesIO(b'abcd'), read=True) @@ -697,6 +720,73 @@ def test_contains(cache): assert 0 not in cache +@setup_cache +def test_add(cache): + assert cache.add(1, 1) + assert cache.get(1) == 1 + assert not cache.add(1, 2) + assert cache.get(1) == 1 + cache.check() + +@setup_cache +def test_add_large_value(cache): + value = b'abcd' * 2 ** 12 + assert cache.add(b'test-key', value) + assert cache.get(b'test-key') == value + assert not cache.add(b'test-key', value * 2) + assert cache.get(b'test-key') == value + cache.check() + +results = co.deque() + + +def stress_add(cache, limit): + total = 0 + for num in xrange(limit): + if cache.add(num, num): + total += 1 + # Stop one thread from running ahead of others. + time.sleep(0.001) + results.append(total) + + +@setup_cache +def test_add_concurrent(cache): + limit = 1000 + + threads = [ + threading.Thread(target=stress_add, args=(cache, limit)) + for _ in range(16) + ] + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + + assert sum(results) == limit + cache.check() + + +@setup_cache +@nt.raises(sqlite3.OperationalError) +def test_add_timeout(cache): + local = mock.Mock() + con = mock.Mock() + execute = mock.Mock() + + local.con = con + con.execute = execute + execute.side_effect = sqlite3.OperationalError + + try: + with mock.patch.object(cache, '_local', local): + cache.add(0, 0) + finally: + cache.check() + + if __name__ == '__main__': import nose nose.runmodule() From a05ae9ebf9f76ad4db499739a283d8dad80d6e7f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Jul 2016 12:47:49 -0700 Subject: [PATCH 005/550] Refactor Cache.set to use transactions --- diskcache/core.py | 75 +++++++++++++++++++++++----------------------- tests/test_core.py | 29 +----------------- 2 files changed, 38 insertions(+), 66 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index d8543c8..e3d104f 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -497,39 +497,28 @@ def set(self, key, value, expire=None, read=False, tag=None): """ sql = self._sql - + now = time.time() db_key, raw = self._disk.put(key) + expire_time = None if expire is None else now + expire + size, mode, filename, db_value = self._disk.store( + value, read, self.large_value_threshold, self._prep_file + ) - # Lookup filename for existing key. + try: + sql('BEGIN IMMEDIATE') + except sqlite3.OperationalError: + self._remove(filename) + raise rows = sql( - 'SELECT version, filename FROM Cache WHERE key = ? AND raw = ?', + 'SELECT rowid, filename FROM Cache WHERE key = ? AND raw = ?', (db_key, raw), ).fetchall() if rows: - (old_version, old_filename), = rows - else: - sql('INSERT OR IGNORE INTO Cache(key, raw) VALUES (?, ?)', - (db_key, raw) - ) - old_version, old_filename = 0, None - - # Prepare value for disk storage. - - size, mode, filename, db_value = self._disk.store( - value, read, self.large_value_threshold, self._prep_file - ) - - next_version = old_version + 1 - now = time.time() - expire_time = None if expire is None else now + expire + (old_rowid, old_filename), = rows - # Update the row. Two step process so that all files remain tracked. - - try: - cursor = sql( - 'UPDATE Cache SET' + sql('UPDATE Cache SET' ' version = ?,' ' store_time = ?,' ' expire_time = ?,' @@ -540,8 +529,8 @@ def set(self, key, value, expire=None, read=False, tag=None): ' mode = ?,' ' filename = ?,' ' value = ?' - ' WHERE key = ? AND raw = ? AND version = ?', ( - next_version, + ' WHERE rowid = ?', ( + 0, # version now, # store_time expire_time, now, # access_time @@ -551,24 +540,34 @@ def set(self, key, value, expire=None, read=False, tag=None): mode, filename, db_value, + old_rowid, + ), + ) + else: + old_filename = None + + sql('INSERT INTO Cache(' + ' key, raw, version, store_time, expire_time, access_time,' + ' access_count, tag, size, mode, filename, value' + ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( db_key, raw, - old_version, + 0, # version + now, # store_time + expire_time, + now, # access_time + 0, # access_count + tag, + size, + mode, + filename, + db_value, ), ) - except sqlite3.OperationalError: - # Most likely a connection timeout occurred. - self._remove(filename) - raise - if cursor.rowcount == 1: - self._remove(old_filename) - else: - # Another client wrote the value before us so abort. - assert cursor.rowcount == 0 - self._remove(filename) - return False + sql('COMMIT') + self._remove(old_filename) self._evict_expired_keys() return True diff --git a/tests/test_core.py b/tests/test_core.py index e07ba3e..6839076 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -276,43 +276,16 @@ def test_set_twice(cache): cache.check() -@setup_cache -def test_set_noupdate(cache): - local = mock.Mock() - con = mock.Mock() - execute = mock.Mock() - cursor = mock.Mock() - fetchall = mock.Mock() - - local.con = con - con.execute = execute - execute.return_value = cursor - cursor.rowcount = 0 - cursor.fetchall = fetchall - fetchall.return_value = None - - del cache.large_value_threshold - - with mock.patch.object(cache, '_local', local): - assert not cache.set(0, b'abcd' * 2 ** 12) - - cache.check() - - @setup_cache @nt.raises(sqlite3.OperationalError) def test_set_timeout(cache): local = mock.Mock() con = mock.Mock() execute = mock.Mock() - cursor = mock.Mock() - fetchall = mock.Mock() local.con = con con.execute = execute - execute.side_effect = [cursor, sqlite3.OperationalError] - cursor.fetchall = fetchall - fetchall.return_value = [(0, None)] + execute.side_effect = sqlite3.OperationalError try: with mock.patch.object(cache, '_local', local): From aba4cd12cbda53d06bcbf276d04076b052782572 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Jul 2016 13:02:03 -0700 Subject: [PATCH 006/550] Add comment about design of Cache.set --- diskcache/core.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index e3d104f..cc940b8 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -510,6 +510,24 @@ def set(self, key, value, expire=None, read=False, tag=None): self._remove(filename) raise + # 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. + rows = sql( 'SELECT rowid, filename FROM Cache WHERE key = ? AND raw = ?', (db_key, raw), From 84e9ebf844c4dc9dc57f3e12a5612cdc286096aa Mon Sep 17 00:00:00 2001 From: Russ Kubik Date: Tue, 26 Jul 2016 18:44:12 -0400 Subject: [PATCH 007/550] New Cache OrderedCache to query by order --- diskcache/__init__.py | 1 + diskcache/ordered.py | 58 +++++++++++++++++++++++++++++ tests/test_ordered.py | 87 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 diskcache/ordered.py create mode 100644 tests/test_ordered.py diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 2146206..b489e75 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -3,6 +3,7 @@ from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning from .core import LIMITS, DEFAULT_SETTINGS, EVICTION_POLICY from .fanout import FanoutCache +from .ordered import OrderedCache try: from .djangocache import DjangoCache diff --git a/diskcache/ordered.py b/diskcache/ordered.py new file mode 100644 index 0000000..445429f --- /dev/null +++ b/diskcache/ordered.py @@ -0,0 +1,58 @@ +"Ordered cache to save keys in order." + +import sqlite3 + +from .core import Cache, Disk, ENOVAL + +class OrderedCache(Cache): + "Cache that saves keys in order." + def __init__(self, directory, timeout=60, disk=Disk(), + **settings): + """Initialize OrderedCache instance. + + :param str directory: cache directory + :param float timeout: SQLite connection timeout + :param disk: `Disk`: instance for serialization + :param settings: any of `DEFAULT_SETTINGS` + + """ + + super(self.__class__, self).__init__(directory, timeout, disk) + + + def first(self, default=None, key=False, read=False, expire_time=False, tag=False): + """ Return the first entry in cache """ + sql = self._sql + + row = sql('SELECT key FROM Cache ORDER BY rowid ASC').fetchone() + + if not row: + return default + + (cache_key), = row + + value = self.get(cache_key, default=default, read=read, expire_time=expire_time, tag=tag) + + if key: + return (cache_key, value) + else: + return value + + + def last(self, default=None, key=False, read=False, expire_time=False, tag=False): + """ Return the last entry in cache """ + sql = self._sql + + row = sql('SELECT key FROM Cache ORDER BY rowid DESC').fetchone() + + if not row: + return default + + (cache_key), = row + + value = self.get(cache_key, default=default, read=read, expire_time=expire_time, tag=tag) + + if key: + return (cache_key, value) + else: + return value diff --git a/tests/test_ordered.py b/tests/test_ordered.py new file mode 100644 index 0000000..d62b2c8 --- /dev/null +++ b/tests/test_ordered.py @@ -0,0 +1,87 @@ +"Test diskcache.ordered.OrderedCache." + +import errno +import functools as ft +import io +import mock +import nose.tools as nt +import os +import random +import shutil +import sqlite3 +import sys +import threading +import time +import warnings + +try: + import cPickle as pickle +except: + import pickle + +import diskcache as dc + +warnings.simplefilter('error') +warnings.simplefilter('ignore', category=dc.EmptyDirWarning) + +if sys.hexversion < 0x03000000: + range = xrange + +def setup_cache(func): + @ft.wraps(func) + def wrapper(): + shutil.rmtree('tmp', ignore_errors=True) + with dc.OrderedCache('tmp') as cache: + func(cache) + shutil.rmtree('tmp', ignore_errors=True) + return wrapper + + +@setup_cache +def test_init(cache): + for key, value in dc.DEFAULT_SETTINGS.items(): + assert getattr(cache, key) == value + + cache.check() + + for key, value in dc.DEFAULT_SETTINGS.items(): + setattr(cache, key, value) + + cache.check() + + +@setup_cache +def test_set_get(cache): + start = 0 + end = 100 + + for value in range(start, end + 1): + cache.set(value, value) + + cache.check() + + assert cache.first() == start + assert cache.last() == end + + assert cache.first(key=True)[0] == start + assert cache.last(key=True)[0] == end + + cache.check() + + cache.delete(start) + cache.delete(end) + + cache.check() + + assert cache.first() == start + 1 + assert cache.last() == end - 1 + + assert cache.first(key=True)[0] == start + 1 + assert cache.last(key=True)[0] == end - 1 + + cache.check() + + +if __name__ == '__main__': + import nose + nose.runmodule() From c0fb452688f5cec033bde7c5209b1201f2d63d50 Mon Sep 17 00:00:00 2001 From: Russ Kubik Date: Tue, 26 Jul 2016 22:51:40 -0400 Subject: [PATCH 008/550] Implement Cache __iter__ and __reversed__ for OrderdCache first/last methods --- diskcache/core.py | 14 +++++++++++++ diskcache/ordered.py | 47 ++++++++++++++----------------------------- tests/test_ordered.py | 20 ++++++++++++------ 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 2c67c9b..6f6044d 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -482,6 +482,20 @@ def _sql(self): return con.execute + def __iter__(self): + sql = self._sql + + for (key,) in sql('SELECT key FROM Cache ORDER BY rowid ASC').fetchall(): + yield key + + + def __reversed__(self): + sql = self._sql + + for (key,) in sql('SELECT key FROM Cache ORDER BY rowid DESC').fetchall(): + yield key + + def set(self, key, value, expire=None, read=False, tag=None): """Store key, value pair in cache. diff --git a/diskcache/ordered.py b/diskcache/ordered.py index 445429f..c64fead 100644 --- a/diskcache/ordered.py +++ b/diskcache/ordered.py @@ -16,43 +16,26 @@ def __init__(self, directory, timeout=60, disk=Disk(), :param settings: any of `DEFAULT_SETTINGS` """ - super(self.__class__, self).__init__(directory, timeout, disk) + + def first(self, default=ENOVAL): + """Retrieve the first key in the cache based on age - def first(self, default=None, key=False, read=False, expire_time=False, tag=False): - """ Return the first entry in cache """ - sql = self._sql - - row = sql('SELECT key FROM Cache ORDER BY rowid ASC').fetchone() - - if not row: + :param key default: default value if cache is empty + """ + try: + return next(iter(self)) + except StopIteration: return default - (cache_key), = row - - value = self.get(cache_key, default=default, read=read, expire_time=expire_time, tag=tag) - - if key: - return (cache_key, value) - else: - return value + def last(self, default=ENOVAL): + """Retrieve the last key in the cache based on age - def last(self, default=None, key=False, read=False, expire_time=False, tag=False): - """ Return the last entry in cache """ - sql = self._sql - - row = sql('SELECT key FROM Cache ORDER BY rowid DESC').fetchone() - - if not row: + :param key default: default value if cache is empty + """ + try: + return next(reversed(self)) + except StopIteration: return default - - (cache_key), = row - - value = self.get(cache_key, default=default, read=read, expire_time=expire_time, tag=tag) - - if key: - return (cache_key, value) - else: - return value diff --git a/tests/test_ordered.py b/tests/test_ordered.py index d62b2c8..29cd8a3 100644 --- a/tests/test_ordered.py +++ b/tests/test_ordered.py @@ -20,6 +20,7 @@ import pickle import diskcache as dc +from diskcache.core import ENOVAL warnings.simplefilter('error') warnings.simplefilter('ignore', category=dc.EmptyDirWarning) @@ -51,7 +52,17 @@ def test_init(cache): @setup_cache -def test_set_get(cache): +def test_first_last(cache): + assert cache.first() == ENOVAL + assert cache.last() == ENOVAL + + cache.check() + + assert cache.first(default=1) == 1 + assert cache.last(default=1) == 1 + + cache.check() + start = 0 end = 100 @@ -63,8 +74,8 @@ def test_set_get(cache): assert cache.first() == start assert cache.last() == end - assert cache.first(key=True)[0] == start - assert cache.last(key=True)[0] == end + assert cache.first(default=True) == start + assert cache.last(default=True) == end cache.check() @@ -76,9 +87,6 @@ def test_set_get(cache): assert cache.first() == start + 1 assert cache.last() == end - 1 - assert cache.first(key=True)[0] == start + 1 - assert cache.last(key=True)[0] == end - 1 - cache.check() From 28cd6d7fef208a9169b55f78e259ab7ba18a9975 Mon Sep 17 00:00:00 2001 From: Russ Kubik Date: Tue, 26 Jul 2016 22:53:43 -0400 Subject: [PATCH 009/550] Fix up tests --- tests/test_ordered.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_ordered.py b/tests/test_ordered.py index 29cd8a3..1f92361 100644 --- a/tests/test_ordered.py +++ b/tests/test_ordered.py @@ -74,8 +74,8 @@ def test_first_last(cache): assert cache.first() == start assert cache.last() == end - assert cache.first(default=True) == start - assert cache.last(default=True) == end + assert cache.first(default=500) == start + assert cache.last(default=600) == end cache.check() From 97cdec7aa2ac31bb45f65aa0bd8622d19d4367ec Mon Sep 17 00:00:00 2001 From: Russ Kubik Date: Wed, 27 Jul 2016 13:38:21 -0600 Subject: [PATCH 010/550] Fix for iter/reversed to convery db key to Python object and loop based on disk chunk size --- diskcache/core.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 6f6044d..7ed414c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -484,16 +484,28 @@ def _sql(self): def __iter__(self): sql = self._sql + chunk = self.cull_limit + + rows = sql( + 'SELECT key, raw FROM Cache ORDER BY rowid ASC LIMIT ?', + (chunk,) + ).fetchall() - for (key,) in sql('SELECT key FROM Cache ORDER BY rowid ASC').fetchall(): - yield key + for (key, raw) in rows: + yield self._disk.get(key, raw) def __reversed__(self): sql = self._sql + chunk = self.cull_limit + + rows = sql( + 'SELECT key, raw FROM Cache ORDER BY rowid DESC LIMIT ?', + (chunk,) + ).fetchall(); - for (key,) in sql('SELECT key FROM Cache ORDER BY rowid DESC').fetchall(): - yield key + for (key, raw) in rows: + yield self._disk.get(key, raw) def set(self, key, value, expire=None, read=False, tag=None): From 332d356323939fd42e13ec8fb6a3aa15f684e4d4 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 27 Jul 2016 22:41:42 +0200 Subject: [PATCH 011/550] #25 workwround bug http://bugs.python.org/issue10211 * this provides support on Python 2.7.3 which is the default on some common Ubuntu versions (12.x) * thanks to https://www.cubicweb.org/revision/10692445 for the fix! --- diskcache/core.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 2c67c9b..1f3c114 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -24,6 +24,13 @@ BytesType = bytes INT_TYPES = int, +if (2, 7) <= sys.version_info < (2, 7, 4): + # work around http://bugs.python.org/issue10211 + from StringIO import StringIO as BytesIO +else: + from io import BytesIO + + DBNAME = 'cache.db' ENOVAL = object() @@ -148,7 +155,7 @@ def get(self, key, raw): if raw: return BytesType(key) if type(key) is sqlite3.Binary else key else: - return pickle.load(io.BytesIO(key)) + return pickle.load(BytesIO(key)) def store(self, value, read, threshold, prep_file): @@ -242,7 +249,7 @@ def fetch(self, directory, mode, filename, value, read): with io.open(op.join(directory, filename), 'rb') as reader: return pickle.load(reader) else: - return pickle.load(io.BytesIO(value)) + return pickle.load(BytesIO(value)) class CachedAttr(object): From dfc166650fd80b4bd44cde0588edec45df39fc6b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 27 Jul 2016 20:37:05 -0700 Subject: [PATCH 012/550] Refactor to use transactions (work in progress) --- diskcache/core.py | 553 ++++++++++++++++++++++++++------------------ diskcache/fanout.py | 53 +++-- 2 files changed, 368 insertions(+), 238 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index cc940b8..e66b5d4 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -17,7 +17,9 @@ TextType = unicode BytesType = str INT_TYPES = int, long - range = xrange # pylint: disable=redefined-builtin,invalid-name + range = xrange # pylint: disable=redefined-builtin,invalid-name + class TimeoutError(OSError): + "Timeout expired." else: import pickle TextType = str @@ -40,15 +42,15 @@ } DEFAULT_SETTINGS = { - u'statistics': 0, # False + u'statistics': 0, # False u'eviction_policy': u'least-recently-stored', - u'size_limit': 2 ** 30, # 1gb + u'size_limit': 2 ** 30, # 1gb u'cull_limit': 10, - u'large_value_threshold': 2 ** 10, # 1kb, min 8 + u'large_value_threshold': 2 ** 10, # 1kb, min 8 u'sqlite_synchronous': u'NORMAL', u'sqlite_journal_mode': u'WAL', - u'sqlite_cache_size': 2 ** 13, # 8,192 pages - u'sqlite_mmap_size': 2 ** 26, # 64mb + u'sqlite_cache_size': 2 ** 13, # 8,192 pages + u'sqlite_mmap_size': 2 ** 26, # 64mb } METADATA = { @@ -65,10 +67,13 @@ ' Cache (store_time)' ), 'get': None, - 'set': ( - 'SELECT rowid, version, filename FROM Cache' - ' ORDER BY store_time LIMIT ?' - ), + 'set': { + 'select': 'SELECT filename FROM Cache ORDER BY store_time LIMIT ?', + 'delete': ( + 'DELETE FROM Cache WHERE rowid IN (' + ' SELECT rowid FROM Cache ORDER BY store_time LIMIT ? )' + ), + }, }, 'least-recently-used': { 'init': ( @@ -80,10 +85,13 @@ ' access_time = ((julianday("now") - 2440587.5) * 86400.0)' ' WHERE rowid = ?' ), - 'set': ( - 'SELECT rowid, version, filename FROM Cache' - ' ORDER BY access_time LIMIT ?' - ), + 'set': { + 'select': 'SELECT filename FROM Cache ORDER BY access_time LIMIT ?', + 'delete': ( + 'DELETE FROM Cache WHERE rowid IN (' + ' SELECT rowid FROM Cache ORDER BY access_time LIMIT ? )' + ), + }, }, 'least-frequently-used': { 'init': ( @@ -95,10 +103,13 @@ ' access_count = access_count + 1' ' WHERE rowid = ?' ), - 'set': ( - 'SELECT rowid, version, filename FROM Cache' - ' ORDER BY access_count LIMIT ?' - ), + 'set': { + 'select': 'SELECT filename FROM Cache ORDER BY access_count LIMIT ?', + 'delete': ( + 'DELETE FROM Cache WHERE rowid IN (' + ' SELECT rowid FROM Cache ORDER BY access_count LIMIT ? )' + ), + }, }, } @@ -335,6 +346,37 @@ class EmptyDirWarning(UserWarning): pass +class Transaction(object): + def __init__(self, cache): + self._cache = cache + self._filenames = [] + + + def __enter__(self): + sql = self._cache._sql + + try: + sql('BEGIN IMMEDIATE') + except sqlite3.OperationalError: + raise TimeoutError + + return sql, self._filenames.append + + + def __exit__(self, exc_type, exc_value, traceback): + sql = self._cache._sql + + if exc_type is None: + sql('COMMIT') + remove = self._cache._remove + for filename in self._filenames: + remove(filename) + else: + sql('ROLLBACK') + + del self._filenames[:] + + class Cache(with_metaclass(CacheMeta, object)): "Disk and file backed cache." # pylint: disable=bad-continuation @@ -482,6 +524,26 @@ def _sql(self): return con.execute + @contextmanager + def _transact(self): + sql = self._sql + filenames = [] + + try: + sql('BEGIN IMMEDIATE') + except sqlite3.OperationalError: + raise TimeoutError + + try: + yield sql, filenames.append + except BaseException: + sql('ROLLBACK') + else: + sql('COMMIT') + for filename in filenames: + self._remove(filename) + + def set(self, key, value, expire=None, read=False, tag=None): """Set key, value pair in cache. @@ -490,25 +552,29 @@ def set(self, key, value, expire=None, read=False, tag=None): :param key: Python key to store :param value: Python value to store - :param expire: seconds until the key expires (default None, no expiry) - :param bool read: read value as raw bytes from file (default False) - :param tag: text to associate with key (default None) - :return: True if item was successfully set + :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) + :return: True if key was successfully set """ sql = self._sql - now = time.time() db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire size, mode, filename, db_value = self._disk.store( - value, read, self.large_value_threshold, self._prep_file + value, read, self.large_value_threshold, self._filename ) + columns = (expire_time, tag, size, mode, filename, db_value) + removes = [] try: sql('BEGIN IMMEDIATE') except sqlite3.OperationalError: self._remove(filename) - raise + return False + + now = time.time() # The order of SELECT, UPDATE, and INSERT is important below. # @@ -528,65 +594,22 @@ def set(self, key, value, expire=None, read=False, tag=None): # violating the UNIQUE constraint. This optimistic approach was # rejected based on the common cache usage pattern. - rows = sql( - 'SELECT rowid, filename FROM Cache WHERE key = ? AND raw = ?', - (db_key, raw), - ).fetchall() + select = 'SELECT rowid, filename FROM Cache WHERE key = ? AND raw = ?' + rows = sql(select, (key, raw)).fetchall() if rows: - (old_rowid, old_filename), = rows - - sql('UPDATE Cache SET' - ' version = ?,' - ' store_time = ?,' - ' expire_time = ?,' - ' access_time = ?,' - ' access_count = ?,' - ' tag = ?,' - ' size = ?,' - ' mode = ?,' - ' filename = ?,' - ' value = ?' - ' WHERE rowid = ?', ( - 0, # version - now, # store_time - expire_time, - now, # access_time - 0, # access_count - tag, - size, - mode, - filename, - db_value, - old_rowid, - ), - ) + (rowid, old_filename), = rows + removes.append(old_filename) + self._row_update(rowid, now, columns) else: - old_filename = None - - sql('INSERT INTO Cache(' - ' key, raw, version, store_time, expire_time, access_time,' - ' access_count, tag, size, mode, filename, value' - ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( - db_key, - raw, - 0, # version - now, # store_time - expire_time, - now, # access_time - 0, # access_count - tag, - size, - mode, - filename, - db_value, - ), - ) + self._row_insert(db_key, raw, now, columns) + + removes.extend(self._cull(now)) sql('COMMIT') - self._remove(old_filename) - self._evict_expired_keys() + for reference in removes: + self._remove(reference) return True @@ -594,50 +617,105 @@ def set(self, key, value, expire=None, read=False, tag=None): __setitem__ = set - def _evict_expired_keys(self): - "Evict expired keys." + def _row_update(self, rowid, now, columns): + sql = self._sql + expire_time, tag, size, mode, filename, value = columns + sql('UPDATE Cache SET' + ' version = ?,' + ' store_time = ?,' + ' expire_time = ?,' + ' access_time = ?,' + ' access_count = ?,' + ' tag = ?,' + ' size = ?,' + ' mode = ?,' + ' filename = ?,' + ' value = ?' + ' WHERE rowid = ?', ( + 0, # version + now, # store_time + expire_time, + now, # access_time + 0, # access_count + tag, + size, + mode, + filename, + value, + rowid, + ), + ) + + + def _row_insert(self, key, raw, now, columns): + sql = self._sql + expire_time, tag, size, mode, filename, value = columns + sql('INSERT INTO Cache(' + ' key, raw, version, store_time, expire_time, access_time,' + ' access_count, tag, size, mode, filename, value' + ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( + key, + raw, + 0, # version + now, # store_time + expire_time, + now, # access_time + 0, # access_count + tag, + size, + mode, + filename, + value, + ), + ) + + + def _cull(self, now): cull_limit = self.cull_limit if cull_limit == 0: - return + return [] sql = self._sql - now = time.time() - rows = sql( - 'SELECT rowid, version, filename FROM Cache' + select_expired = ( + 'SELECT filename FROM Cache' ' WHERE expire_time IS NOT NULL AND expire_time < ?' - ' ORDER BY expire_time LIMIT ?', - (now, cull_limit), - ).fetchall() + ' ORDER BY expire_time LIMIT ?' + ) + delete_expired = ( + 'DELETE FROM Cache WHERE rowid IN ( ' + + select_expired + + ' )' + ) - for rowid, version, filename in rows: - deleted = self._delete(rowid, version, filename) - cull_limit -= deleted + rows = sql(select_expired, (now, cull_limit)).fetchall() + sql(delete_expired, (now, cull_limit)) + cull_limit -= len(rows) + filenames = [filename for filename, in rows] - if cull_limit == 0: - return - - if self.volume() < self.size_limit: - return + if cull_limit == 0 or self.volume() < self.size_limit: + return filenames # Evict keys by policy. - query = EVICTION_POLICY[self.eviction_policy]['set'] + policy = EVICTION_POLICY[self.eviction_policy]['set'] + select = policy['select'] + delete = policy['delete'] - if query is not None: - rows = sql(query, (cull_limit,)) + rows = sql(select, (cull_limit,)).fetchall() + sql(delete, (cull_limit,)) + filenames.extend(filename for filename, in rows) - for rowid, version, filename in rows: - self._delete(rowid, version, filename) + return filenames def add(self, key, value, expire=None, read=False, tag=None): """Add key, value pair to cache. - Similar to `set`, but only set in cache if key not present. + Similar to `set`, but only add to cache if key not present. - This operation is atomic. Only one concurrent add operation for given + This operation is atomic. Only one concurrent add operation for a given key from separate threads or processes will succeed. When `read` is `True`, `value` should be a file-like object opened @@ -649,22 +727,25 @@ def add(self, key, value, expire=None, read=False, tag=None): (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) - :return: True if item was successfully added + :return: True if key was successfully added """ sql = self._sql - now = time.time() db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire size, mode, filename, db_value = self._disk.store( - value, read, self.large_value_threshold, self._prep_file + value, read, self.large_value_threshold, self._filename ) + columns = (expire_time, tag, size, mode, filename, db_value) + removes = [] try: sql('BEGIN IMMEDIATE') except sqlite3.OperationalError: self._remove(filename) - raise + raise DatabaseTimeout + + now = time.time() rows = sql( 'SELECT rowid, filename, expire_time FROM Cache' @@ -673,40 +754,24 @@ def add(self, key, value, expire=None, read=False, tag=None): ).fetchall() if rows: - (old_rowid, old_filename, old_expire_time), = rows + (rowid, old_filename, old_expire_time), = rows - if old_expire_time is not None and old_expire_time < now: - sql('DELETE FROM Cache WHERE rowid = ?', (old_rowid,)) - else: + if old_expire_time is None or old_expire_time >= now: sql('COMMIT') self._remove(filename) return False + + removes.append(old_filename) + self._row_update(rowid, now, columns) else: - old_filename = None + self._row_insert(db_key, raw, now, columns) - sql('INSERT INTO Cache(' - ' key, raw, version, store_time, expire_time, access_time,' - ' access_count, tag, size, mode, filename, value' - ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( - db_key, - raw, - 0, # version - now, # store_time - expire_time, - now, # access_time - 0, # access_count - tag, - size, - mode, - filename, - db_value, - ), - ) + removes.extend(self._cull(now)) sql('COMMIT') - self._remove(old_filename) - self._evict_expired_keys() + for reference in removes: + self._remove(reference) return True @@ -819,42 +884,43 @@ def read(self, key): def __contains__(self, key): "Return True if `key` in Cache." sql = self._sql - db_key, raw = self._disk.put(key) + now = time.time() rows = sql( - 'SELECT store_time, expire_time FROM Cache' - ' WHERE key = ? AND raw = ?', + 'SELECT expire_time FROM Cache WHERE key = ? AND raw = ?', (db_key, raw), ).fetchall() if not rows: return False - (store_time, expire_time), = rows - - if store_time is None: - return False - - return expire_time is None or time.time() < expire_time + return expire_time is None or now < expire_time def __delitem__(self, key): "Delete corresponding item for `key` from Cache." sql = self._sql - db_key, raw = self._disk.put(key) - rows = sql( - 'SELECT rowid, version, filename' - ' FROM Cache WHERE key = ? AND raw = ?', - (db_key, raw), - ).fetchall() + try: + sql('BEGIN IMMEDIATE') + except sqlite3.OperationalError: + raise DatabaseTimeout + + now = time.time() + + select = 'SELECT rowid, filename FROM Cache WHERE key = ? AND raw = ?' + rows = sql(select, (db_key, raw)).fetchall() if rows: - (rowid, version, filename), = rows - return self._delete(rowid, version, filename) + (rowid, filename), = rows + sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) + sql('COMMIT') + self._remove(filename) + return True else: + sql('COMMIT') raise KeyError(key) @@ -870,21 +936,7 @@ def delete(self, key): return False - def _delete(self, rowid, version, filename): - cursor = self._sql( - 'DELETE FROM Cache WHERE rowid = ? AND version = ?', - (rowid, version), - ) - - deleted = cursor.rowcount == 1 - - if deleted: - self._remove(filename) - - return deleted - - - def _prep_file(self): + def _filename(self): hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8') sub_dir = op.join(hex_name[:2], hex_name[2:4]) name = hex_name[4:] + '.val' @@ -938,6 +990,10 @@ def check(self, fix=False): if fix: sql('VACUUM') + try: + sql('BEGIN IMMEDIATE') + except sqlite3.OperationalError: + raise DatabaseTimeout # Check Settings.count against count of Cache rows. @@ -952,20 +1008,6 @@ def check(self, fix=False): if fix: self.count = count - # Report uncommitted rows. - - rows = sql( - 'SELECT rowid, key, raw, version, filename FROM Cache' - ' WHERE store_time IS NULL' - ).fetchall() - - for rowid, key, raw, version, filename in rows: - warnings.warn('row %d partially commited with key %r' % ( - rowid, self._disk.get(key, raw) - )) - if fix: - self._delete(rowid, version, filename) - # Check Cache.filename against file system. filenames = set() @@ -995,7 +1037,7 @@ def check(self, fix=False): warnings.warn('file not found: %s' % full_path) if fix: - self._delete(rowid, version, filename) + sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) del self.size self_size = self.size @@ -1036,85 +1078,148 @@ def check(self, fix=False): if fix: os.rmdir(dirpath) + if fix: + sql('COMMIT') + return warns - def expire(self): - "Remove expired items from Cache." + def expire(self, now=None): + """Remove expired items from Cache. - now = time.time() - sql = self._sql - chunk = self.cull_limit + TODO: docs + + """ + now = now or time.time() + cull_limit = self.cull_limit expire_time = 0 count = 0 - while True: - rows = sql( - 'SELECT rowid, version, expire_time, filename FROM Cache' - ' WHERE ? < expire_time AND expire_time < ?' - ' ORDER BY expire_time LIMIT ?', - (expire_time, now, chunk), - ).fetchall() + select_template = ( + 'SELECT %s FROM Cache' + ' WHERE ? < expire_time AND expire_time < ?' + ' ORDER BY expire_time LIMIT ?' + ) + select = select_template % 'expire_time, filename' + delete = ( + 'DELETE FROM Cache WHERE rowid IN (%s)' + % (select_template % 'rowid') + ) - if not rows: - break + try: + while True: + with self._transact() as (sql, cleanup): + args = expire_time, now, cull_limit + rows = sql(select, args).fetchall() + + if not rows: + break + + count += len(rows) + sql(delete, args) + for expire_time, filename in rows: + cleanup(filename) + except TimeoutError: + status = False + else: + status = True - for rowid, version, expire_time, filename in rows: - count += self._delete(rowid, version, filename) + return status, count - return count + def _create_tag_index(self): + # TODO: Add FanoutCache keyword arg for tag_index. + sql = self._sql + sql('CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON' + ' Cache(tag, rowid)' + ) + + def _drop_tag_index(self): + sql = self._sql + sql('DROP INDEX IF EXISTS Cache_tag_rowid') def evict(self, tag): "Remove items with matching `tag` from Cache." + # TODO: docs notes about _create_tag_index + # TODO: How to limit max rowid? + sql = self._sql - chunk = self.cull_limit rowid = 0 + cull_limit = self.cull_limit count = 0 - sql('CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON' - ' Cache(tag, rowid)' + select_template = ( + 'SELECT %s FROM Cache' + ' WHERE tag = ? AND rowid > ?' + ' ORDER BY rowid LIMIT ?' + ) + select = select_template % 'rowid, filename' + delete = ( + 'DELETE FROM Cache WHERE rowid IN (%s)' + % (select_template % 'rowid') ) - while True: - rows = sql( - 'SELECT rowid, version, filename FROM Cache' - ' WHERE tag = ? AND rowid > ? ORDER BY rowid LIMIT ?', - (tag, rowid, chunk), - ).fetchall() - - if not rows: - break - - for rowid, version, filename in rows: - count += self._delete(rowid, version, filename) + try: + while True: + with self._transact(sql) as transaction: + args = tag, rowid, cull_limit + rows = sql(select, args).fetchall() + + if not rows: + break + + count += len(rows) + sql(delete, args) + for rowid, filename in rows: + transaction.remove(filename) + except TimeoutError: + status = False + else: + status = True - return count + return status, count def clear(self): "Remove all items from Cache." sql = self._sql - chunk = self.cull_limit rowid = 0 + cull_limit = self.cull_limit count = 0 - while True: - rows = sql( - 'SELECT rowid, version, filename FROM Cache' - ' WHERE rowid > ? ORDER BY rowid LIMIT ?', - (rowid, chunk), - ).fetchall() + select_template = ( + 'SELECT %s FROM Cache' + ' WHERE rowid > ?' + ' ORDER BY rowid LIMIT ?' + ) + select = select_template % 'rowid, filename' + delete = ( + 'DELETE FROM Cache WHERE rowid IN (%s)' + % (select_template % 'rowid') + ) - if not rows: - break - for rowid, version, filename in rows: - count += self._delete(rowid, version, filename) + try: + while True: + with self._transact(sql) as transaction: + args = rowid, cull_limit + rows = sql(select, args).fetchall() + + if not rows: + break + + count += len(rows) + sql(delete, args) + for rowid, filename in rows: + transaction.remove(filename) + except TimeoutError: + status = False + else: + status = True - return count + return status, count def stats(self, enable=True, reset=False): @@ -1147,7 +1252,7 @@ def volume(self): """ (page_count,), = self._sql('PRAGMA page_count').fetchall() - del self.size # Update value from database. + del self.size # Update value from database. total_size = self._page_size * page_count + self.size return total_size diff --git a/diskcache/fanout.py b/diskcache/fanout.py index e3e8b46..7de2fd6 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -2,7 +2,7 @@ import sqlite3 -from .core import Cache, Disk, ENOVAL +from .core import Cache, Disk, ENOVAL, DatabaseTimeout class FanoutCache(object): "Cache that shards keys and values." @@ -53,13 +53,8 @@ def set(self, key, value, expire=None, read=False, tag=None): :return: True if item was successfully set """ - try: - index = hash(key) % self._count - return self._shards[index].set( - key, value, expire=expire, read=read, tag=tag, - ) - except sqlite3.OperationalError: - return False + index = hash(key) % self._count + return self._shards[index].set(key, value, expire, read, tag) __setitem__ = set @@ -87,10 +82,8 @@ def add(self, key, value, expire=None, read=False, tag=None): """ try: index = hash(key) % self._count - return self._shards[index].add( - key, value, expire=expire, read=read, tag=tag, - ) - except sqlite3.OperationalError: + return self._shards[index].add(key, value, expire, read, tag) + except DatabaseTimeout: return False @@ -180,8 +173,40 @@ def check(self, fix=False): def expire(self): - "Remove expired items from Cache." - return sum(shard.expire() for shard in self._shards) + """Remove expired items from Cache. + + """ + total = 0 + index = 0 + shards = self._shards + + while len(shards): + shard = shards[index % len(shards)] + try: + total += shard.expire() + except DatabaseTimeout: + index += 1 + else: + del shards[index] + + return total + + def expire(self): + """Remove expired items from Cache. + + Return number of rows expired. + + """ + now = time.time() + total = 0 + sentinel = (True, 0) + + for shard in self._shards: + shard_expire = partial(shard.expire, now) + for status, count in iter(shard_expire, sentinel): + total += count + + return total def evict(self, tag): From 997c4672c225477829900e8a7accf5016153c3ab Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 27 Jul 2016 20:47:06 -0700 Subject: [PATCH 013/550] Remove OrderedCache in favor of __iter__ and __reversed__ --- diskcache/core.py | 5 ++++- diskcache/ordered.py | 41 ----------------------------------------- 2 files changed, 4 insertions(+), 42 deletions(-) delete mode 100644 diskcache/ordered.py diff --git a/diskcache/core.py b/diskcache/core.py index ee15470..586fc31 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -14,18 +14,21 @@ if sys.hexversion < 0x03000000: import cPickle as pickle + # ISSUE #25 Fix for http://bugs.python.org/issue10211 + from cStringIO import StringIO as BytesIO TextType = unicode BytesType = str INT_TYPES = int, long range = xrange # pylint: disable=redefined-builtin,invalid-name else: import pickle + from io import BytesIO TextType = str BytesType = bytes INT_TYPES = int, if (2, 7) <= sys.version_info < (2, 7, 4): - # work around http://bugs.python.org/issue10211 + # work around from StringIO import StringIO as BytesIO else: from io import BytesIO diff --git a/diskcache/ordered.py b/diskcache/ordered.py deleted file mode 100644 index c64fead..0000000 --- a/diskcache/ordered.py +++ /dev/null @@ -1,41 +0,0 @@ -"Ordered cache to save keys in order." - -import sqlite3 - -from .core import Cache, Disk, ENOVAL - -class OrderedCache(Cache): - "Cache that saves keys in order." - def __init__(self, directory, timeout=60, disk=Disk(), - **settings): - """Initialize OrderedCache instance. - - :param str directory: cache directory - :param float timeout: SQLite connection timeout - :param disk: `Disk`: instance for serialization - :param settings: any of `DEFAULT_SETTINGS` - - """ - super(self.__class__, self).__init__(directory, timeout, disk) - - - def first(self, default=ENOVAL): - """Retrieve the first key in the cache based on age - - :param key default: default value if cache is empty - """ - try: - return next(iter(self)) - except StopIteration: - return default - - - def last(self, default=ENOVAL): - """Retrieve the last key in the cache based on age - - :param key default: default value if cache is empty - """ - try: - return next(reversed(self)) - except StopIteration: - return default From e380c40d1fd032392346bae3a5cf15eec8b2d963 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 27 Jul 2016 20:52:25 -0700 Subject: [PATCH 014/550] Remove OrderedCache tests --- diskcache/__init__.py | 1 - diskcache/core.py | 7 ---- tests/test_ordered.py | 95 ------------------------------------------- 3 files changed, 103 deletions(-) delete mode 100644 tests/test_ordered.py diff --git a/diskcache/__init__.py b/diskcache/__init__.py index b489e75..2146206 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -3,7 +3,6 @@ from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning from .core import LIMITS, DEFAULT_SETTINGS, EVICTION_POLICY from .fanout import FanoutCache -from .ordered import OrderedCache try: from .djangocache import DjangoCache diff --git a/diskcache/core.py b/diskcache/core.py index 586fc31..4352a0c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -27,13 +27,6 @@ BytesType = bytes INT_TYPES = int, -if (2, 7) <= sys.version_info < (2, 7, 4): - # work around - from StringIO import StringIO as BytesIO -else: - from io import BytesIO - - DBNAME = 'cache.db' ENOVAL = object() diff --git a/tests/test_ordered.py b/tests/test_ordered.py deleted file mode 100644 index 1f92361..0000000 --- a/tests/test_ordered.py +++ /dev/null @@ -1,95 +0,0 @@ -"Test diskcache.ordered.OrderedCache." - -import errno -import functools as ft -import io -import mock -import nose.tools as nt -import os -import random -import shutil -import sqlite3 -import sys -import threading -import time -import warnings - -try: - import cPickle as pickle -except: - import pickle - -import diskcache as dc -from diskcache.core import ENOVAL - -warnings.simplefilter('error') -warnings.simplefilter('ignore', category=dc.EmptyDirWarning) - -if sys.hexversion < 0x03000000: - range = xrange - -def setup_cache(func): - @ft.wraps(func) - def wrapper(): - shutil.rmtree('tmp', ignore_errors=True) - with dc.OrderedCache('tmp') as cache: - func(cache) - shutil.rmtree('tmp', ignore_errors=True) - return wrapper - - -@setup_cache -def test_init(cache): - for key, value in dc.DEFAULT_SETTINGS.items(): - assert getattr(cache, key) == value - - cache.check() - - for key, value in dc.DEFAULT_SETTINGS.items(): - setattr(cache, key, value) - - cache.check() - - -@setup_cache -def test_first_last(cache): - assert cache.first() == ENOVAL - assert cache.last() == ENOVAL - - cache.check() - - assert cache.first(default=1) == 1 - assert cache.last(default=1) == 1 - - cache.check() - - start = 0 - end = 100 - - for value in range(start, end + 1): - cache.set(value, value) - - cache.check() - - assert cache.first() == start - assert cache.last() == end - - assert cache.first(default=500) == start - assert cache.last(default=600) == end - - cache.check() - - cache.delete(start) - cache.delete(end) - - cache.check() - - assert cache.first() == start + 1 - assert cache.last() == end - 1 - - cache.check() - - -if __name__ == '__main__': - import nose - nose.runmodule() From f2abf5d6b744ea75f62fb88fe24a69d3685cbb4a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 27 Jul 2016 21:56:47 -0700 Subject: [PATCH 015/550] Add __iter__ and __reversed__ with 100% coverage --- diskcache/core.py | 81 +++++++++++++++++++++++++++++--------------- tests/test_core.py | 57 ++++++++++++++++++++++++++++++- tests/test_fanout.py | 1 + 3 files changed, 111 insertions(+), 28 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 4352a0c..6f6c463 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -22,7 +22,7 @@ range = xrange # pylint: disable=redefined-builtin,invalid-name else: import pickle - from io import BytesIO + from io import BytesIO # pylint: disable=ungrouped-imports TextType = str BytesType = bytes INT_TYPES = int, @@ -485,32 +485,6 @@ def _sql(self): return con.execute - def __iter__(self): - sql = self._sql - chunk = self.cull_limit - - rows = sql( - 'SELECT key, raw FROM Cache ORDER BY rowid ASC LIMIT ?', - (chunk,) - ).fetchall() - - for (key, raw) in rows: - yield self._disk.get(key, raw) - - - def __reversed__(self): - sql = self._sql - chunk = self.cull_limit - - rows = sql( - 'SELECT key, raw FROM Cache ORDER BY rowid DESC LIMIT ?', - (chunk,) - ).fetchall(); - - for (key, raw) in rows: - yield self._disk.get(key, raw) - - def set(self, key, value, expire=None, read=False, tag=None): """Set key, value pair in cache. @@ -1146,6 +1120,59 @@ def clear(self): return count + def __iter__(self): + sql = self._sql + rows = sql('SELECT MAX(rowid) FROM Cache').fetchall() + (max_rowid,), = rows + + if max_rowid is None: + return + + chunk = self.cull_limit + rowid = 0 + _disk_get = self._disk.get + + while True: + rows = sql( + 'SELECT rowid, key, raw FROM Cache' + ' WHERE ? < rowid AND rowid < ?' + ' ORDER BY rowid LIMIT ?', + (rowid, max_rowid, chunk), + ).fetchall() + + if not rows: + break + + for rowid, key, raw in rows: + yield _disk_get(key, raw) + + + def __reversed__(self): + sql = self._sql + rows = sql('SELECT MAX(rowid) FROM Cache').fetchall() + (rowid,), = rows + + if rowid is None: + return + + chunk = self.cull_limit + _disk_get = self._disk.get + + while True: + rows = sql( + 'SELECT rowid, key, raw FROM Cache' + ' WHERE 0 < rowid AND rowid < ?' + ' ORDER BY rowid DESC LIMIT ?', + (rowid, chunk), + ).fetchall() + + if not rows: + break + + for rowid, key, raw in rows: + yield _disk_get(key, raw) + + def stats(self, enable=True, reset=False): """Return cache statistics pair: (hits, misses). diff --git a/tests/test_core.py b/tests/test_core.py index 6839076..c79d015 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -715,7 +715,7 @@ def test_add_large_value(cache): def stress_add(cache, limit): total = 0 - for num in xrange(limit): + for num in range(limit): if cache.add(num, num): total += 1 # Stop one thread from running ahead of others. @@ -760,6 +760,61 @@ def test_add_timeout(cache): cache.check() +@setup_cache +def test_iter(cache): + sequence = 'abcdef' + + for index, value in enumerate(sequence): + cache[value] = index + + iterator = iter(cache) + + assert all(one == two for one, two in zip(sequence, iterator)) + + cache['g'] = 6 + + try: + next(iterator) + except StopIteration: + pass + else: + assert False, 'StopIteration expected' + + +@setup_cache +@nt.raises(StopIteration) +def test_iter_error(cache): + next(iter(cache)) + + +@setup_cache +def test_reversed(cache): + sequence = 'abcdef' + + for index, value in enumerate(sequence): + cache[value] = index + + iterator = reversed(cache) + + cache['g'] = 6 + + pairs = zip(reversed(sequence), iterator) + assert all(one == two for one, two in pairs) + + try: + next(iterator) + except StopIteration: + pass + else: + assert False, 'StopIteration expected' + + +@setup_cache +@nt.raises(StopIteration) +def test_reversed_error(cache): + next(reversed(cache)) + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 5a9a23c..2784858 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -112,6 +112,7 @@ def test_operationalerror(): object.__setattr__(cache, '_shards', shards) assert cache.set(0, 0) == False + assert cache.add(0, 0) == False assert cache.get(0) == None assert (0 in cache) == False assert cache.__delitem__(0) == False From 802c3ba0abc7526e40fa2dfb259306d780e08fde Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 27 Jul 2016 21:57:24 -0700 Subject: [PATCH 016/550] Bump version to 1.7.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 2146206..525ec8d 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '1.6.7' -__build__ = 0x010607 +__version__ = '1.7.0' +__build__ = 0x010700 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From a53d9ccb498ebe177e1fa7c4bdf307ac3e874599 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 5 Aug 2016 13:53:49 -0700 Subject: [PATCH 017/550] Refactor diskcache to use transactions --- diskcache/__init__.py | 2 +- diskcache/core.py | 1086 +++++++++++++++++++------------------- diskcache/djangocache.py | 29 +- diskcache/fanout.py | 290 +++++++--- 4 files changed, 755 insertions(+), 652 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 525ec8d..ce269b7 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -1,6 +1,6 @@ "DiskCache: disk and file backed cache." -from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning +from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning, Timeout from .core import LIMITS, DEFAULT_SETTINGS, EVICTION_POLICY from .fanout import FanoutCache diff --git a/diskcache/core.py b/diskcache/core.py index 3252b6f..f9874c7 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1,6 +1,9 @@ -"Core disk and file backed cache API." +"""Core disk and file backed cache API. + +""" import codecs +import contextlib as cl import errno import functools as ft import io @@ -20,11 +23,9 @@ BytesType = str INT_TYPES = int, long range = xrange # pylint: disable=redefined-builtin,invalid-name - class TimeoutError(OSError): - "Timeout expired." else: import pickle - from io import BytesIO # pylint: disable=ungrouped-imports + from io import BytesIO # pylint: disable=ungrouped-imports TextType = str BytesType = bytes INT_TYPES = int, @@ -46,6 +47,7 @@ class TimeoutError(OSError): DEFAULT_SETTINGS = { u'statistics': 0, # False + u'tag_index': 0, # False u'eviction_policy': u'least-recently-stored', u'size_limit': 2 ** 30, # 1gb u'cull_limit': 10, @@ -70,13 +72,7 @@ class TimeoutError(OSError): ' Cache (store_time)' ), 'get': None, - 'set': { - 'select': 'SELECT filename FROM Cache ORDER BY store_time LIMIT ?', - 'delete': ( - 'DELETE FROM Cache WHERE rowid IN (' - ' SELECT rowid FROM Cache ORDER BY store_time LIMIT ? )' - ), - }, + 'set': 'SELECT %s FROM Cache ORDER BY store_time LIMIT ?', }, 'least-recently-used': { 'init': ( @@ -88,13 +84,7 @@ class TimeoutError(OSError): ' access_time = ((julianday("now") - 2440587.5) * 86400.0)' ' WHERE rowid = ?' ), - 'set': { - 'select': 'SELECT filename FROM Cache ORDER BY access_time LIMIT ?', - 'delete': ( - 'DELETE FROM Cache WHERE rowid IN (' - ' SELECT rowid FROM Cache ORDER BY access_time LIMIT ? )' - ), - }, + 'set': 'SELECT %s FROM Cache ORDER BY access_time LIMIT ?', }, 'least-frequently-used': { 'init': ( @@ -106,30 +96,28 @@ class TimeoutError(OSError): ' access_count = access_count + 1' ' WHERE rowid = ?' ), - 'set': { - 'select': 'SELECT filename FROM Cache ORDER BY access_count LIMIT ?', - 'delete': ( - 'DELETE FROM Cache WHERE rowid IN (' - ' SELECT rowid FROM Cache ORDER BY access_count LIMIT ? )' - ), - }, + 'set': 'SELECT %s FROM Cache ORDER BY access_count LIMIT ?', }, } class Disk(object): "Cache key and value serialization for SQLite database and files." - def __init__(self, pickle_protocol=pickle.HIGHEST_PROTOCOL): - """Initialize `Disk` instance. + def __init__(self, directory, size_threshold, pickle_protocol): + """Initialize disk instance. - :param int pickle_protocol: ``pickle.HIGHEST_PROTOCOL`` + :param str directory: directory path + :param int size_threshold: size threshold for large values + :param int pickle_protocol: pickle protocol for serialization """ + self._dir = directory + self._threshold = size_threshold self._protocol = pickle_protocol def put(self, key): - """Convert key to fields (key, raw) for Cache table. + """Convert `key` to fields key and raw for Cache table. :param key: key to convert :return: (database key, raw boolean) pair @@ -151,7 +139,7 @@ def put(self, key): def get(self, key, raw): - """Convert fields (key, raw) from Cache table to key. + """Convert fields `key` and `raw` from Cache table to key. :param key: database key to convert :param bool raw: flag indicating raw database storage @@ -165,47 +153,45 @@ def get(self, key, raw): return pickle.load(BytesIO(key)) - def store(self, value, read, threshold, prep_file): - """Return fields (size, mode, filename, value) for Cache table. + def store(self, value, read): + """Return 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 int threshold: size threshold for large values - :param callable prep_file: initialize (filename, full_path) pair - :return: (size, mode, filename, value) tuple for Cache table. + :return: (size, mode, filename, value) tuple for Cache table """ # pylint: disable=unidiomatic-typecheck type_value = type(value) + _threshold = self._threshold - if ((type_value is TextType and len(value) < threshold) + if ((type_value is TextType and len(value) < _threshold) or (type_value in INT_TYPES and LIMITS[u'min_int'] <= value <= LIMITS[u'max_int']) or (type_value is float)): return 0, MODE_RAW, None, value elif type_value is BytesType: - if len(value) < threshold: + if len(value) < _threshold: return len(value), MODE_RAW, None, sqlite3.Binary(value) else: - filename, full_path = prep_file() + filename, full_path = self.filename() with io.open(full_path, 'wb') as writer: writer.write(value) return len(value), MODE_BINARY, filename, None elif type_value is TextType: - filename, full_path = prep_file() + filename, full_path = self.filename() with io.open(full_path, 'w', encoding='UTF-8') as writer: writer.write(value) size = op.getsize(full_path) - return size, MODE_TEXT, filename, None elif read: size = 0 reader = ft.partial(value.read, 2 ** 22) - filename, full_path = prep_file() + filename, full_path = self.filename() with io.open(full_path, 'wb') as writer: for chunk in iter(reader, b''): @@ -216,10 +202,10 @@ def store(self, value, read, threshold, prep_file): else: result = pickle.dumps(value, protocol=self._protocol) - if len(result) < threshold: + if len(result) < _threshold: return 0, MODE_PICKLE, None, sqlite3.Binary(result) else: - filename, full_path = prep_file() + filename, full_path = self.filename() with io.open(full_path, 'wb') as writer: writer.write(result) @@ -227,10 +213,9 @@ def store(self, value, read, threshold, prep_file): return len(result), MODE_PICKLE, filename, None - def fetch(self, directory, mode, filename, value, read): - """Convert fields (mode, filename, value) from Cache table to value. + def fetch(self, mode, filename, value, read): + """Convert fields mode, filename, and value from Cache table to value. - :param str directory: cache directory :param int mode: value mode raw, binary, text, or pickle :param str filename: filename of corresponding value :param value: database value @@ -243,100 +228,70 @@ def fetch(self, directory, mode, filename, value, read): return BytesType(value) if type(value) is sqlite3.Binary else value elif mode == MODE_BINARY: if read: - return io.open(op.join(directory, filename), 'rb') + return io.open(op.join(self._dir, filename), 'rb') else: - with io.open(op.join(directory, filename), 'rb') as reader: + with io.open(op.join(self._dir, filename), 'rb') as reader: return reader.read() elif mode == MODE_TEXT: - full_path = op.join(directory, filename) + full_path = op.join(self._dir, filename) with io.open(full_path, 'r', encoding='UTF-8') as reader: return reader.read() elif mode == MODE_PICKLE: if value is None: - with io.open(op.join(directory, filename), 'rb') as reader: + with io.open(op.join(self._dir, filename), 'rb') as reader: return pickle.load(reader) else: return pickle.load(BytesIO(value)) -class CachedAttr(object): - "Data descriptor that caches get's and writes set's back to the database." - # pylint: disable=too-few-public-methods - def __init__(self, key): - self._key = key - self._value = '_' + key - self._pragma = key.startswith('sqlite_') and key[7:] - - def __get__(self, cache, cache_type): - return getattr(cache, self._value) - - def __set__(self, cache, value): - "Cache attribute value and write back to database." - # pylint: disable=protected-access,attribute-defined-outside-init - sql = cache._sql - query = 'UPDATE Settings SET value = ? WHERE key = ?' + def filename(self): + """Return filename and full-path tuple for file storage. - sql(query, (value, self._key)) + 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 are slow. - if self._pragma: - - # 2016-02-17 GrantJ - PRAGMA and autocommit_level=None don't always - # play nicely together. Retry setting the PRAGMA. I think some - # PRAGMA statements expect to immediately take an EXCLUSIVE lock on - # the database. I can't find any documentation for this but without - # the retry, stress will intermittently fail with multiple - # processes. + """ + hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8') + sub_dir = op.join(hex_name[:2], hex_name[2:4]) + name = hex_name[4:] + '.val' + directory = op.join(self._dir, sub_dir) - pause = 0.001 - error = sqlite3.OperationalError + try: + os.makedirs(directory) + except OSError as error: + if error.errno != errno.EEXIST: + raise - for _ in range(int(LIMITS[u'pragma_timeout'] / pause)): - try: - sql('PRAGMA %s = %s' % (self._pragma, value)).fetchall() - except sqlite3.OperationalError as exc: - error = exc - time.sleep(pause) - else: - break - else: - raise error + filename = op.join(sub_dir, name) + full_path = op.join(self._dir, filename) + return filename, full_path - del error - setattr(cache, self._value, value) + def remove(self, filename): + """Remove a file given by `filename`. - def __delete__(self, cache): - "Update descriptor value from database." - # pylint: disable=protected-access,attribute-defined-outside-init - query = 'SELECT value FROM Settings WHERE key = ?' - (value,), = cache._sql(query, (self._key,)).fetchall() - setattr(cache, self._value, value) + This method is cross-thread and cross-process safe. If an "error no + entry" occurs, it is suppressed. + :param str filename: relative path to file -class CacheMeta(type): - "Metaclass for Cache to make Settings into attributes." - def __new__(mcs, name, bases, attrs): - for key in DEFAULT_SETTINGS: - attrs[key] = CachedAttr(key) - for key in METADATA: - attrs[key] = CachedAttr(key) - return type.__new__(mcs, name, bases, attrs) + """ + full_path = op.join(self._dir, filename) + try: + os.remove(full_path) + except OSError as error: + if error.errno != errno.ENOENT: + # ENOENT may occur if two caches attempt to delete the same + # file at the same time. + raise -# Copied from bitbucket.org/gutworth/six/six.py Seems excessive to depend on -# `six` when only this snippet is needed. Metaclass syntax changed in Python 3. -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class DummyMetaclass(meta): - "Dummy metaclass for Python 2 and Python 3 compatibility." - # pylint: disable=too-few-public-methods - def __new__(cls, name, _, attrs): - return meta(name, bases, attrs) - return type.__new__(DummyMetaclass, 'temporary_class', (), {}) +class Timeout(Exception): + "Database timeout expired." + pass class UnknownFileWarning(UserWarning): @@ -349,52 +304,20 @@ class EmptyDirWarning(UserWarning): pass -class Transaction(object): - def __init__(self, cache): - self._cache = cache - self._filenames = [] - - - def __enter__(self): - sql = self._cache._sql - - try: - sql('BEGIN IMMEDIATE') - except sqlite3.OperationalError: - raise TimeoutError - - return sql, self._filenames.append - - - def __exit__(self, exc_type, exc_value, traceback): - sql = self._cache._sql - - if exc_type is None: - sql('COMMIT') - remove = self._cache._remove - for filename in self._filenames: - remove(filename) - else: - sql('ROLLBACK') - - del self._filenames[:] - - -class Cache(with_metaclass(CacheMeta, object)): +class Cache(object): "Disk and file backed cache." # pylint: disable=bad-continuation - def __init__(self, directory, timeout=60, disk=Disk(), **settings): - """Initialize Cache instance. + def __init__(self, directory, timeout=60, disk=Disk, **settings): + """Initialize cache instance. :param str directory: cache directory :param float timeout: SQLite connection timeout - :param disk: `Disk` instance for serialization - :param settings: any of `DEFAULT_SETTINGS` + :param disk: disk type or instance for serialization + :param settings: any of DEFAULT_SETTINGS """ self._dir = directory self._timeout = 60 # Use 1 minute timeout for initialization. - self._disk = disk self._local = threading.local() if not op.isdir(directory): @@ -428,17 +351,29 @@ def __init__(self, directory, timeout=60, disk=Disk(), **settings): for key in METADATA: sets.pop(key, None) + # Setup Disk object (must happen after settings initialized). + + if isinstance(disk, Disk): + self._disk = disk + else: + assert issubclass(disk, Disk) + self._disk = disk( # pylint: disable=redefined-variable-type + directory, + sets['large_value_threshold'], + pickle.HIGHEST_PROTOCOL, + ) + # Set cached attributes: updates settings and sets pragmas. for key, value in sets.items(): query = 'INSERT OR REPLACE INTO Settings VALUES (?, ?)' sql(query, (key, value)) - setattr(self, key, value) + self.reset(key, value) for key, value in METADATA.items(): query = 'INSERT OR IGNORE INTO Settings VALUES (?, ?)' sql(query, (key, value)) - delattr(self, key) + setattr(self, key, value) (self._page_size,), = sql('PRAGMA page_size').fetchall() @@ -506,6 +441,13 @@ def __init__(self, directory, timeout=60, disk=Disk(), **settings): ' 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. self.close() @@ -527,57 +469,52 @@ def _sql(self): return con.execute - @contextmanager - def _transact(self): + @cl.contextmanager + def _transact(self, filename=None): sql = self._sql filenames = [] + _disk_remove = self._disk.remove try: sql('BEGIN IMMEDIATE') except sqlite3.OperationalError: - raise TimeoutError + if filename is not None: + _disk_remove(filename) + raise Timeout try: yield sql, filenames.append except BaseException: sql('ROLLBACK') + raise else: sql('COMMIT') for filename in filenames: - self._remove(filename) + if filename is not None: + _disk_remove(filename) def set(self, key, value, expire=None, read=False, tag=None): - """Set key, value pair in cache. + """Set `key` and `value` item in cache. When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. - :param key: Python key to store - :param value: Python value to store - :param float expire: seconds until the key expires + :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) - :return: True if key was successfully set + :return: True if item was successfully set + :raises Timeout: if database timeout expires """ - sql = self._sql + now = time.time() db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire - size, mode, filename, db_value = self._disk.store( - value, read, self.large_value_threshold, self._filename - ) + size, mode, filename, db_value = self._disk.store(value, read) columns = (expire_time, tag, size, mode, filename, db_value) - removes = [] - - try: - sql('BEGIN IMMEDIATE') - except sqlite3.OperationalError: - self._remove(filename) - return False - - now = time.time() # The order of SELECT, UPDATE, and INSERT is important below. # @@ -597,24 +534,23 @@ def set(self, key, value, expire=None, read=False, tag=None): # violating the UNIQUE constraint. This optimistic approach was # rejected based on the common cache usage pattern. - select = 'SELECT rowid, filename FROM Cache WHERE key = ? AND raw = ?' - rows = sql(select, (key, raw)).fetchall() - - if rows: - (rowid, old_filename), = rows - removes.append(old_filename) - self._row_update(rowid, now, columns) - else: - self._row_insert(db_key, raw, now, columns) - - removes.extend(self._cull(now)) + with self._transact(filename) as (sql, cleanup): + rows = sql( + 'SELECT rowid, filename FROM Cache' + ' WHERE key = ? AND raw = ?', + (db_key, raw), + ).fetchall() - sql('COMMIT') + if rows: + (rowid, old_filename), = rows + cleanup(old_filename) + self._row_update(rowid, now, columns) + else: + self._row_insert(db_key, raw, now, columns) - for reference in removes: - self._remove(reference) + self._cull(now, sql, cleanup) - return True + return True __setitem__ = set @@ -673,48 +609,61 @@ def _row_insert(self, key, raw, now, columns): ) - def _cull(self, now): + def _cull(self, now, sql, cleanup): cull_limit = self.cull_limit if cull_limit == 0: - return [] + return - sql = self._sql + # Evict expired keys. - select_expired = ( - 'SELECT filename FROM Cache' + select_expired_template = ( + 'SELECT %s FROM Cache' ' WHERE expire_time IS NOT NULL AND expire_time < ?' ' ORDER BY expire_time LIMIT ?' ) - delete_expired = ( - 'DELETE FROM Cache WHERE rowid IN ( ' - + select_expired - + ' )' - ) + select_expired = select_expired_template % 'filename' rows = sql(select_expired, (now, cull_limit)).fetchall() - sql(delete_expired, (now, cull_limit)) - cull_limit -= len(rows) - filenames = [filename for filename, in rows] - if cull_limit == 0 or self.volume() < self.size_limit: - return filenames + if rows: + delete_expired = ( + 'DELETE FROM Cache WHERE rowid IN (%s)' + % (select_expired_template % 'rowid') + ) + sql(delete_expired, (now, cull_limit)) + + for filename, in rows: + cleanup(filename) + + cull_limit -= len(rows) + + if cull_limit == 0: + return + + if self.volume() < self.size_limit: + return # Evict keys by policy. - policy = EVICTION_POLICY[self.eviction_policy]['set'] - select = policy['select'] - delete = policy['delete'] + select_policy_template = EVICTION_POLICY[self.eviction_policy]['set'] + select_policy = select_policy_template % 'filename' - rows = sql(select, (cull_limit,)).fetchall() - sql(delete, (cull_limit,)) - filenames.extend(filename for filename, in rows) + rows = sql(select_policy, (cull_limit,)).fetchall() - return filenames + if rows: + delete_policy = ( + 'DELETE FROM Cache WHERE rowid IN (%s)' + % (select_policy_template % 'rowid') + ) + sql(delete_policy, (cull_limit,)) + + for filename, in rows: + cleanup(filename) def add(self, key, value, expire=None, read=False, tag=None): - """Add key, value pair to cache. + """Add `key` and `value` item to cache. Similar to `set`, but only add to cache if key not present. @@ -724,63 +673,48 @@ def add(self, key, value, expire=None, read=False, tag=None): When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. - :param key: Python key to store - :param value: Python value to store + :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) - :return: True if key was successfully added + :return: True if item was successfully added + :raises Timeout: if database timeout expires """ - sql = self._sql + now = time.time() db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire - size, mode, filename, db_value = self._disk.store( - value, read, self.large_value_threshold, self._filename - ) + size, mode, filename, db_value = self._disk.store(value, read) columns = (expire_time, tag, size, mode, filename, db_value) - removes = [] - - try: - sql('BEGIN IMMEDIATE') - except sqlite3.OperationalError: - self._remove(filename) - raise DatabaseTimeout - - now = time.time() - 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 is None or old_expire_time >= now: - sql('COMMIT') - self._remove(filename) - return False + with self._transact(filename) as (sql, cleanup): + rows = sql( + 'SELECT rowid, filename, expire_time FROM Cache' + ' WHERE key = ? AND raw = ?', + (db_key, raw), + ).fetchall() - removes.append(old_filename) - self._row_update(rowid, now, columns) - else: - self._row_insert(db_key, raw, now, columns) + if rows: + (rowid, old_filename, old_expire_time), = rows - removes.extend(self._cull(now)) + if old_expire_time is None or old_expire_time > now: + cleanup(filename) + return False - sql('COMMIT') + cleanup(old_filename) + self._row_update(rowid, now, columns) + else: + self._row_insert(db_key, raw, now, columns) - for reference in removes: - self._remove(reference) + self._cull(now, sql, cleanup) - return True + return True def get(self, key, default=None, read=False, expire_time=False, tag=False): - """Retrieve value from cache. If key is missing, return default. + """Retrieve value from cache. If `key` is missing, return `default`. :param key: Python key to retrieve :param default: value to return if key is missing (default None) @@ -790,13 +724,14 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): (default False) :param bool tag: if True, return tag in tuple (default False) :return: corresponding value or `default` if not found + :raises Timeout: if database timeout expires """ - sql = self._sql - cache_hit = 'UPDATE Settings SET value = value + 1 WHERE key = "hits"' - cache_miss = ( - 'UPDATE Settings SET value = value + 1' - ' WHERE key = "misses"' + db_key, raw = self._disk.put(key) + policy_update = EVICTION_POLICY[self.eviction_policy]['get'] + select = ( + 'SELECT rowid, expire_time, tag, mode, filename, value' + ' FROM Cache WHERE key = ? AND raw = ?' ) if expire_time and tag: @@ -804,53 +739,69 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): elif expire_time or tag: default = (default, None) - db_key, raw = self._disk.put(key) + if not self.statistics and policy_update is None: + # Fast path, no transaction necessary. - rows = sql( - 'SELECT rowid, store_time, expire_time, tag,' - ' mode, filename, value' - ' FROM Cache WHERE key = ? AND raw = ?', - (db_key, raw), - ).fetchall() + rows = self._sql(select, (db_key, raw)).fetchall() - if not rows: - if self.statistics: - sql(cache_miss) - return default + if not rows: + return default + + (rowid, db_expire_time, db_tag, mode, filename, db_value), = rows - (rowid, store_time, db_expire_time, db_tag, - mode, filename, db_value), = rows + if db_expire_time is not None and db_expire_time < time.time(): + return default - if store_time is None: - if self.statistics: - sql(cache_miss) - return default + try: + value = self._disk.fetch(mode, filename, db_value, read) + except IOError as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + return default + else: + raise - now = time.time() + else: # Slow path, transaction required. - if db_expire_time is not None and db_expire_time < now: - if self.statistics: - sql(cache_miss) - return default + cache_hit = ( + 'UPDATE Settings SET value = value + 1 WHERE key = "hits"' + ) + cache_miss = ( + 'UPDATE Settings SET value = value + 1 WHERE key = "misses"' + ) - try: - value = self._disk.fetch(self._dir, mode, filename, db_value, read) - except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - if self.statistics: - sql(cache_miss) - return default - else: - raise + with self._transact() as (sql, _): + rows = sql(select, (db_key, raw)).fetchall() - if self.statistics: - sql(cache_hit) + if not rows: + if self.statistics: + sql(cache_miss) + return default - query = EVICTION_POLICY[self.eviction_policy]['get'] + (rowid, db_expire_time, db_tag, + mode, filename, db_value), = rows - if query is not None: - sql(query, (rowid,)) + if db_expire_time is not None and db_expire_time < time.time(): + if self.statistics: + sql(cache_miss) + return default + + try: + value = self._disk.fetch(mode, filename, db_value, read) + except IOError as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + if self.statistics: + sql(cache_miss) + return default + else: + raise + + if self.statistics: + sql(cache_hit) + + if policy_update is not None: + sql(policy_update, (rowid,)) if expire_time and tag: return (value, db_expire_time, db_tag) @@ -863,7 +814,14 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): def __getitem__(self, key): - "Return corresponding value for `key` from Cache." + """Return corresponding value for `key` from cache. + + :param key: key matching item + :return: corresponding value + :raises KeyError: if key is not found + :raises Timeout: if database timeout expires + + """ value = self.get(key, default=ENOVAL) if value is ENOVAL: raise KeyError(key) @@ -871,11 +829,12 @@ def __getitem__(self, key): def read(self, key): - """Return file handle corresponding to `key` from Cache. + """Return file handle value corresponding to `key` from cache. - :param key: Python key to retrieve + :param key: key matching item :return: file open for reading in binary mode :raises KeyError: if key is not found + :raises Timeout: if database timeout expires """ handle = self.get(key, default=ENOVAL, read=True) @@ -885,10 +844,14 @@ def read(self, key): def __contains__(self, key): - "Return True if `key` in Cache." + """Return `True` if `key` matching item is found in cache. + + :param key: key matching item + :return: True if key matching item + + """ sql = self._sql db_key, raw = self._disk.put(key) - now = time.time() rows = sql( 'SELECT expire_time FROM Cache WHERE key = ? AND raw = ?', @@ -898,40 +861,50 @@ def __contains__(self, key): if not rows: return False - return expire_time is None or now < expire_time + (expire_time,), = rows + + return expire_time is None or time.time() < expire_time def __delitem__(self, key): - "Delete corresponding item for `key` from Cache." - sql = self._sql - db_key, raw = self._disk.put(key) + """Delete corresponding item for `key` from cache. - try: - sql('BEGIN IMMEDIATE') - except sqlite3.OperationalError: - raise DatabaseTimeout + :param key: key matching item + :raises KeyError: if key is not found + :raises Timeout: if database timeout expires - now = time.time() + """ + db_key, raw = self._disk.put(key) - select = 'SELECT rowid, filename FROM Cache WHERE key = ? AND raw = ?' - rows = sql(select, (db_key, raw)).fetchall() + with self._transact() as (sql, cleanup): + rows = sql( + 'SELECT rowid, expire_time, filename FROM Cache' + ' WHERE key = ? AND raw = ?', + (db_key, raw), + ).fetchall() - if rows: - (rowid, filename), = rows + if not rows: + raise KeyError(key) + + (rowid, expire_time, filename), = rows sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) - sql('COMMIT') - self._remove(filename) - return True - else: - sql('COMMIT') - raise KeyError(key) + cleanup(filename) + + if expire_time is None or time.time() < expire_time: + return True + else: + raise KeyError(key) def delete(self, key): - """Delete corresponding item for `key` from Cache. + """Delete corresponding item for `key` from cache. Missing keys are ignored. + :param key: key matching item + :return: True if item was deleted else False + :raises Timeout: if database timeout expires + """ try: return self.__delitem__(key) @@ -939,44 +912,20 @@ def delete(self, key): return False - def _filename(self): - hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8') - sub_dir = op.join(hex_name[:2], hex_name[2:4]) - name = hex_name[4:] + '.val' - directory = op.join(self._dir, sub_dir) - - try: - os.makedirs(directory) - except OSError as error: - if error.errno != errno.EEXIST: - raise - - filename = op.join(sub_dir, name) - full_path = op.join(self._dir, filename) - - return filename, full_path - - - def _remove(self, filename): - if filename is None: - return - - full_path = op.join(self._dir, filename) - - try: - os.remove(full_path) - except OSError as error: - if error.errno != errno.ENOENT: - # ENOENT may occur if two caches attempt to delete the same - # file at the same time. - raise - - def check(self, fix=False): """Check database and file system consistency. - :param bool fix: fix inconsistencies + 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. + + :param bool fix: correct inconsistencies :return: list of warnings + :raises Timeout: if database timeout expires """ # pylint: disable=access-member-before-definition,W0201 @@ -993,48 +942,36 @@ def check(self, fix=False): if fix: sql('VACUUM') - try: - sql('BEGIN IMMEDIATE') - except sqlite3.OperationalError: - raise DatabaseTimeout - - # Check Settings.count against count of Cache rows. - del self.count - self_count = self.count - (count,), = sql('SELECT COUNT(key) FROM Cache').fetchall() + with self._transact() as (sql, _): - if self_count != count: - message = 'Settings.count != COUNT(Cache.key); %d != %d' - warnings.warn(message % (self_count, count)) + # Check Cache.filename against file system. - if fix: - self.count = count + filenames = set() + select = ( + 'SELECT rowid, size, filename FROM Cache' + ' WHERE filename IS NOT NULL' + ) - # Check Cache.filename against file system. + rows = sql(select).fetchall() - filenames = set() - chunk = self.cull_limit - rowid = 0 - total_size = 0 - - while True: - rows = sql( - 'SELECT rowid, version, filename FROM Cache' - ' WHERE rowid > ? AND filename IS NOT NULL' - ' ORDER BY rowid LIMIT ?', - (rowid, chunk), - ).fetchall() - - if not rows: - break - - for rowid, version, filename in rows: + for rowid, size, filename in rows: full_path = op.join(self._dir, filename) filenames.add(full_path) if op.exists(full_path): - total_size += op.getsize(full_path) + real_size = op.getsize(full_path) + + if size != real_size: + message = 'wrong file size: %s, %d != %d' + args = full_path, real_size, size + warnings.warn(message % args) + + if fix: + sql('UPDATE Cache SET size = ? WHERE rowid = ?', + (real_size, rowid), + ) + continue warnings.warn('file not found: %s' % full_path) @@ -1042,172 +979,176 @@ def check(self, fix=False): if fix: sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) - del self.size - self_size = self.size - (size,), = sql( - 'SELECT COALESCE(SUM(size), 0) FROM Cache' - ).fetchall() + # Check file system against Cache.filename. - if self_size != size: - message = 'Settings.size != SUM(Cache.size); %d != %d' - warnings.warn(message % (self_size, size)) + for dirpath, _, files in os.walk(self._dir): + paths = [op.join(dirpath, filename) for filename in files] + error = set(paths) - filenames - if fix: - self.size = size + for full_path in error: + if DBNAME in full_path: + continue - # Check file system against Cache.filename. + message = 'unknown file: %s' % full_path + warnings.warn(message, UnknownFileWarning) - for dirpath, _, files in os.walk(self._dir): - paths = [op.join(dirpath, filename) for filename in files] - error = set(paths) - filenames + if fix: + os.remove(full_path) - for full_path in error: - if DBNAME in full_path: - continue + # Check for empty directories. - message = 'unknown file: %s' % full_path - warnings.warn(message, UnknownFileWarning) + for dirpath, dirs, files in os.walk(self._dir): + if not (dirs or files): + message = 'empty directory: %s' % dirpath + warnings.warn(message, EmptyDirWarning) - if fix: - os.remove(full_path) + if fix: + os.rmdir(dirpath) - # Check for empty directories. + # Check Settings.count against count of Cache rows. - for dirpath, dirs, files in os.walk(self._dir): - if not (dirs or files): - message = 'empty directory: %s' % dirpath - warnings.warn(message, EmptyDirWarning) + self.reset('count') + (count,), = sql('SELECT COUNT(key) FROM Cache').fetchall() + + if self.count != count: + message = 'Settings.count != COUNT(Cache.key); %d != %d' + warnings.warn(message % (self.count, count)) if fix: - os.rmdir(dirpath) + sql('UPDATE Settings SET value = ? WHERE key = ?', + (count, 'count'), + ) - if fix: - sql('COMMIT') + # Check Settings.size against sum of Cache.size column. - return warns + self.reset('size') + select_size = 'SELECT COALESCE(SUM(size), 0) FROM Cache' + (size,), = sql(select_size).fetchall() + if self.size != size: + message = 'Settings.size != SUM(Cache.size); %d != %d' + warnings.warn(message % (self.size, size)) - def expire(self, now=None): - """Remove expired items from Cache. + if fix: + sql('UPDATE Settings SET value = ? WHERE key =?', + (size, 'size'), + ) - TODO: docs + return warns - """ - now = now or time.time() - cull_limit = self.cull_limit - expire_time = 0 - count = 0 - select_template = ( - 'SELECT %s FROM Cache' - ' WHERE ? < expire_time AND expire_time < ?' - ' ORDER BY expire_time LIMIT ?' - ) - select = select_template % 'expire_time, filename' - delete = ( - 'DELETE FROM Cache WHERE rowid IN (%s)' - % (select_template % 'rowid') - ) + def create_tag_index(self): + """Create tag index on cache database. - try: - while True: - with self._transact() as (sql, cleanup): - args = expire_time, now, cull_limit - rows = sql(select, args).fetchall() + It's better to initialized cache with `tag_index=True`. - if not rows: - break + :raises Timeout: if database timeout expires - count += len(rows) - sql(delete, args) - for expire_time, filename in rows: - cleanup(filename) - except TimeoutError: - status = False - else: - status = True + """ + sql = self._sql + sql('CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON Cache(tag, rowid)') + self.reset('tag_index', 1) - return status, count + def drop_tag_index(self): + """Drop tag index on cache database. - def _create_tag_index(self): - # TODO: Add FanoutCache keyword arg for tag_index. - sql = self._sql - sql('CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON' - ' Cache(tag, rowid)' - ) + :raises Timeout: if database timeout expires - def _drop_tag_index(self): + """ sql = self._sql sql('DROP INDEX IF EXISTS Cache_tag_rowid') + self.reset('tag_index', 0) + def evict(self, tag): - "Remove items with matching `tag` from Cache." + """Remove items with matching `tag` from cache. - # TODO: docs notes about _create_tag_index - # TODO: How to limit max rowid? + Removing items is an iterative process. In each iteration, a subset of + items is removed. The subset size is equal to the `cull_limit` + attribute. - sql = self._sql - rowid = 0 - cull_limit = self.cull_limit - count = 0 + If a :exc:`Timeout` occurs, the first element of the exception's + `args` attribute will be the number of items removed before the + exception occurred. + + :param str tag: tag identifying items + :return: count of rows removed + :raises Timeout: if database timeout expires - select_template = ( + """ + select = ( 'SELECT %s FROM Cache' ' WHERE tag = ? AND rowid > ?' ' ORDER BY rowid LIMIT ?' ) - select = select_template % 'rowid, filename' - delete = ( - 'DELETE FROM Cache WHERE rowid IN (%s)' - % (select_template % 'rowid') - ) + fields = 'rowid, filename' + args = [tag, 0, self.cull_limit] + return self._select_delete(select, fields, args, index=1) - try: - while True: - with self._transact(sql) as transaction: - args = tag, rowid, cull_limit - rows = sql(select, args).fetchall() - if not rows: - break + def expire(self, now=None): + """Remove expired items from cache. - count += len(rows) - sql(delete, args) - for rowid, filename in rows: - transaction.remove(filename) - except TimeoutError: - status = False - else: - status = True + Removing items is an iterative process. In each iteration, a subset of + items is removed. The subset size is equal to the `cull_limit` + attribute. + + If a :exc:`Timeout` occurs, the first element of the exception's + `args` attribute will be the number of items removed before the + exception occurred. + + :param float now: current time (default None, ``time.time()`` used) + :return: count of items removed + :raises Timeout: if database timeout expires - return status, count + """ + select = ( + 'SELECT %s FROM Cache' + ' WHERE ? < expire_time AND expire_time < ?' + ' ORDER BY expire_time LIMIT ?' + ) + fields = 'expire_time, filename' + args = [0, now or time.time(), self.cull_limit] + return self._select_delete(select, fields, args) def clear(self): - "Remove all items from Cache." + """Remove all items from cache. - sql = self._sql - rowid = 0 - cull_limit = self.cull_limit - count = 0 + Removing items is an iterative process. In each iteration, a subset of + items is removed. The subset size is equal to the `cull_limit` + attribute. + + If a :exc:`Timeout` occurs, the first element of the exception's + `args` attribute will be the number of items removed before the + exception occurred. - select_template = ( + :return: count of rows removed + :raises Timeout: if database timeout expires + + """ + select = ( 'SELECT %s FROM Cache' ' WHERE rowid > ?' ' ORDER BY rowid LIMIT ?' ) - select = select_template % 'rowid, filename' + fields = 'rowid, filename' + args = [0, self.cull_limit] + return self._select_delete(select, fields, args) + + + def _select_delete(self, select_template, fields, args, index=0): + count = 0 + select = select_template % fields delete = ( 'DELETE FROM Cache WHERE rowid IN (%s)' % (select_template % 'rowid') ) - try: while True: - with self._transact(sql) as transaction: - args = rowid, cull_limit + with self._transact() as (sql, cleanup): rows = sql(select, args).fetchall() if not rows: @@ -1215,17 +1156,18 @@ def clear(self): count += len(rows) sql(delete, args) - for rowid, filename in rows: - transaction.remove(filename) - except TimeoutError: - status = False - else: - status = True - return status, count + for row in rows: + args[index] = row[0] + cleanup(row[-1]) + except Timeout: + raise Timeout(count) - def __iter__(self): + return count + + + def _iter(self, ascending=True): sql = self._sql rows = sql('SELECT MAX(rowid) FROM Cache').fetchall() (max_rowid,), = rows @@ -1233,17 +1175,23 @@ def __iter__(self): if max_rowid is None: return - chunk = self.cull_limit - rowid = 0 + bound = max_rowid + 1 + limit = self.cull_limit _disk_get = self._disk.get + rowid = 0 if ascending else bound + select = ( + 'SELECT rowid, key, raw FROM Cache' + ' WHERE ? < rowid AND rowid < ?' + ' ORDER BY rowid %s LIMIT ?' + ) % ('ASC' if ascending else 'DESC') while True: - rows = sql( - 'SELECT rowid, key, raw FROM Cache' - ' WHERE ? < rowid AND rowid < ?' - ' ORDER BY rowid LIMIT ?', - (rowid, max_rowid, chunk), - ).fetchall() + if ascending: + args = (rowid, bound, limit) + else: + args = (0, rowid, limit) + + rows = sql(select, args).fetchall() if not rows: break @@ -1252,34 +1200,16 @@ def __iter__(self): yield _disk_get(key, raw) - def __reversed__(self): - sql = self._sql - rows = sql('SELECT MAX(rowid) FROM Cache').fetchall() - (rowid,), = rows - - if rowid is None: - return - - chunk = self.cull_limit - _disk_get = self._disk.get - - while True: - rows = sql( - 'SELECT rowid, key, raw FROM Cache' - ' WHERE 0 < rowid AND rowid < ?' - ' ORDER BY rowid DESC LIMIT ?', - (rowid, chunk), - ).fetchall() + def __iter__(self): + return self._iter() - if not rows: - break - for rowid, key, raw in rows: - yield _disk_get(key, raw) + def __reversed__(self): + return self._iter(ascending=False) def stats(self, enable=True, reset=False): - """Return cache statistics pair: (hits, misses). + """Return cache statistics hits and misses. :param bool enable: enable collecting statistics (default True) :param bool reset: reset hits and misses to 0 (default False) @@ -1287,16 +1217,13 @@ def stats(self, enable=True, reset=False): """ # pylint: disable=E0203,W0201 - del self.hits - del self.misses - - result = (self.hits, self.misses) + result = (self.reset('hits'), self.reset('misses')) if reset: - self.hits = 0 - self.misses = 0 + self.reset('hits', 0) + self.reset('misses', 0) - self.statistics = enable + self.reset('statistics', enable) return result @@ -1308,13 +1235,14 @@ def volume(self): """ (page_count,), = self._sql('PRAGMA page_count').fetchall() - del self.size # Update value from database. - total_size = self._page_size * page_count + self.size + total_size = self._page_size * page_count + self.reset('size') return total_size def close(self): - "Close database connection." + """Close database connection. + + """ con = getattr(self._local, 'con', None) if con is None: @@ -1337,5 +1265,65 @@ def __exit__(self, *exception): def __len__(self): - del self.count - return self.count + return self.reset('count') + + + def reset(self, key, value=ENOVAL): + """Reset `key` and `value` item from Settings table. + + If `value` is not given, it is reloaded from the Settings + table. Otherwise, the Settings table is updated. + + Settings attributes on cache objects are lazy-loaded and + read-only. Use `reset` to update the value. + + Settings with the "sqlite_" prefix correspond to SQLite + pragmas. Updating the value will execute the corresponding PRAGMA + statement. + + :param str key: Settings key for item + :param value: value for item (optional) + :return: updated value for item + :raises Timeout: if database timeout expires + + """ + if value is ENOVAL: + select = 'SELECT value FROM Settings WHERE key = ?' + (value,), = self._sql(select, (key,)).fetchall() + setattr(self, key, value) + return value + else: + with self._transact() as (sql, _): + update = 'UPDATE Settings SET value = ? WHERE key = ?' + sql(update, (value, key)) + + if key.startswith('sqlite_'): + + # 2016-02-17 GrantJ - PRAGMA and autocommit_level=None + # don't always play nicely together. Retry setting the + # PRAGMA. I think some PRAGMA statements expect to + # immediately take an EXCLUSIVE lock on the database. I + # can't find any documentation for this but without the + # retry, stress will intermittently fail with multiple + # processes. + + pause = 0.001 + error = sqlite3.OperationalError + pragma = key[7:] + + for _ in range(int(LIMITS[u'pragma_timeout'] / pause)): + try: + args = pragma, value + sql('PRAGMA %s = %s' % args).fetchall() + except sqlite3.OperationalError as exc: + error = exc + time.sleep(pause) + else: + break + else: + raise error + + del error + + setattr(self, key, value) + return value diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 61a7fec..2fc6015 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -24,16 +24,14 @@ def __init__(self, directory, params): shards = params.get('SHARDS', 8) timeout = params.get('DATABASE_TIMEOUT', 0.025) options = params.get('OPTIONS', {}) - self._cache = FanoutCache( - directory, shards=shards, timeout=timeout, **options - ) + self._cache = FanoutCache(directory, shards, timeout, **options) def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, read=False, tag=None): - """Set a value in the cache if the key does not already exist. If timeout is - given, that timeout will be used for the key; otherwise the default - cache timeout will be used. + """Set a value in the cache if the key does not already exist. If + timeout is given, that timeout will be used for the key; otherwise the + default cache timeout will be used. Return True if the value was stored, False otherwise. @@ -41,7 +39,7 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, # pylint: disable=arguments-differ key = self.make_key(key, version=version) timeout = self.get_backend_timeout(timeout=timeout) - return self._cache.add(key, value, expire=timeout, read=read, tag=tag) + return self._cache.add(key, value, timeout, read, tag) def get(self, key, default=None, version=None, read=False, @@ -52,9 +50,7 @@ def get(self, key, default=None, version=None, read=False, """ # pylint: disable=arguments-differ key = self.make_key(key, version=version) - return self._cache.get( - key, default=default, read=read, expire_time=expire_time, tag=tag - ) + return self._cache.get(key, default, read, expire_time, tag) def read(self, key, version=None): @@ -70,21 +66,22 @@ def read(self, key, version=None): def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, - read=False, tag=None): - """Set a value in the cache. If timeout is given, that timeout will be used - for the key; otherwise the default cache timeout will be used. + read=False, tag=None, retry=True): + """Set a value in the cache. If timeout is given, that timeout will be + used for the key; otherwise the default cache timeout will be used. """ # pylint: disable=arguments-differ key = self.make_key(key, version=version) timeout = self.get_backend_timeout(timeout=timeout) - return self._cache.set(key, value, expire=timeout, read=read, tag=tag) + return self._cache.set(key, value, timeout, read, tag, retry) - def delete(self, key, version=None): + def delete(self, key, version=None, retry=True): "Delete a key from the cache, failing silently." + # pylint: disable=arguments-differ key = self.make_key(key, version=version) - self._cache.delete(key) + self._cache.delete(key, retry) def has_key(self, key, version=None): diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 7de2fd6..36deb5d 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -1,14 +1,16 @@ "Fanout cache automatically shards keys and values." -import sqlite3 +import os.path as op +import time + +from .core import Cache, Disk, ENOVAL, Timeout -from .core import Cache, Disk, ENOVAL, DatabaseTimeout class FanoutCache(object): "Cache that shards keys and values." - def __init__(self, directory, shards=8, timeout=0.025, disk=Disk(), + def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, **settings): - """Initialize FanoutCache instance. + """Initialize cache instance. :param str directory: cache directory :param int shards: number of shards to distribute writes @@ -17,53 +19,76 @@ def __init__(self, directory, shards=8, timeout=0.025, disk=Disk(), :param settings: any of `DEFAULT_SETTINGS` """ - object.__setattr__(self, '_count', shards) - - object.__setattr__(self, '_shards', tuple( + self._count = shards + self._shards = tuple( Cache( - '%s/%03d' % (directory, num), + op.join(directory, '%03d' % num), timeout=timeout, disk=disk, **settings ) for num in range(shards) - )) + ) def __getattr__(self, name): return getattr(self._shards[0], name) - def __setattr__(self, name, value): - for shard in self._shards: - setattr(shard, name, value) - - - def set(self, key, value, expire=None, read=False, tag=None): - """Set key, value pair in cache. + 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. - :param key: Python key to store - :param value: Python value to store - :param expire: seconds until the key expires (default None, no expiry) + :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 raw bytes from file (default False) - :param tag: text to associate with key (default None) - :return: True if item was successfully set + :param str tag: text to associate with key (default None) + :param bool retry: retry if database timeout expires + :return: True if item is set """ index = hash(key) % self._count - return self._shards[index].set(key, value, expire, read, tag) + set_func = self._shards[index].set + + while True: + try: + set_func(key, value, expire, read, tag) + except Timeout: + if retry: + continue + else: + return False + else: + return True - __setitem__ = set + def __setitem__(self, key, value): + """Set `key` and `value` item in cache. + + :param key: key for item + :param value: value for item + + """ + index = hash(key) % self._count + set_func = self._shards[index].set + + while True: + try: + set_func(key, value) + except Timeout: + continue + else: + break def add(self, key, value, expire=None, read=False, tag=None): - """Add key, value pair to cache. + """Add `key` and `value` item to cache. - Similar to `set`, but only set in cache if key not present. + Similar to `set`, but only add to cache if key not present. This operation is atomic. Only one concurrent add operation for given key from separate threads or processes will succeed. @@ -71,8 +96,8 @@ def add(self, key, value, expire=None, read=False, tag=None): When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. - :param key: Python key to store - :param value: Python value to store + :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) @@ -80,38 +105,46 @@ def add(self, key, value, expire=None, read=False, tag=None): :return: True if item was successfully added """ + index = hash(key) % self._count + try: - index = hash(key) % self._count return self._shards[index].add(key, value, expire, read, tag) - except DatabaseTimeout: + except Timeout: return False def get(self, key, default=None, read=False, expire_time=False, tag=False): - """Retrieve value from cache. If key is missing, return default. + """Retrieve value from cache. If `key` is missing, return `default`. - :param key: Python key to retrieve + :param key: key for item :param default: value to return if key is missing (default None) :param bool read: if True, return file handle to value (default False) :param float expire_time: if True, return expire_time in tuple (default False) :param tag: if True, return tag in tuple (default False) - :return: corresponding value or `default` if not found + :return: value for item if key is found else default """ + index = hash(key) % self._count + try: - index = hash(key) % self._count return self._shards[index].get( key, default=default, read=read, expire_time=expire_time, tag=tag, ) - except sqlite3.OperationalError: + except Timeout: return default def __getitem__(self, key): - "Return corresponding value for `key` from Cache." + """Return corresponding value for `key` from cache. + + :param key: key for item + :return: value for item + :raises KeyError: if key is not found + + """ value = self.get(key, default=ENOVAL) if value is ENOVAL: raise KeyError(key) @@ -119,9 +152,9 @@ def __getitem__(self, key): def read(self, key): - """Return file handle corresponding to `key` from Cache. + """Return file handle corresponding to `key` from cache. - :param key: Python key to retrieve + :param key: key for item :return: file open for reading in binary mode :raises KeyError: if key is not found @@ -133,94 +166,149 @@ def read(self, key): def __contains__(self, key): - "Return True if `key` in Cache." - try: - index = hash(key) % self._count - return key in self._shards[index] - except sqlite3.OperationalError: - return False + """Return `True` if `key` matching item is found in cache. + :param key: key for item + :return: True if key is found - def __delitem__(self, key): - "Delete corresponding item for `key` from Cache." - try: - index = hash(key) % self._count - return self._shards[index].__delitem__(key) - except sqlite3.OperationalError: - return False + """ + index = hash(key) % self._count + return key in self._shards[index] - def delete(self, key): - """Delete corresponding item for `key` from Cache. + def delete(self, key, retry=False): + """Delete corresponding item for `key` from cache. Missing keys are ignored. + :param key: key for item + :param bool retry: retry if database timeout expires + :return: True if item is deleted + """ - try: - return self.__delitem__(key) - except KeyError: - return False + index = hash(key) % self._count + del_func = self._shards[index].__delitem__ + + while True: + try: + del_func(key) + except Timeout: + if retry: + continue + else: + return False + except KeyError: + return False + else: + return True + + + def __delitem__(self, key): + """Delete corresponding item for `key` from cache. + + :param key: key for item + :raises KeyError: if key is not found + + """ + index = hash(key) % self._count + del_func = self._shards[index].__delitem__ + + while True: + try: + del_func(key) + except Timeout: + continue + else: + break def check(self, fix=False): """Check database and file system consistency. - :param bool fix: fix inconsistencies + 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. + + :param bool fix: correct inconsistencies :return: list of warnings + :raises Timeout: if database timeout expires """ return sum((shard.check(fix=fix) for shard in self._shards), []) def expire(self): - """Remove expired items from Cache. + """Remove expired items from cache. + + :return: count of items removed """ - total = 0 - index = 0 - shards = self._shards + return self._remove('expire', (time.time(),)) - while len(shards): - shard = shards[index % len(shards)] - try: - total += shard.expire() - except DatabaseTimeout: - index += 1 - else: - del shards[index] - return total + def create_tag_index(self): + """Create tag index on cache database. - def expire(self): - """Remove expired items from Cache. + It's better to initialized cache with `tag_index=True`. - Return number of rows expired. + :raises Timeout: if database timeout expires """ - now = time.time() - total = 0 - sentinel = (True, 0) - for shard in self._shards: - shard_expire = partial(shard.expire, now) - for status, count in iter(shard_expire, sentinel): - total += count + shard.create_tag_index() - return total + + def drop_tag_index(self): + """Drop tag index on cache database. + + :raises Timeout: if database timeout expires + + """ + for shard in self._shards: + shard.drop_tag_index() def evict(self, tag): - "Remove items with matching `tag` from Cache." - return sum(shard.evict(tag) for shard in self._shards) + """Remove items with matching `tag` from cache. + + :param str tag: tag identifying items + :return: count of items removed + + """ + return self._remove('evict', (tag,)) def clear(self): - "Remove all items from Cache." - return sum(shard.clear() for shard in self._shards) + """Remove all items from cache. + + :return: count of items removed + + """ + return self._remove('clear') + + + def _remove(self, name, args=()): + total = 0 + for shard in self._shards: + method = getattr(shard, name) + while True: + try: + count = method(*args) + total += count + except Timeout as timeout: + total += timeout.args[0] + else: + if not count: + break + return total def stats(self, enable=True, reset=False): - """Return cache statistics pair: (hits, misses). + """Return cache statistics hits and misses. :param bool enable: enable collecting statistics (default True) :param bool reset: reset hits and misses to 0 (default False) @@ -257,3 +345,33 @@ def __exit__(self, *exception): def __len__(self): return sum(len(shard) for shard in self._shards) + + + def reset(self, key, value=ENOVAL): + """Reset `key` and `value` item from Settings table. + + If `value` is not given, it is reloaded from the Settings + table. Otherwise, the Settings table is updated. + + Settings attributes on cache objects are lazy-loaded and + read-only. Use `reset` to update the value. + + Settings with the "sqlite_" prefix correspond to SQLite + pragmas. Updating the value will execute the corresponding PRAGMA + statement. + + :param str key: Settings key for item + :param value: value for item (optional) + :return: updated value for item + :raises Timeout: if database timeout expires + + """ + for shard in self._shards: + while True: + try: + result = shard.reset(key, value) + except Timeout: + pass + else: + break + return result From 5572cdd3da3d7f24a9ca28d7bca52f18e24a149d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 5 Aug 2016 13:53:59 -0700 Subject: [PATCH 018/550] Update tests for 100% coverage --- tests/benchmark_djangocache.py | 2 +- tests/test_core.py | 172 ++++++++++++++++++++------------ tests/test_fanout.py | 177 +++++++++++++++++++++++++++++---- 3 files changed, 265 insertions(+), 86 deletions(-) diff --git a/tests/benchmark_djangocache.py b/tests/benchmark_djangocache.py index 13a50b3..08832ce 100644 --- a/tests/benchmark_djangocache.py +++ b/tests/benchmark_djangocache.py @@ -100,7 +100,7 @@ def dispatch(): from django.core.cache import caches - for name in ['locmem', 'memcached', 'redis', 'diskcache', 'filebased']: + for name in ['diskcache']: # ['locmem', 'memcached', 'redis', 'diskcache', 'filebased']: shutil.rmtree('tmp', ignore_errors=True) preparer = mp.Process(target=prepare, args=(name,)) diff --git a/tests/test_core.py b/tests/test_core.py index c79d015..3850f74 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -47,6 +47,13 @@ def test_init(cache): cache.close() +def test_init_disk(): + with dc.Cache('tmp', disk=dc.Disk('tmp', 2 ** 10, 0)) as cache: + cache['a'] = 0 + cache.check() + shutil.rmtree('tmp', ignore_errors=True) + + @nt.raises(EnvironmentError) def test_init_makedirs(): shutil.rmtree('tmp', ignore_errors=True) @@ -74,9 +81,10 @@ def test_pragma(cache): cursor.fetchall = fetchall fetchall.side_effect = [sqlite3.OperationalError, None] - with mock.patch.object(cache, '_local', local): - cache.sqlite_mmap_size = 2 ** 28 + size = 2 ** 28 + with mock.patch.object(cache, '_local', local): + assert cache.reset('sqlite_mmap_size', size) == size @setup_cache @nt.raises(sqlite3.OperationalError) @@ -91,16 +99,16 @@ def test_pragma_error(cache): con.execute = execute execute.return_value = cursor cursor.fetchall = fetchall - fetchall.side_effect = sqlite3.OperationalError + fetchall.side_effect = [sqlite3.OperationalError] - prev = dc.LIMITS[u'pragma_timeout'] - dc.LIMITS[u'pragma_timeout'] = 0.003 + size = 2 ** 28 + dc.LIMITS[u'pragma_timeout'] = 0 try: with mock.patch.object(cache, '_local', local): - cache.sqlite_mmap_size = 2 ** 28 + cache.reset('sqlite_mmap_size', size) finally: - dc.LIMITS[u'pragma_timeout'] = prev + dc.LIMITS[u'pragma_timeout'] = 60 @setup_cache @@ -172,50 +180,6 @@ def test_get_keyerror1(cache): cache[0] -@nt.raises(KeyError) -@setup_cache -def test_get_keyerror2(cache): - "Test cache miss when store_time is None." - local = mock.Mock() - con = mock.Mock() - execute = mock.Mock() - cursor = mock.Mock() - fetchall = mock.Mock() - - local.con = con - con.execute = execute - execute.return_value = cursor - cursor.fetchall = fetchall - fetchall.return_value = [(0, None, None, None, 0, None, 0)] - - cache.statistics = True - - with mock.patch.object(cache, '_local', local): - cache[0] - - -@nt.raises(KeyError) -@setup_cache -def test_get_keyerror3(cache): - "Test cache miss when expire_time is less than now." - local = mock.Mock() - con = mock.Mock() - execute = mock.Mock() - cursor = mock.Mock() - fetchall = mock.Mock() - - local.con = con - con.execute = execute - execute.return_value = cursor - cursor.fetchall = fetchall - fetchall.return_value = [(0, 0, 0, None, 0, None, 0)] - - cache.statistics = True - - with mock.patch.object(cache, '_local', local): - cache[0] - - @nt.raises(IOError, KeyError) @setup_cache def test_get_keyerror4(cache): @@ -277,7 +241,7 @@ def test_set_twice(cache): @setup_cache -@nt.raises(sqlite3.OperationalError) +@nt.raises(dc.Timeout) def test_set_timeout(cache): local = mock.Mock() con = mock.Mock() @@ -289,7 +253,7 @@ def test_set_timeout(cache): try: with mock.patch.object(cache, '_local', local): - cache.set(0, 0) + cache.set('a', 'b' * 2 ** 12) finally: cache.check() @@ -313,6 +277,61 @@ def test_get(cache): assert cache.get(0, tag=True) == (0, u'number') assert cache.get(0, expire_time=True, tag=True) == (0, None, u'number') +@setup_cache +def test_get_expired_fast_path(cache): + assert cache.set(0, 0, expire=0.001) + time.sleep(0.01) + assert cache.get(0) is None + + +@setup_cache +def test_get_ioerror_fast_path(cache): + assert cache.set(0, 0) + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.ENOENT + fetch.side_effect = io_error + + with mock.patch.object(cache, '_disk', disk): + assert cache.get(0) is None + + +@setup_cache +def test_get_expired_slow_path(cache): + cache.stats(enable=True) + cache.reset('eviction_policy', 'least-recently-used') + assert cache.set(0, 0, expire=0.001) + time.sleep(0.01) + assert cache.get(0) is None + + +@setup_cache +@nt.raises(IOError) +def test_get_ioerror_slow_path(cache): + cache.reset('eviction_policy', 'least-recently-used') + cache.set(0, 0) + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.EACCES + fetch.side_effect = io_error + + with mock.patch.object(cache, '_disk', disk): + cache.get(0) + @setup_cache def test_delete(cache): @@ -329,6 +348,14 @@ def test_del(cache): del cache[0] +@nt.raises(KeyError) +@setup_cache +def test_del_expired(cache): + cache.set(0, 0, expire=0.001) + time.sleep(0.01) + del cache[0] + + @setup_cache def test_stats(cache): cache[0] = 0 @@ -494,7 +521,7 @@ def test_filename_error(cache): func = mock.Mock(side_effect=OSError(errno.EACCES)) with mock.patch('os.makedirs', func): - cache._prep_file() + cache._disk.filename() # TODO: Add test for Windows. Attempting to remove a file that is in use @@ -506,7 +533,7 @@ def test_remove_error(cache): func = mock.Mock(side_effect=OSError(errno.EACCES)) with mock.patch('os.remove', func): - cache._remove('ab/cd/efg.val') + cache._disk.remove('ab/cd/efg.val') @setup_cache @@ -527,9 +554,9 @@ def test_check(cache): full_path = reader.name os.remove(full_path) - cache._sql('UPDATE Cache SET store_time = NULL WHERE rowid > 2') - cache.count = 0 - cache.size = 0 + cache._sql('UPDATE Cache SET size = 0 WHERE rowid > 1') + cache.reset('count', 0) + cache.reset('size', 0) with warnings.catch_warnings(): warnings.filterwarnings('ignore') @@ -581,6 +608,12 @@ def test_expire(cache): assert len(cache.check()) == 0 +def test_tag_index(): + with dc.Cache('tmp', tag_index=True) as cache: + assert cache.tag_index == 1 + shutil.rmtree('tmp', ignore_errors=True) + + @setup_cache def test_evict(cache): colors = ('red', 'blue', 'yellow') @@ -604,6 +637,15 @@ def test_clear(cache): assert len(cache.check()) == 0 +@setup_cache +@nt.raises(dc.Timeout) +def test_clear_timeout(cache): + transact = mock.Mock() + transact.side_effect = dc.Timeout + with mock.patch.object(cache, '_transact', transact): + cache.clear() + + @setup_cache def test_tag(cache): assert cache.set(0, None, tag=u'zero') @@ -689,8 +731,6 @@ def test_contains(cache): assert 0 not in cache cache[0] = 0 assert 0 in cache - cache._sql('UPDATE Cache SET store_time = NULL') - assert 0 not in cache @setup_cache @@ -699,6 +739,10 @@ def test_add(cache): assert cache.get(1) == 1 assert not cache.add(1, 2) assert cache.get(1) == 1 + assert cache.delete(1) + assert cache.add(1, 1, expire=0.001) + time.sleep(0.01) + assert cache.add(1, 1) cache.check() @setup_cache @@ -743,7 +787,7 @@ def test_add_concurrent(cache): @setup_cache -@nt.raises(sqlite3.OperationalError) +@nt.raises(dc.Timeout) def test_add_timeout(cache): local = mock.Mock() con = mock.Mock() @@ -762,7 +806,7 @@ def test_add_timeout(cache): @setup_cache def test_iter(cache): - sequence = 'abcdef' + sequence = list('abcdef') + [('g',)] for index, value in enumerate(sequence): cache[value] = index @@ -771,7 +815,7 @@ def test_iter(cache): assert all(one == two for one, two in zip(sequence, iterator)) - cache['g'] = 6 + cache['h'] = 7 try: next(iterator) @@ -796,8 +840,6 @@ def test_reversed(cache): iterator = reversed(cache) - cache['g'] = 6 - pairs = zip(reversed(sequence), iterator) assert all(one == two for one, two in pairs) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 2784858..7ff6149 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -89,6 +89,136 @@ def test_set_get_delete(cache): cache.check() +@setup_cache +def test_set_timeout(cache): + shards = mock.Mock() + shard = mock.Mock() + set_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.set = set_func + set_func.side_effect = dc.Timeout + + with mock.patch.object(cache, '_shards', shards): + assert not cache.set(0, 0) + + +@setup_cache +def test_set_timeout_retry(cache): + shards = mock.Mock() + shard = mock.Mock() + set_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.set = set_func + set_func.side_effect = [dc.Timeout, True, dc.Timeout, True] + + with mock.patch.object(cache, '_shards', shards): + assert cache.set(0, 0, retry=True) + cache[1] = 1 + + +@setup_cache +def test_add(cache): + assert cache.add(0, 0) + assert not cache.add(0, 1) + assert cache.get(0) == 0 + + +@setup_cache +def test_add_timeout(cache): + shards = mock.Mock() + shard = mock.Mock() + add_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.add = add_func + add_func.side_effect = dc.Timeout + + with mock.patch.object(cache, '_shards', shards): + assert not cache.add(0, 0) + + +@setup_cache +def test_get_timeout(cache): + cache.set(0, 0) + + shards = mock.Mock() + shard = mock.Mock() + get_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.get = get_func + get_func.side_effect = dc.Timeout + + with mock.patch.object(cache, '_shards', shards): + assert cache.get(0) is None + + +@setup_cache +def test_delete_timeout(cache): + shards = mock.Mock() + shard = mock.Mock() + delete_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.__delitem__ = delete_func + delete_func.side_effect = dc.Timeout + + with mock.patch.object(cache, '_shards', shards): + assert not cache.delete(0) + + +@setup_cache +def test_delete_timeout_retry(cache): + shards = mock.Mock() + shard = mock.Mock() + delete_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.__delitem__ = delete_func + delete_func.side_effect = [dc.Timeout, True] + + with mock.patch.object(cache, '_shards', shards): + assert cache.delete(0, retry=True) + + +@setup_cache +def test_delitem(cache): + cache[0] = 0 + assert cache[0] == 0 + del cache[0] + + +@setup_cache +@nt.raises(KeyError) +def test_delitem_keyerror(cache): + del cache[0] + + +@setup_cache +def test_delitem_timeout(cache): + shards = mock.Mock() + shard = mock.Mock() + delete_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.__delitem__ = delete_func + delete_func.side_effect = [dc.Timeout, True] + + with mock.patch.object(cache, '_shards', shards): + del cache[0] + + +@setup_cache +def test_tag_index(cache): + assert cache.tag_index == 0 + cache.create_tag_index() + assert cache.tag_index == 1 + cache.drop_tag_index() + assert cache.tag_index == 0 + + @setup_cache def test_read(cache): cache.set(0, b'abcd' * 2 ** 12) @@ -103,23 +233,6 @@ def test_read_keyerror(cache): pass -def test_operationalerror(): - cache = dc.FanoutCache('tmp', shards=1) - - shards = mock.Mock() - shards.__getitem__ = mock.Mock(side_effect=sqlite3.OperationalError) - - object.__setattr__(cache, '_shards', shards) - - assert cache.set(0, 0) == False - assert cache.add(0, 0) == False - assert cache.get(0) == None - assert (0 in cache) == False - assert cache.__delitem__(0) == False - - shutil.rmtree('tmp') - - @nt.raises(KeyError) @setup_cache def test_getitem_keyerror(cache): @@ -128,15 +241,15 @@ def test_getitem_keyerror(cache): @setup_cache def test_expire(cache): - cache.cull_limit = 0 + cache.reset('cull_limit', 0) for value in range(100): cache.set(value, value, expire=0) assert len(cache) == 100 - cache.cull_limit = 10 - + cache.reset('cull_limit', 10) + assert cache.expire() == 100 @@ -163,6 +276,30 @@ def test_clear(cache): assert len(cache.check()) == 0 +@setup_cache +def test_remove_timeout(cache): + shard = mock.Mock() + clear = mock.Mock() + + shard.clear = clear + clear.side_effect = [1, dc.Timeout(2), 3, 0] + + with mock.patch.object(cache, '_shards', [shard]): + assert cache.clear() == 6 + + +@setup_cache +def test_reset_timeout(cache): + shard = mock.Mock() + reset = mock.Mock() + + shard.reset = reset + reset.side_effect = [dc.Timeout, 0] + + with mock.patch.object(cache, '_shards', [shard]): + assert cache.reset('blah', 1) == 0 + + @setup_cache def test_stats(cache): for value in range(100): From 5de9ecb4d27b6858349d014e0db3a93bcf5c6ca4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 5 Aug 2016 13:54:09 -0700 Subject: [PATCH 019/550] Increase arg limit from 7 to 8 --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 0c4a7d5..ec30778 100644 --- a/.pylintrc +++ b/.pylintrc @@ -318,7 +318,7 @@ exclude-protected=_asdict,_fields,_replace,_source,_make [DESIGN] # Maximum number of arguments for function / method -max-args=7 +max-args=8 # Argument names that match this expression will be ignored. Default to name # with leading underscore From baff1ab82e669672b8d8ccafdbca91b8562ae909 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 5 Aug 2016 15:26:17 -0700 Subject: [PATCH 020/550] Add retry parameter to FanoutCache.add --- diskcache/fanout.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 36deb5d..9444a24 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -85,7 +85,7 @@ def __setitem__(self, key, value): break - def add(self, key, value, expire=None, read=False, tag=None): + 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. @@ -102,15 +102,22 @@ def add(self, key, value, expire=None, read=False, tag=None): (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 expires :return: True if item was successfully added """ index = hash(key) % self._count - try: - return self._shards[index].add(key, value, expire, read, tag) - except Timeout: - return False + while True: + try: + return self._shards[index].add(key, value, expire, read, tag) + except Timeout: + if retry: + continue + else: + return False + else: + return True def get(self, key, default=None, read=False, expire_time=False, tag=False): @@ -356,7 +363,7 @@ def reset(self, key, value=ENOVAL): Settings attributes on cache objects are lazy-loaded and read-only. Use `reset` to update the value. - Settings with the "sqlite_" prefix correspond to SQLite + Settings with the ``sqlite_`` prefix correspond to SQLite pragmas. Updating the value will execute the corresponding PRAGMA statement. From 0b86888505a0f740e6298b48b9e897eb1e784433 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 5 Aug 2016 15:26:28 -0700 Subject: [PATCH 021/550] Add retry parameter to DjangoCache.add --- diskcache/djangocache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 2fc6015..60140e3 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -28,7 +28,7 @@ def __init__(self, directory, params): def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, - read=False, tag=None): + read=False, tag=None, retry=True): """Set a value in the cache if the key does not already exist. If timeout is given, that timeout will be used for the key; otherwise the default cache timeout will be used. @@ -39,7 +39,7 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, # pylint: disable=arguments-differ key = self.make_key(key, version=version) timeout = self.get_backend_timeout(timeout=timeout) - return self._cache.add(key, value, timeout, read, tag) + return self._cache.add(key, value, timeout, read, tag, retry) def get(self, key, default=None, version=None, read=False, From 55d4ae19c1d1d8638f67efc68e52a04c52f41627 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 5 Aug 2016 15:26:45 -0700 Subject: [PATCH 022/550] Improve docs for methods --- diskcache/core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index f9874c7..cff7afc 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -154,7 +154,8 @@ def get(self, key, raw): def store(self, value, read): - """Return fields size, mode, filename, and value for Cache table. + """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 @@ -214,7 +215,8 @@ def store(self, value, read): def fetch(self, mode, filename, value, read): - """Convert fields mode, filename, and value from Cache table to value. + """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 @@ -1277,7 +1279,7 @@ def reset(self, key, value=ENOVAL): Settings attributes on cache objects are lazy-loaded and read-only. Use `reset` to update the value. - Settings with the "sqlite_" prefix correspond to SQLite + Settings with the ``sqlite_`` prefix correspond to SQLite pragmas. Updating the value will execute the corresponding PRAGMA statement. From 6c76bd1de4499e5877f1fd7b61a47f909bc883c2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 18 Aug 2016 10:07:37 -0700 Subject: [PATCH 023/550] Increase coverage with add/get timeout retry tests --- tests/test_fanout.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 7ff6149..1196c01 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -139,6 +139,20 @@ def test_add_timeout(cache): assert not cache.add(0, 0) +@setup_cache +def test_add_timeout_retry(cache): + shards = mock.Mock() + shard = mock.Mock() + add_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.add = add_func + add_func.side_effect = [dc.Timeout, True] + + with mock.patch.object(cache, '_shards', shards): + assert cache.add(0, 0, retry=True) + + @setup_cache def test_get_timeout(cache): cache.set(0, 0) @@ -155,6 +169,20 @@ def test_get_timeout(cache): assert cache.get(0) is None +@setup_cache +def test_get_timeout_retry(cache): + shards = mock.Mock() + shard = mock.Mock() + get_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.get = get_func + get_func.side_effect = [dc.Timeout, 0] + + with mock.patch.object(cache, '_shards', shards): + assert cache.get(0, retry=True) == 0 + + @setup_cache def test_delete_timeout(cache): shards = mock.Mock() From 86075a03122a7921183b441723cb291d56aa4457 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 18 Aug 2016 10:14:34 -0700 Subject: [PATCH 024/550] Update docs and improve source readability --- diskcache/djangocache.py | 56 ++++++++++++++++++++++++++++++++++++---- diskcache/fanout.py | 56 +++++++++++++++++++--------------------- 2 files changed, 78 insertions(+), 34 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 60140e3..2c01e55 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -35,6 +35,16 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, Return True if the value was stored, False otherwise. + :param key: key for item + :param value: value for item + :param float timeout: seconds until the item expires + (default 300 seconds) + :param int version: key version number (default None, cache parameter) + :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 expires (default True) + :return: True if item is added + """ # pylint: disable=arguments-differ key = self.make_key(key, version=version) @@ -43,20 +53,32 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, def get(self, key, default=None, version=None, read=False, - expire_time=False, tag=False): + expire_time=False, tag=False, retry=False): """Fetch a given key from the cache. If the key does not exist, return default, which itself defaults to None. + :param key: key for item + :param default: return value if key is missing (default None) + :param int version: key version number (default None, cache parameter) + :param bool read: if True, return file handle to value + (default False) + :param float expire_time: if True, return expire_time in tuple + (default False) + :param tag: if True, return tag in tuple (default False) + :param bool retry: retry if database timeout expires (default False) + :return: value for item if key is found else default + """ # pylint: disable=arguments-differ key = self.make_key(key, version=version) - return self._cache.get(key, default, read, expire_time, tag) + return self._cache.get(key, default, read, expire_time, tag, retry) def read(self, key, version=None): """Return file handle corresponding to `key` from Cache. :param key: Python key to retrieve + :param int version: key version number (default None, cache parameter) :return: file open for reading in binary mode :raises KeyError: if key is not found @@ -70,6 +92,16 @@ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, """Set a value in the cache. If timeout is given, that timeout will be used for the key; otherwise the default cache timeout will be used. + :param key: key for item + :param value: value for item + :param float timeout: seconds until the item expires + (default 300 seconds) + :param int version: key version number (default None, cache parameter) + :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 expires (default True) + :return: True if item is added + """ # pylint: disable=arguments-differ key = self.make_key(key, version=version) @@ -78,14 +110,27 @@ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, def delete(self, key, version=None, retry=True): - "Delete a key from the cache, failing silently." + """Delete a key from the cache, failing silently. + + :param key: key for item + :param int version: key version number (default None, cache parameter) + :param bool retry: retry if database timeout expires (default True) + :return: True if item is deleted + + """ # pylint: disable=arguments-differ key = self.make_key(key, version=version) self._cache.delete(key, retry) def has_key(self, key, version=None): - "Returns True if the key is in the cache and has not expired." + """Returns True if the key is in the cache and has not expired. + + :param key: key for item + :param int version: key version number (default None, cache parameter) + :return: True if key is found + + """ key = self.make_key(key, version=version) return key in self._cache @@ -105,7 +150,8 @@ def close(self, **kwargs): def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): """Return seconds to expiration. - :param float timeout: seconds to expire (default `DEFAULT_TIMEOUT`) + :param float timeout: seconds until the item expires + (default 300 seconds) """ if timeout == DEFAULT_TIMEOUT: diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 9444a24..5b92683 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -47,7 +47,7 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): (default None, no expiry) :param bool read: read value as raw bytes from file (default False) :param str tag: text to associate with key (default None) - :param bool retry: retry if database timeout expires + :param bool retry: retry if database timeout expires (default False) :return: True if item is set """ @@ -56,14 +56,12 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): while True: try: - set_func(key, value, expire, read, tag) + return set_func(key, value, expire, read, tag) except Timeout: if retry: continue else: return False - else: - return True def __setitem__(self, key, value): @@ -78,11 +76,9 @@ def __setitem__(self, key, value): while True: try: - set_func(key, value) + return set_func(key, value) except Timeout: continue - else: - break def add(self, key, value, expire=None, read=False, tag=None, retry=False): @@ -102,46 +98,52 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): (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 expires - :return: True if item was successfully added + :param bool retry: retry if database timeout expires (default False) + :return: True if item is added """ index = hash(key) % self._count + add_func = self._shards[index].add while True: try: - return self._shards[index].add(key, value, expire, read, tag) + return add_func(key, value, expire, read, tag) except Timeout: if retry: continue else: return False - else: - return True - def get(self, key, default=None, read=False, expire_time=False, tag=False): + 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`. :param key: key for item - :param default: value to return if key is missing (default None) + :param default: return value if key is missing (default None) :param bool read: if True, return file handle to value (default False) :param float expire_time: if True, return expire_time in tuple (default False) :param tag: if True, return tag in tuple (default False) + :param bool retry: retry if database timeout expires (default False) :return: value for item if key is found else default """ index = hash(key) % self._count + get_func = self._shards[index].get - try: - return self._shards[index].get( - key, - default=default, read=read, expire_time=expire_time, tag=tag, - ) - except Timeout: - return default + while True: + try: + return get_func( + key, default=default, read=read, expire_time=expire_time, + tag=tag, + ) + except Timeout: + if retry: + continue + else: + return default def __getitem__(self, key): @@ -166,7 +168,7 @@ def read(self, key): :raises KeyError: if key is not found """ - handle = self.get(key, default=ENOVAL, read=True) + handle = self.get(key, default=ENOVAL, read=True, retry=True) if handle is ENOVAL: raise KeyError(key) return handle @@ -189,7 +191,7 @@ def delete(self, key, retry=False): Missing keys are ignored. :param key: key for item - :param bool retry: retry if database timeout expires + :param bool retry: retry if database timeout expires (default False) :return: True if item is deleted """ @@ -198,7 +200,7 @@ def delete(self, key, retry=False): while True: try: - del_func(key) + return del_func(key) except Timeout: if retry: continue @@ -206,8 +208,6 @@ def delete(self, key, retry=False): return False except KeyError: return False - else: - return True def __delitem__(self, key): @@ -222,11 +222,9 @@ def __delitem__(self, key): while True: try: - del_func(key) + return del_func(key) except Timeout: continue - else: - break def check(self, fix=False): From 6dff15bd72c2a00ee12c73ac6e7c4a82b88724c3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 24 Aug 2016 10:34:02 -0700 Subject: [PATCH 025/550] Add atomic incr and decr methods to Cache/FanoutCache/DjangoCache with 100% coverage --- diskcache/core.py | 129 ++++++++++++++++++++++++++++++++------- diskcache/djangocache.py | 59 ++++++++++++++++++ diskcache/fanout.py | 61 ++++++++++++++++++ tests/test_core.py | 47 ++++++++++++-- tests/test_fanout.py | 66 ++++++++++++++++++++ 5 files changed, 336 insertions(+), 26 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index cff7afc..e78b366 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -72,31 +72,23 @@ ' Cache (store_time)' ), 'get': None, - 'set': 'SELECT %s FROM Cache ORDER BY store_time LIMIT ?', + 'cull': 'SELECT %s FROM Cache ORDER BY store_time LIMIT ?', }, 'least-recently-used': { 'init': ( 'CREATE INDEX IF NOT EXISTS Cache_access_time ON' ' Cache (access_time)' ), - 'get': ( - 'UPDATE Cache SET' - ' access_time = ((julianday("now") - 2440587.5) * 86400.0)' - ' WHERE rowid = ?' - ), - 'set': 'SELECT %s FROM Cache ORDER BY access_time LIMIT ?', + 'get': 'access_time = ((julianday("now") - 2440587.5) * 86400.0)', + 'cull': 'SELECT %s FROM Cache ORDER BY access_time LIMIT ?', }, 'least-frequently-used': { 'init': ( 'CREATE INDEX IF NOT EXISTS Cache_access_count ON' ' Cache (access_count)' ), - 'get': ( - 'UPDATE Cache SET' - ' access_count = access_count + 1' - ' WHERE rowid = ?' - ), - 'set': 'SELECT %s FROM Cache ORDER BY access_count LIMIT ?', + 'get': 'access_count = access_count + 1', + 'cull': 'SELECT %s FROM Cache ORDER BY access_count LIMIT ?', }, } @@ -648,7 +640,7 @@ def _cull(self, now, sql, cleanup): # Evict keys by policy. - select_policy_template = EVICTION_POLICY[self.eviction_policy]['set'] + select_policy_template = EVICTION_POLICY[self.eviction_policy]['cull'] select_policy = select_policy_template % 'filename' rows = sql(select_policy, (cull_limit,)).fetchall() @@ -669,8 +661,8 @@ def add(self, key, value, expire=None, read=False, tag=None): Similar to `set`, but only add to cache if key not present. - This operation is atomic. Only one concurrent add operation for a given - key from separate threads or processes will succeed. + 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. @@ -715,22 +707,115 @@ def add(self, key, value, expire=None, read=False, tag=None): return True + def incr(self, key, delta=1, default=0): + """Increment value by delta for item with key. + + If key is missing and default is None then raise KeyError. Else if key + is missing and default is not None 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. + + :param key: key for item + :param int delta: amount to increment (default 1) + :param int default: value if key is missing (default None) + :return: new value for item + :raises KeyError: if key is not found and default is None + :raises Timeout: if database timeout expires + + """ + 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() as (sql, cleanup): + rows = sql(select, (db_key, raw)).fetchall() + + if not rows: + if default is None: + raise KeyError(key) + + value = default + delta + columns = (None, None) + self._disk.store(value, False) + self._row_insert(db_key, raw, now, columns) + self._cull(now, sql, cleanup) + return value + + (rowid, expire_time, filename, value), = rows + + if expire_time is not None and expire_time < now: + if default is None: + raise KeyError(key) + + value = default + delta + columns = (None, None) + self._disk.store(value, False) + self._row_update(rowid, now, columns) + self._cull(now, sql, cleanup) + cleanup(filename) + return value + + value += delta + + columns = 'store_time = ?, value = ?' + update_column = EVICTION_POLICY[self.eviction_policy]['get'] + columns += '' if update_column is None else ', ' + update_column + update = 'UPDATE Cache SET %s WHERE rowid = ?' % columns + sql(update, (now, value, rowid)) + + return value + + + def decr(self, key, delta=1, default=0): + """Decrement value by delta for item with key. + + If key is missing and default is None then raise KeyError. Else if key + is missing and default is not None 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. + + :param key: key for item + :param int delta: amount to decrement (default 1) + :param int default: value if key is missing (default 0) + :return: new value for item + :raises KeyError: if key is not found and default is None + :raises Timeout: if database timeout expires + + """ + return self.incr(key, -delta, default) + + def get(self, key, default=None, read=False, expire_time=False, tag=False): """Retrieve value from cache. If `key` is missing, return `default`. - :param key: Python key to retrieve + :param key: key for item :param default: value to return if key is missing (default None) :param bool read: if True, return file handle to value (default False) :param bool expire_time: if True, return expire_time in tuple (default False) :param bool tag: if True, return tag in tuple (default False) - :return: corresponding value or `default` if not found + :return: value for item or default if key not found :raises Timeout: if database timeout expires """ db_key, raw = self._disk.put(key) - policy_update = EVICTION_POLICY[self.eviction_policy]['get'] + update_column = EVICTION_POLICY[self.eviction_policy]['get'] + update = 'UPDATE Cache SET %s WHERE rowid = ?' select = ( 'SELECT rowid, expire_time, tag, mode, filename, value' ' FROM Cache WHERE key = ? AND raw = ?' @@ -741,7 +826,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): elif expire_time or tag: default = (default, None) - if not self.statistics and policy_update is None: + if not self.statistics and update_column is None: # Fast path, no transaction necessary. rows = self._sql(select, (db_key, raw)).fetchall() @@ -802,8 +887,8 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): if self.statistics: sql(cache_hit) - if policy_update is not None: - sql(policy_update, (rowid,)) + if update_column is not None: + sql(update % update_column, (rowid,)) if expire_time and tag: return (value, db_expire_time, db_tag) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 2c01e55..7cc4614 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -123,6 +123,65 @@ def delete(self, key, version=None, retry=True): self._cache.delete(key, retry) + def incr(self, key, delta=1, version=None, default=None, retry=True): + """Increment value by delta for item with key. + + If key is missing and default is None then raise KeyError. Else if key + is missing and default is not None 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. + + :param key: key for item + :param int delta: amount to increment (default 1) + :param int version: key version number (default None, cache parameter) + :param int default: value if key is missing (default None) + :param bool retry: retry if database timeout expires (default True) + :return: new value for item on success else None + :raises ValueError: if key is not found and default is None + + """ + # pylint: disable=arguments-differ + key = self.make_key(key, version=version) + try: + return self._cache.incr(key, delta, default, retry) + except KeyError: + raise ValueError("Key '%s' not found" % key) + + + def decr(self, key, delta=1, version=None, default=None, retry=True): + """Decrement value by delta for item with key. + + If key is missing and default is None then raise KeyError. Else if key + is missing and default is not None 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. + + :param key: key for item + :param int delta: amount to decrement (default 1) + :param int version: key version number (default None, cache parameter) + :param int default: value if key is missing (default None) + :param bool retry: retry if database timeout expires (default True) + :return: new value for item on success else None + :raises ValueError: if key is not found and default is None + + """ + # pylint: disable=arguments-differ + return self.incr(key, -delta, version, default, retry) + + def has_key(self, key, version=None): """Returns True if the key is in the cache and has not expired. diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 5b92683..cc950db 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -115,6 +115,67 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): return False + def incr(self, key, delta=1, default=0, retry=False): + """Increment value by delta for item with key. + + If key is missing and default is None then raise KeyError. Else if key + is missing and default is not None 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. + + :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 expires (default False) + :return: new value for item on success else None + :raises KeyError: if key is not found and default is None + + """ + index = hash(key) % self._count + incr_func = self._shards[index].incr + + while True: + try: + return incr_func(key, delta, default) + except Timeout: + if retry: + continue + else: + return None + + + def decr(self, key, delta=1, default=0, retry=False): + """Decrement value by delta for item with key. + + If key is missing and default is None then raise KeyError. Else if key + is missing and default is not None 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. + + :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 expires (default False) + :return: new value for item on success else None + :raises KeyError: if key is not found and default is None + + """ + 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`. diff --git a/tests/test_core.py b/tests/test_core.py index 3850f74..677317c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -745,6 +745,7 @@ def test_add(cache): assert cache.add(1, 1) cache.check() + @setup_cache def test_add_large_value(cache): value = b'abcd' * 2 ** 12 @@ -754,10 +755,8 @@ def test_add_large_value(cache): assert cache.get(b'test-key') == value cache.check() -results = co.deque() - -def stress_add(cache, limit): +def stress_add(cache, limit, results): total = 0 for num in range(limit): if cache.add(num, num): @@ -769,10 +768,11 @@ def stress_add(cache, limit): @setup_cache def test_add_concurrent(cache): + results = co.deque() limit = 1000 threads = [ - threading.Thread(target=stress_add, args=(cache, limit)) + threading.Thread(target=stress_add, args=(cache, limit, results)) for _ in range(16) ] @@ -804,6 +804,45 @@ def test_add_timeout(cache): cache.check() +@setup_cache +def test_incr(cache): + assert cache.incr('key', default=5) == 6 + assert cache.incr('key', 2) == 8 + assert cache.get('key', expire_time=True, tag=True) == (8, None, None) + assert cache.delete('key') + assert cache.set('key', 100, expire=0.100) + assert cache.get('key') == 100 + time.sleep(0.120) + assert cache.incr('key') == 1 + + +@setup_cache +@nt.raises(KeyError) +def test_incr_insert_keyerror(cache): + cache.incr('key', default=None) + + +@setup_cache +@nt.raises(KeyError) +def test_incr_update_keyerror(cache): + assert cache.set('key', 100, expire=0.100) + assert cache.get('key') == 100 + time.sleep(0.120) + cache.incr('key', default=None) + + +@setup_cache +def test_decr(cache): + assert cache.decr('key', default=5) == 4 + assert cache.decr('key', 2) == 2 + assert cache.get('key', expire_time=True, tag=True) == (2, None, None) + assert cache.delete('key') + assert cache.set('key', 100, expire=0.100) + assert cache.get('key') == 100 + time.sleep(0.120) + assert cache.decr('key') == -1 + + @setup_cache def test_iter(cache): sequence = list('abcdef') + [('g',)] diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 1196c01..3f397da 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -153,6 +153,72 @@ def test_add_timeout_retry(cache): assert cache.add(0, 0, retry=True) +@setup_cache +def test_incr(cache): + cache.incr('key', delta=3) == 3 + + +@setup_cache +def test_incr_timeout(cache): + shards = mock.Mock() + shard = mock.Mock() + incr_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.incr = incr_func + incr_func.side_effect = dc.Timeout + + with mock.patch.object(cache, '_shards', shards): + assert cache.incr('key', 1) is None + + +@setup_cache +def test_incr_timeout_retry(cache): + shards = mock.Mock() + shard = mock.Mock() + incr_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.incr = incr_func + incr_func.side_effect = [dc.Timeout, 1] + + with mock.patch.object(cache, '_shards', shards): + assert cache.incr('key', retry=True) == 1 + + +@setup_cache +def test_decr(cache): + cache.decr('key', delta=2) == -2 + + +def stress_incr(cache, limit): + for _ in range(limit): + cache.incr(b'key', retry=True) + time.sleep(0.001) + + +def test_incr_concurrent(): + count = 16 + limit = 500 + + with dc.FanoutCache('tmp', timeout=0.001) as cache: + threads = [ + threading.Thread(target=stress_incr, args=(cache, limit)) + for _ in range(count) + ] + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + + assert cache.get(b'key') == count * limit + cache.check() + + shutil.rmtree('tmp', ignore_errors=True) + + @setup_cache def test_get_timeout(cache): cache.set(0, 0) From 0d08c1df07d5ce55c9c40bd88b9ee9f924ddab22 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 25 Aug 2016 10:43:20 -0700 Subject: [PATCH 026/550] Update sphinx, small changes after review. --- docs/api.rst | 19 ++++--------------- docs/conf.py | 1 + docs/development.rst | 3 +-- docs/index.rst | 8 ++++---- docs/tutorial.rst | 14 ++++++++++++++ 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index c8bac71..1e2181b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3,6 +3,9 @@ DiskCache API Reference The :doc:`tutorial` provides a helpful walkthrough of most methods. +.. contents:: + :local: + DjangoCache ----------- @@ -40,6 +43,7 @@ Constants .. data:: diskcache.DEFAULT_SETTINGS * `statistics` (int) default 0 - disabled when 0, enabled when 1. + * `tag_index` (int) default 0 - disabled when 0, enabled when 1. * `eviction_policy` (str) default "least-recently-stored" - any of the keys in `EVICTION_POLICY` as described below. * `size_limit` (int) default one gigabyte - approximate size limit of cache. @@ -66,18 +70,3 @@ Constants * `least-recently-stored` (default) - evict least recently stored keys first. * `least-recently-used` - evict least recently retrieved keys first. * `least-frequently-used` - evict least frequently used keys first. - -Implementation Notes --------------------- - -:doc:`DiskCache ` is mostly built on SQLite and the filesystem. Some -techniques used to improve performance: - -* Shard database to distribute writes. -* Leverage SQLite native types: integers, floats, unicode, and bytes. -* Use SQLite write-ahead-log so reads and writes don't block each other. -* Use SQLite memory-mapped pages to accelerate reads. -* Store small values in SQLite database and large values in files. -* Always use a SQLite index for queries. -* Keep SQLite transactions short. -* Use SQLite triggers to maintain count of keys and size of database. diff --git a/docs/conf.py b/docs/conf.py index 864c0f2..14fd923 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -127,6 +127,7 @@ 'show_related': True, 'github_user': 'grantjenks', 'github_repo': 'python-diskcache', + 'github_type': 'star', } # Add any paths that contain custom themes here, relative to this directory. diff --git a/docs/development.rst b/docs/development.rst index eb62fea..654e1d3 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -20,7 +20,6 @@ Requests for Contributions #. Command-line interface. Operations to support: get, set, store, delete, expire, evict, clear, path, check, stats. -#. Atomic increment and decrement methods. #. Django admin interface for cache stats and interaction. #. Cache stampede barrier (source prototype in repo). #. API Compatibility @@ -136,7 +135,7 @@ Coverage testing uses `nose `_: OK -It's normal not to see 100% coverage. Some code is specific to the Python +It's normal to not see 100% coverage. Some code is specific to the Python runtime. Stress testing is also based on nose but can be run independently as a diff --git a/docs/index.rst b/docs/index.rst index ae22c09..79cbb02 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -37,10 +37,10 @@ In Python, we can do better. And we can do it in pure-Python! measurements. DiskCache offers cache benchmarks to defend its performance claims. Micro-optimizations are avoided but your mileage may vary. -DiskCache efficiently opens up gigabytes of storage space for caching. By -leveraging rock-solid database libraries and memory-mapped files, cache -performance can match and exceed industry standard solutions. There's no need -for a C compiler or running another process. Performance is a feature and +DiskCache efficiently makes gigabytes of storage space available for +caching. By leveraging rock-solid database libraries and memory-mapped files, +cache performance can match and exceed industry-standard solutions. There's no +need for a C compiler or running another process. Performance is a feature and testing has 100% coverage with unit tests and hours of stress. Testimonials diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 85f43c7..9e8ef1f 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -319,3 +319,17 @@ integers, floats, strings, and bytes. Other datatypes are converted to bytes via the pickle protocol. Beware that integers and floats like ``1`` and ``1.0`` will compare equal as keys just as in Python. All other equality comparisons will require identical types. + +Implementation Notes +-------------------- + +:doc:`DiskCache ` is mostly built on SQLite and the filesystem. Some +techniques used to improve performance: + +* Shard database to distribute writes. +* Leverage SQLite native types: integers, floats, unicode, and bytes. +* Use SQLite write-ahead-log so reads and writes don't block each other. +* Use SQLite memory-mapped pages to accelerate reads. +* Store small values in SQLite database and large values in files. +* Always use a SQLite index for queries. +* Use SQLite triggers to maintain key count and database size. From 0937a71129e571c11e8947bd9990c8c137b31ee1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 25 Aug 2016 10:46:18 -0700 Subject: [PATCH 027/550] Couple more typo updates --- README.rst | 8 ++++---- docs/index.rst | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index ebc8fd2..0f2971f 100644 --- a/README.rst +++ b/README.rst @@ -37,10 +37,10 @@ In Python, we can do better. And we can do it in pure-Python! measurements. DiskCache offers cache benchmarks to defend its performance claims. Micro-optimizations are avoided but your mileage may vary. -DiskCache efficiently opens up gigabytes of storage space for caching. By -leveraging rock-solid database libraries and memory-mapped files, cache -performance can match and exceed industry standard solutions. There's no need -for a C compiler or running another process. Performance is a feature and +DiskCache efficiently makes gigabytes of storage space available for +caching. By leveraging rock-solid database libraries and memory-mapped files, +cache performance can match and exceed industry-standard solutions. There's no +need for a C compiler or running another process. Performance is a feature and testing has 100% coverage with unit tests and hours of stress. Testimonials diff --git a/docs/index.rst b/docs/index.rst index 79cbb02..7ad0ee7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -122,12 +122,11 @@ The MIT, BSD, ISC, and Apache2 licenses are great alternatives to the GPL that allow your open-source software to be used freely in proprietary, closed-source software. -SortedContainers is released under terms of the `Apache2 License`_. +DiskCache is released under terms of the `Apache2 License`_. .. _`GPL Licensed`: http://www.opensource.org/licenses/gpl-license.php .. _`Apache2 License`: http://opensource.org/licenses/Apache-2.0 - DiskCache License ----------------- From bfab19a51c359c34bb4df5e55645552aed34e003 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 26 Aug 2016 12:06:48 -0700 Subject: [PATCH 028/550] Remove cull_limit use from evict, expire, clear, iter, and reversed --- diskcache/core.py | 44 +++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index e78b366..c3eca04 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1152,8 +1152,7 @@ def evict(self, tag): """Remove items with matching `tag` from cache. Removing items is an iterative process. In each iteration, a subset of - items is removed. The subset size is equal to the `cull_limit` - attribute. + items is removed. Concurrent writes may occur between iterations. If a :exc:`Timeout` occurs, the first element of the exception's `args` attribute will be the number of items removed before the @@ -1165,21 +1164,19 @@ def evict(self, tag): """ select = ( - 'SELECT %s FROM Cache' + 'SELECT rowid, filename FROM Cache' ' WHERE tag = ? AND rowid > ?' ' ORDER BY rowid LIMIT ?' ) - fields = 'rowid, filename' - args = [tag, 0, self.cull_limit] - return self._select_delete(select, fields, args, index=1) + args = [tag, 0, 100] + return self._select_delete(select, args, arg_index=1) def expire(self, now=None): """Remove expired items from cache. Removing items is an iterative process. In each iteration, a subset of - items is removed. The subset size is equal to the `cull_limit` - attribute. + items is removed. Concurrent writes may occur between iterations. If a :exc:`Timeout` occurs, the first element of the exception's `args` attribute will be the number of items removed before the @@ -1191,21 +1188,19 @@ def expire(self, now=None): """ select = ( - 'SELECT %s FROM Cache' + 'SELECT rowid, expire_time, filename FROM Cache' ' WHERE ? < expire_time AND expire_time < ?' ' ORDER BY expire_time LIMIT ?' ) - fields = 'expire_time, filename' - args = [0, now or time.time(), self.cull_limit] - return self._select_delete(select, fields, args) + args = [0, now or time.time(), 100] + return self._select_delete(select, args, row_index=1) def clear(self): """Remove all items from cache. Removing items is an iterative process. In each iteration, a subset of - items is removed. The subset size is equal to the `cull_limit` - attribute. + items is removed. Concurrent writes may occur between iterations. If a :exc:`Timeout` occurs, the first element of the exception's `args` attribute will be the number of items removed before the @@ -1216,22 +1211,17 @@ def clear(self): """ select = ( - 'SELECT %s FROM Cache' + 'SELECT rowid, filename FROM Cache' ' WHERE rowid > ?' ' ORDER BY rowid LIMIT ?' ) - fields = 'rowid, filename' - args = [0, self.cull_limit] - return self._select_delete(select, fields, args) + args = [0, 100] + return self._select_delete(select, args) - def _select_delete(self, select_template, fields, args, index=0): + def _select_delete(self, select, args, row_index=0, arg_index=0): count = 0 - select = select_template % fields - delete = ( - 'DELETE FROM Cache WHERE rowid IN (%s)' - % (select_template % 'rowid') - ) + delete = 'DELETE FROM Cache WHERE rowid IN (%s)' try: while True: @@ -1242,10 +1232,10 @@ def _select_delete(self, select_template, fields, args, index=0): break count += len(rows) - sql(delete, args) + sql(delete % ','.join(str(row[0]) for row in rows)) for row in rows: - args[index] = row[0] + args[arg_index] = row[row_index] cleanup(row[-1]) except Timeout: @@ -1263,7 +1253,7 @@ def _iter(self, ascending=True): return bound = max_rowid + 1 - limit = self.cull_limit + limit = 100 _disk_get = self._disk.get rowid = 0 if ascending else bound select = ( From 93380fd3e58a25696e2e795461f7e1b9755fbac9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 26 Aug 2016 12:08:04 -0700 Subject: [PATCH 029/550] Add test_iter_expire; iteration and length includes expired keys --- tests/test_core.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 677317c..5bdf1f2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -844,6 +844,7 @@ def test_decr(cache): @setup_cache +@nt.raises(StopIteration) def test_iter(cache): sequence = list('abcdef') + [('g',)] @@ -856,12 +857,16 @@ def test_iter(cache): cache['h'] = 7 - try: - next(iterator) - except StopIteration: - pass - else: - assert False, 'StopIteration expected' + next(iterator) + + +@setup_cache +def test_iter_expire(cache): + cache.cull_limit = 0 + for num in range(100): + cache.set(num, num, expire=0) + assert len(cache) == 100 + assert list(cache) == list(range(100)) @setup_cache From 8150cde55b070e20f7e36f6454cd7ba94c356c01 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 30 Aug 2016 12:25:41 -0700 Subject: [PATCH 030/550] Add iter and reversed for FanoutCache --- diskcache/core.py | 9 +++++++-- diskcache/fanout.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index c3eca04..c75a395 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1248,6 +1248,7 @@ def _iter(self, ascending=True): sql = self._sql rows = sql('SELECT MAX(rowid) FROM Cache').fetchall() (max_rowid,), = rows + yield # Signal ready. if max_rowid is None: return @@ -1278,11 +1279,15 @@ def _iter(self, ascending=True): def __iter__(self): - return self._iter() + iterator = self._iter() + iterator.next() + return iterator def __reversed__(self): - return self._iter(ascending=False) + iterator = self._iter(ascending=False) + iterator.next() + return iterator def stats(self, enable=True, reset=False): diff --git a/diskcache/fanout.py b/diskcache/fanout.py index cc950db..717b257 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -1,5 +1,6 @@ "Fanout cache automatically shards keys and values." +import itertools as it import os.path as op import time @@ -409,6 +410,16 @@ def __exit__(self, *exception): self.close() + def __iter__(self): + iterators = [iter(shard) for shard in self._shards] + return it.chain.from_iterable(iterators) + + + def __reversed__(self): + iterators = [reversed(shard) for shard in self._shards] + return it.chain.from_iterable(reversed(iterators)) + + def __len__(self): return sum(len(shard) for shard in self._shards) From 3737de99c3382c6e5c505fa2980f85a3b562b1d6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 30 Aug 2016 12:26:29 -0700 Subject: [PATCH 031/550] BugFix: Use reset rather than setattr for metadata initialization --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index c75a395..37e89da 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -367,7 +367,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): for key, value in METADATA.items(): query = 'INSERT OR IGNORE INTO Settings VALUES (?, ?)' sql(query, (key, value)) - setattr(self, key, value) + self.reset(key) (self._page_size,), = sql('PRAGMA page_size').fetchall() From 1699f6bccb6241e5614b98fac8c24e68f5978e69 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 30 Aug 2016 12:27:10 -0700 Subject: [PATCH 032/550] Test iterating FanoutCache --- tests/test_fanout.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 3f397da..2112f63 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -426,6 +426,37 @@ def test_volume(cache): assert volume == cache.volume() +@setup_cache +def test_iter(cache): + for num in range(100): + cache[num] = num + assert set(cache) == set(range(100)) + + +@setup_cache +def test_iter_expire(cache): + """Test iteration with expiration. + + Iteration does not expire keys. + + """ + cache.reset('cull_limit', 0) + for num in range(100): + cache.set(num, num, expire=0) + time.sleep(0.1) + assert set(cache) == set(range(100)) + cache.expire() + assert set(cache) == set() + + +@setup_cache +def test_reversed(cache): + for num in range(100): + cache[num] = num + reverse = list(reversed(cache)) + assert list(cache) == list(reversed(reverse)) + + if __name__ == '__main__': import nose nose.runmodule() From 59df1a3a65d974b1d73322b90e5a09b8c759af0a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 30 Aug 2016 12:28:00 -0700 Subject: [PATCH 033/550] Update tests to use reset method rather than setattr --- tests/test_core.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 5bdf1f2..032073d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -185,7 +185,7 @@ def test_get_keyerror1(cache): def test_get_keyerror4(cache): func = mock.Mock(side_effect=IOError(errno.ENOENT, '')) - cache.statistics = True + cache.reset('statistics', True) cache[0] = b'abcd' * 2 ** 12 with mock.patch('io.open', func): @@ -403,7 +403,7 @@ def test_path(cache): @setup_cache def test_expire_rows(cache): - cache.cull_limit = 0 + cache.reset('cull_limit', 0) for value in range(10): assert cache.set(value, value, expire=0) @@ -413,7 +413,7 @@ def test_expire_rows(cache): assert len(cache) == 15 - cache.cull_limit = 10 + cache.reset('cull_limit', 10) assert cache.set(15, 15) @@ -423,9 +423,9 @@ def test_expire_rows(cache): @setup_cache def test_least_recently_stored(cache): - cache.eviction_policy = u'least-recently-stored' - cache.size_limit = int(10.1e6) - cache.cull_limit = 2 + cache.reset('eviction_policy', u'least-recently-stored') + cache.reset('size_limit', int(10.1e6)) + cache.reset('cull_limit', 2) million = b'x' * int(1e6) @@ -459,9 +459,9 @@ def test_least_recently_stored(cache): @setup_cache def test_least_recently_used(cache): - cache.eviction_policy = u'least-recently-used' - cache.size_limit = int(10.1e6) - cache.cull_limit = 5 + cache.reset('eviction_policy', u'least-recently-used') + cache.reset('size_limit', int(10.1e6)) + cache.reset('cull_limit', 5) million = b'x' * int(1e6) @@ -488,9 +488,9 @@ def test_least_recently_used(cache): @setup_cache def test_least_frequently_used(cache): - cache.eviction_policy = u'least-frequently-used' - cache.size_limit = int(10.1e6) - cache.cull_limit = 5 + cache.reset('eviction_policy', u'least-frequently-used') + cache.reset('size_limit', int(10.1e6)) + cache.reset('cull_limit', 5) million = b'x' * int(1e6) @@ -589,7 +589,7 @@ def test_integrity_check(cache): @setup_cache def test_expire(cache): - cache.cull_limit = 0 # Disable expiring keys on `set`. + cache.reset('cull_limit', 0) # Disable expiring keys on `set`. now = time.time() time_time = mock.Mock(return_value=now) @@ -600,7 +600,7 @@ def test_expire(cache): assert len(cache) == 100 time_time = mock.Mock(return_value=now + 10) - cache.cull_limit = 10 + cache.reset('cull_limit', 10) with mock.patch('time.time', time_time): assert cache.expire() == 10 @@ -862,7 +862,7 @@ def test_iter(cache): @setup_cache def test_iter_expire(cache): - cache.cull_limit = 0 + cache.reset('cull_limit', 0) for num in range(100): cache.set(num, num, expire=0) assert len(cache) == 100 From b7c8f32626e411927367265f446e85254b9cd376 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 6 Sep 2016 10:29:29 -0700 Subject: [PATCH 034/550] Add evict, create_tag_index, drop_tag_index, and expire methods to DjangoCache --- diskcache/djangocache.py | 41 ++++++++++++++++++++++++++++++++++++++- tests/test_djangocache.py | 19 ++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 7cc4614..934dfbe 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -194,10 +194,49 @@ def has_key(self, key, version=None): return key in self._cache + def expire(self): + """Remove expired items from cache. + + :return: count of items removed + + """ + return self._cache.expire() + + + def create_tag_index(self): + """Create tag index on cache database. + + It is better to initialize cache with `tag_index=True` than use this. + + :raises Timeout: if database timeout expires + + """ + self._cache.create_tag_index() + + + def drop_tag_index(self): + """Drop tag index on cache database. + + :raises Timeout: if database timeout expires + + """ + self._cache.drop_tag_index() + + + def evict(self, tag): + """Remove items with matching `tag` from cache. + + :param str tag: tag identifying items + :return: count of items removed + + """ + return self._cache.evict(tag) + + def clear(self, **kwargs): "Remove *all* values from the cache at once." # pylint: disable=unused-argument - self._cache.clear() + return self._cache.clear() def close(self, **kwargs): diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 186f57d..3c4fe2d 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -875,3 +875,22 @@ def test_read(self): error = True self.assertTrue(error) + + def test_expire(self): + cache.clear() + cache.set(b'expire-key', 0, timeout=0.05) + time.sleep(0.1) + self.assertEqual(cache.expire(), 1) + self.assertEqual(cache.get(b'expire-key'), None) + + def test_evict(self): + cache.clear() + for num in range(100): + cache.set(num, num, tag=(num % 4)) + self.assertEqual(cache.evict(1), 25) + cache.create_tag_index() + self.assertEqual(cache.evict(2), 25) + cache.drop_tag_index() + self.assertEqual(cache.evict(3), 25) + for num in range(0, 100, 4): + self.assertEqual(cache.get(num), num) From 6e303e4057591c3c9163b1459de125775febd753 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 6 Sep 2016 10:30:06 -0700 Subject: [PATCH 035/550] Small changes for code style --- diskcache/__init__.py | 2 +- diskcache/core.py | 7 ++++--- diskcache/fanout.py | 6 +++--- diskcache/stampede.py | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index ce269b7..d470abd 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -6,7 +6,7 @@ try: from .djangocache import DjangoCache -except Exception: # pylint: disable=broad-except +except Exception: # pylint: disable=broad-except # Django not installed or not setup so ignore. pass diff --git a/diskcache/core.py b/diskcache/core.py index 37e89da..e256742 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -848,7 +848,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): else: raise - else: # Slow path, transaction required. + else: # Slow path, transaction required. cache_hit = ( 'UPDATE Settings SET value = value + 1 WHERE key = "hits"' @@ -1055,7 +1055,8 @@ def check(self, fix=False): warnings.warn(message % args) if fix: - sql('UPDATE Cache SET size = ? WHERE rowid = ?', + sql('UPDATE Cache SET size = ?' + ' WHERE rowid = ?', (real_size, rowid), ) @@ -1127,7 +1128,7 @@ def check(self, fix=False): def create_tag_index(self): """Create tag index on cache database. - It's better to initialized cache with `tag_index=True`. + It is better to initialize cache with `tag_index=True` than use this. :raises Timeout: if database timeout expires diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 717b257..ff41f6e 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -314,13 +314,13 @@ def expire(self): :return: count of items removed """ - return self._remove('expire', (time.time(),)) + return self._remove('expire', args=(time.time(),)) def create_tag_index(self): """Create tag index on cache database. - It's better to initialized cache with `tag_index=True`. + It is better to initialize cache with `tag_index=True` than use this. :raises Timeout: if database timeout expires @@ -346,7 +346,7 @@ def evict(self, tag): :return: count of items removed """ - return self._remove('evict', (tag,)) + return self._remove('evict', args=(tag,)) def clear(self): diff --git a/diskcache/stampede.py b/diskcache/stampede.py index 1b669a3..0c501f7 100644 --- a/diskcache/stampede.py +++ b/diskcache/stampede.py @@ -43,7 +43,7 @@ def __call__(self, func): cache = self._cache expire = self._expire - ft.wraps(func) + @ft.wraps(func) def wrapper(*args, **kwargs): "Wrapper function to cache function result." key = (args, kwargs) From 9b5b1363b979906413093124f2f41d62b438cc93 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Sep 2016 09:18:25 -0700 Subject: [PATCH 036/550] Small improvements to docstrings --- diskcache/core.py | 6 +++--- diskcache/djangocache.py | 6 +++--- diskcache/fanout.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index e256742..ae7912b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -500,7 +500,7 @@ def set(self, key, value, expire=None, read=False, tag=None): (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) - :return: True if item was successfully set + :return: True if item was set :raises Timeout: if database timeout expires """ @@ -673,7 +673,7 @@ def add(self, key, value, expire=None, read=False, tag=None): (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) - :return: True if item was successfully added + :return: True if item was added :raises Timeout: if database timeout expires """ @@ -989,7 +989,7 @@ def delete(self, key): Missing keys are ignored. :param key: key matching item - :return: True if item was deleted else False + :return: True if item was deleted :raises Timeout: if database timeout expires """ diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 934dfbe..75c5d9a 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -43,7 +43,7 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, :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 expires (default True) - :return: True if item is added + :return: True if item was added """ # pylint: disable=arguments-differ @@ -100,7 +100,7 @@ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, :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 expires (default True) - :return: True if item is added + :return: True if item was set """ # pylint: disable=arguments-differ @@ -115,7 +115,7 @@ def delete(self, key, version=None, retry=True): :param key: key for item :param int version: key version number (default None, cache parameter) :param bool retry: retry if database timeout expires (default True) - :return: True if item is deleted + :return: True if item was deleted """ # pylint: disable=arguments-differ diff --git a/diskcache/fanout.py b/diskcache/fanout.py index ff41f6e..019854e 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -49,7 +49,7 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): :param bool read: read value as raw bytes from file (default False) :param str tag: text to associate with key (default None) :param bool retry: retry if database timeout expires (default False) - :return: True if item is set + :return: True if item was set """ index = hash(key) % self._count @@ -100,7 +100,7 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): :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 expires (default False) - :return: True if item is added + :return: True if item was added """ index = hash(key) % self._count @@ -254,7 +254,7 @@ def delete(self, key, retry=False): :param key: key for item :param bool retry: retry if database timeout expires (default False) - :return: True if item is deleted + :return: True if item was deleted """ index = hash(key) % self._count From cab7780df2807c1974e0b38ac85c594aa64d7ad5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Sep 2016 09:18:59 -0700 Subject: [PATCH 037/550] Add email link under Testimonials --- README.rst | 4 ++-- docs/index.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 0f2971f..a2281c5 100644 --- a/README.rst +++ b/README.rst @@ -46,8 +46,8 @@ testing has 100% coverage with unit tests and hours of stress. Testimonials ------------ -Does your company or website use `DiskCache`_? Send us a message and let us -know. +Does your company or website use `DiskCache`_? Send us a `message +`_ and let us know. Features -------- diff --git a/docs/index.rst b/docs/index.rst index 7ad0ee7..10f840b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -46,8 +46,8 @@ testing has 100% coverage with unit tests and hours of stress. Testimonials ------------ -Does your company or website use `DiskCache`_? Send us a message and let us -know. +Does your company or website use `DiskCache`_? Send us a `message +`_ and let us know. Features -------- From 46173bcd9d22d63d47f9f6e83b9a56c2593dcaee Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Sep 2016 10:12:09 -0700 Subject: [PATCH 038/550] Change FanoutCache to use size_limit / shards --- diskcache/fanout.py | 5 ++++- tests/test_fanout.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 019854e..96de1b2 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -4,7 +4,7 @@ import os.path as op import time -from .core import Cache, Disk, ENOVAL, Timeout +from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout class FanoutCache(object): @@ -21,11 +21,14 @@ def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, """ self._count = shards + default_size_limit = DEFAULT_SETTINGS['size_limit'] + size_limit = settings.pop('size_limit', default_size_limit) / shards self._shards = tuple( Cache( op.join(directory, '%03d' % num), timeout=timeout, disk=disk, + size_limit=size_limit, **settings ) for num in range(shards) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 2112f63..fdec731 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -39,8 +39,11 @@ def wrapper(): @setup_cache def test_init(cache): - for key, value in dc.DEFAULT_SETTINGS.items(): + default_settings = dc.DEFAULT_SETTINGS.copy() + del default_settings['size_limit'] + for key, value in default_settings.items(): assert getattr(cache, key) == value + assert cache.size_limit == 2 ** 27 cache.check() From fab6e7f72ccf280e68bdef22de3dc040587fd940 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Sep 2016 10:12:39 -0700 Subject: [PATCH 039/550] Add docstring on iter, reversed, and len dunder methods for api docs --- diskcache/core.py | 3 +++ diskcache/fanout.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index ae7912b..9b8c2cf 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1280,12 +1280,14 @@ def _iter(self, ascending=True): def __iter__(self): + "Iterate keys in cache including expired items." iterator = self._iter() iterator.next() return iterator def __reversed__(self): + "Reverse iterate keys in cache including expired items." iterator = self._iter(ascending=False) iterator.next() return iterator @@ -1348,6 +1350,7 @@ def __exit__(self, *exception): def __len__(self): + "Count of items in cache including expired items." return self.reset('count') diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 96de1b2..fe114f8 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -414,16 +414,19 @@ def __exit__(self, *exception): def __iter__(self): + "Iterate keys in cache including expired items." iterators = [iter(shard) for shard in self._shards] return it.chain.from_iterable(iterators) def __reversed__(self): + "Reverse iterate keys in cache including expired items." iterators = [reversed(shard) for shard in self._shards] return it.chain.from_iterable(reversed(iterators)) def __len__(self): + "Count of items in cache including expired items." return sum(len(shard) for shard in self._shards) From e773ce82f3ad32d2da74f29ed479534169b9101e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 8 Sep 2016 12:06:18 -0700 Subject: [PATCH 040/550] Update docs for v2 --- docs/api.rst | 37 ++++-- docs/tutorial.rst | 286 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 247 insertions(+), 76 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 1e2181b..0211232 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -9,6 +9,8 @@ The :doc:`tutorial` provides a helpful walkthrough of most methods. DjangoCache ----------- +Read the :ref:`DjangoCache tutorial ` for example usage. + .. autoclass:: diskcache.DjangoCache :members: :special-members: @@ -16,6 +18,8 @@ DjangoCache FanoutCache ----------- +Read the :ref:`FanoutCache tutorial ` for example usage. + .. autoclass:: diskcache.FanoutCache :members: :special-members: @@ -24,15 +28,9 @@ FanoutCache Cache ----- -.. autoclass:: diskcache.Cache - :members: - :special-members: - :exclude-members: __weakref__ - -Disk ----- +Read the :ref:`Cache tutorial ` for example usage. -.. autoclass:: diskcache.Disk +.. autoclass:: diskcache.Cache :members: :special-members: :exclude-members: __weakref__ @@ -40,6 +38,8 @@ Disk Constants --------- +Read the :ref:`Settings tutorial ` for details. + .. data:: diskcache.DEFAULT_SETTINGS * `statistics` (int) default 0 - disabled when 0, enabled when 1. @@ -47,8 +47,8 @@ Constants * `eviction_policy` (str) default "least-recently-stored" - any of the keys in `EVICTION_POLICY` as described below. * `size_limit` (int) default one gigabyte - approximate size limit of cache. - * `cull_limit` (int) default ten - maximum number of keys culled during - `set` operation. + * `cull_limit` (int) default ten - maximum number of items culled during + `set` or `add` operations. * `large_value_threshold` (int) default one kilobyte - values with greater size are stored in files. * `sqlite_synchronous` (str) default "NORMAL" - SQLite synchronous pragma. @@ -68,5 +68,20 @@ Constants .. data:: diskcache.EVICTION_POLICY * `least-recently-stored` (default) - evict least recently stored keys first. - * `least-recently-used` - evict least recently retrieved keys first. + * `least-recently-used` - evict least recently used keys first. * `least-frequently-used` - evict least frequently used keys first. + +Disk +---- + +Read the :ref:`Disk tutorial ` for details. + +.. autoclass:: diskcache.Disk + :members: + :special-members: + :exclude-members: __weakref__ + +Timeout +------- + +.. autoexception:: diskcache.Timeout diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 9e8ef1f..73a8190 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -1,34 +1,39 @@ DiskCache Tutorial ================== +.. contents:: + :depth: 1 + :local: + Installation ------------ This part of the documentation covers the installation of :doc:`DiskCache -`. The first step to using any software package is getting it properly +`. The first step to using any software package is getting it properly installed. Pip & PyPI .......... Installing :doc:`DiskCache ` is simple with `pip -`_:: +`_:: $ pip install diskcache -or, with `easy_install `_:: +or, with `easy_install `_:: $ easy_install diskcache -But, you really `shouldn't do that `_. +But `prefer pip `_ if at all +possible. Get the Code ............ :doc:`DiskCache ` is actively developed on GitHub, where the code is -`always available `_. +always available. -You can either clone the public repository:: +You can either clone the `DiskCache repository `_:: $ git clone git://github.com/grantjenks/python-diskcache.git @@ -45,11 +50,15 @@ or install it into your site-packages easily:: $ python setup.py install -:doc:`DiskCache ` is looking for a Debian package maintainer. Can you -help? +:doc:`DiskCache ` is looking for a Debian package maintainer. If you can +help, please open an issue in the `DiskCache Issue Tracker +`_. + +:doc:`DiskCache ` is looking for a CentOS/RPM package maintainer. If +you can help, please open an issue in the `DiskCache Issue Tracker +`_. -:doc:`DiskCache ` is looking for a CentOS/RPM package maintainer. Can -you help? +.. _tutorial-cache: Cache ----- @@ -59,7 +68,7 @@ represents a disk and file backed cache. As a Cache it supports a familiar Python Mapping interface with additional cache and performance parameters. >>> from diskcache import Cache - >>> cache = Cache('mycachedir') + >>> cache = Cache('/tmp/mycachedir') Initialization requires a directory path reference. If the directory path does not exist, it will be created. Additional keyword parameters are discussed @@ -71,16 +80,16 @@ communication. When created, Cache objects open and maintain a file handle. As such, they may not be pickled and do not survive process forking. Each thread that accesses a cache is also responsible for calling :meth:`close ` on -the cache if used. You can use a Cache reference in a `with` statement to -safeguard calling :meth:`close `. +the cache. You can use a Cache reference in a `with` statement to safeguard +calling :meth:`close `. >>> cache.close() - >>> with Cache('mycachedir') as reference: + >>> with Cache('/tmp/mycachedir') as reference: ... pass Set an item, get a value, and delete a key using the usual operators: - >>> cache = Cache('mycachedir') + >>> cache = Cache('/tmp/mycachedir') >>> cache[b'key'] = b'value' >>> cache[b'key'] 'value' @@ -102,7 +111,7 @@ file-like object, and tag metadata is stored with the key. Another method, >>> cache.get(b'key', default=b'', read=True, expire_time=True, tag=True) (<_io.BufferedReader - name=u'mycachedir/1d/6e/128a921c3b8a9027c1f69989f3ac.val'>, + name=u'/tmp/mycachedir/1d/6e/128a921c3b8a9027c1f69989f3ac.val'>, 1457066214.784396, u'data') @@ -110,30 +119,97 @@ The return value is a tuple containing the value, expire time (seconds from epoch), and tag. Because we passed ``read=True`` the value is returned as a file-like object. +Like :meth:`set `, the method :meth:`add +` can be used to insert an item in the cache. The item is +inserted only if the key is not already present. + + >>> cache.add(b'test', 123) + True + >>> cache[b'test'] + 123 + >>> cache.add(b'test', 456) + False + >>> cache[b'test'] + 123 + +Item values can also be incremented and decremented using :meth:`incr +` and :meth:`decr ` methods. + + >>> cache.incr(b'test') + 124 + >>> cache.decr(b'test', 24) + 100 + +Increment and decrement methods also support a keyword parameter, `default`, +which will be used for missing keys. When ``None``, incrementing or +decrementing a missing key will raise a :exc:`KeyError`. + + >>> cache.incr(u'alice') + 1 + >>> cache.decr(u'bob', default=-9) + -10 + >>> cache.incr(u'carol', default=None) + Traceback (most recent call last): + ... + KeyError: u'carol' + +Increment and decrement operations are atomic and assume the value may be +stored in a SQLite column. Most builds that target machines with 64-bit pointer +widths will support 64-bit signed integers. + Another three methods remove items from the cache. - >>> cache.cull_limit = 0 # Disable evictions. - >>> for num in range(100): - ... cache.set(num, num, expire=0) # Expire immediately. - >>> cache.cull_limit = 10 + >>> cache.reset('cull_limit', 0) # Disable automatic evictions. + >>> for num in range(10): + ... cache.set(num, num, expire=0) # Expire immediately. + >>> len(cache) + 10 + >>> list(cache) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> cache.expire() + 10 :meth:`Expire ` removes all expired keys from the -cache. It does so in chunks according to the cull limit size. +cache. Resetting the `cull_limit` to zero will disable culling during +:meth:`set ` and :meth:`add ` +operations. Because culling is performed lazily, the reported length of the +cache includes expired items. Iteration likewise includes expired items because +it is a read-only operation. To exclude expired items you must explicitly call +:meth:`expire ` which works regardless of the +`cull_limit`. >>> for num in range(100): ... cache.set(num, num, tag=u'odd' if num % 2 else u'even') >>> cache.evict(u'even') :meth:`Evict ` removes all the keys with a matching -key. The default tag is ``None``. Tag values may be any of integer, float, -string, bytes and None. +tag. The default tag is ``None``. Tag values may be any of integer, float, +string, bytes and None. To accelerate the eviction of items by tag, an index +can be created. To do so, initialize the cache with ``tag_index=True``. + + >>> cache = Cache('/tmp/mycachedir', tag_index=True) + >>> for num in range(100): + ... cache.set(num, num, tag=(num % 2)) + >>> cache.evict(0) + +Likewise, the tag index may be created or dropped using methods:: + + >>> cache.drop_tag_index() + >>> cache.tag_index + 0 + >>> cache.create_tag_index() + >>> cache.tag_index + 1 + +But prefer initializing the cache with a tag index rather than explicitly +creating or dropping the tag index. + +:meth:`Clear ` simply removes all items from the cache. >>> cache.clear() -:meth:`Clear ` simply removes all keys from the -cache. Each of these methods is designed to work concurrent to others. None of -them lock or freeze the cache while operating. +Each of these methods is designed to work concurrent to others. None of them +block readers or writers in other threads or processes. Lastly, three methods support metadata about the cache. The first is :meth:`volume ` which returns the estimated total size @@ -146,56 +222,68 @@ The second is :meth:`stats ` which returns cache hits and misses. Cache statistics must first be enabled. >>> cache.stats(enable=True) + (0, 0) >>> for num in range(100): ... cache.set(num, num) >>> for num in range(150): ... cache.get(num) >>> cache.stats(enable=False, reset=True) - (100, 50) + (100, 50) # 100 hits, 50 misses Cache statistics are useful when evaluating different eviction policies as discussed below. By default, statistics are disabled as they incur an extra -overhead on cache retrieval. +overhead on cache lookups. Increment and decrement operations are not accounted +in cache statistics. The third is :meth:`check ` which verifies cache -consistency. It can also fix inconsistencies and reclaimed unused space. +consistency. It can also fix inconsistencies and reclaim unused space. >>> cache.check(fix=True) [] -The value returned is a list of warnings. As such it is useful in assert -statements as ``assert len(cache.check()) == 0``. +The return value is a list of warnings. + +.. _tutorial-fanoutcache: FanoutCache ----------- Built atop :class:`Cache ` is :class:`diskcache.FanoutCache` -which automatically `shards` the underlying database used. `Sharding`_ is the -practice of horizontally partitioning data in a database. Here it is used to -decrease blocking writes. While readers and writers do not block each other, -writers block other writers. Therefore a shard for every concurrent writer is +which automatically `shards` the underlying database. `Sharding`_ is the +practice of horizontally partitioning data. Here it is used to decrease +blocking writes. While readers and writers do not block each other, writers +block other writers. Therefore a shard for every concurrent writer is suggested. This will depend on your scenario. The default value is 8. Another parameter, `timeout`, sets a limit on how long to wait for database -operations. This depends on your requirements and underlying hardware. This -parameter is also present on :class:`diskcache.Cache` but operates differently -there. :class:`FanoutCache ` automatically catches -timeout errors and aborts the operation. This means that a :meth:`set -` or :meth:`delete ` -operation could fail to complete. The default value is 0.025 (25 milliseconds). +transactions. Transactions are used for every operation that writes to the +database. The `timeout` parameter is also present on +:class:`diskcache.Cache`. When a :exc:`diskcache.Timeout` error occurs in +:class:`Cache ` methods, the exception is raised to the +caller. In contrast, :class:`FanoutCache ` catches +timeout errors and aborts the operation. As a result, :meth:`set +` and :meth:`delete ` +methods may silently fail. Most methods that handle :exc:`Timeout +` exceptions also include a `retry` keyword parameter +(default ``False``) to automatically repeat attempts that +timeout. :class:`FanoutCache ` will never raise a +:exc:`Timeout ` exception. The default `timeout` is 0.025 +(25 milliseconds). >>> from diskcache import FanoutCache - >>> cache = FanoutCache('mycachedir', shards=4, timeout=1) + >>> cache = FanoutCache('/tmp/mycachedir', shards=4, timeout=1) -The example above creates a cache in the local ``mycachedir`` directory with -four shards and a one second timeout. The `get`, `set`, and `delete` operations -will attempt to abort if they'll take longer than one second. +The example above creates a cache in the local ``/tmp/mycachedir`` directory +with four shards and a one second timeout. Operations will attempt to abort if +they take longer than one second. The remaining API of :class:`FanoutCache ` matches :class:`Cache ` as described above. .. _`Sharding`: https://en.wikipedia.org/wiki/Shard_(database_architecture) +.. _tutorial-djangocache: + DjangoCache ----------- @@ -221,38 +309,80 @@ DjangoCache As with :class:`FanoutCache ` above, these settings create a Django-compatible cache with four shards and a one second timeout. You can pass further settings via the ``OPTIONS`` mapping as shown in the Django -documentation. +documentation. :class:`DjangoCache ` will never raise a +:exc:`Timeout ` exception. But unlike :class:`FanoutCache +`, the keyword parameter `retry` defaults to ``True`` +for :class:`DjangoCache ` methods. + +The API of :class:`DjangoCache ` is a superset of the +functionality described in the `Django documentation on caching`_ and includes +many :class:`FanoutCache ` features. + +:class:`DjangoCache ` also works well with `X-Sendfile` and +`X-Accel-Redirect` headers. + +:: + + from django.core.cache import cache -The API of :class:`DjangoCache ` is as described in the -`Django documentation on caching`_. + def media(request, path): + try: + with cache.read(path) as reader: + response = HttpResponse() + response['X-Accel-Redirect'] = reader.name + return response + except KeyError: + # Handle cache miss. + +When values are :meth:`set ` using ``read=True`` +they are guaranteed to be stored in files. The full path is available on the +file handle in the `name` attribute. Remember to also include the +`Content-Type` header if known. .. _`Django documentation on caching`: https://docs.djangoproject.com/en/1.9/topics/cache/#the-low-level-cache-api +.. _tutorial-settings: + Settings -------- A variety of settings are available to improve performance. These values are stored in the database for durability and to communicate between processes. Each value is cached in an attribute with matching name. Attributes -are updated when set or deleted. Attributes are set during initialization when -passed as keyword arguments. +are updated using :meth:`reset `. Attributes are set +during initialization when passed as keyword arguments. -* `size_limit`, default one gigabyte. The maximum disk size of the cache. -* `cull_limit`, default ten. The maximum number of keys to cull when setting a +* `size_limit`, default one gigabyte. The maximum on-disk size of the cache. +* `cull_limit`, default ten. The maximum number of keys to cull when adding a new item. Set to zero to disable automatic culling. Some systems may disable - automatic culling in exchange for a cron job that regularly calls - :meth:`expire ` in a separate process. + automatic culling in exchange for a cron-like job that regularly calls + :meth:`expire ` in a separate process. * `large_value_threshold`, default one kilobyte. The minimum size of a value stored in a file on disk rather than in the cache database. -* `eviction_policy`, see section below. +* `eviction_policy`, see descriptions below. - >>> cache = Cache('mycachedir', size_limit=int(4e9), cull_limit=2) + >>> cache = Cache('/tmp/mycachedir', size_limit=int(4e9)) >>> cache.size_limit 4000000000 - >>> cache.cull_limit - 2 >>> cache.large_value_threshold 1024 + >>> cache.reset('cull_limit', 0) # Disable automatic evictions. + 0 + >>> cache.set(b'key', 1.234) + True + >>> cache.count # Stale attribute. + 0 + >>> cache.reset('count') # Prefer: len(cache) + 1 + +The :meth:`reset ` method accepts an optional +second argument that updates the corresponding value in the database. The +return value is the latest retrieved from the database. Notice attributes are +updated lazily. Prefer idioms like :meth:`len `, +:meth:`volume `, :meth:`create_tag_index +`, and :meth:`keyword arguments +` rather than using :meth:`reset +` directly. An additional set of attributes correspond to SQLite pragmas. Changing these values will also execute the appropriate ``PRAGMA`` statement. See the `SQLite @@ -291,17 +421,21 @@ tradeoffs for accessing and storing items. slows accesses. All clients accessing the cache are expected to use the same eviction -policy. The policy can be set during initialization via keyword argument and -changed by attribute. +policy. The policy can be set during initialization using a keyword argument. - >>> cache = Cache('mycachedir', eviction_policy=u'least-recently-used') + >>> cache = Cache('/tmp/mycachedir') >>> cache.eviction_policy + u'least-recently-stored' + >>> cache = Cache('/tmp/mycachedir', eviction_policy=u'least-frequently-used') + >>> cache.eviction_policy + u'least-frequently-used' + >>> cache.reset('eviction_policy', u'least-recently-used') u'least-recently-used' - >>> cache.eviction_policy = u'least-frequently-used' - >>> cache.eviction_policy = u'least-recently-stored' -The eviction policy can be changed at any time but previous indexes will not be -dropped. +Though the eviction policy is changed the previously created indexes will not +be dropped. + +.. _tutorial-disk: Disk ---- @@ -320,6 +454,28 @@ via the pickle protocol. Beware that integers and floats like ``1`` and ``1.0`` will compare equal as keys just as in Python. All other equality comparisons will require identical types. +Caveats +------- + +Though :doc:`DiskCache ` has a dictionary-like interface, Python's `hash +protocol`_ is not used. Neither the `__hash__` nor `__eq__` methods are used +for lookups. Instead lookups depend on the serialization method defined by +:class:`Disk ` objects. For strings, bytes, integers, and +floats equality matches Python's definition. But large integers and all other +types will be converted to bytes using pickling and the bytes representation +will define equality. + +:doc:`DiskCache ` uses SQLite to synchronize database access between +threads and processes and as such inherits all SQLite caveats. Most notably +SQLite is `not recommended`_ for use with Network File System (NFS) mounts. For +this reason, :doc:`DiskCache ` currently `performs poorly`_ on `Python +Anywhere`_. + +.. _`hash protocol`: https://docs.python.org/library/functions.html#hash +.. _`not recommended`: https://www.sqlite.org/faq.html#q5 +.. _`performs poorly`: https://www.pythonanywhere.com/forums/topic/1847/ +.. _`Python Anywhere`: https://www.pythonanywhere.com/ + Implementation Notes -------------------- From 4a92d56ed82fe8b3508b1ca8a5d7e7f9a0fac1ab Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 9 Sep 2016 09:47:25 -0700 Subject: [PATCH 041/550] Update benchmark timings --- tests/timings_core_p1.txt | 41 +++++++++++++++++------------------ tests/timings_core_p8.txt | 41 +++++++++++++++++------------------ tests/timings_djangocache.txt | 41 +++++++++++++++++------------------ 3 files changed, 60 insertions(+), 63 deletions(-) diff --git a/tests/timings_core_p1.txt b/tests/timings_core_p1.txt index a10123a..0c8220a 100644 --- a/tests/timings_core_p1.txt +++ b/tests/timings_core_p1.txt @@ -4,10 +4,10 @@ Timings for diskcache.Cache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 20.981us 25.988us 35.048us 438.929us 1.857s - set 9021 0 323.057us 480.175us 580.072us 1.174ms 3.032s - delete 1012 104 248.909us 283.003us 416.994us 720.978us 232.533ms - Total 98999 5.122s + get 88966 9705 17.881us 28.849us 41.962us 472.069us 1.701s + set 9021 0 300.884us 338.078us 394.106us 658.989us 2.736s + delete 1012 104 261.068us 299.931us 338.078us 598.907us 248.081ms + Total 98999 4.686s ========= ========= ========= ========= ========= ========= ========= ========= @@ -16,10 +16,10 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 15.974us 23.842us 33.855us 165.224us 1.523s - set 9021 0 287.056us 433.922us 520.945us 7.173ms 2.698s - delete 1012 104 222.206us 261.068us 360.012us 592.947us 210.354ms - Total 98999 4.432s + get 88966 9705 15.974us 28.133us 41.962us 522.852us 1.605s + set 9021 0 281.096us 318.050us 388.145us 5.438ms 2.537s + delete 1012 104 237.942us 283.003us 345.945us 2.058ms 231.609ms + Total 98999 4.374s ========= ========= ========= ========= ========= ========= ========= ========= @@ -28,10 +28,10 @@ Timings for diskcache.FanoutCache(shards=8, timeout=0.025) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 15.974us 23.127us 33.140us 786.066us 1.514s - set 9021 0 287.056us 429.869us 523.090us 2.006ms 2.685s - delete 1012 104 226.974us 261.068us 333.071us 540.018us 211.712ms - Total 98999 4.411s + get 88966 9705 15.974us 27.895us 41.008us 562.906us 1.570s + set 9021 0 281.811us 316.858us 398.159us 1.189ms 2.526s + delete 1012 104 240.803us 283.003us 321.150us 499.964us 229.842ms + Total 98999 4.326s ========= ========= ========= ========= ========= ========= ========= ========= @@ -40,10 +40,10 @@ Timings for pylibmc.Client ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 26.941us 30.041us 40.054us 140.905us 2.426s - set 9021 0 28.133us 31.948us 41.962us 363.827us 263.075ms - delete 1012 104 25.988us 29.802us 36.001us 52.929us 27.113ms - Total 98999 2.716s + get 88966 9705 25.988us 30.041us 41.962us 269.890us 2.407s + set 9021 0 28.133us 31.948us 45.061us 88.930us 262.482ms + delete 1012 104 25.988us 29.087us 39.101us 65.804us 27.031ms + Total 98999 2.697s ========= ========= ========= ========= ========= ========= ========= ========= @@ -52,9 +52,8 @@ Timings for redis.StrictRedis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 45.061us 46.015us 72.956us 198.126us 4.095s - set 9021 0 45.061us 46.968us 74.148us 154.018us 420.533ms - delete 1012 104 44.107us 46.015us 72.002us 106.812us 46.097ms - Total 98999 4.562s + get 88966 9705 45.061us 49.114us 77.009us 197.887us 4.171s + set 9021 0 46.015us 50.068us 77.963us 179.052us 429.199ms + delete 1012 104 44.823us 56.982us 77.009us 104.189us 47.746ms + Total 98999 4.648s ========= ========= ========= ========= ========= ========= ========= ========= - diff --git a/tests/timings_core_p8.txt b/tests/timings_core_p8.txt index 6d9cc08..5d103fc 100644 --- a/tests/timings_core_p8.txt +++ b/tests/timings_core_p8.txt @@ -4,10 +4,10 @@ Timings for diskcache.Cache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 71386 17.166us 25.034us 38.147us 260.115us 13.260s - set 71530 785 289.917us 513.077us 19.827ms 6.249s 224.371s - delete 7916 884 226.021us 282.049us 19.075ms 2.034s 13.595s - Total 791992 251.226s + get 712546 72929 16.928us 29.802us 45.061us 517.130us 13.617s + set 71530 0 303.030us 360.966us 36.302ms 6.251s 269.090s + delete 7916 773 265.837us 330.925us 35.141ms 1.339s 17.652s + Total 791992 300.358s ========= ========= ========= ========= ========= ========= ========= ========= @@ -16,10 +16,10 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 71119 20.027us 36.955us 66.996us 24.933ms 16.944s - set 71530 2475 219.107us 1.365ms 8.956ms 131.323ms 60.542s - delete 7916 1021 174.046us 1.305ms 8.974ms 80.959ms 6.115s - Total 791992 83.601s + get 712546 72975 17.166us 34.094us 73.195us 8.381ms 15.575s + set 71530 0 228.882us 1.421ms 19.039ms 333.486ms 79.159s + delete 7916 784 198.126us 1.385ms 19.165ms 107.130ms 8.838s + Total 791992 103.572s ========= ========= ========= ========= ========= ========= ========= ========= @@ -28,10 +28,10 @@ Timings for diskcache.FanoutCache(shards=8, timeout=0.025) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 71255 33.855us 51.975us 87.976us 13.653ms 24.948s - set 71530 2032 267.982us 1.332ms 3.695ms 27.686ms 38.919s - delete 7916 953 205.040us 1.236ms 3.542ms 26.526ms 3.443s - Total 791992 67.310s + get 712546 70780 23.127us 45.061us 86.069us 7.667ms 19.697s + set 71530 31 257.015us 1.410ms 8.780ms 27.772ms 51.284s + delete 7916 767 219.822us 1.366ms 8.804ms 26.998ms 5.474s + Total 791992 76.455s ========= ========= ========= ========= ========= ========= ========= ========= @@ -40,10 +40,10 @@ Timings for pylibmc.Client ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72032 81.062us 101.089us 116.110us 524.998us 59.352s - set 71530 0 82.970us 102.997us 118.017us 466.824us 6.087s - delete 7916 787 79.155us 100.136us 113.964us 190.973us 649.433ms - Total 791992 66.089s + get 712546 72146 83.208us 105.143us 120.878us 520.945us 61.320s + set 71530 0 85.115us 107.050us 123.024us 458.002us 6.285s + delete 7916 792 82.016us 103.951us 119.925us 298.977us 673.505ms + Total 791992 68.279s ========= ========= ========= ========= ========= ========= ========= ========= @@ -52,9 +52,8 @@ Timings for redis.StrictRedis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72543 136.852us 168.085us 200.987us 1.134ms 99.883s - set 71530 0 137.091us 169.039us 201.941us 346.899us 10.089s - delete 7916 801 134.945us 165.939us 198.841us 1.100ms 1.098s - Total 791992 111.070s + get 712546 72652 141.144us 174.999us 210.047us 931.978us 103.515s + set 71530 0 142.097us 174.999us 211.000us 623.941us 10.457s + delete 7916 811 139.952us 172.138us 205.994us 288.963us 1.138s + Total 791992 115.110s ========= ========= ========= ========= ========= ========= ========= ========= - diff --git a/tests/timings_djangocache.txt b/tests/timings_djangocache.txt index ff41df6..ef19271 100644 --- a/tests/timings_djangocache.txt +++ b/tests/timings_djangocache.txt @@ -4,10 +4,10 @@ Timings for locmem ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 140750 35.048us 56.982us 59.128us 10.045ms 28.172s - set 71530 0 36.955us 38.147us 43.154us 9.984ms 2.659s - delete 7916 0 31.948us 34.094us 36.001us 9.987ms 267.255ms - Total 791992 31.099s + get 712546 140750 35.048us 56.982us 59.128us 8.609ms 28.325s + set 71530 0 36.955us 38.147us 46.015us 6.582ms 2.670s + delete 7916 0 31.948us 34.809us 36.955us 2.065ms 255.893ms + Total 791992 31.252s ========= ========= ========= ========= ========= ========= ========= ========= @@ -16,10 +16,10 @@ Timings for memcached ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 68969 87.976us 101.089us 113.010us 449.181us 62.615s - set 71530 0 92.030us 105.143us 117.779us 442.982us 6.565s - delete 7916 0 87.023us 99.897us 113.010us 206.947us 682.936ms - Total 791992 69.863s + get 712546 69192 88.930us 102.043us 123.978us 917.912us 63.269s + set 71530 0 92.030us 106.096us 127.077us 804.901us 6.604s + delete 7916 0 87.023us 100.136us 122.070us 201.941us 687.053ms + Total 791992 70.560s ========= ========= ========= ========= ========= ========= ========= ========= @@ -28,10 +28,10 @@ Timings for redis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 68854 171.900us 211.000us 250.101us 5.437ms 125.218s - set 71530 0 179.052us 216.007us 255.108us 5.327ms 13.051s - delete 7916 781 154.018us 190.020us 230.074us 1.309ms 1.253s - Total 791992 139.522s + get 712546 68891 174.046us 213.146us 251.055us 1.084ms 126.502s + set 71530 0 179.052us 216.007us 252.962us 478.983us 13.056s + delete 7916 770 156.879us 193.119us 227.213us 293.970us 1.268s + Total 791992 140.826s ========= ========= ========= ========= ========= ========= ========= ========= @@ -40,10 +40,10 @@ Timings for diskcache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 70313 50.068us 67.949us 102.043us 14.113ms 35.382s - set 71530 0 355.005us 1.459ms 3.817ms 31.551ms 45.698s - delete 7916 0 240.088us 1.330ms 3.665ms 26.498ms 3.785s - Total 791992 84.865s + get 712546 68585 35.048us 61.989us 107.050us 11.898ms 28.819s + set 71530 0 324.011us 1.491ms 8.872ms 36.179ms 56.072s + delete 7916 0 254.154us 1.410ms 8.748ms 27.164ms 5.651s + Total 791992 90.542s ========= ========= ========= ========= ========= ========= ========= ========= @@ -52,9 +52,8 @@ Timings for filebased ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712580 123599 97.990us 144.958us 257.015us 15.342ms 75.490s - set 71539 0 5.274ms 6.261ms 7.501ms 26.983ms 376.789s - delete 7873 0 139.952us 235.081us 398.874us 1.394ms 1.218s - Total 791992 453.496s + get 712598 99964 101.805us 171.900us 365.973us 5.407ms 83.088s + set 71557 0 7.903ms 10.250ms 12.787ms 34.464ms 578.779s + delete 7837 0 200.987us 346.899us 596.046us 1.250ms 1.736s + Total 791992 663.603s ========= ========= ========= ========= ========= ========= ========= ========= - From fd671ff49f7c8d29299ce6af0346dc5ae9065183 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 9 Sep 2016 09:57:03 -0700 Subject: [PATCH 042/550] Run all djangocache benchmarks --- tests/benchmark_djangocache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/benchmark_djangocache.py b/tests/benchmark_djangocache.py index 08832ce..13a50b3 100644 --- a/tests/benchmark_djangocache.py +++ b/tests/benchmark_djangocache.py @@ -100,7 +100,7 @@ def dispatch(): from django.core.cache import caches - for name in ['diskcache']: # ['locmem', 'memcached', 'redis', 'diskcache', 'filebased']: + for name in ['locmem', 'memcached', 'redis', 'diskcache', 'filebased']: shutil.rmtree('tmp', ignore_errors=True) preparer = mp.Process(target=prepare, args=(name,)) From 8ebc86beeef458346a54a327e5784a28653ab8eb Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 9 Sep 2016 10:26:36 -0700 Subject: [PATCH 043/550] Update benchmark images --- docs/_static/core-p1-delete.png | Bin 42215 -> 39928 bytes docs/_static/core-p1-get.png | Bin 39605 -> 39661 bytes docs/_static/core-p1-set.png | Bin 37432 -> 40461 bytes docs/_static/core-p8-delete.png | Bin 40260 -> 38922 bytes docs/_static/core-p8-get.png | Bin 37391 -> 37352 bytes docs/_static/core-p8-set.png | Bin 39774 -> 39548 bytes docs/_static/djangocache-delete.png | Bin 27332 -> 27115 bytes docs/_static/djangocache-get.png | Bin 25462 -> 27696 bytes docs/_static/djangocache-set.png | Bin 26663 -> 26325 bytes 9 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/_static/core-p1-delete.png b/docs/_static/core-p1-delete.png index 48c522238b7ba4f2faccbb1ea29094ba56cb073c..455d47362a8bc46cc17c06d2d0bca72b2d7dc830 100644 GIT binary patch literal 39928 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 L<|gNj?EU{2#>L1T literal 39605 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�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 Y?{>j{Jr9;$RPaADWAlkIMmE3y7p&*XsQ>@~ diff --git a/docs/_static/core-p8-delete.png b/docs/_static/core-p8-delete.png index 51a8f52c48ec768f51e1fc4a5443a3ef1762ecba..358e2a92d06e97ef6e9bdc119cf69701bc1cf491 100644 GIT binary patch literal 38922 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

7#Jdwe^|L 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 diff --git a/docs/_static/core-p8-get.png b/docs/_static/core-p8-get.png index f857111b912864265f232379263f3b45cd13d402..8cab18211e3db3d4dc6897cab52122fad64ecbb5 100644 GIT binary patch literal 37352 zcmeFZ1yq&qx-U90un5JX1cOjPEK1T=L_(1+5$Wy@LBRwC0VP$sLy(XVQ4ta8Zt3o> z^ZWeowf9=*tbO(wXYX;(9rvC!)?y({=KSJ)pXXQ4_efgu3guS%tt1kOQtax*nWn)^I;Brxvu3;~{O3kYE;^TA5oj>BH)$ z#p8S9O!cd~i3?s@jh9N2*d#B~y`BxZd}=q(?{}tGg&DuBEERoSu-h;ZZkDy+n9!q? zHs??<*Jo%QD(~R(OGLz9OK}~E^y$dLhglpOscD0$f~qV`RDH{qEj|&C zcCrsH4kgbuz0Xd-onceR|8>+}yosFx3P`bXo;29?sCSpGr8|`dnDq!CI zaCxPt07wDj|L;o&?^i+8!e`<=+4zLQ;Ydb89= z>k*&i#1xrRhe*E}lXed`uSyZU71FwDQ_)u~|-NPM+ zb}=y}b#&ZTwJ76h$}-dR?V7JISY^dkGHT7qD9m9n&N{cAoWgW%dAeFE-lRQ$D;1TT zt!k7C$r%TMRaz|_9fRJVuOEAP$>HGR;^J8LeKddd z`gN>vYtE6whp+BcF+JxVe~!4%W9uX zg2k^h#rFMgJrk%!=0Dl@zSc>osE{7=S2j-NEKOsi@|+odU#^s&pI=uMDW15z?8qT1 z+8&pbwEDH{ske9Lsne$!RQb$)U*tG&pm1gCSBT&-W@cs%5s|h)ezSKoZ4P6KQo=JY zYhz{D4VLF8;Z%h6%ZX#!S*cZC-%F!^X zKlknHmoEz8SG+&8+4b!+C=2BAuO3Kup6}emaq3hR_F1O?F=cJ1>B=j0k9TtehuyTax+Be*<$1&7okeoDZoRx>+LWT<-qY2knYTPy%G8{!l3hhylKj<0gVHpw zyvl|7c?D+2@pJFJrmnlmV5?neeRWck6MJIFPhQRJxS(l=YtPu2iST@f`>0mEx=Wtb z#MiybSG*YUJNxOF+Gr_egX#WC39l(H!O@gEnwrA?#Ql+rmJCVo@u4YA<23m3@<^Rl zfU5Ol`4(v>)415!R4>Lecl`YQm8Sa2Gf}G&9LDnM(>SNvWmZn+;5ZqE*-xLYPHQwA zo#5^LyzO{8F5+NnLQYN-icor#L{P7qzmLzIjT<&(UGULTl+)9TMS0NC($dNfkO<~a zEi`-2`MzTsmr5x_&}O#5Q)}Ons^v&i#<_|ON;H{+u2gKIqUyLqkvUdFy=r@Q?TTP9 z$*m0M;^UJ?rK3Ns{S<4a#RVUGv@}i9jw6yMB0Ab(xdppXtSUvLsHOD$g$q`r*?nf)BqJjuwIFc%^yv(nF*T?8PR~)T;|0rU*#5ZZtYX(BCFL|U z-bP7=#9;%T^`vIzb(ZkgB8o+bfYnXhG$y<59j&+|KYsl1v0N;OesSmuZ$t6o%#c;J z1YdTs!@GCy4mz!^%q@ zP*D6ick&}#T)#AhiaaP~E?s(n{nxDBNNvj2RF`m$RM6lpx*D^(vc#w0{5wb+R}-yb z7Y$9-o4VrSD>!I1hOXCJw{4r6KeK;-M?`6eWHgH&_H|r*eD&uC+giOP`P&{%YR(WoyH7+QEi5dI7Vp4fy5Q{WJUSD-Cs3og%h2OQyYOJkwWo~xaYyw4)=;m0gfhRQ1hbPb6GR~Wk6JD9F{_*RV zoTQ|eA#c#-XY@TcEQ+iU`R-&jycA9@Zk9yH*PGJs;^e{8;sV*%Rlkj#LrZ? zrT=zP*6*#t6JK^VWtxO7NzC9n+0V7w2oF|OSI;zPddH@v-MrvIaYnBI=dI{(rIZ=g z9NeihY!b8HgT!u^rP=(h%61C z&Kv){t>A9I&rcUA2|8ZGS8~Z}iDAx*v6anMdG<3m9UXH&7&S+s-i!rMDOROU^s1g zy{b*fw`UB|%EsB3s-q+UaRH8D3(w<=AXrx-3>zC4IdRE09MYwzA@ z(Uk$IXr9r0rzQQyG%b771j~h<01oWtmBks~2pP4RAYP-u)j5|n`Fa(LXZWiqwLJR) zbp6G0m(_47(Un^!xf9>?o^8~XBrfBlhYu@oX<}k!qV+%D-|`s$>@M+@m6Pjr_D#q% z@42=zlk8Ia_MT~Ev$zWfUe^`Uykt1Tj4@v;2tMkR8KrPo)KU&vf<#O9i-VLA7*47@RcA0m- zbLS4d%0hN;pux1a%kl-His7|2c@8$um_^l4yhXLteVgB~6+}nb6B{2zm0D=r6?iCo za8Uo!l`9S{#<`sy4C$zj!pq}DIbB|Ymh}?KC&Yw9mb;^l9eq8eobXgfv zmm6!#OT@MzkX{`SqO+2)T!Mmzs?$s@f%^+tM>v_Sx~gi3)j(tb?>xUKsbe~QzH9TI<7x>#r2z(50!NP@ua}iqjZ9DH z-X~xgQ@Qx`DTSY(pI))ozN1Hv-V_si#CH8vhgw)zSn|)G*U`daaW}T@<+=sL&TGW*QBKCvEYQhoic9WKf_2RD=%N#X__U9(f6ws zm}MFmqCcZvt@;^XTZ~Em!sXN2KXpgmlAT?us}ef~=(aedzBahJ@cqRhx%wnk2BDTR zbZUI(@9ph91K^ky;IuMRPy9<(Ma2;4q}96Fs1Ddb=mUeWeL4^BSZUho+J(-*!OB+z zfW!xxt_gu)_=+S@>l%QK7^vEIK9DmtO(8%Y(W2@IAk|c~&eb~kQZjMiBQ4q0UE;z+ z1px!`$@%)y>Iw=mL@mF6UM#2EM|{`z?QcGRK5tX@NS*d|aPaV`PF2@MbO{05@i$y~ z(>J|)ObS*$$6|3>R4gqfS z90zM+?EwghuCI@q7bs+xcKIpoTZtfEo_@3G{u@Na;SEYu6n|qpHs>MMc0^RH$bnwROgs9}60nY_S#wt0JF#j7oK$SpxiWlE_1QC(2yc zw`bLE8@CD7?OR(8z)Ae!ap%vwfql?B?y_yqcR~p^N|7RwDrR@@-5WNow{(mt_;**A zth6*3C%KrwY~GCr4<68qIy<^Ae))0{oJx17E^$=ZNcn@|4I`sOnw>jac~A(Lnwwg*z+S+vh}GfBo)^MQ`wAMvcpKoqo3+3kqme>Ux(u{01i2-4;J|T1dwpSmZVA=niN9PwK2OHZr+_T%^GkmRvh0V2{*zoXV<%cf69SdCjuK6#U&t7-~$ew*Jl>Boqyf|47UDU(q}M z?`!_gT!FPZCfq|^C&_3Tu7J|`CCF&f5E10(Izi}2q+jOv#g>yNkH*G~4?6#P^00#1 zVK#ke{35XkIQz`AZ{57{wmkfoZ&|V6d)v6gaQbWz+h+$#$) zHccP6**vk@XbOKjgOmZX=?)| z{0P^sjguX$j`lu({=6m1i^0rbZFKD+f*b$`9Rv)|FpyVuS($+b<$HxXpfs|5ZFO$2 zDI-3|a^T&LLzi{Ismn~cNu;w5mA?!1CVqca%6H7JOIA;Y&Y@~mD+}0D4f%;avHbgY zX+_1?*24{NQ9Fqy3FI_7gHG||=g(UJ-8i0f)6Vl+ckk)~Fn$wuUR}_Ez##R}W=swp z__7ycED8w0oK;U7HF2X15dEkM$bC1?3CCjFxtWaQ`r$`$8fQGKjV*Zl5TJkU%Of%@ zVgOo$b%{Ztj+0{e0Na|;wmfBkvZgexC*W-CV#GJ7v>E8%P#yYbn@v!o{L~yqc;>7% zzdgNi@#1}`4N*W~M-Ci#Tf+2g3natIo>Ct3?yD%-hlPYx)|UH33GyfA1I$1`UqYJ5 zSsaWP{$Rz=Nc0b!;oZ#UpuSIJqz=}ZG37NIH5=T23yH|Z{u?t#8tbFl86?^Yl&2s2 z7IjuYwedqgRqO5Ri;jq30pgLT9Ne~T8}x&IDj^@s64rpj`}dzk^B4_;(r0fN@K3t0 zSd~q?6MJ;^z{ydN8%Uzu0>G)Ar1>%tV23!I#sGI*r_@G9=T2?mKL5sz8KYI9Z1==6WzM!sKfD;p4 z#e6YR*RS7_kaz}EICxSt_QMCcM~@zj2D-Ve^B&2c|GkZsm6dKTM%_O&H0g*;ly=zxiA3wn~(40nBxS>Ry0kwI~5*ZnJ{=ETkL+B-5^~tJClK;g`lKPDe)v zt$%jPLw^^ee>0nBz8~_=GVteAe1}s}g3bwrvcne7JGLQXy74yMMze9@EJX-}!lMWjH8@~oe zN3E%us$13JZF<{+M_w^fkw~{6c5L|n?bn!Ew`%OIUHkSOgMR=;*Qoi!NJIu;`VVv& z1%+3ii;Dc9@5jX*^$wJcmm@^@ty{KKLUK-kh7%^|eN6d-?)-S?U|Zft_!cujr@XXR zu3dW!RU7)h)?eGB#iD%W^C8rZrYCY+B%ka(UWbaG;k;sxQZp16pLA16DUfQPK#i^w zcp!v8@L?(_aj_8*7k9Etz3Q5&09!$(^ z^YQkU3hx@9n5axpeBW)#e@j*Mm}k-35?^+{>KpiMqYxK^VQxV|WspN@@CjBi5YL+S z)mgqowLB#hC8i8?$U3Mr8Cs=#&<(_VLT!IK}BRm@*2#Bx|75in$*@)LD4bAHCAY>Q}EE3Ku=%gZlc zzKlrDUyM8F1Zfl(sy10&bS_PtEBhiD8QD=zPRn`Gs=#q`-lPd}I{1iqg)`ekva!BA#$vPnYkyfciZ=wXe6@R~pk z|u^KFL+YPqQT(E8EVR5N8zY_B0%}kCFsVskw~`cXbkEyQX7o6WD}}fe`PpB%`8+_ z{Hz`wLH5Rtw?I^7uTEZo`66v;na(l*sjZ68#0a+%T7TtWZG47tt3WH%v*=;7W!a>_ zKzdXjv72C#P<3y~%d?9eJ$8&}f=j+G0EN|IYs)&%7|+~1ckY~;up0WikuIU){<+lc*jYVh^+2&+wrSJ z^Lkss!jIkPQx^z`{-680nkY@6ML|dG}QSN<0w$hT(Q!lOXLHiy|L=O^F98#jVPn*k{6V}mD z$m~P)aPT|uiYA(_j`TZUw}Og@`%I4)Q5#ggyF^&m)1TH;+ZvhTB}R`X0+!`uWsh)k z-va+4{0r#&aI*vk@|Q-ZdgqC^q3G(s8vNt$OqKCnyLRm+UH}`&YWlD`CF;;Gs&9ov z44`_qyvkc_72(+iO`izhO(s{6vtRW1EiB&}=yUO|t|V25kqn;*bak7DIPw1er|!O% z!}neli%Uux*A?RP(zPU<Az|k#fyl(?3e`{rel>V~R67 zO}{|D=mtHMlZ(RkS9q^eE(h6Yb*X*rLyax2osxua=AfEw=SGkloO@^$R6TTumLbC7 z02LzcDQ=<~^I^QdfnX0~G{mE0>iH^HyqRjTNHSd3oF904#_N=aPR{T+JG0?sA9aMz zw7_Ft>BdELeZM2%DUO>05aYl58dB$5;zF3BN~w1qun?`UI$G-AZX~xw@AX8L?3-hGbD_&C-l{Q#b(mo{rJYO( z5cB5PNh(P~0>nGBiXl(o69Lv!I$&w9a~B6?^wi9h=+IcDLjEg#qH8x$>fci%?DFQ= zDJn_AVwetUU%ot6u$J$|BpmmQfo~AZtsc_#K-mPb0O`Jk#SzjzTrewmE8ja!4<9=w z;ma;n2UeJSMg*6Ew<|qA-gtF+AyCvMA8d6uJ$)SRw}}@eK1Sk9Vjq94;9QGEkOL!e zL~VLbaibm##?l~!>(;FkTE!28DW4|ugHRu3W4lbmI1muvD+V~LV58ggXLm608C!}j z(~uwwX$HH2Z$=|4a3eJ}wT%($kpEuwz{}E}NhKH0qRx!Aeb3BH?K>arwJaB&JAC@I zOkapiXmO4~a^8o=O$haPJ&4L`;P>+n_%JK|rh2m^QO2(@J4HaPFV@UPVMU(`bh?eZ z4Fq}g*m2pqis;?l?Ts8uF_ugfZrmr}R-X;_zK5hb54B+oWRw9B(W+KyibfRSpE+TgYx zRl!-2h*ItPUKIob4jR$N#H!%GNZBhJ!w)SJMUcsrH>#w=vel|Sdo46DaB${db{+X2 z`Ss```Blg&g+!x71=@>tuvLJQ=@$(w8EVe{w2R3Y#SDGF#p!|Y=g+sRI!=65g8isB(*Ws&=;ouX9}!@YXGTJU?iudC zr3w(#f2GNC2x;oPVZAS>oZIExq|^*~z(a?LPoJcTgA z&`V+axzR+3OyWMArTQndeX*Kcy%N9v%^Hy@DJdD%f)|bkQ-#%1`zAjnMH&GGBH-iD z$Qq#X4%GxjJjZ~Xim^Xc)!aPoW0;t?jGh-0R+W>1Uk8iW+qZ9-N{c>!rgsWVD(cjg z{zoZ>+ShEY>aQ115G{>$Uy!a-E_}1B`VfWoh7H4T%Ycl&s#f1Bh_dJYQ11I|DS!ym zjVM1UHv^(VR!08ZxqIZ4^ekeigff8d-@KXI--w(%d4q?CrzyuOeu=jc`qnLF<#@Ca z8wtF+*jn9j;sI>xrw+kCUQfR|t>cA)$0DYur-v$ex@j-=7+rRDHu5umEa77@hJGcT zsFU7Ih(W=UyGX?BnC$xxAONC^xB#WT!*u^!f`(uLDP@~Sa+X#-r8~`i=gVdYmEu05 zIYV5;pAia)aqh(BJlKFrnZ_X^@b=;KIN!7Zba%L8`xln&9veF@Dk|E=RIHq?yF)%4 z$5P}~kkV<&pKdu&#WtkeW3~Z(sQ$~NoqmCVaY?ElVev`9SCN6!_4vsX5Ikh>`jX0F zGT*%$!IjmqZf3Yq1|Z2ECSl9aCQAAUKwP3%L$I1_cPWrdQi&y7Pfk16kYNxYU^AM4 zJ%C&eK_a0F)fG{T8nzIN-Y{`i)e5d_{qn*T1E)9qcPp!m;xtHYM7T#{mO;=u;hWb! zhpL(A!5y%s5me&M#vLYB)stiuRE-macKm<(Yj=AS&gM;?g+K`Uh}yu2DSvL&ULB8) z4Wv)UJ1hluGwh9@teUK2+Nwl?is7pMsmDAvH(e#R)Z@Eo)MA4yv z!wElS{+~nY|Ge7%r&^ObB>pl83|970dDh#9j~sxEFTja&c*QgkcX%l_!_$q*4Og!} z1rpKGAI>*_hnaWpr5z4&;y*eqF&m;b@a%&p@%!=Rb?!aPw7>n@tnPW@nj7u(EyAk! zCot`Q1aAM|_(_J6X&|3TC_<*hHudp&2X!YT{R09uganJ&2}lnTjv#_PH{QcyoEfOT za^S#$9E4Ibpa^S^y};kVp>7IG^r5r}l) zOMm|;r~^DoVJ}|1O-nm9(vW(AKrBa&RNmjRvt?*%YKoAP5)wG^p?aO4A3$cR0MrP| z@C4%0E(>IZVuLgVD5^xLP~)~MU}5Q((EiX!cr?FJ%#1V>nKh*>)3+*I^6nf$K15I; zQT3yoxId?v`Z4?8kJ!^8x{fryzv45!JXLOhvO>?J|8jX{g-81*ZDe#b2NWQmboim_ zE#aYBifSSP*q({{6CEH0=@xyGtnBO`17M~cL2N|bX-3w45vKe`a`HI!0_Rb!02T8` zsNN7T`jhC0u&_1Jww%i^92U|-XSwOBEm0rMFAbVyNp>8oGFT4tJU{&Xva zf<-5uE7W2HFKuxdYcD7u>>0{^0=LkV0JRJ&T>eqWPdo=qt}FbvrjxCbBvY4p&Gdhz zy~#EId+jYS@rowmjVvLKZPo8CJrQyv8k_PzG!rfJc`TvuVrCZ=0&b&;@z{*2{IkY} zDtI*(xNiQzD5W@ zxQONSXUsuD-IRW(z|4}XJ6foRhDpSBp^4POjl+`FLy($?JtO@9HVA>HWd00OWfF-?f#OQUNSxWM8Cg(6+6ClwhO4Q1RECs&+ z|IAtG3JL&S8vBd!5r!@X)EiG+HeFX?oCQ54z@Njaar?q1hrY1mIuB%+rB-S<@;Xb< zd?E}B9!HDKdbBIma?T1w-I=aVi zst4!&Kd+^KFUSDh(lq;r#!9-5(#_5A_w8aGN$niY+VCIRMa%NazTJy#OTV-sSVrNp z1%{~KPFY@mAu!KGwd1Y!*cMW>``@GczcBt^UvaL%mvR@LvOenLBTH3_>yx%}G?cAzecpTTJgV5S_iY1|BqOrIE&Qbo>+|*oGpY@4 zBN=V{&orh9KMq}=e;~dj5;vdQPRSoQBwjALp2(g=VQ42!8;GC%9k^%^jDGz7bF6o? z^Ppebzx+*!XoGr)o1k6f+=~lW&@)LSliTKCdjDh%{=0)EOsHM+pT`~Dmp+^=Vd@WQqdGnVR>@xc7kT`DXXE}) zHe$H};1+?T|5EG3xRM}3nqW;Y5o4u0tsO&*fc!getL;RV1btDp=OvmUb`m+oa50dwkMdfFf-guky>6?k!>dmCV z6(1JVw<}cpCVq$nFNOF-T)!!Fhwzjqvs|2HMxw7O#r$o6p3tjRp-1=H+`6dv3;wb6z*9e$LEEF;;%5_}e%83>S-mrG&Y; z&XFk}iz*cdB)LAHIbye#+Z}9p26oPMoJm$%4Og$p4$Rr@sQc_&p_mg@L`t(VEt=OC# zB35dW7W4I(T5bc#9T6&!8biqYk9M zWk?hm>v%X5bujZa;N&PJ7p&&&W@I#ULP+=+!Gpl+Xo4|#1IVXCIO%G(vb=1V{!5uk9=<4%N|A^e0pR!eavLssZANMch*N+f%y4<+zH_6(5T< zxKRTG14LYyFvk$N(8Hz=JX5{oz0-AWN3g&{04_y%draQ9Y>@{gQq$f!7?mPah8Hhh zT)TR;q9xnH3aQY!MWm*0%Bz??7`FZhl_m!6u&rUi?ZS1$)YHn;`*M+Z#3JI6a3SPq zXeY^#q{(M68uBK`W-RIZ_p1c#*9`kKfs=g+4uPxfC??cQq z5}BrL$8$fOX(ZboEU+1Ie2GBrl9g6_$jmR18=~ZE>2O{*2$v@s!K=YghtV6zT@fFL zti8Cm$YM-{CiQX-BHx(5OM3ixYobEx`Errw@e<48_8e#=LBxOov|l(h20-_L0#y?8AT8bL(`=V{5x@Sb!(1yyxliZ4;HMgn3Pg70`Lr;Ws?Yuz+{Rl!j@3L zgGY0xBtdJk-G32x0igQNSOhLJ;;iwg|5;ch5KAx*(+^BMK(c|b1VF87F^jV@C|^*8lN)_-$aU5b`L(Uo)bg3etjeX`&kA43~# zcPd>#Xw{E}@LO=7>M_n0^@JW&>dU=0TxJamXPWO>MEu%1k3@OpU(5qTn_*=k^m zknSZiTg;z~W_3}Cnb*HrImmByg0M-zh`1f+O+kk35|ERcnr?1im4oqUvj#S7>9R@v z`t_%%ej_{V0~fj5aOr0VL&jyfi#bE5oCy;VIVOb@wkNm)ut!$9nAfDiS!PFD?k6m;)k?ZoBgsvr|>rrt9~ z?ZJ zOaI5}+FFxf?ZD-nic2&(v6s!a_(_WO1m>SU8Gge3K)?-=laKYco>rZwi0!Q2{8sz7 zjLe}MwBgsgPq{D99?FqE#PUMSi%Vx^wZDEfT~JjrJYpfCuPc?=+oEkLug*TX6#rLm zRQ~FohX=V~Q~H*SjEpR+p*@f@ny<&_Ye>jGm|h)yeV))4kih7`HYmc$?q_GO_l=TSjk2s^ zyLY)S@56^Z=&JnI!?Z+L&anAIV)XSW%L6rLiwqT$7d}%_QRQ}~MomHBF2vMI3T{sS z;2@%C>zSCC4j(x}R>7ZOwmff!VWG45A;xV!VL+kjdVNC!EfRvSL|sJSTX~{-Y=*cw z=D~P#bu@kM1Z{6RGvWDC|;NF_-h-nI3?`L|cMjF`Rq>-!fp zgZwxC_EW3izrK@>xGy02=atfjZ=T=BPW%mCe&ns!&cD8U{8GC3(Vw4fy>u<6>mu>n ztq%^e*8J<+`iKY{k$MUqUOZS+S2vqZ_vhV~k3IMGbw9hmzluM$YubN5&7<45Z@Xdy za5nwOzr3KLs_ML%nOT5j0N3-?8ZN)u0zk<(MTk|q9GMdf~D_#%e7K;rj}lmn9`7Go!6KKW`jvNL??wGQAUm!JBvQ)^Tuf*xK0@mX^ksmeRdqsQ6nJ zpj)E%87^d-TufKZwm+v^>~~mo{j{IU)PShV#(tyZ;YrpIiXzj3#Ds+B0Rf(<)0Rga zr}*2*+g=J|2*@=ojG0X;^eL)9|Ikp^Ky@^~-6RXbmiJK3G%>B98ra(`SBDE%wNtt{ zc5kJ8z7AtF-#v6EK&tv`?dh%PFvKoEd`lBeDc1r(;*03&1a+?M#5>P~aOuM@3;0Zm z=gE6aq;B4%xh^4bVEg9V8XETy@M&9sz3!%^*mgo1+s@F?@J(FYR%BLQy?R9~#gQoA z*RQwW-wA4Yfxbxderj)L#trp6yWjQ02VNl|q47UE^U|e_xp{dShK3Z#qEavk(T0YG z9_HZieYl3LVQORJi4Bi@g~pvbZs`A8^5BG#L00j=NFygF=QzqxCVf4UF>j)x$Wc%m znwoxN>_ER@KiPdxOcto-)moB$`t*s^+uNIW$^_S9^23FF?AJ0a7N_-W?d?BfD)N8yg!VuKo#dlZ=Go zU}|oD{`z$ajN#pfwdaVoA}u3R1e=761o)3s``L>ZHz2H*V${sLYuBz%b#?xH2i7pH z_KM%^AaM1L-Mb%j6?+$zms7hePi!jpLRMmaaq$jd*HsCL9>xshx+VJn)$ToeMhO+l z10(H@nBH zAIcZppO?2S-{|q?HS+bq^Brg~sT(>B;r6};?eC6`vzrfe1Loe>QuGN4k=T5I<;DF+EHy53_3zPb0Uvy?SaW*+Vv!U_H>ruWeIqsy=$|tE5BA!z0>&?JN zh2XH>>EOsn*1K9-Cn3K@L`0Yt=_hs$4LyOQgnxB}dyD)I5wsfIm7bn{0yM#Tq$SUR zkMr0umN;b`}U%#*fzG9rt zY~uH&?dMT6NhE|WA1H8+p5P*3o%ud5DJ``y10s0$_AMC_J02i5Hs4E14zRJ2gPR+i zHpXFK__YHdVqa5b^WwOd=N$a^$ z)lf;yNEfDYjo-`~ zfoS>no;=wG-jr>DYi2t?egI6d>1hEN@Mp5dOcQ34v>a?JLnEUX&!4+ugi=h)M)~m3 zqo05MqN-0;F9u<~t)<0kmhM?lAQ~!U=LTo{gZ(PJ_jydJR&z`dSItB)z4%ep$bAow z&tJc8L={l@U`PkryMu;i9k@lP+Em*kC|TGVw=vgIQC(exZM}nuiAq&f)i~D40)XRr zNC@Ry>CN);@^`hh&!fGosi`%**8$_{SU1^-|nA`SW(Z&k3iDk!o%et{^%SZ80+ zQBr}O9hXKjQ&LiFR)9`6|fsBscd(Nl7Yn){fu5-LP;`_Pc6g zWj0e%enp{6!SpLIUD3C19t@+~sHiSu6(J)>_TuUWoIG~So&Jiqx%0(O>D+;rfa^5j z0ocwA?w+2RVP%WR{7__FIkmLU4smnuLJ}$_F3v3~iY*}_p<`sEY+%Rv{d=CU zdgP+=-!?LO2DGRN6QG4%9*o!>6L6*Kpu8| zt4Zx~_&pZxN8PdPMS%*YXf@Zf`_Gz{IJJ2^FrkNoFvv}StR5F8TsE2w>&UEB2*7lH zK%+lS;C};M{XLxdJJ2=?WV8LzMa%Wq|GcL?Z4*ERen87@yBUqNUbFa3;RO_s98M|h zA}=qmmbKww!xYXZFyv#qHgDNNR+@$tqN-g#ba2VZ@svL-SAwKS7ODLQ&ygi7hwO%e- zJM^Z{kbsU_y@H9DVp!f5h&aG~Oql0aat{p-p1AYnVSSuuynOOO+$!vXz7hs{`cG+w z$!njc(M@;I(>KM=*6d*5HFU*Z=jP`Al*XCA&>JNAKuS#P9!fu&D*-9dRvwuc{CahA zJ@gz^Z40Rj4#D^E)`5sLx$in|K!8J|0J2Z~gNMHg3R-K*qAdFoClFhOxZr zL@x^uwO(=$9SZZM;2wI@J*J3akl zaDaz%MyKG#V&L4J=+XFiRE|7J$Lam&t+i z)|;4^V9#}eaeo75L1S=7`pCi_I5j0T^`5skH3bEQTJvZqnp56pI=f5LFXRXHPUmcd z)%cWwFDbgLU-l{E85;Dt5xvu^H20tLGCz-(OZ*I6Xg1n%<|&hKpl@?yqXsgg^RT19=zECGpbG;Cd)e z9z!9e<>kc?Q3Q62?B7o!7!DFX8%ZgdnM9+2%(xYr40ir{61L%b3kwUJng-^MRRcXk zU8!ns6<@w=0OzN%7hN7-f7zRf5^EYfGkq}y&%65D+l6uqZ;2v9kxw0kt3?3L zRyJ{&LnUwD-Vb!cIy^Bs>Gt;R0oQM7X4cl^75wRq+&^tsm+g=iWxjg#>L*N7*h$J* zw6wHjwF7g!;x(eTw6uJakU)VkH(bgL0Y?S~hHYRaINI~r!zcz>mIE|W(fRpI$gEdX zR^CH!SGzp)8DyoaU!X~?Yy%3uj}A&sLF0Jc%wK}-jRGPrt3t$+Lr_3*q5K64oR+L9=uU0e zx^*k9sIw54O4fdjQq+tSE~}0NmEOPoti1eQG+W!n>Dc?D>V?hC44Bk#Jl3a;*@~9& z-_H+R$g&3hsT-75t$yB*wbua@Oi&5BQ}->Hs2KT4J9qAkiHW(5IR0<%9lb zp#{W{4$wS${`z%^FM8$@Kc>CEI7RY-FJH?v|96PR@wd&(5~--4I2&4CLs~ou$PJ^H z^9u`|U!UwXiXt?ytnBOq`}ezkJc-IuSW??sZC9!7WkbOxeK32r&0i2#t#XVVLij z`b=|v++}Ufg}_w!@kxlDsPzX!a6<;(MU2m?PmCok_FP(`9E;FX;{ zJr8gQzsp6}x*JbpdUzRJY93h33~OY#C3|O6Qd( zK#xN>lSIPB`<4-pm|S{X`z5q9tgsRUcya^OA-JNr(5 ztrGYxjoK|}R-m3$6%}W(U>*XN5WrnZ2iqJ!HXm*{8Ea&$tGgfG1L&#`u1U$r@bGt} znIBzc(LbFaeV7IPCn6=~xi1FEcA}Be?Ai0JgmK@#N3UPg#U>~B-My3g3T=bN#%9iX z0-T@ro#Tz$XeEIUlbP|2*^9hOt+$p=oq@<)=`{sNnK@CO!U{M#f zg9WJKEo(q9n5E))kslDS7bv!SHB7`QP`+Kl5!2B)SipoDW4iQ9V6|H)DOpj-gE>V- z!*JS=$D1kSp$X7>9FU2+-dR#qRJ5futs9N#{`T`IJCAUWwop^|4fJ$(Z}-r+ivE5e zB9&|7Ix;**p>qIrZHK>>n1qBx&5eUAH{OK^9ma_CM2kv5|DDgvS?^Q4g~J9BD)z;QgLwK>l)2=!Yrh+d(m3f(+$Pyg{NBjEh6Rn2h)76y zy8WP&ASf##0U&bf5j}_cNdxtHa(X}}w6&}>s^nX5y^KT8PFmW-vJ08HbwPxPt3iAD zU2DcyJh23Clk1?mQ%OnbOLKD&y93ZXj@}SMy>as&-Tv_ZkgkgZykG8|Oj61IoVStp zB%T9YH zCUvx}+j0Qh-zRP;M1vNB`2?FM9@sT*iEwWUx>|Q%pT!hyU|+1A3Le4IRRXcy7`V>>~M zNUp?0ThY7{$EG)CR>6=Spemi+OdtqblQEQISF~Il-3g5G3)xL>NLI`9(S47nWuC|C zJb`DCIASt@P>q3|BhtP?b$}uByF@^y7&=~W3GT5c3|xq2Q{AXBGl*CPS&N^LdYtacpW}a&YVZ4j)~$IpFo^(H7F1 z^z>aQayK*Gv7tvr>KGxb5F*;%<7)SxJP{A**2E+mRK79k#8*< zBpP#6R`vGI@-`+UCLY@%DK7p*uzfdoqCH$pHP3hxELLnzFLtTW{h-O;zJ1%idBasn z$;-I5I%|NxByO(Mv4?d($VhwZAsL+8d=Jd!qJoD;R(jyHvOpu6%lDr@FW$Y&!ngMe zBmxKmxpr4bkXw&xh))Ti1!X1_L}e=RJ!9o1l=o z4%Df`5)bY27awyV4`ht>uh)UEd%qP+?ct+G z=RoI-+w-5{gbtlJu^qz)U!d8Lkx;YtsWDR&5xWNshp3TX?xfx?`hJ3fQ1|ihoj;I5 zwcKP|)ra-q9e7mFRTb()RX!4n{cuE{F=;RMzaL45n2XaPWyyXo1#iEkYp9aNq} zO&5Ue(0a2#ouJUn1a#?ngL6y|6cyxBGxCoI@NLTg9aK`HN8IC!^1{nkuQne$b_|Lb znXaQP&3V(QUJ1~u^}3FS%1TSST2CMsaBlMp+a*SB-R-^QVcsb1UBE%bh*Gi|E`YLj z_V#+<6G$CB@9_|fJ24h`+9Ammg2ZA5`+n$PxSzm{>4LeKMkK*sYIAe-hmKlUSV)F= z@K-3!^?JnvRznKv>dd$${N~-8O|)w8e9ES0IN{B3^Dx4&7f*F7EQzFxTff>|L`0Mc zIGbxRH`eYM9nFsB-#IwA740x~vg%^BRby)&J#L?ag9Bk0p+O1ti5xgU2Be*pn~a0l*j7S)AfrTY(ERgkIrO5$(0G z=c*F_nMzqTm?AY58UT&bIMg*Xl!-~y`6-NY&@6N6?q1k7xDE^uhX8s|Z|HOw@1qQ$45tD{!p#haSYEI^xeawP({K@hj;OBxB2UJ4 z*;rY9#hket%ybPrh>T#Wn6IA+@Dw2|7bxYx?3#GA4Y*t(LtH3`AsRI4F5H7AhYJGe08)6@6SagiT#$D(S2=#V>}VX$>(W=4pC z5F4Nmpl`9wz%3AoBtmBZI&g1)h=sKHg!!?YL^r2F$&uaF$wpxqtould%2$ zc9*pfUsNXzly8M>b5;;RcMkca{X#;F*oJ=niV)n_7oBKb)q223W)*C|gECp)UNr|x z@-mPv(jDWVVRt}T9zK429?viPQgLo-4dTT`ZEZG$bzI?}15On}_iuNcqD;<=wxhjp z;Q}=KPuS>OTwI{kZdf&#d42$)4{q2d47$1j*}&h-Qj5n*!b2wrF$&TumN?^xQ8_6ZEPAsz#<-R3dTI{e*m5pfqW3Cdmw%IBK$efb5CeBg!dr2I)4cHs28tZ zJwy@zR!#*u7S6+=qer)%?P{I_!JI&x>^8FM@M!w}TYG07)$`i-`=2?4grt2ZQ`%H2 zN=cGLC6zIx(j*NC4alrg(Lik>84}tWjFl-ukxFQi5J^cz3YmxJby??}^*rm`&wa0T z&iV7Kb+2XLd#K;<`@OEu_5Qqv&lP|Fm9NS5$mekGKJkU+q(T1y{lT6r^pVJ{QOQH1VYsat4kTf;vQGAe*aun81FTpeNc zE#@v3^U7M0M`uD}GBJ9PmKNnjPSji0Le9;EzyA(pY@QR_?ZWD}0|ha#bZIz*Ag%q} zWMBQsUYWY;58dAcr;M|)v5`VHu(SIIUz3wwSP9q*nU`u8#d&3@+j~5M^A^a;$VfuV z*;1QNTOvP*?+^7^Q$r}b7opiR3?5apy8uV4F#nue57yt>Z@FjGb}-&!2xm5A$=|0= z2|82L`)QVc;jszrJ9H2|#Hc8WirvPzPlGa_yzA)xTE}4hXHCXhhEtuKnAi9D^{SG4 z$Nr}m;M~1oavj@6Tn>lGu~UGpMFTYq!P8Rc}$J_V?y~P5Zx9L8|gj>_ORh)p_slANxTw9j^m_<8acc5WsBP!-S+$ zU3Qt3Ef$WB6~4Z;KbI_761aE)yWULE6h3Kw?C!5#E#B=DW;}eObjDyswS>Q;$I?P@ z+qc~Ea*80-fYb29wi6bCT^T}8(Pr{fzxG=sXw>UWLRhiYh?n!PC0yU8VfLjL}F zI8?aokhV?u7y@97U)@}dr?38IzNR!;p@d_{Zsv1?!&-e#rQ8Q;3n`xuhaOs)5II}p zNR+eE?U4oksSmrOAH=RnIh=lv5e!-+;SY{|p)pCyBcLd<=BI-Y0QU1PF4$pn*}swzdwFK!|2lszGTB_h?fV9E%yIu%$j$hu@86=$;2h;!LEyA`CLTDOjlD6@z7-Da$cr_v- zB6#=ie(airlP7z!TkS6Sh9%8jv`9>~fA#8>Seg(s0(=dt`O4JtV44j<{BuvoHE}-9 zoqMyiGJDg+vhs2*RQQA6X~Tr4sNy8_AZOx&Hlgii&!OlF!@NdGMeRScF%uC>`chiZ+40=E&xjD0tff z4LEPFWA+u#OyFY{=M&3p$R&Eb(9lrfzM&3pqdP{wcsIOu(IwxmR4bE}E45f8EBJCk z9fJ0&u4DsB&=OPaFUz5{#3ypaMQ($zbs+FwXPwCO?;nB#qbFz_Qc?)F$3{MeBYax? zebW1Pbr!fWgMxxYt1kLfK8Pj+q@WG{y2vsY-jukwZXgJuN&qM09sK-sFpUS%Mgi=E z$f`MJOdwsX_etZ0In)2Ydh<`z8y}j-ZD4KON3zxj4JKi26r~u_NC`w4o!a70W54{I zy^cmmY~5LM37C8btE>0mbh`nx?=2&ud2O{9!@vBDxwZp_4 z#%V8T6KpfWXxOkeAfSecV=Bx9xsy?>arJY1EEpc;2V>6;zlfN?7?q;2h2I!}M0>@nevuFN)svy({yFWhp+o`Fw^4dp zYJ+5;O%rhkiE2sgwe@x7n6MU0jpLR*AN>(L<7qc4i{^JedDPPc`GT?!jfSN@nuP(rmq1(yB7nfd%@7ql3-*RT&)va&)E=Rf^| z{LA{fyawo?f|c0T9V*=%tWsQ7R_jXq0;#Cx?b~N=7bpNoPWwhL?q*FLwVEHR z9HlgIc#zDj`SZ=Fc4lkV=vixEIMkG$3`Ue19N{-YMhdN8%35Q@h%f{i_Vc53WlhbU z{56#B+Ev`dG8&iT*c`t(d7Pko?pKtTE3?jO@=?Rl;%e#ZU%FC;T+v^b9mnm7jm$H{ ze(XekIeb-pu!Xg?3a>Ig5}G-`pukf%Ez>>4UZ=9cz}m8|Bx@9&olxpaZ>f&8qCnL? z+we*8)Q^dqe#X<>%TP%i6tLr(0EC1m7Ih1Q+Em^-^G`ZipK5s8#;r*2U*9b%s%rpy zv=*dN@cDkQ>dNmb{yG#H`Ev1TbetoUoZb~_3(sQ*9YzYj#;y-=XqZ~zyJkO20IvC8 zmOkQ{U>2oT8+_s1$>+>719f~s_|9jchjD%kp#MI>`4zf^c+?w*K5r(>Thv7#rfeBH z0QJ8U=*Zv;9y?9f+dKVp5TFF_Nqp@lvwRqRwiTdh z;%#I>Tq6Qzt*@Nisw=h}QxjZJ&2zuJuNMtFB;ENx_OLFzn|EP9!$ol|?L+S{6VcPd z@wMB$ZijNvwfy`r-DipFc#hyby;og;ow8OLF`_GA#dPCFL+Zh(T}dxjlF5otId}TB zpzlBZLhia>QIW?$g*5@3f%7ldI%DxEjAOmd#J+dlifRW2V&ONb_xz3@T z#1OF3^MzO1KQ`YgUkSW|`e;A6BW2)4o8k1mn(bKr;`60@l#0$cE1+%Kk)lWioo+0v z>rmeo%;%a_N)J(njqydP`+Xm6jXNB3HkA1$qHVO+qb>bkm4{rmUl_-(z* zw61#vSHv9OE7B;whI?OviD78$t>pz+|OeRsN3L>whN&JP|u zcp@kLuN}X)Snb1nkJ)cu?&kL3Wo=7Y*_=B#5^&9mwg}t=#)WHLSY>nMSW(fNWoH*a zF$}RQE!dN-Z=IkV5D*|yP-^5Fy*G{9Z#qvuWT+^PE^147LME`0nriM81-^ z$G<4)owmD!WYo#LD5XG|AzJC}v<;2oN<2%v8(jYwFkmo_E6~FeI~gx8FY#?(Dz@89 z6$w%=YC3w(@5$MpaW9u=WXXX@g$8#}O4J z-U)|6Jz?X0>GI{tgJh~9Ze=55sL|dRK3h%_79T!xJ50+qdm<-t15b{F3iQ>($Bz>M z@RKTRVA zEfO_cRFkZ4zU$H}&ducwxHTU@oO7o1Tvs{#UuH6W)=q7k*TaNrw!-e>#Hh_r0#j z_zm4UYbI>2J0mCw32fc9tM}$xAu1kPC(`K|V9K*rpd1VeGRso$sh}+)V|l3dD`_@u zmU-AFJk)M?eyt%JPkEdE{@6LX6Vx2Kuw7UJaB((GX${X4|CzSMbxAt>n2He#9nMF> z;PY|jwx;H8%mT< z1gu_IVsp9C6PG=6dT|SGwRt?LwA(E9#>shEXAMH2Qc7HrB|*V)wE z+^iV;iytY|w{LFnK7GHDAf7cldTzee^r)odY?VIt!Oe`yGkJ(Rb&`X_K=>k&A^8XG z6+*h6U*lgU8jIF(CL0Ztb>iMd*?L&db*|ebzmphW_>3DES^A>#EkKUYf>**oS`~d-@!yDGx)M9=frKFOsYc?^nrv#? zC8VdKF@ZI)5*sYpu6+9WXI;B)4Ga{NIu3~sZyxXIe+a6D2Ynigf;hN%uIxQL+Qz=V z+QKXN>Q(6C!dT%!gkt$qP0a*L^G`1`qa6WKH%LYbE4c)V zN_0@-MXw6Xpex$(Tsg}pQ`K+WxN$fssiL9CV$u1?1+hYEVXjUj;8PyZd>c|NxgqJ0U@AoD=gIfuFu&Jt-RcbWI1Uksg}H?lopGtdS0;T5DgoJ1 z>LY!w8 zIimqxSXI{z>&F@YH?}8LUctW#<07Z$85|R&p7wmIS?cC?Yni!-!}3doKY5UhAo<0b z1lR_015(RtqR1vrPJ>xD<9FAOO0YflZX(&=^Qt7I>Rroq@)8XX{pPtd6L9k%1NI|u%PP@HCvrvyGvnuo! zaP}6LmJWsesI{rRvZi&bkTSsHgH15xK!?fn>eXxcu`-rUJ`3gc=5`&-uomPK{JV`- zMmOe)%Ze;By9*j{hj77QZjaX z7Dhr4XNgpyxDTZ?*V`zi{{;kXwBP000Fh1l(gmSEdRdj@yO0RVF@TpT1iSMNse+-ZbE${fKm3@gSy$N+OItCtOI z2KsBW6)RSRv}Tl_JgVMbxS6kn?aX{^3Jbq-#fm{_X3Er|+}pQ9)fSu$65cv}{0^@C z=HF|C=MKZUlLhs3yREU5NFkD38wBz+S-5aNKBJ6BvG)#)Hzhj}$|_VVoR%8;Mh!pc z(x+Mb$j zNJfgElAd$yYE>BiI={F-|FQz^!`;is$Viyx+VlLG!|9d~r?5lQ4-D_N`BsmhO(AjO0wBUNLITOkn-U@{^q+naW`c+HXv;Zd zKLvL@V$NmtjUtHkMufJEmMv2+P%Y>^R=L|&!*1RN8)p8n=otTwiQ~(nKI&^p=X?X| zyOYgny*l#Qtomgl@+jmK&|fH*ns>xTPryBgW9^LhGee9ZT_*neK1PzC#m?`*<{(r%VUa777A6LMh-?yJMNX5OFfuW2{26-|wX-0=CCJF65&M;TF8k9NAtF_Uz;Vcr|YDnzhz_T$+39*1H>FH=SQUKHp35 z{2~MA^t}ktp(km-(@erXdi=Ps!H9o|8(sMZcd2&~aaVp`6eFx0$vO#VJ>CHV4Pkr- zs!mR6Y0REmB1}M)qsQyaz>RF-puiZPG5*&pSU*Sk&VZlS7pfoK_A31T_1m}K4p)Ew zsR04{g6!w2OCblTPn^7uEGpR01-T$}RY7HhMAF;V++@<6s3=puHRpMcGxMb-W)>DX zd?%b^+v(|tXlV2zqq8aBhL!u}vT;b`pH)3`GzIMg`spGOX>_!`-+@j-5~?<-Vl6-d zI|*nd23g?1=;iJG)+%Wp79&oWaK-U*5(1d*p@9)xE%dBK#J0X~1nL@7?{6|SCIU9c zn2#h8E5Li*qyLY>+yHFZHnBZvP*N)JZ3f7EDH=x-#UK&_1+iWcF9Q_I#hZ{x3Ax$ z&D};ZZRyGTI>UIM=8N%I`?3A-EyJfCfS$1cUmtAj+}zOM;d>Bp%bEHTRb_4g(%y+@ z`*pmwQL4p-hB3p1U{jr)tAed=%FNImJ5v0X-s2)gQ&~=eWox z^vIuFKB53nX-h2kH_&c(*H|@SxntJSPX6A9)#s~FX2j`|3PmMRZGP`duWg?}?`{UbZ){wAB z+z4~Ybs`_crZ0Ba4p*A^^z|jB`q!_gbB2fx9M%?&!G|$KBdwduKPrsme$MT5{up&d zfJw9swzjKka=WsE%G&InK7E?Lv@#k|xa|q7wyc7}G=hCRD&MzH71L}AVT{c!ED`}K zkJP&kBr>lGzc&?ag2`3dnVN1aaL*?#5 ziA{>-hLjmfZKrA*8w)_13$XCfyNmb<=LvDBDM%UIWRk2EYs&RKp*T5P5qpJ)Qd^-1 zlRqe+dF~3uPnwuB*cEi=LdIJ-0+5oi7$iqb;(IA8+$_s}U2~BYK!86IbSfWlwuuKjy0>Wo~UfPbsFqvD9D%g?-6hgBrceX7XRG=~rH{VQ%J7 z8U8_#X3Q`v@u!SibgsGh^TzaOJ+_>T@m_OT74fIk81tH8;!mgHm3b$2al6I_58Z2h z#ZT=%V5MhAC^V0T=UWaOsOtVL{X+{j@efm0cap$8vcyjlfopq6*u|aIbD(Lgyfo(j ztvDK;pro*y;N%i{UYySs>gO30Wccg*d)x9K+FG;$J9%O;n-CXfOhnhv8#hj5`B4A7 z%BNbAgn$U@1RSx6Vvc__Z+*$5NEu`8&ZBF}q~xVLpL}}Zde{E*KE%}@YLZj!*HO;y zrlY3z!vtOJ^ozUc^b7*cW!^5>n35dprSiMk}d&BS}Xck>O?_k<)f5ODe{@N?PIUybV zNo4Q4LT-Vs0>OHQS`V1((Vi1})*BsK5Rs@~ReQU)UverA{cD>_Z8?yf`D_r-rQ8)1 zqt*V&<<(JKIKYo8ZtzWaFWySB%7^d+G$8)$&ezBk9f(eeBo4j(h`eRW+>d#JPJRD= za#ATg(p_9ow_yt+3do?{ho$GphC*|~Fh93II9~=>mP2rgEs0Ny-@x9W%83+wO2j;t z=pO@C%C;r1+OC4(niD=6c*MH#dXOrzslbg`>^3v)?Cv1*ysA8>BDf!Dkj^v1Q^-@7u@rzP(a;-+lQE?6{}w-dunkBdP$US5S%4J~7dHmI}8*zaMBRuPQ{rde_pQ{<+$BJ>|Z zeu2>!IV0*)YRTOfKPEEi4cd18)%D}xsj%;fnMFRt8WmjRj6-jk$g0eIOYuXavq{SS z`Qy_f3{GPB39{|HLe5_jJUJZW`$%rlM=*=%fU-rbreEV634I>UL^mb{m1nLcAo=y< z8!zp;2V_({@8J><&|Y0Sp^D+v!48WVjA83NeRAZvi?_8X8O5sm060X1=l zrlH^pkJcUJM?qf`q&rvh8bxK#D0^ohw(#9UMbq}JZAv>YX4%|Ho}5@LZ~r!iSUvFC z*2Q?<0jO9Pq$aB*EQ_JM=`&1}PaJUthMoZ-ML1u>zmCV`IAvz|*T-O$KL?b#YrCcG z?j|M01kqslH`3n?NmmB~pk57IDSMC0fYi(0f|UQC<$UZY6x9s5CmN?HNhF(+u6BhHlO_o zk#t-l=bE$ypDl#m&n-@Dbj;{F3T<0Ler)*la|1h=&&vpkh^dJm^Jz?7&UzO$yoG{i zt(lpdC$KGuQJjOQ)VJSHNjDiiSLNUi(~4XYO%idSfPUs3OLaKa<)?)q9X53MtX~cU z&t2AWPGG&>4RpY*aGR;40I~Konea&!BR?V;u__C(UEeXRZ`Cn6P6N3LPTTh8y9>~8 zq>_6XtWQ443?|M`af`9l?s0m0_m7(Q>8WJ6bk$M(0hVcnWdmpI?z+^EEH8KE^!x43 zzPUqCH&hY`^gArtrd46aQpJaFhsLaF=3?`04yWp&&l##SkNZSEOL|opcxVe#diY&{ zu&%1siCQ2WrwToDxz*6BE9)w1onNB3LpnQZZ+Bg`%o4h;Qfsf^$(XTK0L6D`{w=I* z*Is9m=Ck6xhL0^`q(UOGa}@ghuf5UtBozH=^e(6*Gw#jpdu-aT3XYi&NKj{QZ;eua zm!ZQF7X1{Y*+oWQ?1l=_AXk)0({?cnyq0K~@7CubKI zBGu-Vlthywn0UqtFbdNV78O;`RkzL%sWgy%`t=iODN|$Pt(Wc_U|Qjmx8fCe&cGe! zEQwAPtBEt+HM}{F6a{v8%9TiJI^|6G)})2{LV9JX6|N6HXOSD{ zr+WBb_H*>~b!Y8owch#z3@=(r&PAG(Br3LVW0|=+;a2?*0TYDQ;HW9O0lfSTyv2++ zd`L#q<)cehruVbE zoDk(_*}nDMSe1M8rV#94$ctT1ChLva|~K)^V4GxKphjF#c$O47{+!Uu5j#l zUQDjfOxPL_ASd04cO!QctS-0LadGn5u+neEp!$N{oxc_I1AW%aEU%i^L_eg^?tqo) z-QBA4(%bsW1=^(SJ_=gRRkfB)nrfU>YF;x0gqr2}Zq9+ty9APuPHa;mBEWXNAMT9y^O*IO8ntN)UQ-`GP^ zICbKJbL6iMH_eNVooj0?8_9j==;@Ab*vT{fV7Hbj*I6dIS4U`T0$R43A60J_{9^gi zC9(QssQUHDBL^eOIY+|BHMP{wTo<(6d!{Pejd8b{DhCZ!L}QKY^786LMRAAEg6eng z1$UiYE&X~UMV%wBccskT%W2lDHV6<{ME7c2f&lG#}c> z$tfRP9>kI|l2Iacd89SV(BJrLblPZJZp)Lf2M=QWSzYCW>FDTU_ni%8&Q(@XsomuD zMS9I$p?);CYwWj^y|CNdwUT)@7M3*6m8GE?k*;gMo=3fL>U5D)mR@lP9D3%pn(yP| zlYMcb^7 zZj#r&@$cKe-+b=Z_vB()Yfa53oMz<#yY1d~Bow9}y;IhsqOKlFT-<3~k5?;O-ptG_ zIWe*JTb3b{n{&dok9T)2E%e?Ovi)(YWGGDc`qSd&sk5_#b!8&FwZHjQo>3~1hOY)B zh0(q#_V~S2M6-Fb+PQN9!NI}ijp}TQ;@UH#Z5k&|$SIMi83pA#-a54${VHMKS~^tw z{3O*TGp)4<>7SAI=D#+-d+Mv^_-=P{I{#~fvW>(u@M^!g@bgQQkaed-P2{nL`5v9L zRM@$5XK+xYdXm(w;K<0-n3z2qC@6j_y(S;7Y{5gU zyl)6%5hn&eOwU!JAwgN=_uOc|UH+r!XukUuFAEDL;^N~!4LKyA4dcs+KC(R5W-a6} zsFGvVp|x$>wt|6hi)^co0-E=Ux%;el zZ{JoKX-<3m=us@D?PqM4n~hDT>-<<6KAug`(l?oX3~f?ZYT{Q)+G5ejgWCb-&$Er2Q9FbMxdk*S`PIIC}XVi5M0( zujMShx4({@mX#e2KFc@hmXX`#`Bu+uRA{ub=xw>#x35yaQ_GVIS|2@nG&uHlTwO;e z@_kuZQ<0k!rgtzlmrG@w+S@oTFYnu_)2E+COS**{Rfj&sT~|;u@V|2yYyVEZVZ&n6 z>RfJ#5auSxWueEvrh?7uTC~@y%Z{BpjjW1h)eQp#ta955C*E(~f8apnA)OG$MGu!uLiK8*KTTOCNt?L3mA zo_6Gh?=udN?JcIe&|BO}G(O|GA47A9u~f#&YgpVj6Mtc+u5R*LHx9 z?+Z1XRD0pt>hFOLr!FDZ`xVRB3;Xx)FaJogVYL(-{Bol=Om6r{v@!bl+ZI9NtLHaJeYT{*WCdHq@9~l@atexwpU=FCzgSX{GnQ^LHI7$| zFn<63{b$U$djk!tglTJz#m`-l&FQ)p%rySQIIV>07dRwsqM|y|<@q~JV4#vBs;q|} z3(mGEX!?SWnw{M%{f^t)IL@i4JaFj^6mf4r$T5+Tk@<{RAn`GN7V+yd5p$TA2zp+vCq6#wu!`n8-7@ljy{cB42(n0~ zQNm^(Kxl~1%&h$!E>tbbOk>~WF~8gJmzQ!=+u&67;f$>4nHh&(2QiP{w3=JH^h_~& zeInRq?H@jTcyeb4*HZizEMs3kza^PqfpR)}`ta`V%SRDJy0WtjE1JJqm4DlifQVd~ zeeHX_HA^}3qHeZj+Z)+ha>nfm!`{aG#l^KX^jzXFOXCsr2s2F9g>@NxM~|vuy)g=0 z@l8%nc9*`naZ9kkfN#Qi#*^5fT_cDQ3c(v`S${>)@7x(@|5Z|Bad9!26A>oIqGh;Q z+xjtQ$$WTZr2G5%r_s^r2!tac%2JbxZWy!22wT@jTNMyAgC9jiq-@%;f3WwybaUxV zinOi}0ok6O9`m{vw=i7^*c=z;JDi>k)I@L%^?51hw%eTzbWFYUT2`dPA;h|{;!|~X zgV)+`{gpANl4)%@M3wZysZWd}gY|Lux^h|D-dLs(Hz)2imhIW)SoQhyg%Zz|#(aB| zl1XY-3C(@`_ARac9v?B1e3xXZrLkq5sHODEObB0oUod0C{gv87hYnphefrj34i1Y( zF=iTsJMwD*2|QwAt>ccSrh~Q#ZW62t^_@j-7cX9nJ7zzyU=?MPZJ6;}Onq&2r3oKn zboJ`s(nvYRXCWKfgH6If*iV z`toJ2BPd2KL5YfnX0As1J9g-ed@TiqJ3oH>2om>u|6WF+o^5r4Vr6x{BqJ+GDN1Zn z(W0ZkF_0DWR&426K(%@EY}eYV1|m+PKt|G7gs9urNN?kKkKc2}wC`W;q@!CXvTBGA z)OvF*$#JB4(Tc(*n}$VfVbx>5m#zd4&lyDj0xVY{90Ti$i&8_Tc&}hkW;&@|5#ox4Fdd78Lf1q$9zYE>Py)n{k5f*O~2=d8h+-Hja~IrK0jKL z^5h8vqW15+5o>%fr&(vA%NZr5bb%7F7~x04maU118A~JjYg!|2W0?M%K@_5YwsLyj z4=TyUzL3_}Cillr&UTmHTGtdDkVGn!g>lC39FLKO#a=5O2ad1YTLJM zd%n8zd!)^}a(N^q3Y%LIA%|K@R&$6hfLI{aVTW4~eRM{K3et7o@<>VPX}`Hvw}L=ja5B{4YRR!CkNrYI7x9)t zvFpDQpFDZux|>!-pHk`gQDP$#v&$&qoN4?eGO4y!MRH}Pfr^%PUYyak!?DGzF;TSu zcSB%}t|DL0u@U2mKcD4zuDCjmwO?ABmR_?z z-#b?K(jQ?jc9*`pYM8!fK9Q4Onwj;*{JEgbjoI3_){#5K?ILF-%UFNjXyKwm^H}uR@~Q6Red;q@>Oen+}YW7 z`>#Vy8v0G``Sv^M>Ejm`oa16+7lO_K%4lt-KU8aYUosvrG7)&{ndeecdeK~K1LM!S zd2g}Ky;6N=BI zbo=}J?*<0Wig!4dZ4ogYZOw7*dno5n!QwQC1uZsSIPLIpr-msO1sC&lR-1c!o-Kkv z^Lko|nELwj#K``MW{c*0VP{8LGFp&>VgXZh-a3t|U%2q7;QDCtLk_uU8#gktCIQYIpXG0GOASvgVbrj^>xpPMV`%LiCrAwZY!2cFR7LQa)AU-0XVMq|cD$PHQ zj9d@kB7dg-tc80N8_C9warrv=^v|y?KR({eF|J|Uxog)*a!UI7GiUD7FpH{KcxY*b z-@SVmNl6$ZloS(l+^{@gvgYfTFY4H`i2Ml{;F_mL<$%;%GYxJ!-q-hBJbl#UOXHU) z@rx=dG1^x7r<0PBh~UtrDI3VRFSjjiYRWFZ!>X(0){S^$pnt7bSMLxwli2(Pz#6lF zH)d0PRe_R%B+=JG4(HFx%kzJgoR1uzeSXsa90uB=y}gFP@cOTB1^^3-z%}j-#Vd2# zy*8rfJUxqnO%PZ*0vH6|Tj%$b`A69HmhU!P9gnwhoJ~~3Y+TB^a%+4Rc>KQGYpIx| zr&2J`pp~H#o?t^^Pn*epxgJXa@AkIwj`>)6trv={8&-(J6`z-|B3sOb5FiV z&g%e6C}KOd0L35zEHO&but}v#y3HX!MAyxikdeBtT?tTiamg2R7_@13OA#`)vCG#9 zj>VcT8Nwr{%%;EL3#*>LI{xx;XlU}q6!qfp)}@OXDe0_DLQPXsQ-aPDmjfAv&b&7H zIv3$Iv!YLwl0@aUY11a8|CGs#-T1gf!-^oG=1nA0c=7V`a&M<)N$E{BclSa&JG;gV zeQ5=`Xr{-&8g(z_S`wAdog=b3HpDOSrZ;8{`!A}i^9TskX@rD?^yap==zEQB7890C zPC%(N-03#ngdnKApqE_YYGIzwcBf(Jk;Bz8Hl{CM1W{i}_effK`+bnQ6MuW59$7;u?#5JX89NfEC1~|@6Eb7yzPg6}M$O`-S%kI*#xl!OS^a7Y|D5phl zx@Np320L04dvAi($?0}O3uy}JS``e0{nYMSy=-MSHA|^sS)G>!8iQ4 zcer_~zgjt24!DQN`2_Yrz-yXq(a)G2Y56@n;aarR%eI#I#mr zNRix(5~4NFr1kIZI@}yCWN|@9Cl$r<8E$TFF`Mq=X|qUZz)SxmQI8G9DspXe;p)xjft&so^shU~u84LqJMIba8_kVg^yhsExkOam+5kjH72M-<%MqIiS)dr?fkOgsv zear6pcB|Ys)?MkyU|rcLRAZqdTH*}@;gs9yT3t|)1)6}sjJmEvalx@{o(m^HS6H`Owq~m$un1eVCjvMx3BCbK z8~C+%vT|g0qXP?)DDXG!pxC;!(wiZir%#_=RJWG*($e}bWdrN8%^K*~*u2j4ZX|V|`!VVKXzQ*7pWtBc^q+;omNUKSx+V_J=C$2@ zcKMtv`I^5?|0K1_`_=u)w(QQcQ^537O^wYNuDZYm=}DoOYE-%wz=4VN=nH-372(6P-jcL>~y$}=gCRM%Af5RtpexrA*b2bC!6Kt*AOdho8ikYTaAOh>jcfM}ifo6W^ZKT*h9*&+;yKUJ zN-e|yALEY>Ul^;$jH-T(nzCdZ7N!j}hG2xxQUbJm% zBq`>Giv;Vw4jtk>e-Yi2gStnvR;P91~5s<9WHia>+CHT5zACX)BtGp+WK9bma5Fh~PKsO!- z33ANJ(#$NWzP_Fgsirwi`yhyYBO@bEq3TfHKZ?cf-AUNM1cM@-#2O)NSr3Y<@%c%L z4I4HLp`1?=2trAI;LxFSsArLu@%3==!Vfbm;5eIj(?{KcUaRiBdWD)6&8e!kwpr-~ z!#oH_g9sEk%?WXFNr{Q_K(655qp7H<2x6dX8GL+PLV^WoCG6ye$Yb`XY~!~u32V+% zZrRdQ`H&;WuK&z2<{+(#9vjg=@U_G0aadS2im&B`$=U3SB+_xUUG)qHb@ObV?^lja zWtD`IHSqcAOB8IU@gy2Vo*G5nnPc9>gH)qXS63Gt%k|9tJYp~yt1ysrD0~l_RX$)R zC=yPy54X3SQB-Vte`|A&Y2Dt@Z&|&*k_C<<@qY-^0D%e?H{EG`>U49rT;#u<8&g%E zp>P4SHT&y(gGic2h8{xmMP$T`cu+f@)!;A}m%h(r61*INPb6JuYLT+CAs>Wj5sNyX zai6Hdc@(8xGvJZB3Jpi@r={r?7ZkqLjg^v9US$ zhvw?_fY#bKpC#L)WDC+s^~eT)z|F^(-n?XZm^?$T_@9?+!UR#*RdP7LZ#zMCF$$VK z0Kfq!t^U&UU!)XQUFe?^|BytcT#L9;tsiwG&t^nMG6PyE2VVfC+TN}c$SPSN>v84E z3tTDNW!kta>5XE9aMG(+#E=(*VrDMsFJ4`;0Fr1cs7OjIzP;?+B3Tn9J|F9|n?y3z zlw9P$fxfqByguBhaMF*uE>$(EYR;kbw`27bZ0<3HQt3~_QV(pH<2q~J0m_r>m+g#d`rW%! z2owtEjf~=OyLf_q3aH^P7#qirM|cs;q5kUBt`56u-aja04!}t9B*5wareX{wF^l>4 zS8<@P`?21yR&G6g>eS#vIo5}qxXYR+0w-C-93E8n*W=lV4EXWhE(*_^1Yj&RSn%{H zvVYrQ-jw_pC{*+!MU3C@n%wfZ3rin0)#t8^et+5&w^G(sY4U@=LgY}0O;_&xzZtIB zg->heS~cC>dGJw4$V+6`WMKLNu(?9eg9$421}le6>tEsh-IBkbAGlwM%PVA5O)Cb$2x#mr0cI-b_n`hfQ>~Z21v~#+mD}G;HQLjpheFuP3GiD$I+aYGr^#ZJ=lTa zWAb0qDAhk?y28rK&7Gu2f%>ZUQPW~t0l|X%zQU_X4aZ*c1B*l)@yb8nIGd}#Nd}n(-%6xy`&=QNKSt~ zN{C+w$=1;gLyf$-?JuB(C?n>Ou^qsh4aIwel<=Kap>r0&c%^CQnnPzW2Te+FB{9b( z=7H#9?z}1%m4IUG^b-l+`}H>e0Bn*&+rVD@^RH2jQDt5b0Ez%$TZ`Os=?`hf;F7~& z)rP@M$CMM3a8;AdcBwB!*`aD**x>80QVqfT&T=XspN0#Xo&SeKmuQ`0M{H`Nf@ zP>}u<8HIe5i;Onxp+fxPLr0sXGPc#2uH^v{Fgo`g7A6dEk4hzLE8~toqntL{xf5^m zo6Q~GuN0XG3bw;_Bn`%kGYSfI3zNM>ydBV}@jOO{_je`f_?7%80o zG9BTB_166R9r1IJbhlz@Tt`1DQP)SctW;m)L9zdiY!!X-Zfw$K7 zUDEF*$7sAsnZB(CVOBjuk5^&1t7OeqxjIev?VD9OSdusuFuO6iYqoDY$VNKa8+S({ zkBN1afbCo_#Xur;qdZQuulT}qhz~xT<6M+V4e^RFPVdGc>TN_pRwu%lzZ#r zt#FaSOSk;9sVl|KNd6Z5-M~#Y)X^R}@neF?D(}WgXWaj*i3g$P-}myDR;ju0@9~Iu zxkHcHD!}ZvEyhRCg^OCLJpmZ6`}U6LWO18r@AQ(@B{2&r$a}6#TQP%bj_i zB^9uj{~5w4eo@F@W?v$YZ4scyl zh~FAlug3BfFI?f(FJ6vyy$0Y2l%a}!Mi_OJ@$8SvF`eNVvYV9tQL6c5A(Jp#)MBC#se!yfS{JUsc<=3UV2Ps0jUvB}M0q*)OhPzdL4L-T;| z#>}K}iu)BD87)kit5&(s3uZ&GogEsCm{S1VyDU1 z=wh=X*V*%?riq|Bz)lc9S%GFgtIfyFtzca=8|rv}N78*(Qn(b12VYpkQ!?{1v;5b^ zY%+h;&8Yy3jAz5kmoF_=4t^fJ-jet)FmFwsaxR5;{MV7$5Y;pShr-CUdTj!oq?9zb$jYRdCsG~-NQ(Ouz#bx=W@+t0ux5Dr^eI%?N1Q1ZOicYh zCjJ51ZlqyO1QDZ;6rG7*5^`UdV9bdUbNn-HX7!xBx&;@?8U7WtB{-Nb;^TvV<6sDA z>oNIp*WaOS*R38bYEVroFc`tFl;*~4Io6)f9Jr1Yvq^mEonPB%v(JXj!$9(F+s?tN z1^{{yV5k)?jMUUaX?jJ3$L!PBuVD>Cz*eCdQXCv7p&l1>`)qEEJG%=DOo_lr)}9!u zM-Lx9N8)>=?Tgcv)*Dt`T!3QEkKyLWT@mpx}c>30C41p!(dX%P?$g+6WL zQXtX_(9K0dS0LbvdU|6byznz*l&rc&t;hy3CqXA=by~gS3iH<`e_FP5dlund1dW``ph-=e}9j_4RF|*C}ETD{TRZ?cD%%r!mW3Zs#s~kvWxG z61<0`$c-5-DL>v{*>{*R@xkq-HO<*a&NJVA4-Xgp?U!7)Zr}ETejD#_(fITA{=-FF zN-Vqn1k1gmcs9-d)vrGu9yE2h;hvoLA><<&Y1Y5nSGFH_m2YUWlYW=*9Dso+yUz~B z$1Nil4Q!teY;je`4p{#x==@iynpKB0$6eQxzVMNA32yoq8-w&kN{1QX^YWc;B$xj} zU%6wmfVGbAL51W})agCzL}bmtcz@ic=^H@bA7URg`0v#3e9knYD_tK?1 zYa!3OGjG33Ez^B5PT#rvL8qxJCd}LTu;?Kfc7umxL%KV!@7d!~CunOcia7jVnazun z-K+$G{Q`#~%6ti7r%ZzJ?QTFoE@Sh|o~3Wk$Y9t8)#fIQf!^$g_Aix+zd zf!W2yh3CM5+9v{r?$fVdza~B!j71XaD*J)Y1d)t_L?uHKQhmHq6wlxFe^@u9NZORg zEirFm*T)j!^x5z$0Z}P%MVL&`fITp)o=99U0t~~g!bt!O*Z#*E+3t&WNau@92ySW+ zBkN%$1k1&%;5t>sg#b<1lt6+g9WI&)_M_hY!l2AgAs8Oc=Z%dQcy;nr?$Pts!-T8! zR!+$9+`C*_?^c&0J#6BGE_KImogyFb(vRMLUn*kaG!@_`u2?C zNK;A}f;m*|k(8mf+>9A{Rd=k*D0hRz|qoX9zE$rLa&9~L< z-6PG;%hW%vKr*?FY!pnlb7#MZq?lMiv&H6iE2qGJrhAypKZyOe$sjKGufU%!rW4X> zV6$U6c3H4lb`^U-o=M4B{dO-eCCnORK}P%=&!&;&`>er)n+0YvqE;D7c-9VE&a_U=te&(=n z3Ym*L4#y(BnAXQCfEFVtNP-cBs7C0B!4B|V4c(-bswET%5JYB+Q~g>+t~q1HzsFoh zR5djd@hn{;YXr+wvfO{Tpg&Zbj+M1@YXoW>!B=>`|9t6TcBmnt(-q9b{};9MyslX4 z2u%Dn;X*0pGgzjmG1{%QP}l2ut;~x3Ch#JMT#&@ra>Hm{k{XwhiAhVo1i(h8b06yP z|3h)>FFwBe7jg}e7Mr^eGEzmpD1mN2{&K=u;>;jjiqDIpKuNlHa0rrW51_E8x;w|? z^`ehz^JlT^Z{t`GlgIz{3$r{A67)nz>m~BvuiF*bPBwh+^{&!Q$BQPNCGL*UWdy%? z#^iQ@v~?@4JzEYm7&~aPZ&lgPr)=+VNy*z&UA%>_JAbe9=Jna(?YfJZ{`rHXt%QCWR8YnG`B|(O=Bg9)^Tf+R{xGM56qA`o;)ur&JQ<`={yq-7lVh zj07Z=F8uuYgWFqkZkuj<(CJUZOeL?6zLFdF%8|cZQo9kc^{=O-6VR}84-+b^y2ENs-5&t9DiLuE2&3$&YEOOMXB`eQu$8$ zyzV|46@8K^Po5~su>YA6BbP(;rtL0M5{Z$%bch<`MAyQtx9_wxU=Be9ZH@e*f@%{l z9GvtzLdQ(M?TKUF*_n>_;_K^~z({YyFa2#Myl2v>#J4^u1~N+eXCIv~yzan*t~0$% z)_6_{A2pIA>@HdUn5E%urQF`dC31HiH^k3d--dN+eIIfp}Z!-J#e>~3v{&lNxJ zK}LEv`Tu^V7CA$pW_O`8HsR7<#q@{uQiUv<6Z7&kHFO;xBQ!g7zq_F|(okQ2;lhPj zWMYCT1>wYa)HuM}6W+%9h6bWx1!Vz|JW&8Q_-$cg%m#uo?S$+=WJ6FT>i1bCp#sFi zAq`QiS@b=J0xS%Ta7+-M=f$8I38MN3151>c3eZoA{zP8biwOhlKbDwhf?>$3+an2l z2EnBCZ>G63XU3OfI>$OY+`{u8~WmG2!NFE8|#G87Y-hlp3*n((lCG2@|HlI z6OQh}J@>pb4CfNm))fbh+@o6GS^~gBHma}hTDWz=k=(W9>a%R8{lCI$44yq&SEBz) zSvV3@oxYT)I+O@lXW<7()GzT2j!jPH!Kj(f4Auz^@7S>e^y%}O2$A1PA%yUS89#IG z+)PtnU*7`nJp3>zPUD?hT3>1roBzSUl>!8Xy4JDhMtzns8YX_UP|$1;7~xN+aC<%dv`7(1IFot-GW`LPL$Ua?Fh| z@#kPO1WRxMwg}IqAuf;U8WF9x*V6!y+AGJYOkOp7=bE0Lehgv={$fQ) zDq1C;ML^$i+-mE*#L386OOu}Ff$F}fsX6F$_7>q0hlg}NrTQ-?$j%)*Uc#_Mz!;S0 zb2V#lh7fSFvfnNVnkPX_bxpw!N_1IxWavBhY|yo<+DB+rFqy`{JZ>;~Q&AtHwgcOV*0P1QPT&3KYVUS}Wlo zTOK$qscj8P3}iUL1-m;PU+M8@6Ev;e(}DIBNFntQj|p*Fa_*a9a8Uci2*G!Q&_!&^ zzFM`tXhNn&yn)4II34<{RJiy%VHgu=$UPb$#g6P zte6vR6oi)zu;@iT&0yLC&>Dyzh8-pX;fv1rJy8)uyh`w1eTtW`IhD^r^|r>0W*@J8xw_Hrml{5ylrv%wD_jYD&!CS2IaNZ zC`>p09$$ex;aP+BhE>lov~d4yax_I`GVU5pCGJHKom{cIhz(7sDFwYj;+$0)H;?YZ z4%#j4Sx88As8WcM)N>l-odnSd)d0!JLmDM)vB?CgYBN}nJ)b9}Tlb>*M!RIO#(D

f_CAFlv|>^*pJGl?G|xUr?h5F0ZwDXA3qzLkZg!(8jI zULlE{e0X^H!Q;mtkutW@(>Lerj$Qt0`+Lq(!gHB{q@toiQdU+LJ$4MMo{V(a#DoH= zaba<>2jckwZthaRpG+7%Dyym-6NVm@aF6CF#FV;ZBqg1&wB*I6@PqEO9ZK~mUkG;; zrDNpkM!~fQnKy5)w{}jQT!zWsX1I|X4LB491qG1Ay#4(t5Ccep)E3Orp3J*<@5YPo zkaIGxeSf9iAS5g-tEtI=MQ~5bW2qbRXDL*vw|o2_m55t

YL*>{3@(N5^ydhdVn2 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 Inb>XqAKT{c`2YX_ diff --git a/docs/_static/core-p8-set.png b/docs/_static/core-p8-set.png index 83e458b270a0f7fa389d280eb9c7842a4a24d888..ffb97ceeb68defcc9b2a209be036298675e1da25 100644 GIT binary patch literal 39548 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 literal 27332 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 z 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 bChyx8bLed7N4F}thDs3;rV9NoI6n7pE>D{* diff --git a/docs/_static/djangocache-get.png b/docs/_static/djangocache-get.png index 28bbb5e835a30f24ed2d7ad93a76889a3f5b998b..5350e8c9cd747a1f077ed35dd21c84ae0781046a 100644 GIT binary patch literal 27696 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 literal 25462 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%0DUmf#OMN(WH}Kk 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 UL+yzrXqhyFr>mdKI;Vst0C1E&0RR91 diff --git a/docs/_static/djangocache-set.png b/docs/_static/djangocache-set.png index e79cdd9f465ab7d704e77218b9afea4b394bb135..dcd41d1fbc1969206a8a4680a424b54324b950fa 100644 GIT binary patch literal 26325 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 YD?6JnwPt!I`7Vue)Mh_C-qs!Y3BlS&>;M1& literal 26663 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 Date: Mon, 12 Sep 2016 10:58:12 -0700 Subject: [PATCH 044/550] Bug Fix: store size=0 for binary values in the SQLite database --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index 9b8c2cf..7d64cf9 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -165,7 +165,7 @@ def store(self, value, read): return 0, MODE_RAW, None, value elif type_value is BytesType: if len(value) < _threshold: - return len(value), MODE_RAW, None, sqlite3.Binary(value) + return 0, MODE_RAW, None, sqlite3.Binary(value) else: filename, full_path = self.filename() From 408698a44441bfdbf299107ad6768c2676051111 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 12 Sep 2016 11:35:25 -0700 Subject: [PATCH 045/550] Update docs for version 2. --- README.rst | 4 +- docs/cache-benchmarks.rst | 116 ++++++++++++++++---------------- docs/djangocache-benchmarks.rst | 61 +++++++++-------- docs/index.rst | 4 +- 4 files changed, 95 insertions(+), 90 deletions(-) diff --git a/README.rst b/README.rst index a2281c5..8df0242 100644 --- a/README.rst +++ b/README.rst @@ -12,8 +12,8 @@ to leverage empty disk space for caching? Django is Python's most popular web framework and ships with several caching backends. Unfortunately the file-based cache in Django is essentially broken. The culling method is random and large caches repeatedly scan a cache -directory which slows linearly with growth. Should it really take ~60ms to -store a key in a cache with a thousand items? +directory which slows linearly with growth. Can you really allow it to take +sixty milliseconds to store a key in a cache with a thousand items? In Python, we can do better. And we can do it in pure-Python! diff --git a/docs/cache-benchmarks.rst b/docs/cache-benchmarks.rst index 37b9c68..00327c4 100644 --- a/docs/cache-benchmarks.rst +++ b/docs/cache-benchmarks.rst @@ -3,24 +3,25 @@ DiskCache Cache Benchmarks Accurately measuring performance is a difficult task. The benchmarks on this page are synthetic in the sense that they were designed to stress getting, -setting, and deleting items repeatedly. Measurements in production system are -much harder to reproduce reliably. So take the following data with a grain of -salt. A stated feature of :doc:`DiskCache ` is performance so we would +setting, and deleting items repeatedly. Measurements in production systems are +much harder to reproduce reliably. So take the following data with a `grain of +salt`_. A stated feature of :doc:`DiskCache ` is performance so we would be remiss not to produce this page with comparisons. The source for all benchmarks can be found under the "tests" directory in the source code repository. Measurements are reported by percentile: median, 90th percentile, 99th percentile, and maximum along with total time and miss -rate. The average is not reported as its less useful in response-time type +rate. The average is not reported as its less useful in response-time scenarios. Each process in the benchmark executes 100,000 operations with ten times as many sets as deletes and ten times as many gets as sets. Each comparison includes `Memcached`_ and `Redis`_ with default client and -server settings. Note that these systems work differently as they communicate +server settings. Note that these backends work differently as they communicate over the localhost network. The also require a server process running and maintained. All keys and values are short byte strings to reduce the network impact. +.. _`grain of salt`: https://en.wikipedia.org/wiki/Grain_of_salt .. _`Memcached`: http://memcached.org/ .. _`Redis`: http://redis.io/ @@ -37,7 +38,7 @@ Get Above displays cache access latency at three percentiles. Notice the performance of :doc:`DiskCache ` is faster than highly optimized -memory backed server solutions. +memory-backed server solutions. Set ... @@ -67,10 +68,10 @@ Timings for diskcache.Cache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 20.981us 25.988us 35.048us 438.929us 1.857s - set 9021 0 323.057us 480.175us 580.072us 1.174ms 3.032s - delete 1012 104 248.909us 283.003us 416.994us 720.978us 232.533ms - Total 98999 5.122s + get 88966 9705 17.881us 28.849us 41.962us 472.069us 1.701s + set 9021 0 300.884us 338.078us 394.106us 658.989us 2.736s + delete 1012 104 261.068us 299.931us 338.078us 598.907us 248.081ms + Total 98999 4.686s ========= ========= ========= ========= ========= ========= ========= ========= The generated workload includes a ~1% cache miss rate. All items were stored @@ -81,10 +82,10 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 15.974us 23.842us 33.855us 165.224us 1.523s - set 9021 0 287.056us 433.922us 520.945us 7.173ms 2.698s - delete 1012 104 222.206us 261.068us 360.012us 592.947us 210.354ms - Total 98999 4.432s + get 88966 9705 15.974us 28.133us 41.962us 522.852us 1.605s + set 9021 0 281.096us 318.050us 388.145us 5.438ms 2.537s + delete 1012 104 237.942us 283.003us 345.945us 2.058ms 231.609ms + Total 98999 4.374s ========= ========= ========= ========= ========= ========= ========= ========= The high maximum store latency is likely an artifact of disk/OS interactions. @@ -94,10 +95,10 @@ Timings for diskcache.FanoutCache(shards=8, timeout=0.025) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 15.974us 23.127us 33.140us 786.066us 1.514s - set 9021 0 287.056us 429.869us 523.090us 2.006ms 2.685s - delete 1012 104 226.974us 261.068us 333.071us 540.018us 211.712ms - Total 98999 4.411s + get 88966 9705 15.974us 27.895us 41.008us 562.906us 1.570s + set 9021 0 281.811us 316.858us 398.159us 1.189ms 2.526s + delete 1012 104 240.803us 283.003us 321.150us 499.964us 229.842ms + Total 98999 4.326s ========= ========= ========= ========= ========= ========= ========= ========= Notice the low overhead of the :class:`FanoutCache @@ -109,10 +110,10 @@ Timings for pylibmc.Client ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 26.941us 30.041us 40.054us 140.905us 2.426s - set 9021 0 28.133us 31.948us 41.962us 363.827us 263.075ms - delete 1012 104 25.988us 29.802us 36.001us 52.929us 27.113ms - Total 98999 2.716s + get 88966 9705 25.988us 30.041us 41.962us 269.890us 2.407s + set 9021 0 28.133us 31.948us 45.061us 88.930us 262.482ms + delete 1012 104 25.988us 29.087us 39.101us 65.804us 27.031ms + Total 98999 2.697s ========= ========= ========= ========= ========= ========= ========= ========= Memcached performance is low latency and very stable. @@ -122,13 +123,14 @@ Timings for redis.StrictRedis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 45.061us 46.015us 72.956us 198.126us 4.095s - set 9021 0 45.061us 46.968us 74.148us 154.018us 420.533ms - delete 1012 104 44.107us 46.015us 72.002us 106.812us 46.097ms - Total 98999 4.562s + get 88966 9705 45.061us 49.114us 77.009us 197.887us 4.171s + set 9021 0 46.015us 50.068us 77.963us 179.052us 429.199ms + delete 1012 104 44.823us 56.982us 77.009us 104.189us 47.746ms + Total 98999 4.648s ========= ========= ========= ========= ========= ========= ========= ========= -Redis performance is roughly half that of Memcached. +Redis performance is roughly half that of Memcached. :doc:`DiskCache ` +performs better than Redis for get operations through the 99th percentile. Concurrent Access ----------------- @@ -174,74 +176,74 @@ Timings for diskcache.Cache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 71386 17.166us 25.034us 38.147us 260.115us 13.260s - set 71530 785 289.917us 513.077us 19.827ms 6.249s 224.371s - delete 7916 884 226.021us 282.049us 19.075ms 2.034s 13.595s - Total 791992 251.226s + get 712546 72929 16.928us 29.802us 45.061us 517.130us 13.617s + set 71530 0 303.030us 360.966us 36.302ms 6.251s 269.090s + delete 7916 773 265.837us 330.925us 35.141ms 1.339s 17.652s + Total 791992 300.358s ========= ========= ========= ========= ========= ========= ========= ========= Notice the unacceptably high maximum store and delete latency. Without sharding, cache writers block each other. By default :class:`Cache ` objects raise a timeout error after sixty seconds. -Also note the cache store miss rate. That indicates that two caches tried to -store the same key concurrently and one aborted because the other -finished. This behavior occurs also with deletes. - ========= ========= ========= ========= ========= ========= ========= ========= Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 71119 20.027us 36.955us 66.996us 24.933ms 16.944s - set 71530 2475 219.107us 1.365ms 8.956ms 131.323ms 60.542s - delete 7916 1021 174.046us 1.305ms 8.974ms 80.959ms 6.115s - Total 791992 83.601s + get 712546 72975 17.166us 34.094us 73.195us 8.381ms 15.575s + set 71530 0 228.882us 1.421ms 19.039ms 333.486ms 79.159s + delete 7916 784 198.126us 1.385ms 19.165ms 107.130ms 8.838s + Total 791992 103.572s ========= ========= ========= ========= ========= ========= ========= ========= -Here :class:`FanoutCache ` imposes a one second limit on -all operations. That reduces the maximum latency by a factor of ten. The cache -store miss rate increased as a result. To mitigate the increase, four shards -were used. +Here :class:`FanoutCache ` uses four shards to +distribute writes. That reduces the maximum latency by a factor of ten. Note +the miss rate is variable due to the interleaved operations of concurrent +workers. ========= ========= ========= ========= ========= ========= ========= ========= Timings for diskcache.FanoutCache(shards=8, timeout=0.025) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 71255 33.855us 51.975us 87.976us 13.653ms 24.948s - set 71530 2032 267.982us 1.332ms 3.695ms 27.686ms 38.919s - delete 7916 953 205.040us 1.236ms 3.542ms 26.526ms 3.443s - Total 791992 67.310s + get 712546 70780 23.127us 45.061us 86.069us 7.667ms 19.697s + set 71530 31 257.015us 1.410ms 8.780ms 27.772ms 51.284s + delete 7916 767 219.822us 1.366ms 8.804ms 26.998ms 5.474s + Total 791992 76.455s ========= ========= ========= ========= ========= ========= ========= ========= With one shard allocated per worker and a low timeout, the maximum latency is -more reasonable. Notice also a decrease in the cache store miss rate. +more reasonable and corresponds to the specified 25 millisecond timeout. Some +set and delete operations were therefore canceled and recorded as cache +misses. The miss rate due to timeout is less than 0.05%. ========= ========= ========= ========= ========= ========= ========= ========= Timings for pylibmc.Client ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72032 81.062us 101.089us 116.110us 524.998us 59.352s - set 71530 0 82.970us 102.997us 118.017us 466.824us 6.087s - delete 7916 787 79.155us 100.136us 113.964us 190.973us 649.433ms - Total 791992 66.089s + get 712546 72146 83.208us 105.143us 120.878us 520.945us 61.320s + set 71530 0 85.115us 107.050us 123.024us 458.002us 6.285s + delete 7916 792 82.016us 103.951us 119.925us 298.977us 673.505ms + Total 791992 68.279s ========= ========= ========= ========= ========= ========= ========= ========= Memcached performance is low latency and stable even under heavy load. Notice that cache gets are half as fast in total as compared with :class:`FanoutCache -`. +`. The superior performance of get operations put the +overall performance of :doc:`DiskCache ` within ten percent of +Memcached. ========= ========= ========= ========= ========= ========= ========= ========= Timings for redis.StrictRedis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72543 136.852us 168.085us 200.987us 1.134ms 99.883s - set 71530 0 137.091us 169.039us 201.941us 346.899us 10.089s - delete 7916 801 134.945us 165.939us 198.841us 1.100ms 1.098s - Total 791992 111.070s + get 712546 72652 141.144us 174.999us 210.047us 931.978us 103.515s + set 71530 0 142.097us 174.999us 211.000us 623.941us 10.457s + delete 7916 811 139.952us 172.138us 205.994us 288.963us 1.138s + Total 791992 115.110s ========= ========= ========= ========= ========= ========= ========= ========= Redis performance is roughly half that of Memcached. Beware the impact of diff --git a/docs/djangocache-benchmarks.rst b/docs/djangocache-benchmarks.rst index 7118d81..5a1a36b 100644 --- a/docs/djangocache-benchmarks.rst +++ b/docs/djangocache-benchmarks.rst @@ -13,8 +13,8 @@ A survey of repositories on Github showed a diversity of cached values. Among those observed values were: 1. Processed text, most commonly HTML. The average HTML page size in 2014 was - 59KB. Javascript assets total an average of 295KB and images range - dramatically but average 1.2MB. + 59KB. Javascript assets totalled an average of 295KB and images range + dramatically but averaged 1.2MB. 2. QuerySets, the building blocks of the Django ORM. 3. Numbers, settings, and labels. Generally small values that vary in how often they change. @@ -113,7 +113,7 @@ Set Not displayed above is the filebased cache backend. At all percentiles, the latency exceeded five milliseconds. Timing data is available below. Though -:doc:`DiskCache ` is the slowest its latency remains competitive. +:doc:`DiskCache ` is the slowest, its latency remains competitive. Delete ...... @@ -136,10 +136,10 @@ Timings for locmem ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 140750 35.048us 56.982us 59.128us 10.045ms 28.172s - set 71530 0 36.955us 38.147us 43.154us 9.984ms 2.659s - delete 7916 0 31.948us 34.094us 36.001us 9.987ms 267.255ms - Total 791992 31.099s + get 712546 140750 35.048us 56.982us 59.128us 8.609ms 28.325s + set 71530 0 36.955us 38.147us 46.015us 6.582ms 2.670s + delete 7916 0 31.948us 34.809us 36.955us 2.065ms 255.893ms + Total 791992 31.252s ========= ========= ========= ========= ========= ========= ========= ========= Notice the high cache miss rate. This reflects the isolation of local memory @@ -151,10 +151,10 @@ Timings for memcached ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 68969 87.976us 101.089us 113.010us 449.181us 62.615s - set 71530 0 92.030us 105.143us 117.779us 442.982us 6.565s - delete 7916 0 87.023us 99.897us 113.010us 206.947us 682.936ms - Total 791992 69.863s + get 712546 69192 88.930us 102.043us 123.978us 917.912us 63.269s + set 71530 0 92.030us 106.096us 127.077us 804.901us 6.604s + delete 7916 0 87.023us 100.136us 122.070us 201.941us 687.053ms + Total 791992 70.560s ========= ========= ========= ========= ========= ========= ========= ========= Memcached performance is low latency and very stable. @@ -164,40 +164,43 @@ Timings for redis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 68854 171.900us 211.000us 250.101us 5.437ms 125.218s - set 71530 0 179.052us 216.007us 255.108us 5.327ms 13.051s - delete 7916 781 154.018us 190.020us 230.074us 1.309ms 1.253s - Total 791992 139.522s + get 712546 68891 174.046us 213.146us 251.055us 1.084ms 126.502s + set 71530 0 179.052us 216.007us 252.962us 478.983us 13.056s + delete 7916 770 156.879us 193.119us 227.213us 293.970us 1.268s + Total 791992 140.826s ========= ========= ========= ========= ========= ========= ========= ========= -Redis performance is roughtly half that of Memcached. But notice also the -maximum latency is a magnitude larger. +Redis performance is roughtly half that of Memcached. Beware the impact of +persistence settings on your Redis performance. Depending on your use of +logging and snapshotting, maximum latency may increase significantly. ========= ========= ========= ========= ========= ========= ========= ========= Timings for diskcache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 70313 50.068us 67.949us 102.043us 14.113ms 35.382s - set 71530 0 355.005us 1.459ms 3.817ms 31.551ms 45.698s - delete 7916 0 240.088us 1.330ms 3.665ms 26.498ms 3.785s - Total 791992 84.865s + get 712546 68585 35.048us 61.989us 107.050us 11.898ms 28.819s + set 71530 0 324.011us 1.491ms 8.872ms 36.179ms 56.072s + delete 7916 0 254.154us 1.410ms 8.748ms 27.164ms 5.651s + Total 791992 90.542s ========= ========= ========= ========= ========= ========= ========= ========= :class:`DjangoCache ` defaults to using eight shards -with a 25 millisecond timeout. The total cache time for all operations is only -20% slower than Memcached. Cache access is in aggregate twice as fast. +with a 25 millisecond timeout. Notice that cache get operations are in +aggregate twice as fast as Memcached. And total cache time for all operations +is only 30% slower. ========= ========= ========= ========= ========= ========= ========= ========= Timings for filebased ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712580 123599 97.990us 144.958us 257.015us 15.342ms 75.490s - set 71539 0 5.274ms 6.261ms 7.501ms 26.983ms 376.789s - delete 7873 0 139.952us 235.081us 398.874us 1.394ms 1.218s - Total 791992 453.496s + get 712598 99964 101.805us 171.900us 365.973us 5.407ms 83.088s + set 71557 0 7.903ms 10.250ms 12.787ms 34.464ms 578.779s + delete 7837 0 200.987us 346.899us 596.046us 1.250ms 1.736s + Total 791992 663.603s ========= ========= ========= ========= ========= ========= ========= ========= -Notice the higher cache miss rate. This reflects the cache's random culling -strategy. +Notice the higher cache miss rate. That's a result of the cache's random +culling strategy. Get and set operations also take two to seven times longer in +aggregate as compared with :class:`DjangoCache `. diff --git a/docs/index.rst b/docs/index.rst index 10f840b..1cbb2dc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,8 +12,8 @@ to leverage empty disk space for caching? Django is Python's most popular web framework and ships with several caching backends. Unfortunately the file-based cache in Django is essentially broken. The culling method is random and large caches repeatedly scan a cache -directory which slows linearly with growth. Should it really take ~60ms to -store a key in a cache with a thousand items? +directory which slows linearly with growth. Can you really allow it to take +sixty milliseconds to store a key in a cache with a thousand items? In Python, we can do better. And we can do it in pure-Python! From 676b752ea2139d5000060f3bcd9391b90551df51 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 12 Sep 2016 11:36:23 -0700 Subject: [PATCH 046/550] Bump version to 2.0.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index d470abd..704dc51 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '1.7.0' -__build__ = 0x010700 +__version__ = '2.0.0' +__build__ = 0x020000 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 377a9f7f55993e21f2fd087bfc86fe7b569e32fa Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 12 Sep 2016 11:42:48 -0700 Subject: [PATCH 047/550] Use next(...) rather than .next() for Python 3 --- diskcache/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 7d64cf9..1a440de 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1282,14 +1282,14 @@ def _iter(self, ascending=True): def __iter__(self): "Iterate keys in cache including expired items." iterator = self._iter() - iterator.next() + next(iterator) return iterator def __reversed__(self): "Reverse iterate keys in cache including expired items." iterator = self._iter(ascending=False) - iterator.next() + next(iterator) return iterator From d069e43dcac7a2cdda0a51d95215be2067833cf5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 12 Sep 2016 11:44:57 -0700 Subject: [PATCH 048/550] Bump version to 2.0.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 704dc51..c2303d7 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '2.0.0' -__build__ = 0x020000 +__version__ = '2.0.1' +__build__ = 0x020001 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 7d1397be0b415810326e85d7c2532b647c06a41f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 12 Sep 2016 11:49:20 -0700 Subject: [PATCH 049/550] Fixes for doc8 --- docs/tutorial.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 73a8190..1d2297b 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -275,7 +275,7 @@ timeout. :class:`FanoutCache ` will never raise a The example above creates a cache in the local ``/tmp/mycachedir`` directory with four shards and a one second timeout. Operations will attempt to abort if -they take longer than one second. +they take longer than one second. The remaining API of :class:`FanoutCache ` matches :class:`Cache ` as described above. @@ -318,8 +318,8 @@ The API of :class:`DjangoCache ` is a superset of the functionality described in the `Django documentation on caching`_ and includes many :class:`FanoutCache ` features. -:class:`DjangoCache ` also works well with `X-Sendfile` and -`X-Accel-Redirect` headers. +:class:`DjangoCache ` also works well with `X-Sendfile` +and `X-Accel-Redirect` headers. :: @@ -423,10 +423,10 @@ tradeoffs for accessing and storing items. All clients accessing the cache are expected to use the same eviction policy. The policy can be set during initialization using a keyword argument. - >>> cache = Cache('/tmp/mycachedir') + >>> cache = Cache('/tmp/mydir') >>> cache.eviction_policy u'least-recently-stored' - >>> cache = Cache('/tmp/mycachedir', eviction_policy=u'least-frequently-used') + >>> cache = Cache('/tmp/mydir', eviction_policy=u'least-frequently-used') >>> cache.eviction_policy u'least-frequently-used' >>> cache.reset('eviction_policy', u'least-recently-used') From 9948c239d5aae6bd0b63e852b83a5e8d35805dee Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 12 Sep 2016 11:51:19 -0700 Subject: [PATCH 050/550] Bump version to 2.0.2 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index c2303d7..c9f89d5 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '2.0.1' -__build__ = 0x020001 +__version__ = '2.0.2' +__build__ = 0x020002 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From bff2d6b6ab2d9c07445bd5e11fad434d3b272bf1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 24 Nov 2016 15:04:11 -0800 Subject: [PATCH 051/550] Add pop method delete item for key and return value --- diskcache/core.py | 60 +++++++++++++++++++++++++++++++++++++ diskcache/djangocache.py | 22 ++++++++++++++ diskcache/fanout.py | 32 ++++++++++++++++++++ tests/test_core.py | 62 +++++++++++++++++++++++++++++++++++++++ tests/test_djangocache.py | 13 ++++++++ tests/test_fanout.py | 37 +++++++++++++++++++++++ 6 files changed, 226 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index 1a440de..a783ead 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -953,6 +953,66 @@ def __contains__(self, key): return expire_time is None or time.time() < expire_time + def pop(self, key, default=None, expire_time=False, tag=False): + """Remove corresponding item for `key` from cache and return value. + + If `key` is missing, return `default`. + + Operation is atomic. Concurrent operations will be serialized. + + :param key: key for item + :param default: value to return if key is missing (default None) + :param bool expire_time: if True, return expire_time in tuple + (default False) + :param bool tag: if True, return tag in tuple (default False) + :return: value for item or default if key not found + :raises Timeout: if database timeout expires + + """ + db_key, raw = self._disk.put(key) + select = ( + 'SELECT rowid, expire_time, tag, mode, filename, value' + ' FROM Cache WHERE key = ? AND raw = ?' + ) + + if expire_time and tag: + default = (default, None, None) + elif expire_time or tag: + default = (default, None) + + with self._transact() as (sql, cleanup): + rows = sql(select, (db_key, raw)).fetchall() + + if not rows: + return default + + (rowid, db_expire_time, db_tag, mode, filename, db_value), = rows + + sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) + cleanup(filename) + + if db_expire_time is not None and db_expire_time < time.time(): + return default + + try: + value = self._disk.fetch(mode, filename, db_value, False) + except IOError as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + return default + else: + raise + + 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): """Delete corresponding item for `key` from cache. diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 75c5d9a..2af0865 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -109,6 +109,28 @@ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, return self._cache.set(key, value, timeout, read, tag, retry) + def pop(self, key, default=None, version=None, expire_time=False, + tag=False, retry=True): + """Remove corresponding item for `key` from cache and return value. + + If `key` is missing, return `default`. + + Operation is atomic. Concurrent operations will be serialized. + + :param key: key for item + :param default: return value if key is missing (default None) + :param int version: key version number (default None, cache parameter) + :param float expire_time: if True, return expire_time in tuple + (default False) + :param tag: if True, return tag in tuple (default False) + :param bool retry: retry if database timeout expires (default True) + :return: value for item if key is found else default + + """ + key = self.make_key(key, version=version) + return self._cache.pop(key, default, expire_time, tag, retry) + + def delete(self, key, version=None, retry=True): """Delete a key from the cache, failing silently. diff --git a/diskcache/fanout.py b/diskcache/fanout.py index fe114f8..0d9210f 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -250,6 +250,38 @@ def __contains__(self, key): return key in self._shards[index] + def pop(self, key, default=None, expire_time=False, tag=False, + retry=False): + """Remove corresponding item for `key` from cache and return value. + + If `key` is missing, return `default`. + + Operation is atomic. Concurrent operations will be serialized. + + :param key: key for item + :param default: return value if key is missing (default None) + :param float expire_time: if True, return expire_time in tuple + (default False) + :param tag: if True, return tag in tuple (default False) + :param bool retry: retry if database timeout expires (default False) + :return: value for item if key is found else default + + """ + index = hash(key) % self._count + pop_func = self._shards[index].pop + + while True: + try: + return pop_func( + key, default=default, expire_time=expire_time, tag=tag, + ) + except Timeout: + if retry: + continue + else: + return default + + def delete(self, key, retry=False): """Delete corresponding item for `key` from cache. diff --git a/tests/test_core.py b/tests/test_core.py index 032073d..650f3f1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -333,6 +333,68 @@ def test_get_ioerror_slow_path(cache): cache.get(0) +@setup_cache +def test_pop(cache): + assert cache.incr('alpha') == 1 + assert cache.pop('alpha') == 1 + assert cache.get('alpha') is None + assert cache.check() == [] + + assert cache.set('alpha', 123, expire=1, tag='blue') + assert cache.pop('alpha', tag=True) == (123, 'blue') + + assert cache.set('beta', 456, expire=0, tag='green') + time.sleep(0.01) + assert cache.pop('beta', 'dne') == 'dne' + + assert cache.set('gamma', 789, tag='red') + assert cache.pop('gamma', expire_time=True, tag=True) == (789, None, 'red') + + assert cache.pop('dne') is None + + assert cache.set('delta', 210) + assert cache.pop('delta', expire_time=True) == (210, None) + + +@setup_cache +def test_pop_ioerror(cache): + assert cache.set(0, 0) + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.ENOENT + fetch.side_effect = io_error + + with mock.patch.object(cache, '_disk', disk): + assert cache.pop(0) is None + + +@setup_cache +@nt.raises(IOError) +def test_pop_ioerror_eacces(cache): + assert cache.set(0, 0) + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.EACCES + fetch.side_effect = io_error + + with mock.patch.object(cache, '_disk', disk): + cache.pop(0) + + @setup_cache def test_delete(cache): cache[0] = 0 diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 3c4fe2d..52089f2 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -894,3 +894,16 @@ def test_evict(self): self.assertEqual(cache.evict(3), 25) for num in range(0, 100, 4): self.assertEqual(cache.get(num), num) + + def test_pop(self): + cache.clear() + for num in range(5): + cache.set(num, num, timeout=None) + self.assertEqual(cache.pop(0), 0) + self.assertEqual(cache.pop(0), None) + self.assertEqual(cache.pop(0, 1), 1) + self.assertEqual(cache.pop(0, default=1), 1) + self.assertEqual(cache.pop(1, expire_time=True), (1, None)) + self.assertEqual(cache.pop(2, tag=True), (2, None)) + self.assertEqual(cache.pop(3, expire_time=True, tag=True), (3, None, None)) + self.assertEqual(cache.pop(4, retry=False), 4) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index fdec731..408ea5d 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -252,6 +252,43 @@ def test_get_timeout_retry(cache): assert cache.get(0, retry=True) == 0 +@setup_cache +def test_pop(cache): + for num in range(100): + cache[num] = num + + for num in range(100): + assert cache.pop(num) == num + + +@setup_cache +def test_pop_timeout(cache): + shards = mock.Mock() + shard = mock.Mock() + pop_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.pop = pop_func + pop_func.side_effect = dc.Timeout + + with mock.patch.object(cache, '_shards', shards): + assert cache.pop(0) is None + + +@setup_cache +def test_pop_timeout_retry(cache): + shards = mock.Mock() + shard = mock.Mock() + pop_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.pop = pop_func + pop_func.side_effect = [dc.Timeout, 0] + + with mock.patch.object(cache, '_shards', shards): + assert cache.pop(0, retry=True) == 0 + + @setup_cache def test_delete_timeout(cache): shards = mock.Mock() From 7208c6861a8086173279784e06ae423933d1fb6c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 24 Nov 2016 16:12:42 -0800 Subject: [PATCH 052/550] Add tutorial docs for pop method --- docs/tutorial.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 1d2297b..4aef843 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -157,6 +157,23 @@ Increment and decrement operations are atomic and assume the value may be stored in a SQLite column. Most builds that target machines with 64-bit pointer widths will support 64-bit signed integers. +Like :meth:`delete ` and :meth:`get +`, the method :meth:`pop ` can be +used to delete an item in the cache and return its value. + + >>> cache.pop(u'alice') + 1 + >>> cache.pop(u'does not exist', default=u'error') + u'error' + >>> cache.set(u'dave', 0, tag=u'admin') + >>> cache.pop(u'dave', expire_time=True, tag=True) + (0, None, u'admin') + +The :meth:`pop ` operation is atomic and using :meth:`incr +` together is an accurate method for counting and dumping +statistics in long-running systems. Unlike :meth:`get ` +the `read` argument is not supported. + Another three methods remove items from the cache. >>> cache.reset('cull_limit', 0) # Disable automatic evictions. From f0e9b71fdc747730b261e33d21092110757fe785 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 24 Nov 2016 16:13:46 -0800 Subject: [PATCH 053/550] Bump version to 2.1.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index c9f89d5..2a3f592 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '2.0.2' -__build__ = 0x020002 +__version__ = '2.1.0' +__build__ = 0x020100 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From c7aeb36d60fc4c1c847875e62f64d9ca6cd8a64f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 15:29:36 -0800 Subject: [PATCH 054/550] Add testserver to allowed hosts for Django cache testing --- tests/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/settings.py b/tests/settings.py index 3fefb25..12b65e2 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -25,7 +25,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = [u'testserver'] # Application definition From fb2b204901da63fd53f4e704612b60b3feb1b2fd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 15:43:04 -0800 Subject: [PATCH 055/550] Improve tutorial snippet about pop method --- docs/tutorial.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 4aef843..f6d79a5 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -163,9 +163,9 @@ used to delete an item in the cache and return its value. >>> cache.pop(u'alice') 1 - >>> cache.pop(u'does not exist', default=u'error') - u'error' - >>> cache.set(u'dave', 0, tag=u'admin') + >>> cache.pop(u'dave', default=u'does not exist') + u'does not exist' + >>> cache.set(u'dave', 0, expire=None, tag=u'admin') >>> cache.pop(u'dave', expire_time=True, tag=True) (0, None, u'admin') From 9bf701500f9a39d0c919db0e141437a2f19a222d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 16:40:03 -0800 Subject: [PATCH 056/550] Add appveyor setup file --- appveyor.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..a0a17df --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,20 @@ +environment: + + matrix: + + - PYTHON: "C:\\Python27" + - PYTHON: "C:\\Python34" + - PYTHON: "C:\\Python35" + - PYTHON: "C:\\Python27-x64" + - PYTHON: "C:\\Python34-x64" + - PYTHON: "C:\\Python35-x64" + +install: + + - "%PYTHON%\\python.exe -m pip install tox nose mock django" + +build: off + +test_script: + + - "%PYTHON%\\python.exe setup.py test" From 19c960378c66837dd84ff59ec195767904b85cb9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 17:00:36 -0800 Subject: [PATCH 057/550] Change appveyor to invoke nose --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a0a17df..897cba3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,10 +11,10 @@ environment: install: - - "%PYTHON%\\python.exe -m pip install tox nose mock django" + - "%PYTHON%\\python.exe -m pip install nose mock django" build: off test_script: - - "%PYTHON%\\python.exe setup.py test" + - "%PYTHON%\\python.exe -m nose" From 6af2213f4e616794db6f8eea582543dd7fd2c902 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 17:06:54 -0800 Subject: [PATCH 058/550] Add verbose switch to nose tests for appveyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 897cba3..fcb9fc4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,4 +17,4 @@ build: off test_script: - - "%PYTHON%\\python.exe -m nose" + - "%PYTHON%\\python.exe -m nose -v" From 678f41585de86932cb54e8a793a7d382a6df3a7c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 20:57:20 -0800 Subject: [PATCH 059/550] Refactor processes and threads goals to parameters for Windows testing --- tests/stress_test_core.py | 62 ++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index f519ed4..a3beb4b 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -32,8 +32,6 @@ DEL_CHANCE = 0.1 WARMUP = 10 EXPIRE = None -THREADS = 1 -PROCESSES = 1 def make_keys(): @@ -125,7 +123,7 @@ def all_ops(): yield next(ops) -def worker(queue, eviction_policy): +def worker(queue, eviction_policy, processes, threads): timings = {'get': [], 'set': [], 'delete': []} cache = Cache('tmp', eviction_policy=eviction_policy) @@ -142,7 +140,7 @@ def worker(queue, eviction_policy): stop = time.time() - if action == 'get' and PROCESSES == 1 and THREADS == 1 and EXPIRE is None: + if action == 'get' and processes == 1 and threads == 1 and EXPIRE is None: assert result == value if index > WARMUP: @@ -153,19 +151,19 @@ def worker(queue, eviction_policy): cache.close() -def dispatch(num, eviction_policy): +def dispatch(num, eviction_policy, processes, threads): with open('input-%s.pkl' % num, 'rb') as reader: process_queue = pickle.load(reader) - thread_queues = [Queue.Queue() for _ in range(THREADS)] - threads = [ + thread_queues = [Queue.Queue() for _ in range(threads)] + subthreads = [ threading.Thread( - target=worker, args=(thread_queue, eviction_policy) + target=worker, args=(thread_queue, eviction_policy, processes, threads) ) for thread_queue in thread_queues ] for index, triplet in enumerate(process_queue): - thread_queue = thread_queues[index % THREADS] + thread_queue = thread_queues[index % threads] thread_queue.put(triplet) for thread_queue in thread_queues: @@ -173,10 +171,10 @@ def dispatch(num, eviction_policy): start = time.time() - for thread in threads: + for thread in subthreads: thread.start() - for thread in threads: + for thread in subthreads: thread.join() stop = time.time() @@ -206,35 +204,37 @@ def percentile(sequence, percent): return values[pos] -def stress_test(create=True, delete=True, eviction_policy=u'least-recently-stored'): +def stress_test(create=True, delete=True, + eviction_policy=u'least-recently-stored', + processes=1, threads=1): shutil.rmtree('tmp', ignore_errors=True) - if PROCESSES == 1: + if processes == 1: # Use threads. func = threading.Thread else: func = mp.Process - processes = [ - func(target=dispatch, args=(num, eviction_policy)) - for num in range(PROCESSES) + subprocs = [ + func(target=dispatch, args=(num, eviction_policy, processes, threads)) + for num in range(processes) ] if create: operations = list(all_ops()) - process_queue = [[] for _ in range(PROCESSES)] + process_queue = [[] for _ in range(processes)] for index, ops in enumerate(operations): - process_queue[index % PROCESSES].append(ops) + process_queue[index % processes].append(ops) - for num in range(PROCESSES): + for num in range(processes): with open('input-%s.pkl' % num, 'wb') as writer: pickle.dump(process_queue[num], writer, protocol=2) - for process in processes: + for process in subprocs: process.start() - for process in processes: + for process in subprocs: process.join() with Cache('tmp') as cache: @@ -245,14 +245,14 @@ def stress_test(create=True, delete=True, eviction_policy=u'least-recently-store timings = {'get': [], 'set': [], 'delete': [], 'self': 0.0} - for num in range(PROCESSES): + for num in range(processes): with open('output-%s.pkl' % num, 'rb') as reader: data = pickle.load(reader) for key in data: timings[key] += data[key] if delete: - for num in range(PROCESSES): + for num in range(processes): os.remove('input-%s.pkl' % num) os.remove('output-%s.pkl' % num) @@ -273,13 +273,7 @@ def stress_test_lfu(): def stress_test_mp(): "Stress test multiple threads and processes." - global PROCESSES, THREADS - - PROCESSES = THREADS = 4 - - stress_test() - - PROCESSES = THREADS = 1 + stress_test(processes=4, threads=4) if __name__ == '__main__': @@ -313,11 +307,11 @@ def stress_test_mp(): help='Number of seconds before key expires', ) parser.add_argument( - '-t', '--threads', type=int, default=THREADS, + '-t', '--threads', type=int, default=1, help='Number of threads to start in each process', ) parser.add_argument( - '-p', '--processes', type=int, default=PROCESSES, + '-p', '--processes', type=int, default=1, help='Number of processes to start', ) parser.add_argument( @@ -345,8 +339,6 @@ def stress_test_mp(): DEL_CHANCE = args.del_chance WARMUP = int(args.warmup) EXPIRE = args.expire - THREADS = args.threads - PROCESSES = args.processes random.seed(args.seed) @@ -355,6 +347,8 @@ def stress_test_mp(): create=args.create, delete=args.delete, eviction_policy=args.eviction_policy, + processes=args.processes, + threads=args.threads, ) end = time.time() print('Total wall clock time: %.3f seconds' % (end - start)) From f58a4b62a4ffcb6aea33f732f1d8b363d296ae98 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 21:24:38 -0800 Subject: [PATCH 060/550] Catch WindowsError for os.remove --- diskcache/core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index a783ead..3c6d412 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -30,6 +30,12 @@ BytesType = bytes INT_TYPES = int, +try: + WindowsError +except NameError: + class WindowsError(Exception): + pass + DBNAME = 'cache.db' ENOVAL = object() @@ -276,6 +282,8 @@ def remove(self, filename): try: os.remove(full_path) + except WindowsError: + pass except OSError as error: if error.errno != errno.ENOENT: # ENOENT may occur if two caches attempt to delete the same From efaf3ad976886a0491583fabc506f6f298917d55 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 21:33:45 -0800 Subject: [PATCH 061/550] Update stress test fanout for Windows --- tests/stress_test_fanout.py | 62 +++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index 6d6c383..2024a42 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -32,8 +32,6 @@ DEL_CHANCE = 0.1 WARMUP = 10 EXPIRE = None -THREADS = 1 -PROCESSES = 1 def make_keys(): @@ -125,7 +123,7 @@ def all_ops(): yield next(ops) -def worker(queue, eviction_policy): +def worker(queue, eviction_policy, processes, threads): timings = {'get': [], 'set': [], 'delete': []} cache = FanoutCache('tmp', eviction_policy=eviction_policy) @@ -142,7 +140,7 @@ def worker(queue, eviction_policy): stop = time.time() - if action == 'get' and PROCESSES == 1 and THREADS == 1 and EXPIRE is None: + if action == 'get' and processes == 1 and threads == 1 and EXPIRE is None: assert result == value if index > WARMUP: @@ -153,19 +151,19 @@ def worker(queue, eviction_policy): cache.close() -def dispatch(num, eviction_policy): +def dispatch(num, eviction_policy, processes, threads): with open('input-%s.pkl' % num, 'rb') as reader: process_queue = pickle.load(reader) - thread_queues = [Queue.Queue() for _ in range(THREADS)] - threads = [ + thread_queues = [Queue.Queue() for _ in range(threads)] + subthreads = [ threading.Thread( - target=worker, args=(thread_queue, eviction_policy) + target=worker, args=(thread_queue, eviction_policy, processes, threads) ) for thread_queue in thread_queues ] for index, triplet in enumerate(process_queue): - thread_queue = thread_queues[index % THREADS] + thread_queue = thread_queues[index % threads] thread_queue.put(triplet) for thread_queue in thread_queues: @@ -173,10 +171,10 @@ def dispatch(num, eviction_policy): start = time.time() - for thread in threads: + for thread in subthreads: thread.start() - for thread in threads: + for thread in subthreads: thread.join() stop = time.time() @@ -206,35 +204,37 @@ def percentile(sequence, percent): return values[pos] -def stress_test(create=True, delete=True, eviction_policy=u'least-recently-stored'): +def stress_test(create=True, delete=True, + eviction_policy=u'least-recently-stored', + processes=1, threads=1): shutil.rmtree('tmp', ignore_errors=True) - if PROCESSES == 1: + if processes == 1: # Use threads. func = threading.Thread else: func = mp.Process - processes = [ - func(target=dispatch, args=(num, eviction_policy)) - for num in range(PROCESSES) + subprocs = [ + func(target=dispatch, args=(num, eviction_policy, processes, threads)) + for num in range(processes) ] if create: operations = list(all_ops()) - process_queue = [[] for _ in range(PROCESSES)] + process_queue = [[] for _ in range(processes)] for index, ops in enumerate(operations): - process_queue[index % PROCESSES].append(ops) + process_queue[index % processes].append(ops) - for num in range(PROCESSES): + for num in range(processes): with open('input-%s.pkl' % num, 'wb') as writer: pickle.dump(process_queue[num], writer, protocol=2) - for process in processes: + for process in subprocs: process.start() - for process in processes: + for process in subprocs: process.join() with FanoutCache('tmp') as cache: @@ -245,14 +245,14 @@ def stress_test(create=True, delete=True, eviction_policy=u'least-recently-store timings = {'get': [], 'set': [], 'delete': [], 'self': 0.0} - for num in range(PROCESSES): + for num in range(processes): with open('output-%s.pkl' % num, 'rb') as reader: data = pickle.load(reader) for key in data: timings[key] += data[key] if delete: - for num in range(PROCESSES): + for num in range(processes): os.remove('input-%s.pkl' % num) os.remove('output-%s.pkl' % num) @@ -273,13 +273,7 @@ def stress_test_lfu(): def stress_test_mp(): "Stress test multiple threads and processes." - global PROCESSES, THREADS - - PROCESSES = THREADS = 4 - - stress_test() - - PROCESSES = THREADS = 1 + stress_test(processes=4, threads=4) if __name__ == '__main__': @@ -313,11 +307,11 @@ def stress_test_mp(): help='Number of seconds before key expires', ) parser.add_argument( - '-t', '--threads', type=int, default=THREADS, + '-t', '--threads', type=int, default=1, help='Number of threads to start in each process', ) parser.add_argument( - '-p', '--processes', type=int, default=PROCESSES, + '-p', '--processes', type=int, default=1, help='Number of processes to start', ) parser.add_argument( @@ -345,8 +339,6 @@ def stress_test_mp(): DEL_CHANCE = args.del_chance WARMUP = int(args.warmup) EXPIRE = args.expire - THREADS = args.threads - PROCESSES = args.processes random.seed(args.seed) @@ -355,6 +347,8 @@ def stress_test_mp(): create=args.create, delete=args.delete, eviction_policy=args.eviction_policy, + processes=args.processes, + threads=args.threads, ) end = time.time() print('Total wall clock time: %.3f seconds' % (end - start)) From d78453cc5e4653dd98970df8854a541a224b928f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 21:40:50 -0800 Subject: [PATCH 062/550] Improve test_fanout.test_incr_concurrent for slower systems --- tests/test_fanout.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 408ea5d..db11981 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -216,6 +216,7 @@ def test_incr_concurrent(): for thread in threads: thread.join() + with dc.FanoutCache('tmp') as cache: assert cache.get(b'key') == count * limit cache.check() From 75be9547e7691e3a7164325b215368e07e2377bc Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 21:53:47 -0800 Subject: [PATCH 063/550] Change test tear down to ignore errors when removing cache directory --- tests/test_djangocache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 52089f2..565ed2f 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -825,7 +825,7 @@ def setUp(self): def tearDown(self): super(DiskCacheTests, self).tearDown() cache.close() - shutil.rmtree(self.dirname) + shutil.rmtree(self.dirname, ignore_errors=True) def test_ignores_non_cache_files(self): fname = os.path.join(self.dirname, 'not-a-cache-file') From 2bdb079dfda298ee7dea94b995cdd8f5419e3f15 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 26 Nov 2016 21:57:58 -0800 Subject: [PATCH 064/550] Sleep for 10ms during expire tests for fast systems --- tests/test_core.py | 1 + tests/test_fanout.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 650f3f1..29115fd 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -475,6 +475,7 @@ def test_expire_rows(cache): assert len(cache) == 15 + time.sleep(0.01) cache.reset('cull_limit', 10) assert cache.set(15, 15) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index db11981..d276358 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -383,6 +383,7 @@ def test_expire(cache): assert len(cache) == 100 + time.sleep(0.01) cache.reset('cull_limit', 10) assert cache.expire() == 100 From 2dda1ccf4f6724039dc217ca53d6e3dc7568e606 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 14:35:18 -0800 Subject: [PATCH 065/550] Add more fixes for Windows testing --- tests/test_core.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 29115fd..8d3214f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -533,6 +533,8 @@ def test_least_recently_used(cache): assert len(cache) == 10 + time.sleep(0.01) + cache[0] cache[1] cache[7] @@ -587,10 +589,14 @@ def test_filename_error(cache): cache._disk.filename() -# TODO: Add test for Windows. Attempting to remove a file that is in use -# (i.e. open for reading) causes an exception. +try: + WindowsError +except NameError: + class WindowsError(Exception): + pass + -@nt.raises(OSError) +@nt.raises(OSError, WindowsError) @setup_cache def test_remove_error(cache): func = mock.Mock(side_effect=OSError(errno.EACCES)) From 5552b9d232ece4f04d9e85e59faab039acc714b1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 15:14:44 -0800 Subject: [PATCH 066/550] Add travis and appveyor badges --- README.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.rst b/README.rst index 8df0242..d226d6b 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,14 @@ DiskCache: Disk Backed Cache ============================ +.. image:: https://api.travis-ci.org/grantjenks/python-diskcache.svg?branch=master + :target: http://www.grantjenks.com/docs/diskcache/ + :width: 120px + +.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-diskcache?branch=master&svg=true + :target: http://www.grantjenks.com/docs/diskcache/ + :width: 120px + `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. From 94140f0cce9eee1cd11032abfb71a75444896248 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 15:15:54 -0800 Subject: [PATCH 067/550] Try 2 to fix Windows error handling for remove test --- tests/test_core.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 8d3214f..1b71f46 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -52,7 +52,7 @@ def test_init_disk(): cache['a'] = 0 cache.check() shutil.rmtree('tmp', ignore_errors=True) - + @nt.raises(EnvironmentError) def test_init_makedirs(): @@ -589,14 +589,6 @@ def test_filename_error(cache): cache._disk.filename() -try: - WindowsError -except NameError: - class WindowsError(Exception): - pass - - -@nt.raises(OSError, WindowsError) @setup_cache def test_remove_error(cache): func = mock.Mock(side_effect=OSError(errno.EACCES)) @@ -605,6 +597,12 @@ def test_remove_error(cache): cache._disk.remove('ab/cd/efg.val') +if os.name == 'nt': + pass # All Windows errors are passed. +else: + test_remove_error = nt.raises(OSError)(test_remove_error) + + @setup_cache def test_check(cache): blob = b'a' * 2 ** 14 From 86dffb78e6fadc7eecf64a76f4e8f5f7a45deed6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 15:18:12 -0800 Subject: [PATCH 068/550] Remove image width value --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index d226d6b..7805cc5 100644 --- a/README.rst +++ b/README.rst @@ -3,11 +3,9 @@ DiskCache: Disk Backed Cache .. image:: https://api.travis-ci.org/grantjenks/python-diskcache.svg?branch=master :target: http://www.grantjenks.com/docs/diskcache/ - :width: 120px .. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-diskcache?branch=master&svg=true :target: http://www.grantjenks.com/docs/diskcache/ - :width: 120px `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. From f62e38bffe7a0a373192f42f18884066228ed48f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 15:34:27 -0800 Subject: [PATCH 069/550] Try 3 for Windows support of test_remove_error --- tests/test_core.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 1b71f46..13f0c7d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -593,14 +593,13 @@ def test_filename_error(cache): def test_remove_error(cache): func = mock.Mock(side_effect=OSError(errno.EACCES)) - with mock.patch('os.remove', func): - cache._disk.remove('ab/cd/efg.val') - - -if os.name == 'nt': - pass # All Windows errors are passed. -else: - test_remove_error = nt.raises(OSError)(test_remove_error) + try: + with mock.patch('os.remove', func): + cache._disk.remove('ab/cd/efg.val') + except OSError: + pass + else: + raise Exception('test_remove_error failed') @setup_cache From ecd42cb57189d2d705f07ead234564e9c90e5ad2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 15:37:22 -0800 Subject: [PATCH 070/550] Try 2 adding badges and testing to Features --- README.rst | 14 ++++++++------ docs/index.rst | 8 ++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 7805cc5..abbf0d5 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,6 @@ DiskCache: Disk Backed Cache ============================ -.. image:: https://api.travis-ci.org/grantjenks/python-diskcache.svg?branch=master - :target: http://www.grantjenks.com/docs/diskcache/ - -.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-diskcache?branch=master&svg=true - :target: http://www.grantjenks.com/docs/diskcache/ - `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. @@ -70,6 +64,14 @@ Features - Keys support "tag" metadata and eviction - Developed on Python 2.7 - Tested on CPython 2.7, 3.4, 3.5 and PyPy +- Tested on Linux, Mac OS X, and Windows +- Tested using Travis CI and AppVeyor CI + +.. image:: https://api.travis-ci.org/grantjenks/python-diskcache.svg?branch=master + :target: http://www.grantjenks.com/docs/diskcache/ + +.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-diskcache?branch=master&svg=true + :target: http://www.grantjenks.com/docs/diskcache/ Quickstart ---------- diff --git a/docs/index.rst b/docs/index.rst index 1cbb2dc..b0b8288 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -64,6 +64,14 @@ Features - Keys support "tag" metadata and eviction - Developed on Python 2.7 - Tested on CPython 2.7, 3.4, 3.5 and PyPy +- Tested on Linux, Mac OS X, and Windows +- Tested using Travis CI and AppVeyor CI + +.. image:: https://api.travis-ci.org/grantjenks/python-diskcache.svg?branch=master + :target: http://www.grantjenks.com/docs/diskcache/ + +.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-diskcache?branch=master&svg=true + :target: http://www.grantjenks.com/docs/diskcache/ Quickstart ---------- From 260553f10624e8b3257d11e36692e94135834a66 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 15:57:59 -0800 Subject: [PATCH 071/550] Try 4 for test_remove_error support on Windows --- tests/test_core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 13f0c7d..d997c88 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -599,7 +599,10 @@ def test_remove_error(cache): except OSError: pass else: - raise Exception('test_remove_error failed') + if os.name == 'nt': + pass # File delete errors ignored on Windows. + else: + raise Exception('test_remove_error failed') @setup_cache From 9b57e7252bf06f2aa5d40c529e26dcc57aca7b40 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 17:22:21 -0800 Subject: [PATCH 072/550] Bump version to 2.2.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 2a3f592..1e2d730 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '2.1.0' -__build__ = 0x020100 +__version__ = '2.2.0' +__build__ = 0x020200 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 699f893a54197492d42dd1f09fd3391f243b5c7f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 27 Nov 2016 17:23:58 -0800 Subject: [PATCH 073/550] Add comment on WindowsError place-holder for platforms without support --- diskcache/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/core.py b/diskcache/core.py index 3c6d412..5ea47fc 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -34,6 +34,7 @@ WindowsError except NameError: class WindowsError(Exception): + "Windows error place-holder on platforms without support." pass DBNAME = 'cache.db' From a79f279a9bae42342f8b5ff125b4a281516b8338 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 2 Dec 2016 16:07:52 -0800 Subject: [PATCH 074/550] Add script for benchmarking cache.incr method --- tests/benchmark_incr.py | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/benchmark_incr.py diff --git a/tests/benchmark_incr.py b/tests/benchmark_incr.py new file mode 100644 index 0000000..4a01628 --- /dev/null +++ b/tests/benchmark_incr.py @@ -0,0 +1,69 @@ +"""Benchmark cache.incr method. + +""" + +from __future__ import print_function + +import json +import multiprocessing as mp +import shutil +import time + +import diskcache as dc + +from .utils import secs + +COUNT = int(1e3) +PROCS = 8 + + +def worker(num): + "Rapidly increment key and time operation." + time.sleep(0.1) # Let other workers start. + + cache = dc.Cache('tmp') + values = [] + + for _ in range(COUNT): + start = time.time() + cache.incr(b'key') + end = time.time() + values.append(end - start) + + with open('output-%s.json' % num, 'w') as writer: + json.dump(values, writer) + + +def main(): + "Run workers and print percentile results." + shutil.rmtree('tmp', ignore_errors=True) + + processes = [ + mp.Process(target=worker, args=(num,)) for num in range(PROCS) + ] + + for process in processes: + process.start() + + for process in processes: + process.join() + + with dc.Cache('tmp') as cache: + assert cache.get(b'key') == COUNT * PROCS + + for num in range(PROCS): + values = [] + with open('output-%s.json' % num) as reader: + values += json.load(reader) + + values.sort() + p50 = int(len(values) * 0.50) - 1 + p90 = int(len(values) * 0.90) - 1 + p99 = int(len(values) * 0.99) - 1 + p00 = len(values) - 1 + print(['{0:9s}'.format(val) for val in 'p50 p90 p99 max'.split()]) + print([secs(values[pos]) for pos in [p50, p90, p99, p00]]) + + +if __name__ == '__main__': + main() From 89fbf60af65e55213d6389cb821a4bab9f692499 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 2 Dec 2016 16:14:13 -0800 Subject: [PATCH 075/550] Catch sqlite3 OperationalError in FanoutCache.get to support sqlite_journal_mode OFF --- diskcache/fanout.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 0d9210f..62eec40 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -2,6 +2,7 @@ import itertools as it import os.path as op +import sqlite3 import time from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout @@ -204,7 +205,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, key, default=default, read=read, expire_time=expire_time, tag=tag, ) - except Timeout: + except (Timeout, sqlite3.OperationalError): if retry: continue else: From 180aa99e31d788b1cf0e39445247c5a100fb0874 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Dec 2016 12:57:05 -0800 Subject: [PATCH 076/550] Remove large_value_threshold and add disk_min_file_size and disk_pickle_protocol instead --- diskcache/core.py | 56 ++++++++++++++---------- docs/tutorial.rst | 104 ++++++++++++++++++++++++++++++++++----------- tests/test_core.py | 82 ++++++++++++++++++++++++++++++++++- 3 files changed, 193 insertions(+), 49 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 5ea47fc..d4092d0 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -58,11 +58,12 @@ class WindowsError(Exception): u'eviction_policy': u'least-recently-stored', u'size_limit': 2 ** 30, # 1gb u'cull_limit': 10, - u'large_value_threshold': 2 ** 10, # 1kb, min 8 u'sqlite_synchronous': u'NORMAL', u'sqlite_journal_mode': u'WAL', - u'sqlite_cache_size': 2 ** 13, # 8,192 pages - u'sqlite_mmap_size': 2 ** 26, # 64mb + u'sqlite_cache_size': 2 ** 13, # 8,192 pages + u'sqlite_mmap_size': 2 ** 26, # 64mb + u'disk_min_file_size': 2 ** 10, # 1kb + u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, } METADATA = { @@ -102,17 +103,17 @@ class WindowsError(Exception): class Disk(object): "Cache key and value serialization for SQLite database and files." - def __init__(self, directory, size_threshold, pickle_protocol): + def __init__(self, directory, min_file_size=0, pickle_protocol=0): """Initialize disk instance. :param str directory: directory path - :param int size_threshold: size threshold for large values + :param int min_file_size: minimum size for file use :param int pickle_protocol: pickle protocol for serialization """ self._dir = directory - self._threshold = size_threshold - self._protocol = pickle_protocol + self.min_file_size = min_file_size + self.pickle_protocol = pickle_protocol def put(self, key): @@ -133,7 +134,7 @@ def put(self, key): or (type_key is float)): return key, True else: - result = pickle.dumps(key, protocol=self._protocol) + result = pickle.dumps(key, protocol=self.pickle_protocol) return sqlite3.Binary(result), False @@ -163,15 +164,15 @@ def store(self, value, read): """ # pylint: disable=unidiomatic-typecheck type_value = type(value) - _threshold = self._threshold + min_file_size = self.min_file_size - if ((type_value is TextType and len(value) < _threshold) + if ((type_value is TextType and len(value) < min_file_size) or (type_value in INT_TYPES and LIMITS[u'min_int'] <= value <= LIMITS[u'max_int']) or (type_value is float)): return 0, MODE_RAW, None, value elif type_value is BytesType: - if len(value) < _threshold: + if len(value) < min_file_size: return 0, MODE_RAW, None, sqlite3.Binary(value) else: filename, full_path = self.filename() @@ -200,9 +201,9 @@ def store(self, value, read): return size, MODE_BINARY, filename, None else: - result = pickle.dumps(value, protocol=self._protocol) + result = pickle.dumps(value, protocol=self.pickle_protocol) - if len(result) < _threshold: + if len(result) < min_file_size: return 0, MODE_PICKLE, None, sqlite3.Binary(result) else: filename, full_path = self.filename() @@ -315,10 +316,15 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): :param str directory: cache directory :param float timeout: SQLite connection timeout - :param disk: disk type or instance for serialization + :param disk: Disk type or subclass for serialization :param settings: any of DEFAULT_SETTINGS """ + try: + assert issubclass(disk, Disk) + except (TypeError, AssertionError): + raise ValueError('disk must subclass diskcache.Disk') + self._dir = directory self._timeout = 60 # Use 1 minute timeout for initialization. self._local = threading.local() @@ -356,15 +362,11 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): # Setup Disk object (must happen after settings initialized). - if isinstance(disk, Disk): - self._disk = disk - else: - assert issubclass(disk, Disk) - self._disk = disk( # pylint: disable=redefined-variable-type - directory, - sets['large_value_threshold'], - pickle.HIGHEST_PROTOCOL, - ) + kwargs = { + key[5:]: value for key, value in sets.items() + if key.startswith('disk_') + } + self._disk = disk(directory, **kwargs) # Set cached attributes: updates settings and sets pragmas. @@ -1432,6 +1434,10 @@ def reset(self, key, value=ENOVAL): Settings attributes on cache objects are lazy-loaded and read-only. Use `reset` to update the value. + Settings with the ``disk_`` prefix correspond to Disk + attributes. Updating the value will change the unprefixed attribute on + the associated Disk instance. + Settings with the ``sqlite_`` prefix correspond to SQLite pragmas. Updating the value will execute the corresponding PRAGMA statement. @@ -1480,5 +1486,9 @@ def reset(self, key, value=ENOVAL): del error + elif key.startswith('disk_'): + attr = key[5:] + setattr(self._disk, attr, value) + setattr(self, key, value) return value diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f6d79a5..994ebc6 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -199,6 +199,8 @@ it is a read-only operation. To exclude expired items you must explicitly call ... cache.set(num, num, tag=u'odd' if num % 2 else u'even') >>> cache.evict(u'even') +.. _tutorial-tag-index: + :meth:`Evict ` removes all the keys with a matching tag. The default tag is ``None``. Tag values may be any of integer, float, string, bytes and None. To accelerate the eviction of items by tag, an index @@ -235,6 +237,8 @@ in bytes of the cache directory on disk. >>> cache.volume() 9216 +.. _tutorial-statistics: + The second is :meth:`stats ` which returns cache hits and misses. Cache statistics must first be enabled. @@ -247,10 +251,10 @@ and misses. Cache statistics must first be enabled. >>> cache.stats(enable=False, reset=True) (100, 50) # 100 hits, 50 misses -Cache statistics are useful when evaluating different eviction policies as -discussed below. By default, statistics are disabled as they incur an extra -overhead on cache lookups. Increment and decrement operations are not accounted -in cache statistics. +Cache statistics are useful when evaluating different :ref:`eviction policies +`. By default, statistics are disabled as they +incur an extra overhead on cache lookups. Increment and decrement operations +are not counted in cache statistics. The third is :meth:`check ` which verifies cache consistency. It can also fix inconsistencies and reclaim unused space. @@ -309,7 +313,7 @@ DjangoCache :doc:`DiskCache ` installed, you can use :class:`DjangoCache ` in your settings file. -:: +.. code-block:: python CACHES = { 'default': { @@ -338,7 +342,7 @@ many :class:`FanoutCache ` features. :class:`DjangoCache ` also works well with `X-Sendfile` and `X-Accel-Redirect` headers. -:: +.. code-block:: python from django.core.cache import cache @@ -374,9 +378,22 @@ during initialization when passed as keyword arguments. new item. Set to zero to disable automatic culling. Some systems may disable automatic culling in exchange for a cron-like job that regularly calls :meth:`expire ` in a separate process. -* `large_value_threshold`, default one kilobyte. The minimum size of a value - stored in a file on disk rather than in the cache database. -* `eviction_policy`, see descriptions below. +* `statistics`, default False, disabled. The setting to collect :ref:`cache + statistics `. +* `tag_index`, default False, disabled. The setting to create a database + :ref:`tag index ` for :meth:`evict + `. +* `eviction_policy`, default "least-recently-stored". The setting to determine + :ref:`eviction policy `. + +The :meth:`reset ` method accepts an optional +second argument that updates the corresponding value in the database. The +return value is the latest retrieved from the database. Notice that attributes +are updated lazily. Prefer idioms like :meth:`len +`, :meth:`volume +`, and :meth:`keyword arguments +` rather than using :meth:`reset +` directly. >>> cache = Cache('/tmp/mycachedir', size_limit=int(4e9)) >>> cache.size_limit @@ -392,14 +409,15 @@ during initialization when passed as keyword arguments. >>> cache.reset('count') # Prefer: len(cache) 1 -The :meth:`reset ` method accepts an optional -second argument that updates the corresponding value in the database. The -return value is the latest retrieved from the database. Notice attributes are -updated lazily. Prefer idioms like :meth:`len `, -:meth:`volume `, :meth:`create_tag_index -`, and :meth:`keyword arguments -` rather than using :meth:`reset -` directly. +More settings correspond to :ref:`Disk ` attributes. Each of +these may be specified when initializing the :ref:`Cache +`. Changing these values will update the unprefixed attribute +on the :class:`Disk ` object. + +* `disk_min_file_size`, default one kilobyte. The minimum size to store a value + in a file. +* `disk_pickle_protocol`, default highest pickle protocol. The pickle protocol + to use for data types that are not natively supported. An additional set of attributes correspond to SQLite pragmas. Changing these values will also execute the appropriate ``PRAGMA`` statement. See the `SQLite @@ -417,6 +435,8 @@ accessible at :data:`diskcache.DEFAULT_SETTINGS`. .. _`SQLite pragma documentation`: https://www.sqlite.org/pragma.html +.. _tutorial-eviction-policies: + Eviction Policies ----------------- @@ -449,8 +469,9 @@ policy. The policy can be set during initialization using a keyword argument. >>> cache.reset('eviction_policy', u'least-recently-used') u'least-recently-used' -Though the eviction policy is changed the previously created indexes will not -be dropped. +Though the eviction policy is changed, the previously created indexes will not +be dropped. Prefer to always specify the eviction policy as a keyword argument +to initialize the cache. .. _tutorial-disk: @@ -460,10 +481,45 @@ Disk :class:`diskcache.Disk` objects are responsible for serializing and deserializing data stored in the cache. Serialization behavior differs between keys and values. In particular, keys are always stored in the cache metadata -database while values are sometimes stored separately in files. To customize -serialization, you can pass in a :class:`Disk ` object during -cache initialization. All clients accessing the cache are expected to use the -same serialization. +database while values are sometimes stored separately in files. + +To customize serialization, you may pass in a :class:`Disk ` +subclass to initialize the cache. All clients accessing the cache are expected +to use the same serialization. The default implementation uses pickle and the +example below uses compressed JSON. + +.. code-block:: python + + import json, zlib + + class JSONDisk(diskcache.Disk): + def __init__(self, directory, compress_level=1, **kwargs): + self.compress_level = compress_level + super(JSONDisk, self).__init__(directory, **kwargs) + + def put(self, key): + json_bytes = json.dumps(key).encode('utf-8') + data = zlib.compress(json_bytes, self.compress_level) + return super(JSONDisk, self).put(data) + + def get(self, key, raw): + data = super(JSONDisk, self).get(key, raw) + return json.loads(zlib.decompress(data).decode('utf-8')) + + def store(self, value, read): + if not read: + json_bytes = json.dumps(value).encode('utf-8') + value = zlib.compress(json_bytes, self.compress_level) + return super(JSONDisk, self).store(value, read) + + def fetch(self, mode, filename, value, read): + data = super(JSONDisk, self).fetch(mode, filename, value, read) + if not read: + data = json.loads(zlib.decompress(data).decode('utf-8')) + return data + + with Cache('/tmp/dir', disk=JSONDisk, disk_compress_level=6) as cache: + pass Four data types can be stored natively in the cache metadata database: integers, floats, strings, and bytes. Other datatypes are converted to bytes @@ -478,7 +534,7 @@ Though :doc:`DiskCache ` has a dictionary-like interface, Python's `hash protocol`_ is not used. Neither the `__hash__` nor `__eq__` methods are used for lookups. Instead lookups depend on the serialization method defined by :class:`Disk ` objects. For strings, bytes, integers, and -floats equality matches Python's definition. But large integers and all other +floats, equality matches Python's definition. But large integers and all other types will be converted to bytes using pickling and the bytes representation will define equality. diff --git a/tests/test_core.py b/tests/test_core.py index d997c88..93796af 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4,6 +4,7 @@ import errno import functools as ft import io +import json import mock import nose.tools as nt import os @@ -14,12 +15,14 @@ import threading import time import warnings +import zlib try: import cPickle as pickle except: import pickle +import diskcache import diskcache as dc warnings.simplefilter('error') @@ -48,9 +51,84 @@ def test_init(cache): def test_init_disk(): - with dc.Cache('tmp', disk=dc.Disk('tmp', 2 ** 10, 0)) as cache: - cache['a'] = 0 + with dc.Cache('tmp', disk_pickle_protocol=1, disk_min_file_size=2 ** 20) as cache: + key = (None, 0, 'abc') + cache[key] = 0 cache.check() + assert cache.disk_min_file_size == 2 ** 20 + assert cache.disk_pickle_protocol == 1 + shutil.rmtree('tmp', ignore_errors=True) + + +def test_disk_reset(): + with dc.Cache('tmp', disk_min_file_size=0, disk_pickle_protocol=0) as cache: + value = (None, 0, 'abc') + + cache[0] = value + cache.check() + + assert cache.disk_min_file_size == 0 + assert cache.disk_pickle_protocol == 0 + assert cache._disk.min_file_size == 0 + assert cache._disk.pickle_protocol == 0 + + cache.reset('disk_min_file_size', 2 ** 10) + cache.reset('disk_pickle_protocol', 2) + + cache[1] = value + cache.check() + + assert cache.disk_min_file_size == 2 ** 10 + assert cache.disk_pickle_protocol == 2 + assert cache._disk.min_file_size == 2 ** 10 + assert cache._disk.pickle_protocol == 2 + + shutil.rmtree('tmp', ignore_errors=True) + + +@nt.raises(ValueError) +def test_disk_valueerror(): + with dc.Cache('tmp', disk=dc.Disk('tmp')) as cache: + pass + + +class JSONDisk(diskcache.Disk): + def __init__(self, directory, compress_level=1, **kwargs): + self.compress_level = compress_level + super(JSONDisk, self).__init__(directory, **kwargs) + + def put(self, key): + json_bytes = json.dumps(key).encode('utf-8') + data = zlib.compress(json_bytes, self.compress_level) + return super(JSONDisk, self).put(data) + + def get(self, key, raw): + data = super(JSONDisk, self).get(key, raw) + return json.loads(zlib.decompress(data).decode('utf-8')) + + def store(self, value, read): + if not read: + json_bytes = json.dumps(value).encode('utf-8') + value = zlib.compress(json_bytes, self.compress_level) + return super(JSONDisk, self).store(value, read) + + def fetch(self, mode, filename, value, read): + data = super(JSONDisk, self).fetch(mode, filename, value, read) + if not read: + data = json.loads(zlib.decompress(data).decode('utf-8')) + return data + + +def test_custom_disk(): + with dc.Cache('tmp', disk=JSONDisk, disk_compress_level=6) as cache: + values = [None, True, 0, 1.23, {}, [None] * 10000] + + for value in values: + cache[value] = value + + for value in values: + assert cache[value] == value + shutil.rmtree('tmp', ignore_errors=True) From a7f1e9d6dd0367e98bd5cdf40f65dae9d7b1624e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Dec 2016 12:57:20 -0800 Subject: [PATCH 077/550] Bump version to 2.3.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 1e2d730..9a4b682 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '2.2.0' -__build__ = 0x020200 +__version__ = '2.3.0' +__build__ = 0x020300 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 55d36b056f22d5e03dd8526b1dbd9e173408467a Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Mon, 20 Feb 2017 15:54:18 +0100 Subject: [PATCH 078/550] Upgrade git clone URL scheme to HTTPS Ref: https://github.com/travis-ci/travis-ci/issues/4384 --- docs/development.rst | 2 +- docs/tutorial.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development.rst b/docs/development.rst index 654e1d3..1c728fc 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -41,7 +41,7 @@ Get the Code You can either clone the public repository:: - $ git clone git://github.com/grantjenks/python-diskcache.git + $ git clone https://github.com/grantjenks/python-diskcache.git Download the `tarball `_:: diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 994ebc6..a9053de 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -35,7 +35,7 @@ always available. You can either clone the `DiskCache repository `_:: - $ git clone git://github.com/grantjenks/python-diskcache.git + $ git clone https://github.com/grantjenks/python-diskcache.git Download the `tarball `_:: From 78f237aecbc97248cf1c0b6bf7442d51fdb551ac Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 24 Feb 2017 09:46:53 -0800 Subject: [PATCH 079/550] Move diskcache import --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 74c520f..7339d73 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ -import diskcache import io from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand import sys +import diskcache class Tox(TestCommand): def finalize_options(self): @@ -15,7 +15,6 @@ def run_tests(self): errno = tox.cmdline(self.test_args) sys.exit(errno) - with io.open('README.rst', encoding='UTF-8') as reader: readme = reader.read() From 36f18dc556725862bc02ede4cf7e1b970e184362 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 24 Feb 2017 10:31:11 -0800 Subject: [PATCH 080/550] Bump version to 2.3.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 9a4b682..432afee 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '2.3.0' -__build__ = 0x020300 +__version__ = '2.3.1' +__build__ = 0x020301 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 17d38b2863c70b88bd42dd6bcf8c51ccf672970d Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Sat, 25 Mar 2017 23:37:57 -0400 Subject: [PATCH 081/550] Specify what unit is used for constants --- docs/api.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 0211232..f923074 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -46,15 +46,15 @@ Read the :ref:`Settings tutorial ` for details. * `tag_index` (int) default 0 - disabled when 0, enabled when 1. * `eviction_policy` (str) default "least-recently-stored" - any of the keys in `EVICTION_POLICY` as described below. - * `size_limit` (int) default one gigabyte - approximate size limit of cache. + * `size_limit` (int, in bytes) default one gigabyte - approximate size limit of cache. * `cull_limit` (int) default ten - maximum number of items culled during `set` or `add` operations. - * `large_value_threshold` (int) default one kilobyte - values with greater + * `large_value_threshold` (int, in bytes) default one kilobyte - values with greater size are stored in files. * `sqlite_synchronous` (str) default "NORMAL" - SQLite synchronous pragma. * `sqlite_journal_mode` (str) default "WAL" - SQLite journal mode pragma. - * `sqlite_cache_size` (int) default 8,192 - SQLite cache size pragma. - * `sqlite_mmap_size` (int) default 64 megabytes - SQLite mmap size pragma. + * `sqlite_cache_size` (int, in bytes) default 8,192 - SQLite cache size pragma. + * `sqlite_mmap_size` (int, in bytes) default 64 megabytes - SQLite mmap size pragma. .. data:: diskcache.LIMITS From fb46dc6a62e24c954ed590b100d4014c6ffde1b7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 27 Mar 2017 12:41:56 -0700 Subject: [PATCH 082/550] Small docs changes: add disk_ settings to api, capitalize Pickle, and fix Disk example code --- docs/api.rst | 17 ++++++++++++----- docs/tutorial.rst | 10 +++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index f923074..9a8a129 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -46,15 +46,22 @@ Read the :ref:`Settings tutorial ` for details. * `tag_index` (int) default 0 - disabled when 0, enabled when 1. * `eviction_policy` (str) default "least-recently-stored" - any of the keys in `EVICTION_POLICY` as described below. - * `size_limit` (int, in bytes) default one gigabyte - approximate size limit of cache. + * `size_limit` (int, in bytes) default one gigabyte - approximate size limit + of cache. * `cull_limit` (int) default ten - maximum number of items culled during `set` or `add` operations. - * `large_value_threshold` (int, in bytes) default one kilobyte - values with greater - size are stored in files. + * `large_value_threshold` (int, in bytes) default one kilobyte - values with + greater size are stored in files. * `sqlite_synchronous` (str) default "NORMAL" - SQLite synchronous pragma. * `sqlite_journal_mode` (str) default "WAL" - SQLite journal mode pragma. - * `sqlite_cache_size` (int, in bytes) default 8,192 - SQLite cache size pragma. - * `sqlite_mmap_size` (int, in bytes) default 64 megabytes - SQLite mmap size pragma. + * `sqlite_cache_size` (int, in pages) default 8,192 - SQLite cache size + pragma. + * `sqlite_mmap_size` (int, in bytes) default 64 megabytes - SQLite mmap size + pragma. + * `disk_min_file_size` (int, in bytes) default one kilobyte - values with + greater size are stored in files. + * `disk_pickle_protocol` (int) default highest Pickle protocol - the Pickle + protocol to use for data types that are not natively supported. .. data:: diskcache.LIMITS diff --git a/docs/tutorial.rst b/docs/tutorial.rst index a9053de..621197e 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -416,7 +416,7 @@ on the :class:`Disk ` object. * `disk_min_file_size`, default one kilobyte. The minimum size to store a value in a file. -* `disk_pickle_protocol`, default highest pickle protocol. The pickle protocol +* `disk_pickle_protocol`, default highest Pickle protocol. The Pickle protocol to use for data types that are not natively supported. An additional set of attributes correspond to SQLite pragmas. Changing these @@ -485,7 +485,7 @@ database while values are sometimes stored separately in files. To customize serialization, you may pass in a :class:`Disk ` subclass to initialize the cache. All clients accessing the cache are expected -to use the same serialization. The default implementation uses pickle and the +to use the same serialization. The default implementation uses Pickle and the example below uses compressed JSON. .. code-block:: python @@ -518,12 +518,12 @@ example below uses compressed JSON. data = json.loads(zlib.decompress(data).decode('utf-8')) return data - with Cache('/tmp/dir', disk=JSONDisk, disk_compress_level=6) as cache: - pass + with Cache('/tmp/dir', disk=JSONDisk, disk_compress_level=6) as cache: + pass Four data types can be stored natively in the cache metadata database: integers, floats, strings, and bytes. Other datatypes are converted to bytes -via the pickle protocol. Beware that integers and floats like ``1`` and ``1.0`` +via the Pickle protocol. Beware that integers and floats like ``1`` and ``1.0`` will compare equal as keys just as in Python. All other equality comparisons will require identical types. From a77e7f6e440d331aeefd7070827424bc07715e17 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 27 Mar 2017 12:42:52 -0700 Subject: [PATCH 083/550] Change fanout subdir perms to 0o755 from 0o700 --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index d4092d0..4892cf4 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -331,7 +331,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): if not op.isdir(directory): try: - os.makedirs(directory, 0o700) + os.makedirs(directory, 0o755) except OSError as error: if error.errno != errno.EEXIST: raise EnvironmentError( From 26050bb8a64ba43e5c33934058e4e145c519c3ad Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 27 Mar 2017 14:29:36 -0700 Subject: [PATCH 084/550] Add Python 3.6 to test matrix and update tests for support --- .travis.yml | 2 ++ appveyor.yml | 2 ++ tests/test_djangocache.py | 12 ++++++++++-- tox.ini | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 74edfc1..26300ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,9 @@ python: - "2.7" - "3.4" - "3.5" + - "3.6" - "pypy" + - "pypy3" install: - pip install -r requirements.txt script: diff --git a/appveyor.yml b/appveyor.yml index fcb9fc4..a382718 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,9 +5,11 @@ environment: - PYTHON: "C:\\Python27" - PYTHON: "C:\\Python34" - PYTHON: "C:\\Python35" + - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python27-x64" - PYTHON: "C:\\Python34-x64" - PYTHON: "C:\\Python35-x64" + - PYTHON: "C:\\Python36-x64" install: diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 565ed2f..fe233ca 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# The entirety of this file was copied from: +# Most of this file was copied from: # https://raw.githubusercontent.com/django/django/master/tests/cache/tests.py # Unit tests for cache framework @@ -51,8 +51,16 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') -import django +############################################################################ +# GrantJ 2017-03-27 Ignore deprecation warnings. Django's metaclass magic does +# not always play well with Python 3.6. Read +# http://stackoverflow.com/questions/41343263/ for details +############################################################################ + +import warnings +warnings.filterwarnings('ignore', category=DeprecationWarning) +import django django.setup() from .models import Poll, expensive_calculation diff --git a/tox.ini b/tox.ini index 3ca5c74..df548ab 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,py34,py35 +envlist=py27,py34,py35,py36 [testenv] deps=nose mock From d7765a618aa1b7f975e77887cbbd997f1bd78491 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 27 Mar 2017 14:30:13 -0700 Subject: [PATCH 085/550] Bump version to 2.4.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 432afee..d92e210 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '2.3.1' -__build__ = 0x020301 +__version__ = '2.4.0' +__build__ = 0x020400 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From cfa517f1ed6e7a368f2742ba4118b01812a4ff30 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 27 Mar 2017 14:44:19 -0700 Subject: [PATCH 086/550] Add 3.6 to docs and remove pypy3 testing --- .travis.yml | 1 - README.rst | 2 +- docs/development.rst | 1 + docs/index.rst | 2 +- setup.py | 1 + 5 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 26300ae..c72311a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ python: - "3.5" - "3.6" - "pypy" - - "pypy3" install: - pip install -r requirements.txt script: diff --git a/README.rst b/README.rst index abbf0d5..dbb5811 100644 --- a/README.rst +++ b/README.rst @@ -63,7 +63,7 @@ Features - Supports multiple eviction policies (LRU and LFU included) - Keys support "tag" metadata and eviction - Developed on Python 2.7 -- Tested on CPython 2.7, 3.4, 3.5 and PyPy +- Tested on CPython 2.7, 3.4, 3.5, 3.6 and PyPy - Tested on Linux, Mac OS X, and Windows - Tested using Travis CI and AppVeyor CI diff --git a/docs/development.rst b/docs/development.rst index 1c728fc..49fe04c 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -71,6 +71,7 @@ Testing * CPython 2.7 * CPython 3.4 * CPython 3.5 +# CPython 3.6 * PyPy2 Testing uses `tox `_. If you don't want to diff --git a/docs/index.rst b/docs/index.rst index b0b8288..4266d03 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -63,7 +63,7 @@ Features - Supports multiple eviction policies (LRU and LFU included) - Keys support "tag" metadata and eviction - Developed on Python 2.7 -- Tested on CPython 2.7, 3.4, 3.5 and PyPy +- Tested on CPython 2.7, 3.4, 3.5, 3.6 and PyPy - Tested on Linux, Mac OS X, and Windows - Tested using Travis CI and AppVeyor CI diff --git a/setup.py b/setup.py index 7339d73..0f00554 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ def run_tests(self): 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ), From 18c981219b243d3a7093916b280d2dbf992d1af5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 27 Mar 2017 14:44:42 -0700 Subject: [PATCH 087/550] Bump version to 2.4.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index d92e210..7d20ca6 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -12,8 +12,8 @@ __title__ = 'diskcache' -__version__ = '2.4.0' -__build__ = 0x020400 +__version__ = '2.4.1' +__build__ = 0x020401 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From bed6f168f43ed36fba9a7aa154d51481948884c2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 27 Mar 2017 14:47:54 -0700 Subject: [PATCH 088/550] Fix typo --- docs/development.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development.rst b/docs/development.rst index 49fe04c..406bf1a 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -71,7 +71,7 @@ Testing * CPython 2.7 * CPython 3.4 * CPython 3.5 -# CPython 3.6 +* CPython 3.6 * PyPy2 Testing uses `tox `_. If you don't want to From 9b8d1832af23dc800a9d82a2f8f611be6d068f58 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 8 May 2017 20:30:40 -0700 Subject: [PATCH 089/550] Remove references to large_value_threshold from docs --- docs/api.rst | 2 -- docs/tutorial.rst | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 9a8a129..9ccb4c9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -50,8 +50,6 @@ Read the :ref:`Settings tutorial ` for details. of cache. * `cull_limit` (int) default ten - maximum number of items culled during `set` or `add` operations. - * `large_value_threshold` (int, in bytes) default one kilobyte - values with - greater size are stored in files. * `sqlite_synchronous` (str) default "NORMAL" - SQLite synchronous pragma. * `sqlite_journal_mode` (str) default "WAL" - SQLite journal mode pragma. * `sqlite_cache_size` (int, in pages) default 8,192 - SQLite cache size diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 621197e..831684c 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -398,7 +398,7 @@ are updated lazily. Prefer idioms like :meth:`len >>> cache = Cache('/tmp/mycachedir', size_limit=int(4e9)) >>> cache.size_limit 4000000000 - >>> cache.large_value_threshold + >>> cache.disk_min_file_size 1024 >>> cache.reset('cull_limit', 0) # Disable automatic evictions. 0 From 53dd1e532f0bd32c65127e824e2e593ba70821c3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 8 May 2017 20:38:50 -0700 Subject: [PATCH 090/550] Add initial prototype for Cache.push and Cache.pull --- diskcache/core.py | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index 4892cf4..bc958b9 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1070,6 +1070,117 @@ def delete(self, key): return False + def push(self, queue, value, expire=None, read=False, tag=None): + """Push `value` onto end of `queue` in cache. + + Operation is atomic. Concurrent operations will be serialized. + + When `read` is `True`, `value` should be a file-like object opened + for reading in binary mode. + + :param str queue: queue name + :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) + :return: key for item in cache + :raises Timeout: if database timeout expires + + """ + min_key = queue + '-000000000000000' + max_key = queue + '-999999999999999' + now = time.time() + raw = True + expire_time = None if expire is None else now + expire + size, mode, filename, db_value = self._disk.store(value, read) + columns = (expire_time, tag, size, mode, filename, db_value) + + with self._transact(filename) as (sql, cleanup): + rows = sql( + 'SELECT key FROM Cache' + ' WHERE ? < key AND key < ? AND raw = ?' + ' ORDER BY key DESC LIMIT 1', + (min_key, max_key, raw), + ).fetchall() + + if rows: + (key,), = rows + num = int(key[(key.rfind('-') + 1):]) + 1 + else: + num = 500000000000000 + + db_key = '{0}-{1:015d}'.format(queue, num) + + self._row_insert(db_key, raw, now, columns) + self._cull(now, sql, cleanup) + + return db_key + + + def pull(self, queue, default=(None, None), expire_time=False, tag=False): + """Pull key and value item from start of `queue` in cache. + + If queue is empty, return `default`. + + Operation is atomic. Concurrent operations will be serialized. + + :param str queue: queue name + :param default: value to return if key is missing + (default (None, None)) + :param bool expire_time: if True, return expire_time in tuple + (default False) + :param bool tag: if True, return tag in tuple (default False) + :return: key and value item or default if key not found + :raises Timeout: if database timeout expires + + """ + min_key = queue + '-000000000000000' + max_key = queue + '-999999999999999' + select = ( + 'SELECT rowid, key, expire_time, tag, mode, filename, value' + ' FROM Cache WHERE ? < key AND key < ? AND raw = 1' + ' AND (expire_time IS NULL OR expire_time > ?)' + ' ORDER BY key LIMIT 1' + ) + + if expire_time and tag: + default = default, None, None + elif expire_time or tag: + default = default, None + + with self._transact() as (sql, cleanup): + rows = sql(select, (min_key, max_key, time.time())).fetchall() + + if not rows: + return default + + (rowid, key, expire_time, tag, mode, filename, db_value), = rows + + sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) + + try: + value = self._disk.fetch(mode, filename, db_value, False) + except IOError as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + return default + else: + raise + finally: + if filename is not None: + self._disk.remove(filename) + + if expire_time and tag: + return (key, value), db_expire, db_tag + elif expire_time: + return (key, value), db_expire_time + elif tag: + return (key, value), db_tag + else: + return key, value + + def check(self, fix=False): """Check database and file system consistency. From c68fbfaa8664d189a6055ed2ab8ab673bcbc329e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 9 May 2017 10:39:46 -0700 Subject: [PATCH 091/550] Add: 'expire_time IS NULL OR expire_time > ?' clause to get and __contains__ selects --- diskcache/core.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index bc958b9..6044c9b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -538,6 +538,9 @@ def set(self, key, value, expire=None, read=False, tag=None): # 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(filename) as (sql, cleanup): rows = sql( @@ -830,6 +833,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): 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: @@ -840,16 +844,13 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): if not self.statistics and update_column is None: # Fast path, no transaction necessary. - rows = self._sql(select, (db_key, raw)).fetchall() + rows = self._sql(select, (db_key, raw, time.time())).fetchall() if not rows: return default (rowid, db_expire_time, db_tag, mode, filename, db_value), = rows - if db_expire_time is not None and db_expire_time < time.time(): - return default - try: value = self._disk.fetch(mode, filename, db_value, read) except IOError as error: @@ -869,7 +870,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): ) with self._transact() as (sql, _): - rows = sql(select, (db_key, raw)).fetchall() + rows = sql(select, (db_key, raw, time.time())).fetchall() if not rows: if self.statistics: @@ -879,11 +880,6 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): (rowid, db_expire_time, db_tag, mode, filename, db_value), = rows - if db_expire_time is not None and db_expire_time < time.time(): - if self.statistics: - sql(cache_miss) - return default - try: value = self._disk.fetch(mode, filename, db_value, read) except IOError as error: @@ -950,18 +946,15 @@ def __contains__(self, key): """ 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 > ?)' + ) - rows = sql( - 'SELECT expire_time FROM Cache WHERE key = ? AND raw = ?', - (db_key, raw), - ).fetchall() - - if not rows: - return False - - (expire_time,), = rows + rows = sql(select, (db_key, raw, time.time())).fetchall() - return expire_time is None or time.time() < expire_time + return bool(rows) def pop(self, key, default=None, expire_time=False, tag=False): From a6caf688a72cffe9bd9d9e5a0d9016c015968c82 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 9 May 2017 10:55:37 -0700 Subject: [PATCH 092/550] Update Cache.pop to include expire_time clause in select query and remove filename after fetching --- diskcache/core.py | 20 ++++++++++---------- tests/test_core.py | 3 +++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 6044c9b..9a5e359 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -977,15 +977,16 @@ def pop(self, key, default=None, expire_time=False, tag=False): 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) + default = default, None, None elif expire_time or tag: - default = (default, None) + default = default, None with self._transact() as (sql, cleanup): - rows = sql(select, (db_key, raw)).fetchall() + rows = sql(select, (db_key, raw, time.time())).fetchall() if not rows: return default @@ -993,10 +994,6 @@ def pop(self, key, default=None, expire_time=False, tag=False): (rowid, db_expire_time, db_tag, mode, filename, db_value), = rows sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) - cleanup(filename) - - if db_expire_time is not None and db_expire_time < time.time(): - return default try: value = self._disk.fetch(mode, filename, db_value, False) @@ -1006,13 +1003,16 @@ def pop(self, key, default=None, expire_time=False, tag=False): return default else: raise + finally: + if filename is not None: + self._disk.remove(filename) if expire_time and tag: - return (value, db_expire_time, db_tag) + return value, db_expire_time, db_tag elif expire_time: - return (value, db_expire_time) + return value, db_expire_time elif tag: - return (value, db_tag) + return value, db_tag else: return value diff --git a/tests/test_core.py b/tests/test_core.py index 93796af..643f76d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -433,6 +433,9 @@ def test_pop(cache): assert cache.set('delta', 210) assert cache.pop('delta', expire_time=True) == (210, None) + assert cache.set('epsilon', '0' * 2 ** 12) + assert cache.pop('epsilon') == '0' * 2 ** 12 + @setup_cache def test_pop_ioerror(cache): From 1b0ab8abb6f85da4148567e8b960adda8d3089c0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 9 May 2017 11:01:02 -0700 Subject: [PATCH 093/550] Update Cache.__delitem__ to use expire_time clause in select query --- diskcache/core.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 9a5e359..92061d7 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1029,22 +1029,20 @@ def __delitem__(self, key): with self._transact() as (sql, cleanup): rows = sql( - 'SELECT rowid, expire_time, filename FROM Cache' - ' WHERE key = ? AND raw = ?', - (db_key, raw), + 'SELECT rowid, filename FROM Cache' + ' WHERE key = ? AND raw = ?' + ' AND (expire_time IS NULL OR expire_time > ?)', + (db_key, raw, time.time()), ).fetchall() if not rows: raise KeyError(key) - (rowid, expire_time, filename), = rows + (rowid, filename), = rows sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) cleanup(filename) - if expire_time is None or time.time() < expire_time: - return True - else: - raise KeyError(key) + return True def delete(self, key): From e867c6bed19fa177b45913be73764d3e0aa1d5be Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 9 May 2017 11:16:54 -0700 Subject: [PATCH 094/550] Use open built-in rather than io.open when able --- diskcache/core.py | 18 ++++++++++-------- tests/test_core.py | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 92061d7..92ef64b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -23,12 +23,14 @@ BytesType = str INT_TYPES = int, long range = xrange # pylint: disable=redefined-builtin,invalid-name + io_open = io.open else: import pickle from io import BytesIO # pylint: disable=ungrouped-imports TextType = str BytesType = bytes INT_TYPES = int, + io_open = open try: WindowsError @@ -177,14 +179,14 @@ def store(self, value, read): else: filename, full_path = self.filename() - with io.open(full_path, 'wb') as writer: + with open(full_path, 'wb') as writer: writer.write(value) return len(value), MODE_BINARY, filename, None elif type_value is TextType: filename, full_path = self.filename() - with io.open(full_path, 'w', encoding='UTF-8') as writer: + with io_open(full_path, 'w', encoding='UTF-8') as writer: writer.write(value) size = op.getsize(full_path) @@ -194,7 +196,7 @@ def store(self, value, read): reader = ft.partial(value.read, 2 ** 22) filename, full_path = self.filename() - with io.open(full_path, 'wb') as writer: + with open(full_path, 'wb') as writer: for chunk in iter(reader, b''): size += len(chunk) writer.write(chunk) @@ -208,7 +210,7 @@ def store(self, value, read): else: filename, full_path = self.filename() - with io.open(full_path, 'wb') as writer: + with open(full_path, 'wb') as writer: writer.write(result) return len(result), MODE_PICKLE, filename, None @@ -230,17 +232,17 @@ def fetch(self, mode, filename, value, read): return BytesType(value) if type(value) is sqlite3.Binary else value elif mode == MODE_BINARY: if read: - return io.open(op.join(self._dir, filename), 'rb') + return open(op.join(self._dir, filename), 'rb') else: - with io.open(op.join(self._dir, filename), 'rb') as reader: + with open(op.join(self._dir, filename), 'rb') as reader: return reader.read() elif mode == MODE_TEXT: full_path = op.join(self._dir, filename) - with io.open(full_path, 'r', encoding='UTF-8') as reader: + with io_open(full_path, 'r', encoding='UTF-8') as reader: return reader.read() elif mode == MODE_PICKLE: if value is None: - with io.open(op.join(self._dir, filename), 'rb') as reader: + with open(op.join(self._dir, filename), 'rb') as reader: return pickle.load(reader) else: return pickle.load(BytesIO(value)) diff --git a/tests/test_core.py b/tests/test_core.py index 643f76d..8f4596e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -266,7 +266,7 @@ def test_get_keyerror4(cache): cache.reset('statistics', True) cache[0] = b'abcd' * 2 ** 12 - with mock.patch('io.open', func): + with mock.patch('diskcache.core.open', func): cache[0] @@ -277,7 +277,7 @@ def test_get_keyerror5(cache): cache[0] = b'abcd' * 2 ** 12 - with mock.patch('io.open', func): + with mock.patch('diskcache.core.open', func): cache[0] From 53473d13176fc2c0501b7a9d643d440e7e7fc0aa Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 9 May 2017 11:56:37 -0700 Subject: [PATCH 095/550] Improve pull algorithm to iterate and evict --- diskcache/core.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 92ef64b..ebc69d7 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1124,7 +1124,7 @@ def pull(self, queue, default=(None, None), expire_time=False, tag=False): :param bool expire_time: if True, return expire_time in tuple (default False) :param bool tag: if True, return tag in tuple (default False) - :return: key and value item or default if key not found + :return: key and value item or default if queue is empty :raises Timeout: if database timeout expires """ @@ -1133,7 +1133,6 @@ def pull(self, queue, default=(None, None), expire_time=False, tag=False): select = ( 'SELECT rowid, key, expire_time, tag, mode, filename, value' ' FROM Cache WHERE ? < key AND key < ? AND raw = 1' - ' AND (expire_time IS NULL OR expire_time > ?)' ' ORDER BY key LIMIT 1' ) @@ -1142,15 +1141,21 @@ def pull(self, queue, default=(None, None), expire_time=False, tag=False): elif expire_time or tag: default = default, None - with self._transact() as (sql, cleanup): - rows = sql(select, (min_key, max_key, time.time())).fetchall() + while True: + with self._transact() as (sql, cleanup): + rows = sql(select, (min_key, max_key)).fetchall() - if not rows: - return default + if not rows: + return default - (rowid, key, expire_time, tag, mode, filename, db_value), = rows + (rowid, key, db_expire, tag, mode, filename, db_value), = rows - sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) + sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) + + if db_expire is not None and db_expire < time.time(): + cleanup(filename) + else: + break try: value = self._disk.fetch(mode, filename, db_value, False) @@ -1167,7 +1172,7 @@ def pull(self, queue, default=(None, None), expire_time=False, tag=False): if expire_time and tag: return (key, value), db_expire, db_tag elif expire_time: - return (key, value), db_expire_time + return (key, value), db_expire elif tag: return (key, value), db_tag else: From 9e5892094d4332a75b1140b2c97b00c73944ae9c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 9 May 2017 11:57:04 -0700 Subject: [PATCH 096/550] Add push/pull to FanoutCache and DjangoCache --- diskcache/djangocache.py | 45 ++++++++++++++++++++++++++++ diskcache/fanout.py | 63 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 2af0865..0b9868e 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -216,6 +216,51 @@ def has_key(self, key, version=None): return key in self._cache + def push(self, queue, value, expire=None, read=False, tag=None, + retry=True): + """Push `value` onto end of `queue` in cache. + + Operation is atomic. Concurrent operations will be serialized. + + When `read` is `True`, `value` should be a file-like object opened + for reading in binary mode. + + :param str queue: queue name + :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 expires (default True) + :return: key in cache if item was pushed else None + + """ + key = self.make_key(queue) + return self._cache.push(key, value, expire, read, tag, retry) + + + def pull(self, queue, default=(None, None), expire_time=False, tag=False, + retry=True): + """Pull key and value item from start of `queue` in cache. + + If queue is empty, return `default`. + + Operation is atomic. Concurrent operations will be serialized. + + :param str queue: queue name + :param default: value to return if key is missing + (default (None, None)) + :param bool expire_time: if True, return expire_time in tuple + (default False) + :param bool tag: if True, return tag in tuple (default False) + :param bool retry: retry if database timeout expires (default True) + :return: key and value item or default if queue is empty + + """ + key = self.make_key(queue) + return self._cache.pull(key, default, expire_time, tag, retry) + + def expire(self): """Remove expired items from cache. diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 62eec40..14ce6b3 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -325,6 +325,69 @@ def __delitem__(self, key): continue + def push(self, queue, value, expire=None, read=False, tag=None, + retry=False): + """Push `value` onto end of `queue` in cache. + + Operation is atomic. Concurrent operations will be serialized. + + When `read` is `True`, `value` should be a file-like object opened + for reading in binary mode. + + :param str queue: queue name + :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 expires (default False) + :return: key in cache if item was pushed else None + + """ + index = hash(queue) % self._count + push_func = self._shards[index].push + + while True: + try: + return push_func(queue, value, expire, read, tag) + except Timeout: + if retry: + continue + else: + return None + + + def pull(self, queue, default=(None, None), expire_time=False, tag=False, + retry=False): + """Pull key and value item from start of `queue` in cache. + + If queue is empty, return `default`. + + Operation is atomic. Concurrent operations will be serialized. + + :param str queue: queue name + :param default: value to return if key is missing + (default (None, None)) + :param bool expire_time: if True, return expire_time in tuple + (default False) + :param bool tag: if True, return tag in tuple (default False) + :param bool retry: retry if database timeout expires (default False) + :return: key and value item or default if queue is empty + + """ + index = hash(queue) % self._count + pull_func = self._shards[index].pull + + while True: + try: + return pull_func(queue, default, expire_time, tag) + except Timeout: + if retry: + continue + else: + return default + + def check(self, fix=False): """Check database and file system consistency. From 708b06da5e85cddb4c1b45b72dce6c40f55080a2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 17 May 2017 10:25:33 -0700 Subject: [PATCH 097/550] Update push/pull and remove from FanoutCache and DjangoCache --- diskcache/core.py | 44 +++++++++++++++++++--------- diskcache/djangocache.py | 45 ---------------------------- diskcache/fanout.py | 63 ---------------------------------------- 3 files changed, 31 insertions(+), 121 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index ebc69d7..b699d12 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1063,16 +1063,16 @@ def delete(self, key): return False - def push(self, queue, value, expire=None, read=False, tag=None): - """Push `value` onto end of `queue` in cache. + def push(self, value, prefix=None, expire=None, read=False, tag=None): + """Push `value` onto end of queue in cache. Operation is atomic. Concurrent operations will be serialized. When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. - :param str queue: queue name :param value: value for item + :param str prefix: key prefix (default None, key is integer) :param float expire: seconds until the key expires (default None, no expiry) :param bool read: read value as bytes from file (default False) @@ -1081,8 +1081,13 @@ def push(self, queue, value, expire=None, read=False, tag=None): :raises Timeout: if database timeout expires """ - min_key = queue + '-000000000000000' - max_key = queue + '-999999999999999' + if prefix is None: + min_key = 0 + max_key = 999999999999999 + else: + min_key = prefix + '-000000000000000' + max_key = prefix + '-999999999999999' + now = time.time() raw = True expire_time = None if expire is None else now + expire @@ -1099,11 +1104,17 @@ def push(self, queue, value, expire=None, read=False, tag=None): if rows: (key,), = rows - num = int(key[(key.rfind('-') + 1):]) + 1 + if prefix is not None: + num = int(key[(key.rfind('-') + 1):]) + 1 + else: + num = key + 1 else: num = 500000000000000 - db_key = '{0}-{1:015d}'.format(queue, num) + if prefix is not None: + db_key = '{0}-{1:015d}'.format(prefix, num) + else: + db_key = num self._row_insert(db_key, raw, now, columns) self._cull(now, sql, cleanup) @@ -1111,14 +1122,15 @@ def push(self, queue, value, expire=None, read=False, tag=None): return db_key - def pull(self, queue, default=(None, None), expire_time=False, tag=False): - """Pull key and value item from start of `queue` in cache. + def pull(self, prefix=None, default=(None, None), expire_time=False, + tag=False): + """Pull key and value item from start of queue in cache. If queue is empty, return `default`. Operation is atomic. Concurrent operations will be serialized. - :param str queue: queue name + :param str prefix: key prefix (default None, key is integer) :param default: value to return if key is missing (default (None, None)) :param bool expire_time: if True, return expire_time in tuple @@ -1128,8 +1140,14 @@ def pull(self, queue, default=(None, None), expire_time=False, tag=False): :raises Timeout: if database timeout expires """ - min_key = queue + '-000000000000000' - max_key = queue + '-999999999999999' + if prefix is None: + min_key = 0 + max_key = 999999999999999 + else: + min_key = prefix + '-000000000000000' + max_key = prefix + '-999999999999999' + + now = time.time() select = ( 'SELECT rowid, key, expire_time, tag, mode, filename, value' ' FROM Cache WHERE ? < key AND key < ? AND raw = 1' @@ -1152,7 +1170,7 @@ def pull(self, queue, default=(None, None), expire_time=False, tag=False): sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) - if db_expire is not None and db_expire < time.time(): + if db_expire is not None and db_expire < now: cleanup(filename) else: break diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 0b9868e..2af0865 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -216,51 +216,6 @@ def has_key(self, key, version=None): return key in self._cache - def push(self, queue, value, expire=None, read=False, tag=None, - retry=True): - """Push `value` onto end of `queue` in cache. - - Operation is atomic. Concurrent operations will be serialized. - - When `read` is `True`, `value` should be a file-like object opened - for reading in binary mode. - - :param str queue: queue name - :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 expires (default True) - :return: key in cache if item was pushed else None - - """ - key = self.make_key(queue) - return self._cache.push(key, value, expire, read, tag, retry) - - - def pull(self, queue, default=(None, None), expire_time=False, tag=False, - retry=True): - """Pull key and value item from start of `queue` in cache. - - If queue is empty, return `default`. - - Operation is atomic. Concurrent operations will be serialized. - - :param str queue: queue name - :param default: value to return if key is missing - (default (None, None)) - :param bool expire_time: if True, return expire_time in tuple - (default False) - :param bool tag: if True, return tag in tuple (default False) - :param bool retry: retry if database timeout expires (default True) - :return: key and value item or default if queue is empty - - """ - key = self.make_key(queue) - return self._cache.pull(key, default, expire_time, tag, retry) - - def expire(self): """Remove expired items from cache. diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 14ce6b3..62eec40 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -325,69 +325,6 @@ def __delitem__(self, key): continue - def push(self, queue, value, expire=None, read=False, tag=None, - retry=False): - """Push `value` onto end of `queue` in cache. - - Operation is atomic. Concurrent operations will be serialized. - - When `read` is `True`, `value` should be a file-like object opened - for reading in binary mode. - - :param str queue: queue name - :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 expires (default False) - :return: key in cache if item was pushed else None - - """ - index = hash(queue) % self._count - push_func = self._shards[index].push - - while True: - try: - return push_func(queue, value, expire, read, tag) - except Timeout: - if retry: - continue - else: - return None - - - def pull(self, queue, default=(None, None), expire_time=False, tag=False, - retry=False): - """Pull key and value item from start of `queue` in cache. - - If queue is empty, return `default`. - - Operation is atomic. Concurrent operations will be serialized. - - :param str queue: queue name - :param default: value to return if key is missing - (default (None, None)) - :param bool expire_time: if True, return expire_time in tuple - (default False) - :param bool tag: if True, return tag in tuple (default False) - :param bool retry: retry if database timeout expires (default False) - :return: key and value item or default if queue is empty - - """ - index = hash(queue) % self._count - pull_func = self._shards[index].pull - - while True: - try: - return pull_func(queue, default, expire_time, tag) - except Timeout: - if retry: - continue - else: - return default - - def check(self, fix=False): """Check database and file system consistency. From 2eb1437b17b0e03343a71e49736da5e53ac3ddaf Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 17 May 2017 11:12:34 -0700 Subject: [PATCH 098/550] Add coverage testing for Cache.push/pull --- diskcache/core.py | 13 +++-- tests/test_core.py | 119 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 7 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index b699d12..e6d2e61 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1147,7 +1147,6 @@ def pull(self, prefix=None, default=(None, None), expire_time=False, min_key = prefix + '-000000000000000' max_key = prefix + '-999999999999999' - now = time.time() select = ( 'SELECT rowid, key, expire_time, tag, mode, filename, value' ' FROM Cache WHERE ? < key AND key < ? AND raw = 1' @@ -1166,17 +1165,17 @@ def pull(self, prefix=None, default=(None, None), expire_time=False, if not rows: return default - (rowid, key, db_expire, tag, mode, filename, db_value), = rows + (rowid, key, db_expire, db_tag, mode, name, db_value), = rows sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) - if db_expire is not None and db_expire < now: - cleanup(filename) + if db_expire is not None and db_expire < time.time(): + cleanup(name) else: break try: - value = self._disk.fetch(mode, filename, db_value, False) + value = self._disk.fetch(mode, name, db_value, False) except IOError as error: if error.errno == errno.ENOENT: # Key was deleted before we could retrieve result. @@ -1184,8 +1183,8 @@ def pull(self, prefix=None, default=(None, None), expire_time=False, else: raise finally: - if filename is not None: - self._disk.remove(filename) + if name is not None: + self._disk.remove(name) if expire_time and tag: return (key, value), db_expire, db_tag diff --git a/tests/test_core.py b/tests/test_core.py index 8f4596e..2766e12 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1051,6 +1051,125 @@ def test_reversed_error(cache): next(reversed(cache)) +@setup_cache +def test_push_pull(cache): + for value in range(10): + cache.push(value) + + for value in range(10): + _, pull_value = cache.pull() + assert pull_value == value + + assert len(cache) == 0 + + +@setup_cache +def test_push_pull_prefix(cache): + for value in range(10): + cache.push(value, prefix='key') + + for value in range(10): + key, pull_value = cache.pull(prefix='key') + assert key.startswith('key') + assert pull_value == value + + assert len(cache) == 0 + assert len(cache.check()) == 0 + + +@setup_cache +def test_push_pull_extras(cache): + cache.push('test') + assert cache.pull() == (500000000000000, 'test') + assert len(cache) == 0 + + cache.push('test', expire=10) + (key, value), expire_time = cache.pull(expire_time=True) + assert key == 500000000000000 + assert value == 'test' + assert expire_time > time.time() + assert len(cache) == 0 + + cache.push('test', tag='foo') + (key, value), tag = cache.pull(tag=True) + assert key == 500000000000000 + assert value == 'test' + assert tag == 'foo' + assert len(cache) == 0 + + cache.push('test') + (key, value), expire_time, tag = cache.pull(expire_time=True, tag=True) + assert key == 500000000000000 + assert value == 'test' + assert expire_time is None + assert tag is None + assert len(cache) == 0 + + assert cache.pull(default=(0, 1)) == (0, 1) + + assert len(cache.check()) == 0 + + +@setup_cache +def test_push_pull_expire(cache): + cache.push(0, expire=0.1) + cache.push(0, expire=0.1) + cache.push(0, expire=0.1) + cache.push(1) + time.sleep(0.2) + assert cache.pull() == (500000000000003, 1) + assert len(cache) == 0 + assert len(cache.check()) == 0 + + +@setup_cache +def test_push_pull_large_value(cache): + value = b'test' * (2 ** 12) + cache.push(value) + assert cache.pull() == (500000000000000, value) + assert len(cache) == 0 + assert len(cache.check()) == 0 + + +@setup_cache +def test_pull_ioerror(cache): + assert cache.push(0) == 500000000000000 + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.ENOENT + fetch.side_effect = io_error + + with mock.patch.object(cache, '_disk', disk): + assert cache.pull() == (None, None) + + +@setup_cache +@nt.raises(IOError) +def test_pull_ioerror_eacces(cache): + assert cache.push(0) == 500000000000000 + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.EACCES + fetch.side_effect = io_error + + with mock.patch.object(cache, '_disk', disk): + cache.pull() + + if __name__ == '__main__': import nose nose.runmodule() From 14cd2858ccd216a94e85f9fc5c74b44120a3830a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 17 May 2017 11:42:04 -0700 Subject: [PATCH 099/550] Add cache.directory attribute --- diskcache/core.py | 6 ++++++ diskcache/djangocache.py | 7 +++++++ diskcache/fanout.py | 10 ++++++++-- tests/test_core.py | 1 + tests/test_djangocache.py | 3 +++ tests/test_fanout.py | 6 ++++-- 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index e6d2e61..40ba312 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -462,6 +462,12 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): assert self._sql + @property + def directory(self): + """Cache directory.""" + return self._dir + + @property def _sql(self): con = getattr(self._local, 'con', None) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 2af0865..0722241 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -24,9 +24,16 @@ def __init__(self, directory, params): shards = params.get('SHARDS', 8) timeout = params.get('DATABASE_TIMEOUT', 0.025) options = params.get('OPTIONS', {}) + self._dir = directory self._cache = FanoutCache(directory, shards, timeout, **options) + @property + def directory(self): + """Cache directory.""" + return self._dir + + def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, read=False, tag=None, retry=True): """Set a value in the cache if the key does not already exist. If diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 62eec40..730ec43 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -21,6 +21,7 @@ def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, :param settings: any of `DEFAULT_SETTINGS` """ + self._dir = directory self._count = shards default_size_limit = DEFAULT_SETTINGS['size_limit'] size_limit = settings.pop('size_limit', default_size_limit) / shards @@ -36,6 +37,12 @@ def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, ) + @property + def directory(self): + """Cache directory.""" + return self._dir + + def __getattr__(self, name): return getattr(self._shards[0], name) @@ -405,8 +412,7 @@ def _remove(self, name, args=()): except Timeout as timeout: total += timeout.args[0] else: - if not count: - break + break return total diff --git a/tests/test_core.py b/tests/test_core.py index 2766e12..d3576e1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -55,6 +55,7 @@ def test_init_disk(): key = (None, 0, 'abc') cache[key] = 0 cache.check() + assert cache.directory == 'tmp' assert cache.disk_min_file_size == 2 ** 20 assert cache.disk_pickle_protocol == 1 shutil.rmtree('tmp', ignore_errors=True) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index fe233ca..4bdbb3d 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -867,6 +867,9 @@ def test_zero_cull(self): def test_invalid_keys(self): pass # DiskCache supports any Pickleable value as a key. + def test_directory(self): + self.assertTrue('tmp' in cache.directory) + def test_read(self): value = b'abcd' * 2 ** 12 result = cache.set(b'test-key', value) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index d276358..f08b734 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -39,6 +39,8 @@ def wrapper(): @setup_cache def test_init(cache): + assert cache.directory == 'tmp' + default_settings = dc.DEFAULT_SETTINGS.copy() del default_settings['size_limit'] for key, value in default_settings.items(): @@ -418,10 +420,10 @@ def test_remove_timeout(cache): clear = mock.Mock() shard.clear = clear - clear.side_effect = [1, dc.Timeout(2), 3, 0] + clear.side_effect = [dc.Timeout(2), 3] with mock.patch.object(cache, '_shards', [shard]): - assert cache.clear() == 6 + assert cache.clear() == 5 @setup_cache From db4f389de8090b8f66eee7020b8c6a8f223b8d0b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 17 May 2017 11:59:48 -0700 Subject: [PATCH 100/550] Add 'none' eviction policy with tests --- diskcache/core.py | 12 +++++++++--- tests/stress_test_core.py | 5 +++++ tests/stress_test_fanout.py | 5 +++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 40ba312..8d3431c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -76,6 +76,11 @@ class WindowsError(Exception): } EVICTION_POLICY = { + 'none': { + 'init': None, + 'get': None, + 'cull': None, + }, 'least-recently-stored': { 'init': ( 'CREATE INDEX IF NOT EXISTS Cache_store_time ON' @@ -657,12 +662,13 @@ def _cull(self, now, sql, cleanup): if cull_limit == 0: return - if self.volume() < self.size_limit: - return - # Evict keys by policy. select_policy_template = EVICTION_POLICY[self.eviction_policy]['cull'] + + if select_policy_template is None or self.volume() < self.size_limit: + return + select_policy = select_policy_template % 'filename' rows = sql(select_policy, (cull_limit,)).fetchall() diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index a3beb4b..3ce95d6 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -271,6 +271,11 @@ def stress_test_lfu(): stress_test(eviction_policy=u'least-frequently-used') +def stress_test_none(): + "Stress test 'none' eviction policy." + stress_test(eviction_policy=u'none') + + def stress_test_mp(): "Stress test multiple threads and processes." stress_test(processes=4, threads=4) diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index 2024a42..053c6a6 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -271,6 +271,11 @@ def stress_test_lfu(): stress_test(eviction_policy=u'least-frequently-used') +def stress_test_none(): + "Stress test 'none' eviction policy." + stress_test(eviction_policy=u'none') + + def stress_test_mp(): "Stress test multiple threads and processes." stress_test(processes=4, threads=4) From b08652f4a96941a8ec87aa078f4db69159904807 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 30 May 2017 09:49:57 -0700 Subject: [PATCH 101/550] Add 'side' to push/pull and iterkeys method --- diskcache/core.py | 154 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 17 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 8d3431c..687b6fc 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1075,16 +1075,39 @@ def delete(self, key): return False - def push(self, value, prefix=None, expire=None, read=False, tag=None): - """Push `value` onto end of queue in cache. + def push(self, value, prefix=None, side='back', expire=None, read=False, + tag=None): + """Push `value` onto `side` of queue identified by `prefix` in cache. + + When prefix is None, integer keys are used. Otherwise, string keys are + used in the format "prefix-integer". Integer starts at 500 trillion. + + Defaults to pushing value on back of queue. Set side to 'front' to push + value on front of queue. Side must be one of 'back' or 'front'. Operation is atomic. Concurrent operations will be serialized. When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. + See also `Cache.pull`. + + >>> cache = Cache('/tmp/test') + >>> _ = cache.clear() + >>> cache.push('first value') + 500000000000000 + >>> cache.get(500000000000000) + 'first value' + >>> cache.push('second value') + 500000000000001 + >>> cache.push('third value', side='front') + 499999999999999 + >>> cache.push(1234, prefix='userids') + 'userids-500000000000000' + :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) @@ -1105,21 +1128,29 @@ def push(self, value, prefix=None, expire=None, read=False, tag=None): expire_time = None if expire is None else now + expire size, mode, filename, db_value = self._disk.store(value, read) columns = (expire_time, tag, size, mode, filename, db_value) + order = {'back': 'DESC', 'front': 'ASC'} + select = ( + 'SELECT key FROM Cache' + ' WHERE ? < key AND key < ? AND raw = ?' + ' ORDER BY key %s LIMIT 1' + ) % order[side] with self._transact(filename) as (sql, cleanup): - rows = sql( - 'SELECT key FROM Cache' - ' WHERE ? < key AND key < ? AND raw = ?' - ' ORDER BY key DESC LIMIT 1', - (min_key, max_key, raw), - ).fetchall() + rows = sql(select, (min_key, max_key, raw)).fetchall() if rows: (key,), = rows + if prefix is not None: - num = int(key[(key.rfind('-') + 1):]) + 1 + num = int(key[(key.rfind('-') + 1):]) else: - num = key + 1 + num = key + + if side == 'back': + num += 1 + else: + assert side == 'front' + num -= 1 else: num = 500000000000000 @@ -1134,21 +1165,49 @@ def push(self, value, prefix=None, expire=None, read=False, tag=None): return db_key - def pull(self, prefix=None, default=(None, None), expire_time=False, - tag=False): - """Pull key and value item from start of queue in cache. + def pull(self, prefix=None, default=(None, None), side='front', + expire_time=False, tag=False): + """Pull key and value item pair from `side` of queue in cache. + + When prefix is None, 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`. + 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. + See also `Cache.push` and `Cache.get`. + + >>> cache = Cache('/tmp/test') + >>> _ = cache.clear() + >>> cache.pull() + (None, None) + >>> for letter in 'abc': + ... cache.push(letter) + 500000000000000 + 500000000000001 + 500000000000002 + >>> cache.pull() + (500000000000000, 'a') + >>> cache.pull(side='back') + (500000000000002, 'c') + >>> cache.push(1234, 'userids') + 'userids-500000000000000' + >>> cache.pull('userids') + (u'userids-500000000000000', 1234) + :param str prefix: key prefix (default None, key is integer) :param default: value to return if key is missing (default (None, None)) + :param str side: either 'front' or 'back' (default 'front') :param bool expire_time: if True, return expire_time in tuple (default False) :param bool tag: if True, return tag in tuple (default False) - :return: key and value item or default if queue is empty + :return: key and value item pair or default if queue is empty :raises Timeout: if database timeout expires """ @@ -1159,11 +1218,12 @@ def pull(self, prefix=None, default=(None, None), expire_time=False, min_key = prefix + '-000000000000000' max_key = prefix + '-999999999999999' + order = {'front': 'ASC', 'back': 'DESC'} select = ( 'SELECT rowid, key, expire_time, tag, mode, filename, value' ' FROM Cache WHERE ? < key AND key < ? AND raw = 1' - ' ORDER BY key LIMIT 1' - ) + ' ORDER BY key %s LIMIT 1' + ) % order[side] if expire_time and tag: default = default, None, None @@ -1454,6 +1514,66 @@ def _select_delete(self, select, args, row_index=0, arg_index=0): return count + def iterkeys(self, reverse=False): + """Iterate Cache keys in database sort order. + + >>> cache = Cache('/tmp/diskcache') + >>> _ = cache.clear() + >>> for key in [4, 1, 3, 0, 2]: + ... cache[key] = key + >>> list(cache.iterkeys()) + [0, 1, 2, 3, 4] + >>> list(cache.iterkeys(reverse=True)) + [4, 3, 2, 1, 0] + + :param bool reverse: reverse sort order (default False) + :return: iterator of Cache keys + + """ + sql = self._sql + limit = 100 + _disk_get = self._disk.get + + if reverse: + select = ( + 'SELECT key, raw FROM Cache' + ' ORDER BY key DESC, raw DESC LIMIT 1' + ) + iterate = ( + 'SELECT key, raw FROM Cache' + ' WHERE key = ? AND raw < ? OR key < ?' + ' ORDER BY key DESC, raw DESC LIMIT ?' + ) + else: + select = ( + 'SELECT key, raw FROM Cache' + ' ORDER BY key ASC, raw ASC LIMIT 1' + ) + iterate = ( + 'SELECT key, raw FROM Cache' + ' WHERE key = ? AND raw > ? OR key > ?' + ' ORDER BY key ASC, raw ASC LIMIT ?' + ) + + row = sql(select).fetchall() + + if row: + (key, raw), = row + else: + return + + yield _disk_get(key, raw) + + while True: + rows = sql(iterate, (key, raw, key, limit)).fetchall() + + if not rows: + break + + for key, raw in rows: + yield _disk_get(key, raw) + + def _iter(self, ascending=True): sql = self._sql rows = sql('SELECT MAX(rowid) FROM Cache').fetchall() From 692ddb19e6b195efd97d8da404f97bcdce47dba3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 31 May 2017 11:11:38 -0700 Subject: [PATCH 102/550] Add hash method to Disk and change Fanout to use portable hash --- diskcache/core.py | 30 ++++++++++++++++++++++++++++++ diskcache/fanout.py | 19 ++++++++++--------- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 687b6fc..5b51956 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -2,6 +2,7 @@ """ +import binascii import codecs import contextlib as cl import errno @@ -10,6 +11,7 @@ import os import os.path as op import sqlite3 +import struct import sys import threading import time @@ -123,6 +125,28 @@ def __init__(self, directory, min_file_size=0, pickle_protocol=0): self.pickle_protocol = pickle_protocol + def hash(self, key): + """Compute portable hash for `key`. + + :param key: key to hash + :return: hash value + + """ + mask = 0xFFFFFFFF + disk_key, _ = self.put(key) + type_disk_key = type(disk_key) + + if type_disk_key is sqlite3.Binary: + return binascii.crc32(disk_key) & mask + elif type_disk_key is TextType: + return binascii.crc32(disk_key.encode('utf-8')) & mask + elif type_disk_key in INT_TYPES: + return disk_key % mask + else: + assert type_disk_key is float + return binascii.crc32(struct.pack('!d', disk_key)) & mask + + def put(self, key): """Convert `key` to fields key and raw for Cache table. @@ -473,6 +497,12 @@ def directory(self): return self._dir + @property + def disk(self): + """Disk used for serialization.""" + return self._disk + + @property def _sql(self): con = getattr(self._local, 'con', None) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 730ec43..0125d41 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -35,6 +35,7 @@ def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, ) for num in range(shards) ) + self._hash = self._shards[0].disk.hash @property @@ -63,7 +64,7 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): :return: True if item was set """ - index = hash(key) % self._count + index = self._hash(key) % self._count set_func = self._shards[index].set while True: @@ -83,7 +84,7 @@ def __setitem__(self, key, value): :param value: value for item """ - index = hash(key) % self._count + index = self._hash(key) % self._count set_func = self._shards[index].set while True: @@ -114,7 +115,7 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): :return: True if item was added """ - index = hash(key) % self._count + index = self._hash(key) % self._count add_func = self._shards[index].add while True: @@ -148,7 +149,7 @@ def incr(self, key, delta=1, default=0, retry=False): :raises KeyError: if key is not found and default is None """ - index = hash(key) % self._count + index = self._hash(key) % self._count incr_func = self._shards[index].incr while True: @@ -203,7 +204,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, :return: value for item if key is found else default """ - index = hash(key) % self._count + index = self._hash(key) % self._count get_func = self._shards[index].get while True: @@ -254,7 +255,7 @@ def __contains__(self, key): :return: True if key is found """ - index = hash(key) % self._count + index = self._hash(key) % self._count return key in self._shards[index] @@ -275,7 +276,7 @@ def pop(self, key, default=None, expire_time=False, tag=False, :return: value for item if key is found else default """ - index = hash(key) % self._count + index = self._hash(key) % self._count pop_func = self._shards[index].pop while True: @@ -300,7 +301,7 @@ def delete(self, key, retry=False): :return: True if item was deleted """ - index = hash(key) % self._count + index = self._hash(key) % self._count del_func = self._shards[index].__delitem__ while True: @@ -322,7 +323,7 @@ def __delitem__(self, key): :raises KeyError: if key is not found """ - index = hash(key) % self._count + index = self._hash(key) % self._count del_func = self._shards[index].__delitem__ while True: From d51f6fe65ec85193da8d96644293d06c5e337d1b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 27 Jul 2017 10:54:47 -0700 Subject: [PATCH 103/550] Change from binascii.crc32 to zlib.adler32 for portable hash --- diskcache/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 5b51956..8f6ab0f 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -2,7 +2,6 @@ """ -import binascii import codecs import contextlib as cl import errno @@ -16,6 +15,7 @@ import threading import time import warnings +import zlib if sys.hexversion < 0x03000000: import cPickle as pickle @@ -137,14 +137,14 @@ def hash(self, key): type_disk_key = type(disk_key) if type_disk_key is sqlite3.Binary: - return binascii.crc32(disk_key) & mask + return zlib.adler32(disk_key) & mask elif type_disk_key is TextType: - return binascii.crc32(disk_key.encode('utf-8')) & mask + return zlib.adler32(disk_key.encode('utf-8')) & mask elif type_disk_key in INT_TYPES: return disk_key % mask else: assert type_disk_key is float - return binascii.crc32(struct.pack('!d', disk_key)) & mask + return zlib.adler32(struct.pack('!d', disk_key)) & mask def put(self, key): From 0d9deeb5a7a00be9bfc3dc0d027283225ccfb262 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 27 Jul 2017 10:55:53 -0700 Subject: [PATCH 104/550] Test docstrings using doctest in core, djangocache, fanout, and persistent modules --- tests/test_doctest.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/test_doctest.py diff --git a/tests/test_doctest.py b/tests/test_doctest.py new file mode 100644 index 0000000..721a8e6 --- /dev/null +++ b/tests/test_doctest.py @@ -0,0 +1,26 @@ +import doctest + +import diskcache.core +import diskcache.djangocache +import diskcache.fanout +import diskcache.persistent + + +def test_core(): + failures, _ = doctest.testmod(diskcache.core) + assert failures == 0 + + +def test_djangocache(): + failures, _ = doctest.testmod(diskcache.djangocache) + assert failures == 0 + + +def test_fanout(): + failures, _ = doctest.testmod(diskcache.fanout) + assert failures == 0 + + +def test_persistent(): + failures, _ = doctest.testmod(diskcache.persistent) + assert failures == 0 From 1dc791759e7d8287db63f27b25a4d32e80c1db82 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 27 Jul 2017 10:58:06 -0700 Subject: [PATCH 105/550] Add prototype code for Web Crawler case study using persistent data structures --- docs/web-crawler-case-study.rst | 113 ++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 docs/web-crawler-case-study.rst diff --git a/docs/web-crawler-case-study.rst b/docs/web-crawler-case-study.rst new file mode 100644 index 0000000..5102c1c --- /dev/null +++ b/docs/web-crawler-case-study.rst @@ -0,0 +1,113 @@ +import requests +import time + +def get(url): + response = requests.get(url) + content = response.content + time.sleep(1) + return content + + +import bs4 +import urllib.parse as up + +def links(root, url, content): + soup = bs4.BeautifulSoup(content, 'lxml') + anchors = soup.find_all('a', href=True) + hrefs = [anchor['href'] for anchor in anchors] + join_links = [up.urljoin(url, href) for href in hrefs] + defrag_links = [up.urldefrag(url)[0] for url in join_links] + root_links = [url for url in defrag_links if url.startswith(root)] + return root_links + + +def main1(): + "Single-process, ephemeral." + root = 'http://www.grantjenks.com' + + results = {} + urls = [root] + + while urls: + url = urls.pop() + + if url in results: + continue + + print(url) + content = get(url) + + for link in links(root, url, content): + urls.append(link) + + results[url] = content + + print(len(results)) + + +import diskcache as dc + +def main2(): + "Multi-process, persistent." + root = 'http://www.grantjenks.com' + + results = dc.Index('data/results') + urls = dc.Queue('data/urls') + + while urls: + url = urls.pull() + + if url is None or url in results: + continue + + print(url) + content = get(url) + + for link in links(root, url, content): + urls.push(link) + + results[url] = content + + print(len(results)) + + +import diskcache as dc + +def main3(): + "Multi-process, persistent, reliable." + root = 'http://www.grantjenks.com' + + results = dc.Index('data/results') + urls = dc.Queue('data/urls') + + # TODO + + state = dc.Index('data/state') + + state.setdefault(root, None) + + while True: + for key in state: + if state[key] is None: + urls.push(key) + + while urls: + url = urls.pull() + + if url is None or url in results: + continue + + print(url) + content = get(url) + + for link in links(root, url, content): + results.setdefault(link, None) + urls.push(link) + + results[url] = content + + print(len(results)) + + +if __name__ == '__main__': + main() From d506092deb3c93c6f2ddb21e12b1bcfbedba2378 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 13:46:24 -0700 Subject: [PATCH 106/550] Add persistent Deque and Index implementation --- .pylintrc | 2 +- diskcache/__init__.py | 18 +- diskcache/core.py | 18 +- diskcache/djangocache.py | 20 + diskcache/fanout.py | 67 +++ diskcache/persistent.py | 1037 +++++++++++++++++++++++++++++++++ docs/api.rst | 16 + tests/stress_test_deque.py | 152 +++++ tests/stress_test_deque_mp.py | 144 +++++ tests/stress_test_index.py | 97 +++ tests/stress_test_index_mp.py | 123 ++++ tests/test_core.py | 5 + tests/test_deque.py | 344 +++++++++++ tests/test_djangocache.py | 10 + tests/test_index.py | 219 +++++++ 15 files changed, 2265 insertions(+), 7 deletions(-) create mode 100644 diskcache/persistent.py create mode 100644 tests/stress_test_deque.py create mode 100644 tests/stress_test_deque_mp.py create mode 100644 tests/stress_test_index.py create mode 100644 tests/stress_test_index_mp.py create mode 100644 tests/test_deque.py create mode 100644 tests/test_index.py diff --git a/.pylintrc b/.pylintrc index ec30778..58fbeae 100644 --- a/.pylintrc +++ b/.pylintrc @@ -346,7 +346,7 @@ max-attributes=10 min-public-methods=2 # Maximum number of public methods for a class (see R0904). -max-public-methods=20 +max-public-methods=25 # Maximum number of boolean expressions in a if statement max-bool-expr=10 diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 7d20ca6..784dd04 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -3,9 +3,25 @@ from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning, Timeout from .core import LIMITS, DEFAULT_SETTINGS, EVICTION_POLICY from .fanout import FanoutCache +from .persistent import Deque, Index + +__all__ = [ + 'Cache', + 'Disk', + 'UnknownFileWarning', + 'EmptyDirWarning', + 'Timeout', + 'LIMITS', + 'DEFAULT_SETTINGS', + 'EVICTION_POLICY', + 'FanoutCache', + 'Deque', + 'Index', +] try: - from .djangocache import DjangoCache + from .djangocache import DjangoCache # pylint: disable=wrong-import-position + __all__.append('DjangoCache') except Exception: # pylint: disable=broad-except # Django not installed or not setup so ignore. pass diff --git a/diskcache/core.py b/diskcache/core.py index 8f6ab0f..75dfbd2 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -25,14 +25,14 @@ BytesType = str INT_TYPES = int, long range = xrange # pylint: disable=redefined-builtin,invalid-name - io_open = io.open + io_open = io.open # pylint: disable=invalid-name else: import pickle from io import BytesIO # pylint: disable=ungrouped-imports TextType = str BytesType = bytes INT_TYPES = int, - io_open = open + io_open = open # pylint: disable=invalid-name try: WindowsError @@ -41,8 +41,16 @@ class WindowsError(Exception): "Windows error place-holder on platforms without support." pass +class Constant(tuple): + "Pretty display of immutable constant." + def __new__(cls, name): + return tuple.__new__(cls, (name,)) + + def __repr__(self): + return self[0] + DBNAME = 'cache.db' -ENOVAL = object() +ENOVAL = Constant('ENOVAL') MODE_NONE = 0 MODE_RAW = 1 @@ -139,7 +147,7 @@ def hash(self, key): if type_disk_key is sqlite3.Binary: return zlib.adler32(disk_key) & mask elif type_disk_key is TextType: - return zlib.adler32(disk_key.encode('utf-8')) & mask + return zlib.adler32(disk_key.encode('utf-8')) & mask # pylint: disable=no-member elif type_disk_key in INT_TYPES: return disk_key % mask else: @@ -1029,7 +1037,7 @@ def pop(self, key, default=None, expire_time=False, tag=False): elif expire_time or tag: default = default, None - with self._transact() as (sql, cleanup): + with self._transact() as (sql, _): rows = sql(select, (db_key, raw, time.time())).fetchall() if not rows: diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 0722241..af4261f 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -34,6 +34,26 @@ def directory(self): return self._dir + def deque(self, name): + """Return Deque with given `name` in subdirectory. + + :param str name: subdirectory name for Deque + :return: Deque with given name + + """ + return self._cache.deque(name) + + + def index(self, name): + """Return Index with given `name` in subdirectory. + + :param str name: subdirectory name for Index + :return: Index with given name + + """ + return self._cache.index(name) + + def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, read=False, tag=None, retry=True): """Set a value in the cache if the key does not already exist. If diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 0125d41..67055f4 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -6,6 +6,7 @@ import time from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout +from .persistent import Deque, Index class FanoutCache(object): @@ -36,6 +37,8 @@ def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, for num in range(shards) ) self._hash = self._shards[0].disk.hash + self._deques = {} + self._indexes = {} @property @@ -44,6 +47,68 @@ def directory(self): return self._dir + def deque(self, name): + """Return Deque with given `name` in subdirectory. + + >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> deque = cache.deque('test') + >>> deque.clear() + >>> deque.extend('abc') + >>> deque.popleft() + 'a' + >>> deque.pop() + 'c' + >>> len(deque) + 1 + + :param str name: subdirectory name for Deque + :return: Deque with given name + + """ + _deques = self._deques + + try: + return _deques[name] + except KeyError: + parts = name.split('/') + directory = op.join(self._dir, 'deque', *parts) + temp = Deque(directory=directory) + _deques[name] = temp + return temp + + + def index(self, name): + """Return Index with given `name` in subdirectory. + + >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> index = cache.index('test') + >>> index['abc'] = 123 + >>> index['def'] = 456 + >>> index['ghi'] = 789 + >>> index.popitem() + ('ghi', 789) + >>> del index['abc'] + >>> len(index) + 1 + >>> index['def'] + 456 + + :param str name: subdirectory name for Index + :return: Index with given name + + """ + _indexes = self._indexes + + try: + return _indexes[name] + except KeyError: + parts = name.split('/') + directory = op.join(self._dir, 'index', *parts) + temp = Index(directory) + _indexes[name] = temp + return temp + + def __getattr__(self, name): return getattr(self._shards[0], name) @@ -443,6 +508,8 @@ def close(self): "Close database connection." for shard in self._shards: shard.close() + self._deques.clear() + self._indexes.clear() def __enter__(self): diff --git a/diskcache/persistent.py b/diskcache/persistent.py new file mode 100644 index 0000000..370a6e3 --- /dev/null +++ b/diskcache/persistent.py @@ -0,0 +1,1037 @@ +"""Persistent Data Types + +""" + +import operator as op +import sys + +from collections import MutableMapping, OrderedDict, Sequence +from itertools import islice +from shutil import rmtree +from tempfile import mkdtemp + +from .core import BytesType, Cache, ENOVAL, TextType, Timeout + +if sys.hexversion < 0x03000000: + from itertools import izip as zip # pylint: disable=redefined-builtin,ungrouped-imports,wrong-import-order + range = xrange # pylint: disable=redefined-builtin,invalid-name + + +def _make_compare(seq_op, doc): + "Make compare method with Sequence semantics." + def compare(self, that): + "Compare method for deque and sequence." + if not isinstance(that, Sequence): + return NotImplemented + + len_self = len(self) + len_that = len(that) + + if len_self != len_that: + if seq_op is op.eq: + return False + if seq_op is op.ne: + return True + + for alpha, beta in zip(self, that): + if alpha != beta: + return seq_op(alpha, beta) + + return seq_op(len_self, len_that) + + compare.__name__ = '__{0}__'.format(seq_op.__name__) + doc_str = 'Return True if and only if deque is {0} `that`.' + compare.__doc__ = doc_str.format(doc) + + return compare + + +class Deque(Sequence): + """Persistent sequence with double-ended queue semantics. + + Double-ended queue is an ordered collection with optimized access at its + endpoints. + + Items are serialized to disk. Deque may be initialized from directory path + where items are stored. + + >>> deque = Deque() + >>> for value in range(5): + ... deque.append(value) + >>> list(deque) + [0, 1, 2, 3, 4] + >>> for value in range(5): + ... deque.appendleft(-value) + >>> list(deque) + [-4, -3, -2, -1, 0, 0, 1, 2, 3, 4] + >>> deque.pop() + 4 + >>> deque.popleft() + -4 + + """ + def __init__(self, iterable=(), directory=None): + """Initialize deque instance. + + If directory is None then temporary directory created. The directory + will *not* be automatically removed. + + :param iterable: iterable of items to append to deque + :param directory: deque directory (default None) + + """ + if directory is None: + directory = mkdtemp() + self._cache = Cache(directory, eviction_policy='none') + self.extend(iterable) + + + @property + def directory(self): + "Directory path where deque is stored." + return self._cache.directory + + + def _key(self, index): + len_self = len(self) + + if index < 0: + index += len_self + if index < 0: + raise IndexError('deque index out of range') + elif index >= len_self: + raise IndexError('deque index out of range') + + diff = len_self - index - 1 + + try: + if index <= diff: + iter_keys = self._cache.iterkeys() + key = next(islice(iter_keys, index, index + 1)) + else: + iter_keys = self._cache.iterkeys(reverse=True) + key = next(islice(iter_keys, diff, diff + 1)) + except StopIteration: + raise IndexError('deque index out of range') + + return key + + + def __getitem__(self, index): + """deque.__getitem__(index) <==> deque[index] + + Return corresponding item for `index` in deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.extend('abcde') + >>> deque[0] + 'a' + >>> deque[-1] + 'e' + >>> deque[2] + 'c' + + :param int index: index of item + :return: corresponding item + :raises IndexError: if index out of range + + """ + while True: + try: + key = self._key(index) + return self._cache[key] + except (KeyError, Timeout): + continue + + + def __setitem__(self, index, value): + """deque.__setitem__(index, value) <==> deque[index] = value + + Store `value` in deque at `index`. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.extend([None] * 3) + >>> deque[0] = 'a' + >>> deque[1] = 'b' + >>> deque[-1] = 'c' + >>> ''.join(deque) + 'abc' + + :param int index: index of value + :param value: value to store + :raises IndexError: if index out of range + + """ + while True: + try: + key = self._key(index) + self._cache[key] = value + return + except Timeout: + continue + + + def __delitem__(self, index): + """deque.__delitem__(index) <==> del deque[index] + + Delete item in deque at `index`. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.extend([None] * 3) + >>> del deque[0] + >>> del deque[1] + >>> del deque[-1] + >>> len(deque) + 0 + + :param int index: index of item + :raises IndexError: if index out of range + + """ + while True: + try: + key = self._key(index) + del self._cache[key] + return + except (KeyError, Timeout): + continue + + + def __repr__(self): + """deque.__repr__() <==> repr(deque) + + Return string with printable representation of deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque + Deque(directory='/tmp/diskcache/deque') + + """ + name = type(self).__name__ + return '{0}(directory={1!r})'.format(name, self.directory) + + + __eq__ = _make_compare(op.eq, 'equal to') + __ne__ = _make_compare(op.ne, 'not equal to') + __lt__ = _make_compare(op.lt, 'less than') + __gt__ = _make_compare(op.gt, 'greater than') + __le__ = _make_compare(op.le, 'less than or equal to') + __ge__ = _make_compare(op.ge, 'greater than or equal to') + + + def __iadd__(self, iterable): + """deque.__iadd__(iterable) <==> deque += iterable + + Extend back side of deque with items from iterable. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += 'abc' + >>> list(deque) + ['a', 'b', 'c'] + + """ + self.extend(iterable) + return self + + + def __iter__(self): + """deque.__iter__() <==> iter(deque) + + Return iterator of deque from front to back. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += 'abc' + >>> iterator = iter(deque) + >>> next(iterator) + 'a' + >>> list(iterator) + ['b', 'c'] + + """ + _cache = self._cache + + for key in _cache.iterkeys(): + try: + yield _cache[key] + except (KeyError, Timeout): + pass + + + def __len__(self): + """deque.__len__() <==> len(deque) + + Return length of deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> len(deque) + 0 + >>> deque.extend('abc') + >>> len(deque) + 3 + + """ + return len(self._cache) + + + def __reversed__(self): + """deque.__reversed__() <==> reversed(deque) + + Return iterator of deque from back to front. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.extend('abc') + >>> iterator = reversed(deque) + >>> next(iterator) + 'c' + >>> list(iterator) + ['b', 'a'] + + """ + _cache = self._cache + + for key in _cache.iterkeys(reverse=True): + try: + yield _cache[key] + except (KeyError, Timeout): + pass + + + def __getstate__(self): + return self.directory + + + def __setstate__(self, state): + self.__init__(directory=state) + + + def append(self, value): + """Add `value` to back of deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.append('a') + >>> deque.append('b') + >>> deque.append('c') + >>> list(deque) + ['a', 'b', 'c'] + + :param value: value to add to back of deque + + """ + while True: + try: + self._cache.push(value) + return + except Timeout: + continue + + + def appendleft(self, value): + """Add `value` to front of deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.appendleft('a') + >>> deque.appendleft('b') + >>> deque.appendleft('c') + >>> list(deque) + ['c', 'b', 'a'] + + :param value: value to add to front of deque + + """ + while True: + try: + self._cache.push(value, side='front') + return + except Timeout: + continue + + + def clear(self): + """Remove all elements from deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += range(10) + >>> len(deque) + 10 + >>> deque.clear() + >>> len(deque) + 0 + + """ + self._cache.clear() + + + def count(self, value): + """Return number of occurrences of `value` in deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += [num for num in range(1, 5) for _ in range(num)] + >>> deque.count(0) + 0 + >>> deque.count(1) + 1 + >>> deque.count(4) + 4 + + :param value: value to count in deque + + """ + return sum(1 for item in self if item == value) + + + def extend(self, iterable): + """Extend back side of deque with values from `iterable`. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.extend('abc') + >>> list(deque) + ['a', 'b', 'c'] + + :param iterable: iterable of values + + """ + for value in iterable: + self.append(value) + + + def extendleft(self, iterable): + """Extend front side of deque with value from `iterable`. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.extendleft('abc') + >>> list(deque) + ['c', 'b', 'a'] + + :param iterable: iterable of values + + """ + for value in iterable: + self.appendleft(value) + + + def pop(self): + """Remove and return value at back of deque. + + If deque is empty then raise IndexError. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += 'ab' + >>> deque.pop() + 'b' + >>> deque.pop() + 'a' + >>> deque.pop() + Traceback (most recent call last): + ... + IndexError: pop from an empty deque + + :raises IndexError: if deque is empty + + """ + while True: + try: + default = None, ENOVAL + _, value = self._cache.pull(default=default, side='back') + except Timeout: + continue + else: + if value is ENOVAL: + raise IndexError('pop from an empty deque') + return value + + + def popleft(self): + """Remove and return value at front of deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += 'ab' + >>> deque.popleft() + 'a' + >>> deque.popleft() + 'b' + >>> deque.popleft() + Traceback (most recent call last): + ... + IndexError: pop from an empty deque + + """ + while True: + try: + default = None, ENOVAL + _, value = self._cache.pull(default=default) + except Timeout: + continue + else: + if value is ENOVAL: + raise IndexError('pop from an empty deque') + return value + + + def remove(self, value): + """Remove first occurrence of `value` in deque. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += 'aab' + >>> deque.remove('a') + >>> list(deque) + ['a', 'b'] + >>> deque.remove('b') + >>> list(deque) + ['a'] + >>> deque.remove('c') + Traceback (most recent call last): + ... + ValueError: deque.remove(value): value not in deque + + :param value: value to remove + :raises ValueError: if value not in deque + + """ + _cache = self._cache + + for key in _cache.iterkeys(): + try: + item = _cache[key] + except KeyError: + continue + else: + if value == item: + try: + while True: + try: + del _cache[key] + except Timeout: + continue + else: + return + except KeyError: + continue + + raise ValueError('deque.remove(value): value not in deque') + + + def reverse(self): + """Reverse deque in place. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.extend('abc') + >>> deque.reverse() + >>> list(deque) + ['c', 'b', 'a'] + + """ + directory = mkdtemp() + + try: + temp = Deque(iterable=reversed(self), directory=directory) + self.clear() + self.extend(temp) + finally: + rmtree(directory) + + + def rotate(self, steps=1): + """Rotate deque right by `steps`. + + If steps is negative then rotate left. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += range(5) + >>> deque.rotate(2) + >>> list(deque) + [3, 4, 0, 1, 2] + >>> deque.rotate(-1) + >>> list(deque) + [4, 0, 1, 2, 3] + + :param int steps: number of steps to rotate (default 1) + + """ + if not isinstance(steps, int): + type_name = type(steps).__name__ + raise TypeError('integer argument expected, got %s' % type_name) + + if steps >= 0: + for _ in range(steps): + try: + value = self.pop() + except IndexError: + return + else: + self.appendleft(value) + else: + for _ in range(-steps): + try: + value = self.popleft() + except IndexError: + return + else: + self.append(value) + + + __hash__ = None + + +class Index(MutableMapping): + """Persistent mutable mapping with insertion order iteration. + + Items are serialized to disk. Index may be initialized from directory path + where items are stored. + + Hashing protocol is not used. Keys are looked up by their serialized + format. See ``diskcache.Disk`` for details. + + >>> index = Index([('apple', 1), ('beans', 2), ('cherries', 3)]) + >>> index['apple'] + 1 + >>> list(index) + ['apple', 'beans', 'cherries'] + >>> len(index) + 3 + >>> del index['beans'] + >>> index.popitem() + ('cherries', 3) + + """ + def __init__(self, *args, **kwargs): + """Initialize index in directory and update items. + + Optional first argument may be string specifying directory where items + are stored. When None or not given, temporary directory is created. + + >>> index = Index({'apples': 1, 'beans': 2, 'cherries': 3}) + >>> len(index) + 3 + >>> directory = index.directory + >>> inventory = Index(directory, dates=4) + >>> inventory['beans'] + 2 + >>> len(inventory) + 4 + + """ + if args and isinstance(args[0], (BytesType, TextType)): + directory = args[0] + args = args[1:] + else: + if args and args[0] is None: + args = args[1:] + directory = mkdtemp(prefix='diskcache-') + self._cache = Cache(directory, eviction_policy='none', timeout=0.001) + self.update(*args, **kwargs) + + + @property + def directory(self): + "Directory path where items are stored." + return self._cache.directory + + + def __getitem__(self, key): + """index.__getitem__(key) <==> index[key] + + Return corresponding value for `key` in index. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b': 1}) + >>> index['a'] + 0 + >>> index['b'] + 1 + >>> index['c'] + Traceback (most recent call last): + ... + KeyError: 'c' + + :param key: key for item + :raises KeyError: if key is not found + + """ + _cache = self._cache + + while True: + try: + return _cache[key] + except Timeout: + continue + + + def __setitem__(self, key, value): + """index.__setitem__(key, value) <==> index[key] = value + + Set `key` and `value` item in index. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index['a'] = 0 + >>> index[0] = None + >>> len(index) + 2 + + :param key: key for item + :param value: value for item + + """ + _cache = self._cache + + while True: + try: + _cache[key] = value + except Timeout: + continue + else: + return + + + def __delitem__(self, key): + """index.__delitem__(key) <==> del index[key] + + Delete corresponding item for `key` from index. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b': 1}) + >>> del index['a'] + >>> del index['b'] + >>> len(index) + 0 + >>> del index['c'] + Traceback (most recent call last): + ... + KeyError: 'c' + + :param key: key for item + :raises KeyError: if key is not found + + """ + _cache = self._cache + + while True: + try: + del _cache[key] + except Timeout: + continue + else: + return + + + def pop(self, key, default=ENOVAL): + """Remove corresponding item for `key` from index and return value. + + If `key` is missing then return `default`. If `default` is `ENOVAL` + then raise KeyError. + + >>> index = Index('/tmp/diskcache/index', {'a': 0, 'b': 1}) + >>> index.pop('a') + 0 + >>> index.pop('b') + 1 + >>> index.pop('c', default=2) + 2 + >>> index.pop('d') + Traceback (most recent call last): + ... + KeyError: 'd' + + :param key: key for item + :param default: return value if key is missing (default ENOVAL) + :return: value for item if key is found else default + :raises KeyError: if key is not found and default is ENOVAL + + """ + + _cache = self._cache + + while True: + try: + value = _cache.pop(key, default=default) + except Timeout: + continue + else: + if value is ENOVAL: + raise KeyError(key) + return value + + + def popitem(self, last=True): + """Remove and return item pair. + + Item pairs are returned in last-in-first-out (LIFO) order if last is + True else first-in-first-out (FIFO) order. LIFO order imitates a stack + and FIFO order imitates a queue. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('apples', 1), ('beans', 2), ('cherries', 3)]) + >>> index.popitem() + ('cherries', 3) + >>> index.popitem() + ('beans', 2) + >>> index.popitem() + ('apples', 1) + >>> index.popitem() + Traceback (most recent call last): + ... + KeyError + + :param bool last: pop last item pair (default True) + :return: key and value item pair + :raises KeyError: if index is empty + + """ + # pylint: disable=arguments-differ + _cache = self._cache + + while True: + try: + if last: + key = next(reversed(_cache)) + else: + key = next(iter(_cache)) + except StopIteration: + raise KeyError + + try: + value = _cache.pop(key) + except (KeyError, Timeout): + continue + else: + return key, value + + + def push(self, value, prefix=None, side='back'): + """Push `value` onto `side` of queue in index identified by `prefix`. + + When prefix is None, integer keys are used. Otherwise, string keys are + used in the format "prefix-integer". Integer starts at 500 trillion. + + Defaults to pushing value on back of queue. Set side to 'front' to push + value on front of queue. Side must be one of 'back' or 'front'. + + See also `Index.pull`. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.push('apples') + 500000000000000 + >>> index.push('beans') + 500000000000001 + >>> index.push('cherries', side='front') + 499999999999999 + >>> index[500000000000001] + 'beans' + >>> index.push('dates', prefix='fruit') + 'fruit-500000000000000' + + :param value: value for item + :param str prefix: key prefix (default None, key is integer) + :param str side: either 'back' or 'front' (default 'back') + :return: key for item in cache + + """ + _cache_push = self._cache.push + + while True: + try: + return _cache_push(value, prefix, side) + except Timeout: + continue + + + def pull(self, prefix=None, default=(None, None), side='front'): + """Pull key and value item pair from `side` of queue in index. + + When prefix is None, 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'. + + See also `Index.push`. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> for letter in 'abc': + ... index.push(letter) + 500000000000000 + 500000000000001 + 500000000000002 + >>> index.pull() + (500000000000000, 'a') + >>> index.pull(side='back') + (500000000000002, 'c') + >>> index.pull(prefix='fruit') + (None, None) + + :param str prefix: key prefix (default None, key is integer) + :param default: value to return if key is missing + (default (None, None)) + :param str side: either 'front' or 'back' (default 'front') + :return: key and value item pair or default if queue is empty + + """ + _cache_pull = self._cache.pull + + while True: + try: + return _cache_pull(prefix, default, side) + except Timeout: + continue + + + def __iter__(self): + """index.__iter__() <==> iter(index) + + Return iterator of index keys in insertion order. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> iterator = iter(index) + >>> next(iterator) + 'a' + >>> list(iterator) + ['b', 'c'] + + """ + return iter(self._cache) + + + def __reversed__(self): + """index.__reversed__() <==> reversed(index) + + Return iterator of index keys in reversed insertion order. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> iterator = reversed(index) + >>> next(iterator) + 'c' + >>> list(iterator) + ['b', 'a'] + + """ + return reversed(self._cache) + + + def __len__(self): + """index.__len__() <==> len(index) + + Return length of index. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> len(index) + 0 + >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> len(index) + 3 + + """ + return len(self._cache) + + + __hash__ = None + + + def __getstate__(self): + return self.directory + + + def __setstate__(self, state): + self.__init__(state) + + + def __eq__(self, other): + """index.__eq__(other) <==> index == other + + Compare equality for index and `other`. + + Comparison to another index or ordered dictionary is + order-sensitive. Comparison to all other mappings is order-insensitive. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> pairs = [('a', 0), ('b', 1), ('c', 2)] + >>> index.update(pairs) + >>> from collections import OrderedDict + >>> od = OrderedDict(pairs) + >>> index == od + True + >>> index == {'c': 2, 'b': 1, 'a': 0} + True + + :param other: other mapping in equality comparison + + """ + if len(self) != len(other): + return False + + if isinstance(other, (Index, OrderedDict)): + alpha = ((key, self[key]) for key in self) + beta = ((key, other[key]) for key in other) + pairs = zip(alpha, beta) + return not any(a != x or b != y for (a, b), (x, y) in pairs) + else: + return all(self[key] == other.get(key, ENOVAL) for key in self) + + + def __ne__(self, other): + """index.__ne__(other) <==> index != other + + Compare inequality for index and `other`. + + Comparison to another index or ordered dictionary is + order-sensitive. Comparison to all other mappings is order-insensitive. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> from collections import OrderedDict + >>> od = OrderedDict([('c', 2), ('b', 1), ('a', 0)]) + >>> index != od + True + >>> index != {'a': 0, 'b': 1} + True + + :param other: other mapping in inequality comparison + + """ + return not self == other + + + def __repr__(self): + """index.__repr__() <==> repr(index) + + Return string with printable representation of index. + + >>> index = Index('/tmp/diskcache/index') + >>> index + Index('/tmp/diskcache/index') + + """ + name = type(self).__name__ + return '{0}({1!r})'.format(name, self.directory) diff --git a/docs/api.rst b/docs/api.rst index 9ccb4c9..4455909 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -90,3 +90,19 @@ Timeout ------- .. autoexception:: diskcache.Timeout + +Deque +----- + +.. autoclass:: diskcache.Deque + :members: + :special-members: + :exclude-members: __weakref__ + +Index +----- + +.. autoclass:: diskcache.Index + :members: + :special-members: + :exclude-members: __weakref__ diff --git a/tests/stress_test_deque.py b/tests/stress_test_deque.py new file mode 100644 index 0000000..cf48812 --- /dev/null +++ b/tests/stress_test_deque.py @@ -0,0 +1,152 @@ +"""Stress test diskcache.persistent.Deque.""" + +from __future__ import print_function + +import collections as co +import functools as ft +import itertools as it +import random + +import diskcache as dc + +OPERATIONS = 1000 +SEED = 0 +SIZE = 10 + +functions = [] + + +def register(function): + functions.append(function) + return function + + +def lencheck(function): + @ft.wraps(function) + def wrapper(sequence, deque): + assert len(sequence) == len(deque) + + if not deque: + return + + function(sequence, deque) + + return wrapper + + +@register +@lencheck +def stress_get(sequence, deque): + index = random.randrange(len(sequence)) + assert sequence[index] == deque[index] + + +@register +@lencheck +def stress_set(sequence, deque): + index = random.randrange(len(sequence)) + value = random.random() + sequence[index] = value + deque[index] = value + + +@register +@lencheck +def stress_del(sequence, deque): + index = random.randrange(len(sequence)) + del sequence[index] + del deque[index] + + +@register +def stress_iadd(sequence, deque): + values = [random.random() for _ in range(5)] + sequence += values + deque += values + + +@register +def stress_iter(sequence, deque): + assert all(alpha == beta for alpha, beta in zip(sequence, deque)) + + +@register +def stress_reversed(sequence, deque): + reversed_sequence = reversed(sequence) + reversed_deque = reversed(deque) + pairs = zip(reversed_sequence, reversed_deque) + assert all(alpha == beta for alpha, beta in pairs) + + +@register +def stress_append(sequence, deque): + value = random.random() + sequence.append(value) + deque.append(value) + + +@register +def stress_appendleft(sequence, deque): + value = random.random() + sequence.appendleft(value) + deque.appendleft(value) + + +@register +@lencheck +def stress_pop(sequence, deque): + assert sequence.pop() == deque.pop() + + +register(stress_pop) +register(stress_pop) + + +@register +@lencheck +def stress_popleft(sequence, deque): + assert sequence.popleft() == deque.popleft() + + +register(stress_popleft) +register(stress_popleft) + + +@register +def stress_reverse(sequence, deque): + sequence.reverse() + deque.reverse() + assert all(alpha == beta for alpha, beta in zip(sequence, deque)) + + +@register +@lencheck +def stress_rotate(sequence, deque): + assert len(sequence) == len(deque) + steps = random.randrange(len(deque)) + sequence.rotate(steps) + deque.rotate(steps) + assert all(alpha == beta for alpha, beta in zip(sequence, deque)) + + +def stress(sequence, deque): + for count in range(OPERATIONS): + function = random.choice(functions) + function(sequence, deque) + + if count % 100 == 0: + print('\r', len(sequence), ' ' * 7, end='') + + print() + + +def test(): + random.seed(SEED) + sequence = co.deque(range(SIZE)) + deque = dc.Deque(range(SIZE)) + stress(sequence, deque) + assert all(alpha == beta for alpha, beta in zip(sequence, deque)) + + +if __name__ == '__main__': + test() diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py new file mode 100644 index 0000000..2a72e9a --- /dev/null +++ b/tests/stress_test_deque_mp.py @@ -0,0 +1,144 @@ +"""Stress test diskcache.persistent.Deque.""" + +from __future__ import print_function + +import functools as ft +import multiprocessing as mp +import itertools as it +import random +import time + +import diskcache as dc + +OPERATIONS = 1000 +SEED = 0 +SIZE = 10 + +functions = [] + + +def register(function): + functions.append(function) + return function + + +@register +def stress_get(deque): + index = random.randrange(max(1, len(deque))) + + try: + deque[index] + except IndexError: + pass + + +@register +def stress_set(deque): + index = random.randrange(max(1, len(deque))) + value = random.random() + + try: + deque[index] = value + except IndexError: + pass + + +@register +def stress_del(deque): + index = random.randrange(max(1, len(deque))) + + try: + del deque[index] + except IndexError: + pass + + +@register +def stress_iadd(deque): + values = [random.random() for _ in range(5)] + deque += values + + +@register +def stress_append(deque): + value = random.random() + deque.append(value) + + +@register +def stress_appendleft(deque): + value = random.random() + deque.appendleft(value) + + +@register +def stress_pop(deque): + try: + deque.pop() + except IndexError: + pass + + +register(stress_pop) +register(stress_pop) +register(stress_pop) + + +@register +def stress_popleft(deque): + try: + deque.popleft() + except IndexError: + pass + + +register(stress_popleft) +register(stress_popleft) +register(stress_popleft) + + +@register +def stress_reverse(deque): + deque.reverse() + + +@register +def stress_rotate(deque): + steps = random.randrange(max(1, len(deque))) + deque.rotate(steps) + + +def stress(seed, deque): + random.seed(seed) + for count in range(OPERATIONS): + function = random.choice(functions) + function(deque) + + +def test(status=False): + random.seed(SEED) + deque = dc.Deque(range(SIZE)) + processes = [] + + for count in range(8): + process = mp.Process(target=stress, args=(SEED + count, deque)) + process.start() + processes.append(process) + + for value in it.count(): + time.sleep(1) + + if status: + print('\r', value, 's', len(deque), 'items', ' ' * 20, end='') + + if all(not process.is_alive() for process in processes): + break + + if status: + print('') + + assert all(process.exitcode == 0 for process in processes) + + +if __name__ == '__main__': + test(status=True) diff --git a/tests/stress_test_index.py b/tests/stress_test_index.py new file mode 100644 index 0000000..2846d9c --- /dev/null +++ b/tests/stress_test_index.py @@ -0,0 +1,97 @@ +"""Stress test diskcache.persistent.Index.""" + +from __future__ import print_function + +import collections as co +import itertools as it +import random + +import diskcache as dc + +KEYS = 100 +OPERATIONS = 25000 +SEED = 0 + +functions = [] + + +def register(function): + functions.append(function) + return function + + +@register +def stress_get(mapping, index): + key = random.randrange(KEYS) + assert mapping.get(key, None) == index.get(key, None) + + +@register +def stress_set(mapping, index): + key = random.randrange(KEYS) + value = random.random() + mapping[key] = value + index[key] = value + + +register(stress_set) +register(stress_set) +register(stress_set) + + +@register +def stress_pop(mapping, index): + key = random.randrange(KEYS) + assert mapping.pop(key, None) == index.pop(key, None) + + +@register +def stress_popitem(mapping, index): + if len(mapping) == len(index) == 0: + return + elif random.randrange(2): + assert mapping.popitem() == index.popitem() + else: + assert mapping.popitem(last=False) == index.popitem(last=False) + + +@register +def stress_iter(mapping, index): + iterator = it.islice(zip(mapping, index), 5) + assert all(alpha == beta for alpha, beta in iterator) + + +@register +def stress_reversed(mapping, index): + reversed_mapping = reversed(mapping) + reversed_index = reversed(index) + pairs = it.islice(zip(reversed_mapping, reversed_index), 5) + assert all(alpha == beta for alpha, beta in pairs) + + +@register +def stress_len(mapping, index): + assert len(mapping) == len(index) + + +def stress(mapping, index): + for count in range(OPERATIONS): + function = random.choice(functions) + function(mapping, index) + + if count % 1000 == 0: + print('\r', len(mapping), ' ' * 7, end='') + + print() + + +def test(): + random.seed(SEED) + mapping = co.OrderedDict(enumerate(range(KEYS))) + index = dc.Index(enumerate(range(KEYS))) + stress(mapping, index) + assert mapping == index + + +if __name__ == '__main__': + test() diff --git a/tests/stress_test_index_mp.py b/tests/stress_test_index_mp.py new file mode 100644 index 0000000..b07757d --- /dev/null +++ b/tests/stress_test_index_mp.py @@ -0,0 +1,123 @@ +"""Stress test diskcache.persistent.Index.""" + +from __future__ import print_function + +import itertools as it +import multiprocessing as mp +import random +import time + +import diskcache as dc + +KEYS = 100 +OPERATIONS = 10000 +SEED = 0 + +functions = [] + + +def register(function): + functions.append(function) + return function + + +@register +def stress_get(index): + key = random.randrange(KEYS) + index.get(key, None) + + +@register +def stress_set(index): + key = random.randrange(KEYS) + value = random.random() + index[key] = value + + +register(stress_set) +register(stress_set) +register(stress_set) + + +@register +def stress_del(index): + key = random.randrange(KEYS) + + try: + del index[key] + except KeyError: + pass + + +@register +def stress_pop(index): + key = random.randrange(KEYS) + index.pop(key, None) + + +@register +def stress_popitem(index): + try: + if random.randrange(2): + index.popitem() + else: + index.popitem(last=False) + except KeyError: + pass + + +@register +def stress_iter(index): + iterator = it.islice(index, 5) + + for key in iterator: + pass + + +@register +def stress_reversed(index): + iterator = it.islice(reversed(index), 5) + + for key in iterator: + pass + + +@register +def stress_len(index): + len(index) + + +def stress(seed, index): + random.seed(seed) + for count in range(OPERATIONS): + function = random.choice(functions) + function(index) + + +def test(status=False): + random.seed(SEED) + index = dc.Index(enumerate(range(KEYS))) + processes = [] + + for count in range(8): + process = mp.Process(target=stress, args=(SEED + count, index)) + process.start() + processes.append(process) + + for value in it.count(): + time.sleep(1) + + if status: + print('\r', value, 's', len(index), 'keys', ' ' * 20, end='') + + if all(not process.is_alive() for process in processes): + break + + if status: + print('') + + assert all(process.exitcode == 0 for process in processes) + + +if __name__ == '__main__': + test(status=True) diff --git a/tests/test_core.py b/tests/test_core.py index d3576e1..be24a5f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1171,6 +1171,11 @@ def test_pull_ioerror_eacces(cache): cache.pull() +@setup_cache +def test_iterkeys(cache): + assert list(cache.iterkeys()) == [] + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/test_deque.py b/tests/test_deque.py new file mode 100644 index 0000000..909b97a --- /dev/null +++ b/tests/test_deque.py @@ -0,0 +1,344 @@ +"Test diskcache.persistent.Deque." + +import functools as ft +import mock +import nose.tools as nt +import pickle +import shutil + +import diskcache as dc + + +def rmdir(directory): + try: + shutil.rmtree(directory) + except OSError: + pass + + +def setup_deque(func): + @ft.wraps(func) + def wrapper(): + deque = dc.Deque() + try: + func(deque) + except Exception: + rmdir(deque.directory) + raise + + return wrapper + + +def test_init(): + directory = '/tmp/diskcache/deque' + sequence = list('abcde') + deque = dc.Deque(sequence, None) + + assert deque == sequence + + rmdir(deque.directory) + del deque + + rmdir(directory) + deque = dc.Deque(sequence, directory) + + assert deque.directory == directory + assert deque == sequence + + other = dc.Deque(directory=directory) + + assert other == deque + + del deque + del other + rmdir(directory) + + +@setup_deque +def test_getsetdel(deque): + sequence = list('abcde') + assert len(deque) == 0 + + for key in sequence: + deque.append(key) + + assert len(deque) == len(sequence) + + for index in range(len(sequence)): + assert deque[index] == sequence[index] + + for index in range(len(sequence)): + deque[index] = index + + for index in range(len(sequence)): + assert deque[index] == index + + for index in range(len(sequence)): + if index % 2: + del deque[-1] + else: + del deque[0] + + assert len(deque) == 0 + + +@setup_deque +def test_reversed(deque): + sequence = list('abcde') + deque += sequence + assert list(reversed(deque)) == list(reversed(sequence)) + + +@setup_deque +def test_state(deque): + sequence = list('abcde') + deque.extend(sequence) + assert deque == sequence + state = pickle.dumps(deque) + values = pickle.loads(state) + assert values == sequence + + +@setup_deque +def test_compare(deque): + assert not (deque == {}) + assert not (deque == [0]) + assert deque != [1] + deque.append(0) + assert deque <= [0] + assert deque <= [1] + + +@nt.raises(IndexError) +@setup_deque +def test_indexerror_negative(deque): + deque[-1] + + +@nt.raises(IndexError) +@setup_deque +def test_indexerror(deque): + deque[0] + + +@nt.raises(IndexError) +@setup_deque +def test_indexerror_islice(deque): + islice = mock.Mock(side_effect=StopIteration) + + deque.append(0) + + with mock.patch('diskcache.persistent.islice', islice): + deque[0] + + +@setup_deque +def test_get_timeout(deque): + cache = mock.MagicMock() + cache.__len__.return_value = 1 + cache.iterkeys.side_effect = [iter([0]), iter([0])] + cache.__getitem__.side_effect = [dc.Timeout, 0] + + deque.append(0) + + with mock.patch.object(deque, '_cache', cache): + deque[0] + + +@setup_deque +def test_set_timeout(deque): + cache = mock.MagicMock() + cache.__len__.return_value = 1 + cache.iterkeys.side_effect = [iter([0]), iter([0])] + cache.__setitem__.side_effect = [dc.Timeout, None] + + deque.append(0) + + with mock.patch.object(deque, '_cache', cache): + deque[0] = 0 + + +@setup_deque +def test_del_timeout(deque): + cache = mock.MagicMock() + cache.__len__.return_value = 1 + cache.iterkeys.side_effect = [iter([0]), iter([0])] + cache.__delitem__.side_effect = [dc.Timeout, None] + + deque.append(0) + + with mock.patch.object(deque, '_cache', cache): + del deque[0] + + +def test_repr(): + directory = '/tmp/diskcache/deque' + deque = dc.Deque(directory=directory) + assert repr(deque) == 'Deque(directory=%r)' % directory + + +@setup_deque +def test_iter_timeout(deque): + cache = mock.MagicMock() + cache.iterkeys.side_effect = [iter([0, 1])] + cache.__getitem__.side_effect = [dc.Timeout, 0] + + with mock.patch.object(deque, '_cache', cache): + assert list(deque) == [0] + + +@setup_deque +def test_reversed_timeout(deque): + cache = mock.MagicMock() + cache.iterkeys.side_effect = [iter([0, 1])] + cache.__getitem__.side_effect = [dc.Timeout, 0] + + with mock.patch.object(deque, '_cache', cache): + assert list(reversed(deque)) == [0] + + +@setup_deque +def test_append_timeout(deque): + cache = mock.MagicMock() + cache.push.side_effect = [dc.Timeout, None] + + with mock.patch.object(deque, '_cache', cache): + deque.append(0) + + +@setup_deque +def test_appendleft_timeout(deque): + cache = mock.MagicMock() + cache.push.side_effect = [dc.Timeout, None] + + with mock.patch.object(deque, '_cache', cache): + deque.appendleft(0) + + +@setup_deque +def test_count(deque): + deque += 'abbcccddddeeeee' + + for index, value in enumerate('abcde', 1): + assert deque.count(value) == index + + +@setup_deque +def test_extend(deque): + sequence = list('abcde') + deque.extend(sequence) + assert deque == sequence + + +@setup_deque +def test_extendleft(deque): + sequence = list('abcde') + deque.extendleft(sequence) + assert deque == list(reversed(sequence)) + + +@setup_deque +def test_pop(deque): + sequence = list('abcde') + deque.extend(sequence) + + while sequence: + assert deque.pop() == sequence.pop() + + +@nt.raises(IndexError) +@setup_deque +def test_pop_indexerror(deque): + deque.pop() + + +@setup_deque +def test_pop_timeout(deque): + cache = mock.MagicMock() + cache.pull.side_effect = [dc.Timeout, (None, 0)] + + with mock.patch.object(deque, '_cache', cache): + assert deque.pop() == 0 + + +@setup_deque +def test_popleft(deque): + sequence = list('abcde') + deque.extend(sequence) + + while sequence: + value = sequence[0] + assert deque.popleft() == value + del sequence[0] + + +@nt.raises(IndexError) +@setup_deque +def test_popleft_indexerror(deque): + deque.popleft() + + +@setup_deque +def test_popleft_timeout(deque): + cache = mock.MagicMock() + cache.pull.side_effect = [dc.Timeout, (None, 0)] + + with mock.patch.object(deque, '_cache', cache): + assert deque.popleft() == 0 + + +@setup_deque +def test_remove(deque): + deque.extend('abaca') + deque.remove('a') + assert deque == 'baca' + deque.remove('a') + assert deque == 'bca' + deque.remove('a') + assert deque == 'bc' + + +@setup_deque +def test_remove_timeout(deque): + cache = mock.MagicMock() + cache.iterkeys.side_effect = [iter([0, 1, 2, 3, 4])] + cache.__getitem__.side_effect = [0, KeyError, 3, 3] + cache.__delitem__.side_effect = [KeyError, dc.Timeout, None] + + with mock.patch.object(deque, '_cache', cache): + deque.remove(3) + + +@nt.raises(ValueError) +@setup_deque +def test_remove_valueerror(deque): + deque.remove(0) + + +@setup_deque +def test_reverse(deque): + deque += 'abcde' + deque.reverse() + assert deque == 'edcba' + + +@nt.raises(TypeError) +@setup_deque +def test_rotate_typeerror(deque): + deque.rotate(0.5) + + +@setup_deque +def test_rotate(deque): + deque.rotate(1) + deque.rotate(-1) + deque += 'abcde' + deque.rotate(3) + assert deque == 'cdeab' + + +@setup_deque +def test_rotate_negative(deque): + deque += 'abcde' + deque.rotate(-2) + assert deque == 'cdeab' diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 4bdbb3d..6685e66 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -918,3 +918,13 @@ def test_pop(self): self.assertEqual(cache.pop(2, tag=True), (2, None)) self.assertEqual(cache.pop(3, expire_time=True, tag=True), (3, None, None)) self.assertEqual(cache.pop(4, retry=False), 4) + + def test_deque(self): + deque = cache.deque('test') + directory = os.path.join(cache.directory, 'deque', 'test') + self.assertEqual(deque.directory, directory) + + def test_index(self): + index = cache.index('test') + directory = os.path.join(cache.directory, 'index', 'test') + self.assertEqual(index.directory, directory) diff --git a/tests/test_index.py b/tests/test_index.py new file mode 100644 index 0000000..e181066 --- /dev/null +++ b/tests/test_index.py @@ -0,0 +1,219 @@ +"Test diskcache.persistent.Index." + +import functools as ft +import mock +import nose.tools as nt +import pickle +import shutil + +import diskcache as dc + + +def rmdir(directory): + try: + shutil.rmtree(directory) + except OSError: + pass + + +def setup_index(func): + @ft.wraps(func) + def wrapper(): + index = dc.Index() + try: + func(index) + except Exception: + rmdir(index.directory) + raise + + return wrapper + + +def test_init(): + directory = '/tmp/diskcache/index' + mapping = {'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1} + index = dc.Index(None, mapping) + + assert index == mapping + + rmdir(index.directory) + del index + + rmdir(directory) + index = dc.Index(directory, mapping) + + assert index.directory == directory + assert index == mapping + + other = dc.Index(directory) + + assert other == index + + del index + del other + rmdir(directory) + index = dc.Index(directory, mapping.items()) + + assert index == mapping + + del index + rmdir(directory) + index = dc.Index(directory, a=5, b=4, c=3, d=2, e=1) + + assert index == mapping + + +@setup_index +def test_getsetdel(index): + letters = 'abcde' + assert len(index) == 0 + + for num, key in enumerate(letters): + index[key] = num + + for num, key in enumerate(letters): + assert index[key] == num + + for key in letters: + del index[key] + + assert len(index) == 0 + + +@setup_index +def test_get_timeout(index): + cache = mock.MagicMock() + cache.__getitem__.side_effect = [dc.Timeout, 0] + + with mock.patch.object(index, '_cache', cache): + assert index[0] == 0 + + +@setup_index +def test_set_timeout(index): + cache = mock.MagicMock() + cache.__setitem__.side_effect = [dc.Timeout, None] + + with mock.patch.object(index, '_cache', cache): + index[0] = 0 + + +@setup_index +def test_del_timeout(index): + cache = mock.MagicMock() + cache.__delitem__.side_effect = [dc.Timeout, None] + + with mock.patch.object(index, '_cache', cache): + del index[0] + + +@setup_index +def test_pop(index): + letters = 'abcde' + assert len(index) == 0 + + for num, key in enumerate(letters): + index[key] = num + + assert index.pop('a') == 0 + assert index.pop('c') == 2 + assert index.pop('e') == 4 + assert index.pop('b') == 1 + assert index.pop('d') == 3 + assert len(index) == 0 + + +@nt.raises(KeyError) +@setup_index +def test_pop_keyerror(index): + index.pop('a') + + +@setup_index +def test_pop_timeout(index): + cache = mock.MagicMock() + cache.pop.side_effect = [dc.Timeout, 1] + + with mock.patch.object(index, '_cache', cache): + assert index.pop(0) == 1 + + +@setup_index +def test_popitem(index): + letters = 'abcde' + + for num, key in enumerate(letters): + index[key] = num + + assert index.popitem() == ('e', 4) + assert index.popitem(last=True) == ('d', 3) + assert index.popitem(last=False) == ('a', 0) + assert len(index) == 2 + + +@nt.raises(KeyError) +@setup_index +def test_popitem_keyerror(index): + index.popitem() + + +@setup_index +def test_popitem_timeout(index): + cache = mock.MagicMock() + cache.__reversed__ = mock.Mock() + cache.__reversed__.side_effect = [iter([0]), iter([0])] + cache.pop.side_effect = [dc.Timeout, 1] + + with mock.patch.object(index, '_cache', cache): + value = index.popitem() + assert value == (0, 1) + + +@setup_index +def test_iter(index): + letters = 'abcde' + + for num, key in enumerate(letters): + index[key] = num + + for num, key in enumerate(index): + assert index[key] == num + + +@setup_index +def test_reversed(index): + letters = 'abcde' + + for num, key in enumerate(letters): + index[key] = num + + for num, key in enumerate(reversed(index)): + assert index[key] == (len(letters) - num - 1) + + +@setup_index +def test_state(index): + mapping = {'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1} + index.update(mapping) + assert index == mapping + state = pickle.dumps(index) + values = pickle.loads(state) + assert values == mapping + + +@setup_index +def test_push_timeout(index): + cache = mock.MagicMock() + cache.push.side_effect = [dc.Timeout, None] + + with mock.patch.object(index, '_cache', cache): + index.push(0) + + +@setup_index +def test_pull_timeout(index): + cache = mock.MagicMock() + cache.pull.side_effect = [dc.Timeout, None] + + with mock.patch.object(index, '_cache', cache): + index.pull(0) From 1dbbd6b5765555a2c8dc3c9a9e522b8f74469ff5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 13:48:20 -0700 Subject: [PATCH 107/550] Bump version to 2.5.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 784dd04..6fdcd67 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -28,8 +28,8 @@ __title__ = 'diskcache' -__version__ = '2.4.1' -__build__ = 0x020401 +__version__ = '2.5.0' +__build__ = 0x020500 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From f9ab7fc916bca69bf0ca16b2a70d206452ba7ba1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 14:09:40 -0700 Subject: [PATCH 108/550] Fix doctest on FanoutCache --- diskcache/fanout.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 67055f4..1e652be 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -82,6 +82,7 @@ def index(self, name): >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') >>> index = cache.index('test') + >>> index.clear() >>> index['abc'] = 123 >>> index['def'] = 456 >>> index['ghi'] = 789 From e6a08be3c858bed5d1dae6b2b05070eb07f9313e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 14:10:01 -0700 Subject: [PATCH 109/550] Limit pickle protocol to 2 (highest supported by Python 2.7) --- diskcache/core.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 75dfbd2..15755a3 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -75,7 +75,7 @@ def __repr__(self): u'sqlite_cache_size': 2 ** 13, # 8,192 pages u'sqlite_mmap_size': 2 ** 26, # 64mb u'disk_min_file_size': 2 ** 10, # 1kb - u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, + u'disk_pickle_protocol': max(2, pickle.HIGHEST_PROTOCOL), } METADATA = { @@ -1235,8 +1235,9 @@ def pull(self, prefix=None, default=(None, None), side='front', (500000000000002, 'c') >>> cache.push(1234, 'userids') 'userids-500000000000000' - >>> cache.pull('userids') - (u'userids-500000000000000', 1234) + >>> _, value = cache.pull('userids') + >>> value + 1234 :param str prefix: key prefix (default None, key is integer) :param default: value to return if key is missing From f47b80b6db28f4d23784c276c1bd270975be6aca Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 14:12:04 -0700 Subject: [PATCH 110/550] Undo pickle protocol limit --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index 15755a3..0b9382d 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -75,7 +75,7 @@ def __repr__(self): u'sqlite_cache_size': 2 ** 13, # 8,192 pages u'sqlite_mmap_size': 2 ** 26, # 64mb u'disk_min_file_size': 2 ** 10, # 1kb - u'disk_pickle_protocol': max(2, pickle.HIGHEST_PROTOCOL), + u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, } METADATA = { From 985746dd7376caa7a94888ac8c25a9510d37ff29 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 14:13:56 -0700 Subject: [PATCH 111/550] Remove tmp directory for doctests before run --- tests/test_doctest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_doctest.py b/tests/test_doctest.py index 721a8e6..3560d28 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -1,4 +1,5 @@ import doctest +import shutil import diskcache.core import diskcache.djangocache @@ -6,21 +7,32 @@ import diskcache.persistent +def rmdir(directory): + try: + shutil.rmtree(directory) + except OSError: + pass + + def test_core(): + rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.core) assert failures == 0 def test_djangocache(): + rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.djangocache) assert failures == 0 def test_fanout(): + rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.fanout) assert failures == 0 def test_persistent(): + rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.persistent) assert failures == 0 From 4bc176c9f5172df087d5b3f797313661f2e6e4b1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 14:19:09 -0700 Subject: [PATCH 112/550] Bump version to 2.5.1 (small fix to upgrade twine) --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 6fdcd67..10acf9b 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -28,8 +28,8 @@ __title__ = 'diskcache' -__version__ = '2.5.0' -__build__ = 0x020500 +__version__ = '2.5.1' +__build__ = 0x020501 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From d529d95b5e86475911ba9aeb6d667e89f9b988cc Mon Sep 17 00:00:00 2001 From: Tamirlan Date: Tue, 1 Aug 2017 01:13:56 +0300 Subject: [PATCH 113/550] merged python3.6 lru_cache decorator with with diskcache. Also implemented silly tests --- diskcache/decorators.py | 237 +++++++++++++++++++++++++++++++++++++++ tests/test_decorators.py | 61 ++++++++++ 2 files changed, 298 insertions(+) create mode 100644 diskcache/decorators.py create mode 100644 tests/test_decorators.py diff --git a/diskcache/decorators.py b/diskcache/decorators.py new file mode 100644 index 0000000..acadf4e --- /dev/null +++ b/diskcache/decorators.py @@ -0,0 +1,237 @@ +from collections import namedtuple + +from .core import Cache + +import uuid + +try: + from _thread import RLock +except ImportError: + class RLock: + 'Dummy reentrant lock for builds without threads' + + def __enter__(self): pass + + def __exit__(self, exctype, excinst, exctb): pass + +_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + +WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', + '__annotations__') +WRAPPER_UPDATES = ('__dict__',) + + +def update_wrapper(wrapper, + wrapped, + assigned=WRAPPER_ASSIGNMENTS, + updated=WRAPPER_UPDATES): + """Update a wrapper function to look like the wrapped function + + wrapper is the function to be updated + wrapped is the original function + assigned is a tuple naming the attributes assigned directly + from the wrapped function to the wrapper function (defaults to + functools.WRAPPER_ASSIGNMENTS) + updated is a tuple naming the attributes of the wrapper that + are updated with the corresponding attribute from the wrapped + function (defaults to functools.WRAPPER_UPDATES) + """ + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + pass + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + # Issue #17482: set __wrapped__ last so we don't inadvertently copy it + # from the wrapped function when updating __dict__ + wrapper.__wrapped__ = wrapped + # Return the wrapper so this can be used as a decorator via partial() + return wrapper + + +def _make_key(args, kwds, typed, + kwd_mark=(object(),), + fasttypes={int, str, frozenset, type(None)}, + sorted=sorted, tuple=tuple, type=type, len=len): + """Make a cache key from optionally typed positional and keyword arguments + + The key is constructed in a way that is flat as possible rather than + as a nested structure that would take more memory. + + If there is only a single argument and its data type is known to cache + its hash value, then that argument is returned without a wrapper. This + saves space and improves lookup speed. + + """ + key = args + if kwds: + sorted_items = sorted(kwds.items()) + key += kwd_mark + for item in sorted_items: + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for k, v in sorted_items) + return key + + +def lru_cache(maxsize=128, typed=False, cache_path=None): + """Least-recently-used cache decorator. + + If *maxsize* is set to None, the LRU features are disabled and the cache + can grow without bound. + + If *typed* is True, arguments of different types will be cached separately. + For example, f(3.0) and f(3) will be treated as distinct calls with + distinct results. + + Arguments to the cached function must be hashable. + + View the cache statistics named tuple (hits, misses, maxsize, currsize) + with f.cache_info(). Clear the cache and statistics with f.cache_clear(). + Access the underlying function with f.__wrapped__. + + See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used + + """ + + # Users should only access the lru_cache through its public API: + # cache_info, cache_clear, and f.__wrapped__ + # The internals of the lru_cache are encapsulated for thread safety and + # to allow the implementation to change (including a possible C version). + + # Early detection of an erroneous call to @lru_cache without any arguments + # resulting in the inner function being passed to maxsize instead of an + # integer or None. + if maxsize is not None and not isinstance(maxsize, int): + raise TypeError('Expected maxsize to be an integer or None') + + def decorating_function(user_function): + wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo, cache_path) + return update_wrapper(wrapper, user_function) + + return decorating_function + + +def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo, cache_path): + # Constants shared by all lru cache instances: + sentinel = object() # unique object used to signal cache misses + make_key = _make_key # build a key from the function arguments + PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields + if cache_path is None: + cache_path = str(uuid.uuid4()) + + with Cache(cache_path) as cache: + cache_get = cache.get # bound method to lookup a key or return None + lock = RLock() # because linkedlist updates aren't threadsafe + root = [] # root of the circular doubly linked list + root[:] = [root, root, None, None] # initialize by pointing to self + nonlocal_data = { + 'hits': 0, + 'misses': 0, + 'full': False, + 'root': root, + } + + if maxsize == 0: + def wrapper(*args, **kwds): + # No caching -- just a statistics update after a successful call + result = user_function(*args, **kwds) + nonlocal_data['misses'] += 1 + return result + + elif maxsize is None: + def wrapper(*args, **kwds): + # Simple caching without ordering or size limit + key = make_key(args, kwds, typed) + result = cache_get(key, sentinel) + if result is not sentinel: + nonlocal_data['hits'] += 1 + return result + result = user_function(*args, **kwds) + cache[key] = result + nonlocal_data['misses'] += 1 + return result + + else: + def wrapper(*args, **kwds): + # Size limited caching that tracks accesses by recency + root = nonlocal_data['root'] + key = make_key(args, kwds, typed) + with lock: + link = cache_get(key) + if link is not None: + # Move the link to the front of the circular queue + link_prev, link_next, _key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + nonlocal_data['hits'] += 1 + return result + result = user_function(*args, **kwds) + with lock: + if key in cache: + # Getting here means that this same key was added to the + # cache while the lock was released. Since the link + # update is already done, we need only return the + # computed result and update the count of misses. + pass + elif nonlocal_data['full']: + # Use the old root to store the new key and result. + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + # Empty the oldest link and make it the new root. + # Keep a reference to the old key and old result to + # prevent their ref counts from going to zero during the + # update. That will prevent potentially arbitrary object + # clean-up code (i.e. __del__) from running while we're + # still adjusting the links. + root = oldroot[NEXT] + oldkey = root[KEY] + oldresult = root[RESULT] + root[KEY] = root[RESULT] = None + # Now update the cache dictionary. + del cache[oldkey] + # Save the potentially reentrant cache[key] assignment + # for last, after the root and links have been put in + # a consistent state. + cache[key] = oldroot + nonlocal_data['root'] = root + else: + # Put result in a new link at the front of the queue. + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + # Use the __len__() method instead of the len() function + # which could potentially be wrapped in an lru_cache itself. + nonlocal_data['full'] = (cache.__len__() >= maxsize) + nonlocal_data['misses'] += 1 + return result + + def cache_info(): + """Report cache statistics""" + with lock: + return _CacheInfo(nonlocal_data['hits'], nonlocal_data['misses'], maxsize, cache.__len__()) + + def cache_clear(): + """Clear the cache and cache statistics""" + + with lock: + cache.clear() + root[:] = [root, root, None, None] + nonlocal_data['root'] = root + nonlocal_data['hits'] = nonlocal_data['misses'] = 0 + nonlocal_data['full'] = False + + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return wrapper + diff --git a/tests/test_decorators.py b/tests/test_decorators.py new file mode 100644 index 0000000..4ec4cdb --- /dev/null +++ b/tests/test_decorators.py @@ -0,0 +1,61 @@ +import sys + +from diskcache.decorators import lru_cache + +if sys.hexversion < 0x03000000: + range = xrange + + +def test_lru_cache_decorator_with_infinite_cache_size(): + # settings cache_size to infinite (fib function will cache every value) + @lru_cache(maxsize=None) + def fib(num): + if num <= 2: + return 1 + return fib(num-1) + fib(num-2) + + for i in range(1000): + fib(i) + + hist_counter = fib.cache_info().hits + initial_miss_counter = fib.cache_info().misses + + for i in range(1000): + fib(i) + # ensuring that every value was cached during invoking fib function in second for loop + assert fib.cache_info().hits == hist_counter + 1000 + # ensuring that no miss were made during second for loop + assert fib.cache_info().misses == initial_miss_counter + + +def test_lru_cache_decorator_with_empty_cache_size(): + # settings cache_size to zero (this function won't cache any value at all) + @lru_cache(maxsize=0) + def fib(num): + if num <= 2: + return 1 + return fib(num - 1) + fib(num - 2) + + for i in range(20): + fib(i) + + assert fib.cache_info().hits == 0 + + +def test_lru_cache_decorator(): + @lru_cache(maxsize=10) + def printer(x): + return x + + for i in range(100): + printer(i) + + hist_counter = printer.cache_info().hits + initial_miss_counter = printer.cache_info().misses + + for i in range(90, 100): + printer(i) + + # ensuring that no miss were made during second for loop + assert printer.cache_info().misses == initial_miss_counter + assert printer.cache_info().hits == hist_counter + 10 \ No newline at end of file From 07b3ff9e762cc8906f9f3d325e4318d1ee1dac19 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 20:57:03 -0700 Subject: [PATCH 114/550] Improve deque.clear and index.clear --- diskcache/persistent.py | 76 +++++++++++++++++++++++++++++------ tests/stress_test_deque_mp.py | 2 + tests/test_deque.py | 9 +++++ tests/test_index.py | 9 +++++ 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 370a6e3..7e72427 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -103,13 +103,14 @@ def _key(self, index): raise IndexError('deque index out of range') diff = len_self - index - 1 + _cache_iterkeys = self._cache.iterkeys try: if index <= diff: - iter_keys = self._cache.iterkeys() + iter_keys = _cache_iterkeys() key = next(islice(iter_keys, index, index + 1)) else: - iter_keys = self._cache.iterkeys(reverse=True) + iter_keys = _cache_iterkeys(reverse=True) key = next(islice(iter_keys, diff, diff + 1)) except StopIteration: raise IndexError('deque index out of range') @@ -137,10 +138,13 @@ def __getitem__(self, index): :raises IndexError: if index out of range """ + _key = self._key + _cache = self._cache + while True: try: - key = self._key(index) - return self._cache[key] + key = _key(index) + return _cache[key] except (KeyError, Timeout): continue @@ -164,10 +168,13 @@ def __setitem__(self, index, value): :raises IndexError: if index out of range """ + _key = self._key + _cache = self._cache + while True: try: - key = self._key(index) - self._cache[key] = value + key = _key(index) + _cache[key] = value return except Timeout: continue @@ -191,10 +198,13 @@ def __delitem__(self, index): :raises IndexError: if index out of range """ + _key = self._key + _cache = self._cache + while True: try: - key = self._key(index) - del self._cache[key] + key = _key(index) + del _cache[key] return except (KeyError, Timeout): continue @@ -325,9 +335,11 @@ def append(self, value): :param value: value to add to back of deque """ + _cache_push = self._cache.push + while True: try: - self._cache.push(value) + _cache_push(value) return except Timeout: continue @@ -347,9 +359,11 @@ def appendleft(self, value): :param value: value to add to front of deque """ + _cache_push = self._cache.push + while True: try: - self._cache.push(value, side='front') + _cache_push(value, side='front') return except Timeout: continue @@ -368,7 +382,14 @@ def clear(self): 0 """ - self._cache.clear() + _cache_clear = self._cache.clear + + while True: + try: + _cache_clear() + return + except Timeout: + continue def count(self, value): @@ -442,10 +463,12 @@ def pop(self): :raises IndexError: if deque is empty """ + _cache_pull = self._cache.pull + while True: try: default = None, ENOVAL - _, value = self._cache.pull(default=default, side='back') + _, value = _cache_pull(default=default, side='back') except Timeout: continue else: @@ -470,10 +493,12 @@ def popleft(self): IndexError: pop from an empty deque """ + _cache_pull = self._cache.pull + while True: try: default = None, ENOVAL - _, value = self._cache.pull(default=default) + _, value = _cache_pull(default=default) except Timeout: continue else: @@ -538,12 +563,14 @@ def reverse(self): """ directory = mkdtemp() + temp = None try: temp = Deque(iterable=reversed(self), directory=directory) self.clear() self.extend(temp) finally: + del temp rmtree(directory) @@ -901,6 +928,29 @@ def pull(self, prefix=None, default=(None, None), side='front'): continue + def clear(self): + """Remove all items from index. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> len(index) + 3 + >>> index.clear() + >>> len(index) + 0 + + """ + _cache_clear = self._cache.clear + + while True: + try: + _cache_clear() + return + except Timeout: + continue + + def __iter__(self): """index.__iter__() <==> iter(index) diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py index 2a72e9a..3aa50bd 100644 --- a/tests/stress_test_deque_mp.py +++ b/tests/stress_test_deque_mp.py @@ -82,6 +82,7 @@ def stress_pop(deque): register(stress_pop) register(stress_pop) register(stress_pop) +register(stress_pop) @register @@ -95,6 +96,7 @@ def stress_popleft(deque): register(stress_popleft) register(stress_popleft) register(stress_popleft) +register(stress_popleft) @register diff --git a/tests/test_deque.py b/tests/test_deque.py index 909b97a..23eba00 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -342,3 +342,12 @@ def test_rotate_negative(deque): deque += 'abcde' deque.rotate(-2) assert deque == 'cdeab' + + +@setup_deque +def test_clear_timeout(deque): + cache = mock.MagicMock() + cache.clear.side_effect = [dc.Timeout, None] + + with mock.patch.object(deque, '_cache', cache): + deque.clear() diff --git a/tests/test_index.py b/tests/test_index.py index e181066..a536587 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -217,3 +217,12 @@ def test_pull_timeout(index): with mock.patch.object(index, '_cache', cache): index.pull(0) + + +@setup_index +def test_clear_timeout(index): + cache = mock.MagicMock() + cache.clear.side_effect = [dc.Timeout, None] + + with mock.patch.object(index, '_cache', cache): + index.clear() From 8aa1c2a7000fff300ac8df4de2c38d955935424e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 20:57:26 -0700 Subject: [PATCH 115/550] Bump version to 2.5.2 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 10acf9b..90d8e3c 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -28,8 +28,8 @@ __title__ = 'diskcache' -__version__ = '2.5.1' -__build__ = 0x020501 +__version__ = '2.5.2' +__build__ = 0x020502 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From b50fcd227efc8378f948ce40d8da875f6904e426 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 21:25:43 -0700 Subject: [PATCH 116/550] Disable deque and index mp tests on Travis (filesystem vm issues) --- tests/stress_test_deque_mp.py | 6 +++++- tests/stress_test_index_mp.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py index 3aa50bd..de26c9e 100644 --- a/tests/stress_test_deque_mp.py +++ b/tests/stress_test_deque_mp.py @@ -3,8 +3,9 @@ from __future__ import print_function import functools as ft -import multiprocessing as mp import itertools as it +import multiprocessing as mp +import os import random import time @@ -118,6 +119,9 @@ def stress(seed, deque): def test(status=False): + if os.environ.get('TRAVIS') == 'true': + return + random.seed(SEED) deque = dc.Deque(range(SIZE)) processes = [] diff --git a/tests/stress_test_index_mp.py b/tests/stress_test_index_mp.py index b07757d..ea540a2 100644 --- a/tests/stress_test_index_mp.py +++ b/tests/stress_test_index_mp.py @@ -4,6 +4,7 @@ import itertools as it import multiprocessing as mp +import os import random import time @@ -95,6 +96,9 @@ def stress(seed, index): def test(status=False): + if os.environ.get('TRAVIS') == 'true': + return + random.seed(SEED) index = dc.Index(enumerate(range(KEYS))) processes = [] From 8390a97425caa905601c52fc9bb513d8f15f11d6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 21:28:10 -0700 Subject: [PATCH 117/550] Close cache before removing directory in deque.reverse --- diskcache/persistent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 7e72427..b40e01a 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -570,6 +570,7 @@ def reverse(self): self.clear() self.extend(temp) finally: + temp._cache.close() del temp rmtree(directory) From 11f5587297d0964479e5a574f80bc820907b096a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 22:37:07 -0700 Subject: [PATCH 118/550] Disable deque and index mp tests on AppVeyor (filesystem vm issues) --- tests/stress_test_deque_mp.py | 3 +++ tests/stress_test_index_mp.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py index de26c9e..cdd964d 100644 --- a/tests/stress_test_deque_mp.py +++ b/tests/stress_test_deque_mp.py @@ -122,6 +122,9 @@ def test(status=False): if os.environ.get('TRAVIS') == 'true': return + if os.environ.get('APPVEYOR') == 'True': + return + random.seed(SEED) deque = dc.Deque(range(SIZE)) processes = [] diff --git a/tests/stress_test_index_mp.py b/tests/stress_test_index_mp.py index ea540a2..b3ed813 100644 --- a/tests/stress_test_index_mp.py +++ b/tests/stress_test_index_mp.py @@ -99,6 +99,9 @@ def test(status=False): if os.environ.get('TRAVIS') == 'true': return + if os.environ.get('APPVEYOR') == 'True': + return + random.seed(SEED) index = dc.Index(enumerate(range(KEYS))) processes = [] From ec73dc41072988c6cd624a232dfef65e59472edb Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 22:47:41 -0700 Subject: [PATCH 119/550] Increase min_file_size from 1kb to 64kb --- diskcache/core.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 0b9382d..114fbd0 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -70,11 +70,12 @@ def __repr__(self): u'eviction_policy': u'least-recently-stored', u'size_limit': 2 ** 30, # 1gb u'cull_limit': 10, - u'sqlite_synchronous': u'NORMAL', - u'sqlite_journal_mode': u'WAL', u'sqlite_cache_size': 2 ** 13, # 8,192 pages + u'sqlite_journal_mode': u'WAL', u'sqlite_mmap_size': 2 ** 26, # 64mb - u'disk_min_file_size': 2 ** 10, # 1kb +# u'sqlite_page_size': 4096, + u'sqlite_synchronous': u'NORMAL', + u'disk_min_file_size': 2 ** 16, # 64kb u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, } From c539a9d3f0b4c19ab3104509a3ddde95db5e397d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 31 Jul 2017 22:53:16 -0700 Subject: [PATCH 120/550] After further measurement, decrease min_file_size from 64kb to 32kb --- diskcache/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 114fbd0..96dfb07 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -73,9 +73,8 @@ def __repr__(self): u'sqlite_cache_size': 2 ** 13, # 8,192 pages u'sqlite_journal_mode': u'WAL', u'sqlite_mmap_size': 2 ** 26, # 64mb -# u'sqlite_page_size': 4096, u'sqlite_synchronous': u'NORMAL', - u'disk_min_file_size': 2 ** 16, # 64kb + u'disk_min_file_size': 2 ** 15, # 32kb u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, } From 9efeb43b0b501d6448be7d02c1f019876ec77432 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 10:18:24 -0700 Subject: [PATCH 121/550] Update tests for disk_min_file_size change --- tests/stress_test_core.py | 4 ++-- tests/stress_test_fanout.py | 4 ++-- tests/test_core.py | 30 +++++++++++++++--------------- tests/test_djangocache.py | 2 +- tests/test_fanout.py | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index 3ce95d6..a3e7c8b 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -78,13 +78,13 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)) - size = random.randint(1, int(2000 / 13)) + size = random.randint(1, int(2 ** 16 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)).encode('utf-8') - size = random.randint(1, int(2000 / 13)) + size = random.randint(1, int(2 ** 16 / 13)) return word * size def make_float(): diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index 053c6a6..080b8d8 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -78,13 +78,13 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)) - size = random.randint(1, int(2000 / 13)) + size = random.randint(1, int(2 ** 16 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)).encode('utf-8') - size = random.randint(1, int(2000 / 13)) + size = random.randint(1, int(2 ** 16 / 13)) return word * size def make_float(): diff --git a/tests/test_core.py b/tests/test_core.py index be24a5f..67c7ffc 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -210,15 +210,15 @@ def __getattr__(self, name): def test_getsetdel(cache): values = [ (None, False), - ((None,) * 2 ** 12, False), + ((None,) * 2 ** 20, False), (1234, False), (2 ** 512, False), (56.78, False), (u'hello', False), - (u'hello' * 2 ** 12, False), + (u'hello' * 2 ** 20, False), (b'world', False), - (b'world' * 2 ** 12, False), - (io.BytesIO(b'world' * 2 ** 12), True), + (b'world' * 2 ** 20, False), + (io.BytesIO(b'world' * 2 ** 20), True), ] for key, (value, file_like) in enumerate(values): @@ -265,7 +265,7 @@ def test_get_keyerror4(cache): func = mock.Mock(side_effect=IOError(errno.ENOENT, '')) cache.reset('statistics', True) - cache[0] = b'abcd' * 2 ** 12 + cache[0] = b'abcd' * 2 ** 20 with mock.patch('diskcache.core.open', func): cache[0] @@ -276,7 +276,7 @@ def test_get_keyerror4(cache): def test_get_keyerror5(cache): func = mock.Mock(side_effect=IOError(errno.EACCES, '')) - cache[0] = b'abcd' * 2 ** 12 + cache[0] = b'abcd' * 2 ** 20 with mock.patch('diskcache.core.open', func): cache[0] @@ -284,7 +284,7 @@ def test_get_keyerror5(cache): @setup_cache def test_read(cache): - cache.set(0, b'abcd' * 2 ** 12) + cache.set(0, b'abcd' * 2 ** 20) with cache.read(0) as reader: assert reader is not None @@ -298,7 +298,7 @@ def test_read_keyerror(cache): @setup_cache def test_set_twice(cache): - large_value = b'abcd' * 2 ** 12 + large_value = b'abcd' * 2 ** 20 cache[0] = 0 cache[0] = 1 @@ -332,7 +332,7 @@ def test_set_timeout(cache): try: with mock.patch.object(cache, '_local', local): - cache.set('a', 'b' * 2 ** 12) + cache.set('a', 'b' * 2 ** 20) finally: cache.check() @@ -434,8 +434,8 @@ def test_pop(cache): assert cache.set('delta', 210) assert cache.pop('delta', expire_time=True) == (210, None) - assert cache.set('epsilon', '0' * 2 ** 12) - assert cache.pop('epsilon') == '0' * 2 ** 12 + assert cache.set('epsilon', '0' * 2 ** 20) + assert cache.pop('epsilon') == '0' * 2 ** 20 @setup_cache @@ -528,7 +528,7 @@ def test_stats(cache): @setup_cache def test_path(cache): cache[0] = u'abc' - large_value = b'abc' * 2 ** 12 + large_value = b'abc' * 2 ** 20 cache[1] = large_value assert cache.get(0, read=True) == u'abc' @@ -689,7 +689,7 @@ def test_remove_error(cache): @setup_cache def test_check(cache): - blob = b'a' * 2 ** 14 + blob = b'a' * 2 ** 20 keys = (0, 1, 1234, 56.78, u'hello', b'world', None) for key in keys: @@ -899,7 +899,7 @@ def test_add(cache): @setup_cache def test_add_large_value(cache): - value = b'abcd' * 2 ** 12 + value = b'abcd' * 2 ** 20 assert cache.add(b'test-key', value) assert cache.get(b'test-key') == value assert not cache.add(b'test-key', value * 2) @@ -1125,7 +1125,7 @@ def test_push_pull_expire(cache): @setup_cache def test_push_pull_large_value(cache): - value = b'test' * (2 ** 12) + value = b'test' * (2 ** 20) cache.push(value) assert cache.pull() == (500000000000000, value) assert len(cache) == 0 diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 6685e66..97edff7 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -871,7 +871,7 @@ def test_directory(self): self.assertTrue('tmp' in cache.directory) def test_read(self): - value = b'abcd' * 2 ** 12 + value = b'abcd' * 2 ** 20 result = cache.set(b'test-key', value) self.assertTrue(result) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index f08b734..f53a9ea 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -358,7 +358,7 @@ def test_tag_index(cache): @setup_cache def test_read(cache): - cache.set(0, b'abcd' * 2 ** 12) + cache.set(0, b'abcd' * 2 ** 20) with cache.read(0) as reader: assert reader is not None From 2d7ff79c471371684208ff7d44d41969d1d9fc51 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 11:08:14 -0700 Subject: [PATCH 122/550] Support pickle for Cache, FanoutCache, and DjangoCache --- diskcache/core.py | 44 +++++++----- diskcache/djangocache.py | 4 +- diskcache/fanout.py | 138 ++++++++++++++++++++------------------ docs/tutorial.rst | 10 +-- tests/test_core.py | 12 ++++ tests/test_djangocache.py | 13 ++++ tests/test_fanout.py | 12 ++++ 7 files changed, 146 insertions(+), 87 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 96dfb07..3bc526a 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -128,7 +128,7 @@ def __init__(self, directory, min_file_size=0, pickle_protocol=0): :param int pickle_protocol: pickle protocol for serialization """ - self._dir = directory + self._directory = directory self.min_file_size = min_file_size self.pickle_protocol = pickle_protocol @@ -269,17 +269,17 @@ def fetch(self, mode, filename, value, read): return BytesType(value) if type(value) is sqlite3.Binary else value elif mode == MODE_BINARY: if read: - return open(op.join(self._dir, filename), 'rb') + return open(op.join(self._directory, filename), 'rb') else: - with open(op.join(self._dir, filename), 'rb') as reader: + with open(op.join(self._directory, filename), 'rb') as reader: return reader.read() elif mode == MODE_TEXT: - full_path = op.join(self._dir, filename) + full_path = op.join(self._directory, filename) with io_open(full_path, 'r', encoding='UTF-8') as reader: return reader.read() elif mode == MODE_PICKLE: if value is None: - with open(op.join(self._dir, filename), 'rb') as reader: + with open(op.join(self._directory, filename), 'rb') as reader: return pickle.load(reader) else: return pickle.load(BytesIO(value)) @@ -297,7 +297,7 @@ def filename(self): hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8') sub_dir = op.join(hex_name[:2], hex_name[2:4]) name = hex_name[4:] + '.val' - directory = op.join(self._dir, sub_dir) + directory = op.join(self._directory, sub_dir) try: os.makedirs(directory) @@ -306,7 +306,7 @@ def filename(self): raise filename = op.join(sub_dir, name) - full_path = op.join(self._dir, filename) + full_path = op.join(self._directory, filename) return filename, full_path @@ -319,7 +319,7 @@ def remove(self, filename): :param str filename: relative path to file """ - full_path = op.join(self._dir, filename) + full_path = op.join(self._directory, filename) try: os.remove(full_path) @@ -364,7 +364,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): except (TypeError, AssertionError): raise ValueError('disk must subclass diskcache.Disk') - self._dir = directory + self._directory = directory self._timeout = 60 # Use 1 minute timeout for initialization. self._local = threading.local() @@ -376,7 +376,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): raise EnvironmentError( error.errno, 'Cache directory "%s" does not exist' - ' and could not be created' % self._dir + ' and could not be created' % self._directory ) sql = self._sql @@ -502,7 +502,13 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): @property def directory(self): """Cache directory.""" - return self._dir + return self._directory + + + @property + def timeout(self): + """SQLite connection timeout value in seconds.""" + return self._timeout @property @@ -517,7 +523,7 @@ def _sql(self): if con is None: con = self._local.con = sqlite3.connect( - op.join(self._dir, DBNAME), + op.join(self._directory, DBNAME), timeout=self._timeout, isolation_level=None, ) @@ -1351,7 +1357,7 @@ def check(self, fix=False): rows = sql(select).fetchall() for rowid, size, filename in rows: - full_path = op.join(self._dir, filename) + full_path = op.join(self._directory, filename) filenames.add(full_path) if op.exists(full_path): @@ -1377,7 +1383,7 @@ def check(self, fix=False): # Check file system against Cache.filename. - for dirpath, _, files in os.walk(self._dir): + for dirpath, _, files in os.walk(self._directory): paths = [op.join(dirpath, filename) for filename in files] error = set(paths) - filenames @@ -1393,7 +1399,7 @@ def check(self, fix=False): # Check for empty directories. - for dirpath, dirs, files in os.walk(self._dir): + for dirpath, dirs, files in os.walk(self._directory): if not (dirs or files): message = 'empty directory: %s' % dirpath warnings.warn(message, EmptyDirWarning) @@ -1722,6 +1728,14 @@ def __len__(self): return self.reset('count') + def __getstate__(self): + return (self.directory, self.timeout, type(self.disk)) + + + def __setstate__(self, state): + self.__init__(*state) + + def reset(self, key, value=ENOVAL): """Reset `key` and `value` item from Settings table. diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index af4261f..9ceb4ac 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -24,14 +24,14 @@ def __init__(self, directory, params): shards = params.get('SHARDS', 8) timeout = params.get('DATABASE_TIMEOUT', 0.025) options = params.get('OPTIONS', {}) - self._dir = directory + self._directory = directory self._cache = FanoutCache(directory, shards, timeout, **options) @property def directory(self): """Cache directory.""" - return self._dir + return self._directory def deque(self, name): diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 1e652be..646dfc0 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -22,7 +22,7 @@ def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, :param settings: any of `DEFAULT_SETTINGS` """ - self._dir = directory + self._directory = directory self._count = shards default_size_limit = DEFAULT_SETTINGS['size_limit'] size_limit = settings.pop('size_limit', default_size_limit) / shards @@ -44,70 +44,7 @@ def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, @property def directory(self): """Cache directory.""" - return self._dir - - - def deque(self, name): - """Return Deque with given `name` in subdirectory. - - >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') - >>> deque = cache.deque('test') - >>> deque.clear() - >>> deque.extend('abc') - >>> deque.popleft() - 'a' - >>> deque.pop() - 'c' - >>> len(deque) - 1 - - :param str name: subdirectory name for Deque - :return: Deque with given name - - """ - _deques = self._deques - - try: - return _deques[name] - except KeyError: - parts = name.split('/') - directory = op.join(self._dir, 'deque', *parts) - temp = Deque(directory=directory) - _deques[name] = temp - return temp - - - def index(self, name): - """Return Index with given `name` in subdirectory. - - >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') - >>> index = cache.index('test') - >>> index.clear() - >>> index['abc'] = 123 - >>> index['def'] = 456 - >>> index['ghi'] = 789 - >>> index.popitem() - ('ghi', 789) - >>> del index['abc'] - >>> len(index) - 1 - >>> index['def'] - 456 - - :param str name: subdirectory name for Index - :return: Index with given name - - """ - _indexes = self._indexes - - try: - return _indexes[name] - except KeyError: - parts = name.split('/') - directory = op.join(self._dir, 'index', *parts) - temp = Index(directory) - _indexes[name] = temp - return temp + return self._directory def __getattr__(self, name): @@ -521,6 +458,14 @@ def __exit__(self, *exception): self.close() + def __getstate__(self): + return (self._directory, self._count, self.timeout, type(self.disk)) + + + def __setstate__(self, state): + self.__init__(*state) + + def __iter__(self): "Iterate keys in cache including expired items." iterators = [iter(shard) for shard in self._shards] @@ -566,3 +511,66 @@ def reset(self, key, value=ENOVAL): else: break return result + + + def deque(self, name): + """Return Deque with given `name` in subdirectory. + + >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> deque = cache.deque('test') + >>> deque.clear() + >>> deque.extend('abc') + >>> deque.popleft() + 'a' + >>> deque.pop() + 'c' + >>> len(deque) + 1 + + :param str name: subdirectory name for Deque + :return: Deque with given name + + """ + _deques = self._deques + + try: + return _deques[name] + except KeyError: + parts = name.split('/') + directory = op.join(self._directory, 'deque', *parts) + temp = Deque(directory=directory) + _deques[name] = temp + return temp + + + def index(self, name): + """Return Index with given `name` in subdirectory. + + >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> index = cache.index('test') + >>> index.clear() + >>> index['abc'] = 123 + >>> index['def'] = 456 + >>> index['ghi'] = 789 + >>> index.popitem() + ('ghi', 789) + >>> del index['abc'] + >>> len(index) + 1 + >>> index['def'] + 456 + + :param str name: subdirectory name for Index + :return: Index with given name + + """ + _indexes = self._indexes + + try: + return _indexes[name] + except KeyError: + parts = name.split('/') + directory = op.join(self._directory, 'index', *parts) + temp = Index(directory) + _indexes[name] = temp + return temp diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 831684c..9280475 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -77,11 +77,11 @@ Cache objects may also reference the same directory from separate threads or processes. In this way, they are also process-safe and support cross-process communication. -When created, Cache objects open and maintain a file handle. As such, they may -not be pickled and do not survive process forking. Each thread that accesses a -cache is also responsible for calling :meth:`close ` on -the cache. You can use a Cache reference in a `with` statement to safeguard -calling :meth:`close `. +When created, Cache objects open and maintain a file handle. As such, they do +not survive process forking but they may be serialized using Pickle. Each +thread that accesses a cache is also responsible for calling :meth:`close +` on the cache. You can use a Cache reference in a +`with` statement to safeguard calling :meth:`close `. >>> cache.close() >>> with Cache('/tmp/mycachedir') as reference: diff --git a/tests/test_core.py b/tests/test_core.py index 67c7ffc..0a1d852 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1176,6 +1176,18 @@ def test_iterkeys(cache): assert list(cache.iterkeys()) == [] +@setup_cache +def test_pickle(cache): + for num, val in enumerate('abcde'): + cache[val] = num + + data = pickle.dumps(cache) + other = pickle.loads(data) + + for key in other: + assert other[key] == cache[key] + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 97edff7..36738a1 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -919,6 +919,19 @@ def test_pop(self): self.assertEqual(cache.pop(3, expire_time=True, tag=True), (3, None, None)) self.assertEqual(cache.pop(4, retry=False), 4) + def test_pickle(self): + letters = 'abcde' + cache.clear() + + for num, val in enumerate(letters): + cache.set(val, num) + + data = pickle.dumps(cache) + other = pickle.loads(data) + + for key in letters: + self.assertEqual(other.get(key), cache.get(key)) + def test_deque(self): deque = cache.deque('test') directory = os.path.join(cache.directory, 'deque', 'test') diff --git a/tests/test_fanout.py b/tests/test_fanout.py index f53a9ea..e77e6c1 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -501,6 +501,18 @@ def test_reversed(cache): assert list(cache) == list(reversed(reverse)) +@setup_cache +def test_pickle(cache): + for num, val in enumerate('abcde'): + cache[val] = num + + data = pickle.dumps(cache) + other = pickle.loads(data) + + for key in other: + assert other[key] == cache[key] + + if __name__ == '__main__': import nose nose.runmodule() From 25ec375973f6fb5a5a4c4dcf24c03ac34588e0f2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 13:01:24 -0700 Subject: [PATCH 123/550] Pylint fix for Deque.reverse --- diskcache/persistent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index b40e01a..35827ec 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -562,6 +562,7 @@ def reverse(self): ['c', 'b', 'a'] """ + # pylint: disable=protected-access directory = mkdtemp() temp = None From 41281fa8e1966143bfbc7f2ad5900b4be50dea27 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 13:01:49 -0700 Subject: [PATCH 124/550] Bump version to 2.6.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 90d8e3c..00ad808 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -28,8 +28,8 @@ __title__ = 'diskcache' -__version__ = '2.5.2' -__build__ = 0x020502 +__version__ = '2.6.0' +__build__ = 0x020600 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 899282b9422fc0dd232f539b49519420e4fd33c6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 16:08:30 -0700 Subject: [PATCH 125/550] Add fromcache and keys, values, items methods --- diskcache/persistent.py | 254 +++++++++++++++++++++++++++++++++++++++- tests/test_index.py | 20 ++++ 2 files changed, 273 insertions(+), 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 35827ec..7eea734 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -6,6 +6,7 @@ import sys from collections import MutableMapping, OrderedDict, Sequence +from collections import KeysView, ValuesView, ItemsView from itertools import islice from shutil import rmtree from tempfile import mkdtemp @@ -86,6 +87,31 @@ def __init__(self, iterable=(), directory=None): self.extend(iterable) + @classmethod + def fromcache(cls, cache, iterable=()): + """Initialize deque using `cache`. + + >>> cache = Cache('/tmp/diskcache/index') + >>> _ = cache.clear() + >>> deque = Deque.fromcache(cache, [5, 6, 7, 8]) + >>> len(deque) + 4 + >>> 7 in deque + True + >>> deque.popleft() + 5 + + :param Cache cache: cache to use + :param iterable: iterable of items + :return: initialized Deque + + """ + self = cls.__new__(cls) + self._cache = cache + self.extend(iterable) + return self + + @property def directory(self): "Directory path where deque is stored." @@ -664,10 +690,36 @@ def __init__(self, *args, **kwargs): if args and args[0] is None: args = args[1:] directory = mkdtemp(prefix='diskcache-') - self._cache = Cache(directory, eviction_policy='none', timeout=0.001) + self._cache = Cache(directory, eviction_policy='none') self.update(*args, **kwargs) + @classmethod + def fromcache(cls, cache, *args, **kwargs): + """Initialize index using `cache` and update items. + + >>> cache = Cache('/tmp/diskcache/index') + >>> _ = cache.clear() + >>> index = Index.fromcache(cache, {'a': 0, 'b': 1, 'c': 2}) + >>> len(index) + 3 + >>> 'b' in index + True + >>> index['c'] + 2 + + :param Cache cache: cache to use + :param args: mapping or sequence of items + :param kwargs: mapping of items + :return: initialized Index + + """ + self = cls.__new__(cls) + self._cache = cache + self.update(*args, **kwargs) + return self + + @property def directory(self): "Directory path where items are stored." @@ -1006,6 +1058,206 @@ def __len__(self): return len(self._cache) + if sys.hexversion < 0x03000000: + def keys(self): + """List of index keys. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.keys() + ['a', 'b', 'c'] + + :return: list of keys + + """ + return list(self._cache) + + + def values(self): + """List of index values. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.values() + [0, 1, 2] + + :return: list of values + + """ + return list(self.itervalues()) + + + def items(self): + """List of index items. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.items() + [('a', 0), ('b', 1), ('c', 2)] + + :return: list of items + + """ + return list(self.iteritems()) + + + def iterkeys(self): + """Iterator of index keys. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> list(index.iterkeys()) + ['a', 'b', 'c'] + + :return: iterator of keys + + """ + return iter(self._cache) + + + def itervalues(self): + """Iterator of index values. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> list(index.itervalues()) + [0, 1, 2] + + :return: iterator of values + + """ + _cache = self._cache + + for key in _cache: + try: + yield _cache[key] + except KeyError: + continue + + + def iteritems(self): + """Iterator of index items. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> list(index.iteritems()) + [('a', 0), ('b', 1), ('c', 2)] + + :return: iterator of items + + """ + _cache = self._cache + + for key in _cache: + try: + yield key, _cache[key] + except KeyError: + continue + + + def viewkeys(self): + """Set-like object providing a view of index keys. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> keys_view = index.viewkeys() + >>> 'b' in keys_view + True + + :return: keys view + + """ + return KeysView(self) + + + def viewvalues(self): + """Set-like object providing a view of index values. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> values_view = index.viewvalues() + >>> 1 in values_view + True + + :return: values view + + """ + return ValuesView(self) + + + def viewitems(self): + """Set-like object providing a view of index items. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> items_view = index.viewitems() + >>> ('b', 1) in items_view + True + + :return: items view + + """ + return ItemsView(self) + + + else: + def keys(self): + """Set-like object providing a view of index keys. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b', 1, 'c': 2}) + >>> keys_view = index.keys() + >>> 'b' in keys_view + True + + :return: keys view + + """ + return KeysView(self) + + + def values(self): + """Set-like object providing a view of index values. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b', 1, 'c': 2}) + >>> values_view = index.values() + >>> 1 in values_view + True + + :return: values view + + """ + return ValuesView(self) + + + def items(self): + """Set-like object providing a view of index items. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update({'a': 0, 'b', 1, 'c': 2}) + >>> items_view = index.items() + >>> ('b', 1) in items_view + True + + :return: items view + + """ + return ItemsView(self) + + __hash__ = None diff --git a/tests/test_index.py b/tests/test_index.py index a536587..7480ac1 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -226,3 +226,23 @@ def test_clear_timeout(index): with mock.patch.object(index, '_cache', cache): index.clear() + + +@setup_index +def test_itervalues_timeout(index): + cache = mock.MagicMock() + cache.__iter__.side_effect = [iter([0, 1, 2])] + cache.__getitem__.side_effect = [KeyError, 1, 2] + + with mock.patch.object(index, '_cache', cache): + assert list(index.itervalues()) == [1, 2] + + +@setup_index +def test_iteritems_timeout(index): + cache = mock.MagicMock() + cache.__iter__.side_effect = [iter([0, 1, 2])] + cache.__getitem__.side_effect = [KeyError, 1, 2] + + with mock.patch.object(index, '_cache', cache): + assert list(index.iteritems()) == [(1, 1), (2, 2)] From 95327e73be8d74f6c76bb3c7d3c5809e6958aa78 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 16:09:01 -0700 Subject: [PATCH 126/550] Bump version to 2.6.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 00ad808..9615a31 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -28,8 +28,8 @@ __title__ = 'diskcache' -__version__ = '2.6.0' -__build__ = 0x020600 +__version__ = '2.6.1' +__build__ = 0x020601 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 2983529c7722fb49dc1184de679996a1e61dac47 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 16:11:35 -0700 Subject: [PATCH 127/550] Pylint fixes --- diskcache/persistent.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 7eea734..72faaf3 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -106,6 +106,7 @@ def fromcache(cls, cache, iterable=()): :return: initialized Deque """ + # pylint: disable=no-member,protected-access self = cls.__new__(cls) self._cache = cache self.extend(iterable) @@ -714,6 +715,7 @@ def fromcache(cls, cache, *args, **kwargs): :return: initialized Index """ + # pylint: disable=no-member,protected-access self = cls.__new__(cls) self._cache = cache self.update(*args, **kwargs) From 48cb849486209869791e34d00c6c618910858e05 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 16:11:59 -0700 Subject: [PATCH 128/550] Bump version to 2.6.2 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 9615a31..c8de549 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -28,8 +28,8 @@ __title__ = 'diskcache' -__version__ = '2.6.1' -__build__ = 0x020601 +__version__ = '2.6.2' +__build__ = 0x020602 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From f0fdeb898aa3cde575590ced4a636fde6213d93e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 16:15:53 -0700 Subject: [PATCH 129/550] Fix typo in doctest for Py3 index views --- diskcache/persistent.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 72faaf3..4b979d4 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -1217,7 +1217,7 @@ def keys(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b', 1, 'c': 2}) + >>> index.update({'a': 0, 'b': 1, 'c': 2}) >>> keys_view = index.keys() >>> 'b' in keys_view True @@ -1233,7 +1233,7 @@ def values(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b', 1, 'c': 2}) + >>> index.update({'a': 0, 'b': 1, 'c': 2}) >>> values_view = index.values() >>> 1 in values_view True @@ -1249,7 +1249,7 @@ def items(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b', 1, 'c': 2}) + >>> index.update({'a': 0, 'b': 1, 'c': 2}) >>> items_view = index.items() >>> ('b', 1) in items_view True From fd75d514397cd782c72515b62abdcdb9ac3cdd46 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 16:16:12 -0700 Subject: [PATCH 130/550] Bump version to 2.6.3 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index c8de549..60e67bb 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -28,8 +28,8 @@ __title__ = 'diskcache' -__version__ = '2.6.2' -__build__ = 0x020602 +__version__ = '2.6.3' +__build__ = 0x020603 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 8ae83c474674fa5e497a5ba06f9357d2e024fdd8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 16:21:44 -0700 Subject: [PATCH 131/550] Remove tests for itervalues and iteritems on Python 3 --- tests/test_index.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/test_index.py b/tests/test_index.py index 7480ac1..b288af4 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -5,6 +5,7 @@ import nose.tools as nt import pickle import shutil +import sys import diskcache as dc @@ -228,21 +229,22 @@ def test_clear_timeout(index): index.clear() -@setup_index -def test_itervalues_timeout(index): - cache = mock.MagicMock() - cache.__iter__.side_effect = [iter([0, 1, 2])] - cache.__getitem__.side_effect = [KeyError, 1, 2] +if sys.hexversion < 0x03000000: + @setup_index + def test_itervalues_timeout(index): + cache = mock.MagicMock() + cache.__iter__.side_effect = [iter([0, 1, 2])] + cache.__getitem__.side_effect = [KeyError, 1, 2] - with mock.patch.object(index, '_cache', cache): - assert list(index.itervalues()) == [1, 2] + with mock.patch.object(index, '_cache', cache): + assert list(index.itervalues()) == [1, 2] -@setup_index -def test_iteritems_timeout(index): - cache = mock.MagicMock() - cache.__iter__.side_effect = [iter([0, 1, 2])] - cache.__getitem__.side_effect = [KeyError, 1, 2] + @setup_index + def test_iteritems_timeout(index): + cache = mock.MagicMock() + cache.__iter__.side_effect = [iter([0, 1, 2])] + cache.__getitem__.side_effect = [KeyError, 1, 2] - with mock.patch.object(index, '_cache', cache): - assert list(index.iteritems()) == [(1, 1), (2, 2)] + with mock.patch.object(index, '_cache', cache): + assert list(index.iteritems()) == [(1, 1), (2, 2)] From 4e707e4b57700a66ee6f28bf49a1b4a355004ce2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 1 Aug 2017 16:21:57 -0700 Subject: [PATCH 132/550] Bump version to 2.6.4 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 60e67bb..11007c7 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -28,8 +28,8 @@ __title__ = 'diskcache' -__version__ = '2.6.3' -__build__ = 0x020603 +__version__ = '2.6.4' +__build__ = 0x020604 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 8940c20be4484b83997a835e41a4ac9dc49797d0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 2 Aug 2017 20:09:35 -0700 Subject: [PATCH 133/550] Use Django 1.8 for testing on Travis and AppVeyor --- appveyor.yml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a382718..ec6ce57 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,7 @@ environment: install: - - "%PYTHON%\\python.exe -m pip install nose mock django" + - "%PYTHON%\\python.exe -m pip install nose mock django==1.8.18" build: off diff --git a/requirements.txt b/requirements.txt index 890bcf7..245b63d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ mock==1.3.0 nose==1.3.7 -django==1.9.2 +django==1.8.18 From 63bd2c82732bc7bfe43b742657535cb23895d06e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 2 Aug 2017 20:18:23 -0700 Subject: [PATCH 134/550] Remove LIMITS constant --- diskcache/__init__.py | 3 +-- diskcache/core.py | 13 ++++--------- docs/api.rst | 9 --------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 11007c7..b76fa08 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -1,7 +1,7 @@ "DiskCache: disk and file backed cache." from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning, Timeout -from .core import LIMITS, DEFAULT_SETTINGS, EVICTION_POLICY +from .core import DEFAULT_SETTINGS, EVICTION_POLICY from .fanout import FanoutCache from .persistent import Deque, Index @@ -11,7 +11,6 @@ 'UnknownFileWarning', 'EmptyDirWarning', 'Timeout', - 'LIMITS', 'DEFAULT_SETTINGS', 'EVICTION_POLICY', 'FanoutCache', diff --git a/diskcache/core.py b/diskcache/core.py index 3bc526a..b6d90b0 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -58,12 +58,6 @@ def __repr__(self): MODE_TEXT = 3 MODE_PICKLE = 4 -LIMITS = { - u'min_int': -sys.maxsize - 1, - u'max_int': sys.maxsize, - u'pragma_timeout': 60, -} - DEFAULT_SETTINGS = { u'statistics': 0, # False u'tag_index': 0, # False @@ -169,7 +163,7 @@ def put(self, key): return sqlite3.Binary(key), True elif ((type_key is TextType) or (type_key in INT_TYPES - and LIMITS[u'min_int'] <= key <= LIMITS[u'max_int']) + and -9223372036854775808 <= key <= 9223372036854775807) or (type_key is float)): return key, True else: @@ -207,7 +201,7 @@ def store(self, value, read): if ((type_value is TextType and len(value) < min_file_size) or (type_value in INT_TYPES - and LIMITS[u'min_int'] <= value <= LIMITS[u'max_int']) + and -9223372036854775808 <= value <= 9223372036854775807) or (type_value is float)): return 0, MODE_RAW, None, value elif type_value is BytesType: @@ -1780,10 +1774,11 @@ def reset(self, key, value=ENOVAL): # processes. pause = 0.001 + count = 60000 # 60 / 0.001 error = sqlite3.OperationalError pragma = key[7:] - for _ in range(int(LIMITS[u'pragma_timeout'] / pause)): + for _ in range(count): try: args = pragma, value sql('PRAGMA %s = %s' % args).fetchall() diff --git a/docs/api.rst b/docs/api.rst index 4455909..12fbba3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -61,15 +61,6 @@ Read the :ref:`Settings tutorial ` for details. * `disk_pickle_protocol` (int) default highest Pickle protocol - the Pickle protocol to use for data types that are not natively supported. -.. data:: diskcache.LIMITS - - * `min_int` (int) default ``-sys.maxsize - 1`` - smallest integer stored - natively in SQLite. - * `max_int` (int) default ``sys.maxsize`` - largest integer stored natively - in SQLite. - * `pragma_timeout` (int) default 60 - seconds to retry setting SQLite - pragmas. - .. data:: diskcache.EVICTION_POLICY * `least-recently-stored` (default) - evict least recently stored keys first. From b7af8a637f119d9aeb019b36835b46057ef4ef51 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 2 Aug 2017 20:24:05 -0700 Subject: [PATCH 135/550] Change django to version 1.9.13 for tests --- appveyor.yml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ec6ce57..1e66e98 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,7 @@ environment: install: - - "%PYTHON%\\python.exe -m pip install nose mock django==1.8.18" + - "%PYTHON%\\python.exe -m pip install nose mock django==1.9.13" build: off diff --git a/requirements.txt b/requirements.txt index 245b63d..0a47640 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ mock==1.3.0 nose==1.3.7 -django==1.8.18 +django==1.9.13 From 735886af4bda81045e7fbeaf378a9cb411388e64 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 2 Aug 2017 20:30:17 -0700 Subject: [PATCH 136/550] Update test for pragma error code --- tests/test_core.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 0a1d852..63609a2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -178,16 +178,13 @@ def test_pragma_error(cache): con.execute = execute execute.return_value = cursor cursor.fetchall = fetchall - fetchall.side_effect = [sqlite3.OperationalError] + fetchall.side_effect = [sqlite3.OperationalError] * 60000 size = 2 ** 28 - dc.LIMITS[u'pragma_timeout'] = 0 - try: + with mock.patch('time.sleep', lambda num: 0): with mock.patch.object(cache, '_local', local): cache.reset('sqlite_mmap_size', size) - finally: - dc.LIMITS[u'pragma_timeout'] = 60 @setup_cache From 17ed536368c3d44861d6059cf0ecc6feb38de73c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 2 Aug 2017 20:30:48 -0700 Subject: [PATCH 137/550] Bump version to 2.6.5 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index b76fa08..4209fab 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -27,8 +27,8 @@ __title__ = 'diskcache' -__version__ = '2.6.4' -__build__ = 0x020604 +__version__ = '2.6.5' +__build__ = 0x020605 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 7d129372becdcd86884526937c11b4fd11213772 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 10:15:53 -0700 Subject: [PATCH 138/550] Add explicit cache close to __del__ for Deque and Index --- diskcache/persistent.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 4b979d4..1492b4e 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -643,6 +643,10 @@ def rotate(self, steps=1): self.append(value) + def __del__(self): + self._cache.close() + + __hash__ = None @@ -1341,3 +1345,7 @@ def __repr__(self): """ name = type(self).__name__ return '{0}({1!r})'.format(name, self.directory) + + + def __del__(self): + self._cache.close() From aa803c09cb555f24b9a923296788586ec194fa3a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 10:36:22 -0700 Subject: [PATCH 139/550] Add retry loop for lookup in Deque.remove --- diskcache/persistent.py | 8 +++++++- tests/test_deque.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 1492b4e..67c2d84 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -559,7 +559,13 @@ def remove(self, value): for key in _cache.iterkeys(): try: - item = _cache[key] + while True: + try: + item = _cache[key] + except Timeout: + continue + else: + break except KeyError: continue else: diff --git a/tests/test_deque.py b/tests/test_deque.py index 23eba00..284d409 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -302,7 +302,7 @@ def test_remove(deque): def test_remove_timeout(deque): cache = mock.MagicMock() cache.iterkeys.side_effect = [iter([0, 1, 2, 3, 4])] - cache.__getitem__.side_effect = [0, KeyError, 3, 3] + cache.__getitem__.side_effect = [0, dc.Timeout, KeyError, 3, 3] cache.__delitem__.side_effect = [KeyError, dc.Timeout, None] with mock.patch.object(deque, '_cache', cache): From c46cbc3ea4f253467d12a9ba13646d4728f6582f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 10:37:29 -0700 Subject: [PATCH 140/550] Reduce steps mod length in Deque.rotate --- diskcache/persistent.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 67c2d84..de98158 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -631,7 +631,14 @@ def rotate(self, steps=1): type_name = type(steps).__name__ raise TypeError('integer argument expected, got %s' % type_name) + len_self = len(self) + + if not len_self: + return + if steps >= 0: + steps %= len_self + for _ in range(steps): try: value = self.pop() @@ -640,7 +647,10 @@ def rotate(self, steps=1): else: self.appendleft(value) else: - for _ in range(-steps): + steps *= -1 + steps %= len_self + + for _ in range(steps): try: value = self.popleft() except IndexError: From a2339c858223c7cbe9fdd60c1c3d19fb270c20d3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 10:39:48 -0700 Subject: [PATCH 141/550] Add retry loop for lookup in Index itervalues and iteritems --- diskcache/persistent.py | 26 ++++++++++++++++++-------- tests/test_index.py | 4 ++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index de98158..13ea50e 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -1156,10 +1156,15 @@ def itervalues(self): _cache = self._cache for key in _cache: - try: - yield _cache[key] - except KeyError: - continue + while True: + try: + yield _cache[key] + except KeyError: + break + except Timeout: + continue + else: + break def iteritems(self): @@ -1177,10 +1182,15 @@ def iteritems(self): _cache = self._cache for key in _cache: - try: - yield key, _cache[key] - except KeyError: - continue + while True: + try: + yield key, _cache[key] + except KeyError: + break + except Timeout: + continue + else: + break def viewkeys(self): diff --git a/tests/test_index.py b/tests/test_index.py index b288af4..af7453c 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -234,7 +234,7 @@ def test_clear_timeout(index): def test_itervalues_timeout(index): cache = mock.MagicMock() cache.__iter__.side_effect = [iter([0, 1, 2])] - cache.__getitem__.side_effect = [KeyError, 1, 2] + cache.__getitem__.side_effect = [dc.Timeout, KeyError, 1, 2] with mock.patch.object(index, '_cache', cache): assert list(index.itervalues()) == [1, 2] @@ -244,7 +244,7 @@ def test_itervalues_timeout(index): def test_iteritems_timeout(index): cache = mock.MagicMock() cache.__iter__.side_effect = [iter([0, 1, 2])] - cache.__getitem__.side_effect = [KeyError, 1, 2] + cache.__getitem__.side_effect = [dc.Timeout, KeyError, 1, 2] with mock.patch.object(index, '_cache', cache): assert list(index.iteritems()) == [(1, 1), (2, 2)] From 7f1f0406378c5e0d0c9cfacf9c82ee5bc88606e8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 10:54:23 -0700 Subject: [PATCH 142/550] Support Python 2.7 32-bit in doctests --- diskcache/core.py | 20 ++++++++++++-------- diskcache/persistent.py | 20 ++++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index b6d90b0..e17435b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1132,13 +1132,13 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, >>> cache = Cache('/tmp/test') >>> _ = cache.clear() - >>> cache.push('first value') + >>> print(cache.push('first value')) 500000000000000 >>> cache.get(500000000000000) 'first value' - >>> cache.push('second value') + >>> print(cache.push('second value')) 500000000000001 - >>> cache.push('third value', side='front') + >>> print(cache.push('third value', side='front')) 499999999999999 >>> cache.push(1234, prefix='userids') 'userids-500000000000000' @@ -1225,14 +1225,18 @@ def pull(self, prefix=None, default=(None, None), side='front', >>> cache.pull() (None, None) >>> for letter in 'abc': - ... cache.push(letter) + ... print(cache.push(letter)) 500000000000000 500000000000001 500000000000002 - >>> cache.pull() - (500000000000000, 'a') - >>> cache.pull(side='back') - (500000000000002, 'c') + >>> 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') diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 13ea50e..30d536a 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -934,11 +934,11 @@ def push(self, value, prefix=None, side='back'): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.push('apples') + >>> print(index.push('apples')) 500000000000000 - >>> index.push('beans') + >>> print(index.push('beans')) 500000000000001 - >>> index.push('cherries', side='front') + >>> print(index.push('cherries', side='front')) 499999999999999 >>> index[500000000000001] 'beans' @@ -977,14 +977,18 @@ def pull(self, prefix=None, default=(None, None), side='front'): >>> index = Index('/tmp/diskcache/index') >>> index.clear() >>> for letter in 'abc': - ... index.push(letter) + ... print(index.push(letter)) 500000000000000 500000000000001 500000000000002 - >>> index.pull() - (500000000000000, 'a') - >>> index.pull(side='back') - (500000000000002, 'c') + >>> key, value = index.pull() + >>> print(key) + 500000000000000 + >>> value + 'a' + >>> _, value = index.pull(side='back') + >>> value + 'c' >>> index.pull(prefix='fruit') (None, None) From a21f01b0fddd7008b404fbea373935eb639dd86e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 11:02:22 -0700 Subject: [PATCH 143/550] Remove redundant doctest strings for Deque --- diskcache/persistent.py | 68 ++++++++--------------------------------- 1 file changed, 13 insertions(+), 55 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 30d536a..efb1ca8 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -56,19 +56,26 @@ class Deque(Sequence): Items are serialized to disk. Deque may be initialized from directory path where items are stored. - >>> deque = Deque() - >>> for value in range(5): - ... deque.append(value) + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque + Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += range(5) >>> list(deque) [0, 1, 2, 3, 4] >>> for value in range(5): ... deque.appendleft(-value) + >>> len(deque) + 10 >>> list(deque) [-4, -3, -2, -1, 0, 0, 1, 2, 3, 4] >>> deque.pop() 4 >>> deque.popleft() -4 + >>> deque.reverse() + >>> list(deque) + [3, 2, 1, 0, 0, -1, -2, -3] """ def __init__(self, iterable=(), directory=None): @@ -242,10 +249,6 @@ def __repr__(self): Return string with printable representation of deque. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque - Deque(directory='/tmp/diskcache/deque') - """ name = type(self).__name__ return '{0}(directory={1!r})'.format(name, self.directory) @@ -264,12 +267,6 @@ def __iadd__(self, iterable): Extend back side of deque with items from iterable. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() - >>> deque += 'abc' - >>> list(deque) - ['a', 'b', 'c'] - """ self.extend(iterable) return self @@ -280,15 +277,6 @@ def __iter__(self): Return iterator of deque from front to back. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() - >>> deque += 'abc' - >>> iterator = iter(deque) - >>> next(iterator) - 'a' - >>> list(iterator) - ['b', 'c'] - """ _cache = self._cache @@ -304,14 +292,6 @@ def __len__(self): Return length of deque. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() - >>> len(deque) - 0 - >>> deque.extend('abc') - >>> len(deque) - 3 - """ return len(self._cache) @@ -323,12 +303,12 @@ def __reversed__(self): >>> deque = Deque(directory='/tmp/diskcache/deque') >>> deque.clear() - >>> deque.extend('abc') + >>> deque.extend('abcd') >>> iterator = reversed(deque) >>> next(iterator) - 'c' + 'd' >>> list(iterator) - ['b', 'a'] + ['c', 'b', 'a'] """ _cache = self._cache @@ -399,15 +379,6 @@ def appendleft(self, value): def clear(self): """Remove all elements from deque. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() - >>> deque += range(10) - >>> len(deque) - 10 - >>> deque.clear() - >>> len(deque) - 0 - """ _cache_clear = self._cache.clear @@ -441,12 +412,6 @@ def count(self, value): def extend(self, iterable): """Extend back side of deque with values from `iterable`. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() - >>> deque.extend('abc') - >>> list(deque) - ['a', 'b', 'c'] - :param iterable: iterable of values """ @@ -587,13 +552,6 @@ def remove(self, value): def reverse(self): """Reverse deque in place. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() - >>> deque.extend('abc') - >>> deque.reverse() - >>> list(deque) - ['c', 'b', 'a'] - """ # pylint: disable=protected-access directory = mkdtemp() From 47cf28c1b2a1166a66327905568e3f62b8520697 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 11:12:17 -0700 Subject: [PATCH 144/550] Remove redundant doctest strings for Index and stardize key/value examples --- diskcache/persistent.py | 134 ++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 80 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index efb1ca8..ae7889e 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -633,16 +633,20 @@ class Index(MutableMapping): Hashing protocol is not used. Keys are looked up by their serialized format. See ``diskcache.Disk`` for details. - >>> index = Index([('apple', 1), ('beans', 2), ('cherries', 3)]) - >>> index['apple'] + >>> index = Index('/tmp/diskcache/index') + >>> index + Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) + >>> index['a'] 1 >>> list(index) - ['apple', 'beans', 'cherries'] + ['a', 'b', 'c'] >>> len(index) 3 - >>> del index['beans'] + >>> del index['b'] >>> index.popitem() - ('cherries', 3) + ('c', 3) """ def __init__(self, *args, **kwargs): @@ -651,12 +655,12 @@ def __init__(self, *args, **kwargs): Optional first argument may be string specifying directory where items are stored. When None or not given, temporary directory is created. - >>> index = Index({'apples': 1, 'beans': 2, 'cherries': 3}) + >>> index = Index({'a': 1, 'b': 2, 'c': 3}) >>> len(index) 3 >>> directory = index.directory - >>> inventory = Index(directory, dates=4) - >>> inventory['beans'] + >>> inventory = Index(directory, d=4) + >>> inventory['b'] 2 >>> len(inventory) 4 @@ -679,13 +683,13 @@ def fromcache(cls, cache, *args, **kwargs): >>> cache = Cache('/tmp/diskcache/index') >>> _ = cache.clear() - >>> index = Index.fromcache(cache, {'a': 0, 'b': 1, 'c': 2}) + >>> index = Index.fromcache(cache, {'a': 1, 'b': 2, 'c': 3}) >>> len(index) 3 >>> 'b' in index True >>> index['c'] - 2 + 3 :param Cache cache: cache to use :param args: mapping or sequence of items @@ -713,11 +717,11 @@ def __getitem__(self, key): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b': 1}) + >>> index.update({'a': 1, 'b': 2}) >>> index['a'] - 0 - >>> index['b'] 1 + >>> index['b'] + 2 >>> index['c'] Traceback (most recent call last): ... @@ -743,7 +747,7 @@ def __setitem__(self, key, value): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index['a'] = 0 + >>> index['a'] = 1 >>> index[0] = None >>> len(index) 2 @@ -770,7 +774,7 @@ def __delitem__(self, key): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b': 1}) + >>> index.update({'a': 1, 'b': 2}) >>> del index['a'] >>> del index['b'] >>> len(index) @@ -801,13 +805,13 @@ def pop(self, key, default=ENOVAL): If `key` is missing then return `default`. If `default` is `ENOVAL` then raise KeyError. - >>> index = Index('/tmp/diskcache/index', {'a': 0, 'b': 1}) + >>> index = Index('/tmp/diskcache/index', {'a': 1, 'b': 2}) >>> index.pop('a') - 0 - >>> index.pop('b') 1 - >>> index.pop('c', default=2) + >>> index.pop('b') 2 + >>> index.pop('c', default=3) + 3 >>> index.pop('d') Traceback (most recent call last): ... @@ -842,13 +846,13 @@ def popitem(self, last=True): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('apples', 1), ('beans', 2), ('cherries', 3)]) - >>> index.popitem() - ('cherries', 3) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index.popitem() - ('beans', 2) + ('c', 3) + >>> index.popitem(last=False) + ('a', 1) >>> index.popitem() - ('apples', 1) + ('b', 2) >>> index.popitem() Traceback (most recent call last): ... @@ -969,15 +973,6 @@ def pull(self, prefix=None, default=(None, None), side='front'): def clear(self): """Remove all items from index. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() - >>> index.update({'a': 0, 'b': 1, 'c': 2}) - >>> len(index) - 3 - >>> index.clear() - >>> len(index) - 0 - """ _cache_clear = self._cache.clear @@ -994,15 +989,6 @@ def __iter__(self): Return iterator of index keys in insertion order. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) - >>> iterator = iter(index) - >>> next(iterator) - 'a' - >>> list(iterator) - ['b', 'c'] - """ return iter(self._cache) @@ -1014,7 +1000,7 @@ def __reversed__(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> iterator = reversed(index) >>> next(iterator) 'c' @@ -1030,14 +1016,6 @@ def __len__(self): Return length of index. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() - >>> len(index) - 0 - >>> index.update({'a': 0, 'b': 1, 'c': 2}) - >>> len(index) - 3 - """ return len(self._cache) @@ -1048,7 +1026,7 @@ def keys(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index.keys() ['a', 'b', 'c'] @@ -1063,9 +1041,9 @@ def values(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index.values() - [0, 1, 2] + [1, 2, 3] :return: list of values @@ -1078,9 +1056,9 @@ def items(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index.items() - [('a', 0), ('b', 1), ('c', 2)] + [('a', 1), ('b', 2), ('c', 3)] :return: list of items @@ -1093,7 +1071,7 @@ def iterkeys(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> list(index.iterkeys()) ['a', 'b', 'c'] @@ -1108,9 +1086,9 @@ def itervalues(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> list(index.itervalues()) - [0, 1, 2] + [1, 2, 3] :return: iterator of values @@ -1134,9 +1112,9 @@ def iteritems(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> list(index.iteritems()) - [('a', 0), ('b', 1), ('c', 2)] + [('a', 1), ('b', 2), ('c', 3)] :return: iterator of items @@ -1160,7 +1138,7 @@ def viewkeys(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> keys_view = index.viewkeys() >>> 'b' in keys_view True @@ -1176,9 +1154,9 @@ def viewvalues(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> values_view = index.viewvalues() - >>> 1 in values_view + >>> 2 in values_view True :return: values view @@ -1192,9 +1170,9 @@ def viewitems(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> items_view = index.viewitems() - >>> ('b', 1) in items_view + >>> ('b', 2) in items_view True :return: items view @@ -1209,7 +1187,7 @@ def keys(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> keys_view = index.keys() >>> 'b' in keys_view True @@ -1225,9 +1203,9 @@ def values(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> values_view = index.values() - >>> 1 in values_view + >>> 2 in values_view True :return: values view @@ -1241,9 +1219,9 @@ def items(self): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update({'a': 0, 'b': 1, 'c': 2}) + >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> items_view = index.items() - >>> ('b', 1) in items_view + >>> ('b', 2) in items_view True :return: items view @@ -1273,13 +1251,13 @@ def __eq__(self, other): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> pairs = [('a', 0), ('b', 1), ('c', 2)] + >>> pairs = [('a', 1), ('b', 2), ('c', 3)] >>> index.update(pairs) >>> from collections import OrderedDict >>> od = OrderedDict(pairs) >>> index == od True - >>> index == {'c': 2, 'b': 1, 'a': 0} + >>> index == {'c': 3, 'b': 2, 'a': 1} True :param other: other mapping in equality comparison @@ -1307,12 +1285,12 @@ def __ne__(self, other): >>> index = Index('/tmp/diskcache/index') >>> index.clear() - >>> index.update([('a', 0), ('b', 1), ('c', 2)]) + >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> from collections import OrderedDict - >>> od = OrderedDict([('c', 2), ('b', 1), ('a', 0)]) + >>> od = OrderedDict([('c', 3), ('b', 2), ('a', 1)]) >>> index != od True - >>> index != {'a': 0, 'b': 1} + >>> index != {'a': 1, 'b': 2} True :param other: other mapping in inequality comparison @@ -1326,10 +1304,6 @@ def __repr__(self): Return string with printable representation of index. - >>> index = Index('/tmp/diskcache/index') - >>> index - Index('/tmp/diskcache/index') - """ name = type(self).__name__ return '{0}({1!r})'.format(name, self.directory) From c6861e98042e363cdea0afdf9007e82371b8a32e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 11:23:02 -0700 Subject: [PATCH 145/550] Add tests for rotate IndexError scenario --- tests/test_deque.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_deque.py b/tests/test_deque.py index 284d409..14329d1 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -7,6 +7,7 @@ import shutil import diskcache as dc +from diskcache.core import ENOVAL def rmdir(directory): @@ -344,6 +345,30 @@ def test_rotate_negative(deque): assert deque == 'cdeab' +@setup_deque +def test_rotate_indexerror(deque): + deque += 'abc' + + cache = mock.MagicMock() + cache.__len__.return_value = 3 + cache.pull.side_effect = [(None, ENOVAL)] + + with mock.patch.object(deque, '_cache', cache): + deque.rotate(1) + + +@setup_deque +def test_rotate_indexerror_negative(deque): + deque += 'abc' + + cache = mock.MagicMock() + cache.__len__.return_value = 3 + cache.pull.side_effect = [(None, ENOVAL)] + + with mock.patch.object(deque, '_cache', cache): + deque.rotate(-1) + + @setup_deque def test_clear_timeout(deque): cache = mock.MagicMock() From 5fbb0ab7e913512372996dc641e9f9ca003828fc Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 11:59:35 -0700 Subject: [PATCH 146/550] Add blurbs about Deque and Index objects --- docs/tutorial.rst | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 9280475..6989328 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -362,6 +362,70 @@ file handle in the `name` attribute. Remember to also include the .. _`Django documentation on caching`: https://docs.djangoproject.com/en/1.9/topics/cache/#the-low-level-cache-api +Deque +----- + +:class:`diskcache.Deque` (pronounced "deck") uses a :class:`Cache +` to provide a `collections.deque +`_-compatible +double-ended queue. Deques are a generalization of stacks and queues with fast +access and editing at both front and back sides. :class:`Deque +` objects inherit the benefits of the :class:`Cache +` objects but never evict items. + + >>> from diskcache import Deque + >>> deque = Deque(range(5, 10)) + >>> deque.pop() + 9 + >>> deque.popleft() + 5 + >>> deque.appendleft('foo') + >>> len(deque) + 4 + >>> deque.directory + '/tmp/...' + >>> other = Deque(directory=deque.directory) + >>> len(other) + 4 + >>> other.popleft() + 'foo' + +:class:`Deque ` objects provide an efficient and safe means of +cross-thread and cross-process communication. :class:`Deque ` +objects are also useful in scenarios where contents should remain persistent or +limitations prohibit holding all items in memory at the same time. + +Index +----- + +:class:`diskcache.Index` uses a :class:`Cache ` to provide a +`mutable mapping +`_ +and `ordered dictionary +`_ +interface. :class:`Index ` objects inherit the benefits of +:class:`Cache ` objects but never evict items. + + >>> from diskcache import Index + >>> index = Index([('a', 1), ('b', 2), ('c', 3)]) + >>> 'b' in index + True + >>> index['c'] + 3 + >>> del index['a'] + >>> len(index) + 2 + >>> other = Index(index.directory) + >>> len(other) + 2 + >>> other.popitem(last=False) + ('b', 2) + +:class:`Index ` objects provide an efficient and safe means of +cross-thread and cross-process communication. :class:`Index ` +objects are also useful in scenarios where contents should remain persistent or +limitations prohibit holding all items in memory at the same time. + .. _tutorial-settings: Settings From 871b51aebb432ab676a5e00ffb1c22f71acfc720 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 3 Aug 2017 12:02:08 -0700 Subject: [PATCH 147/550] Bump version to 2.7.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 4209fab..f2af20c 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -27,8 +27,8 @@ __title__ = 'diskcache' -__version__ = '2.6.5' -__build__ = 0x020605 +__version__ = '2.7.0' +__build__ = 0x020700 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 4a6e2b9218c6855c694a68483887dcba672c2ca0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 4 Aug 2017 22:10:20 -0700 Subject: [PATCH 148/550] Remove web crawler case study --- docs/web-crawler-case-study.rst | 113 -------------------------------- 1 file changed, 113 deletions(-) delete mode 100644 docs/web-crawler-case-study.rst diff --git a/docs/web-crawler-case-study.rst b/docs/web-crawler-case-study.rst deleted file mode 100644 index 5102c1c..0000000 --- a/docs/web-crawler-case-study.rst +++ /dev/null @@ -1,113 +0,0 @@ -import requests -import time - -def get(url): - response = requests.get(url) - content = response.content - time.sleep(1) - return content - - -import bs4 -import urllib.parse as up - -def links(root, url, content): - soup = bs4.BeautifulSoup(content, 'lxml') - anchors = soup.find_all('a', href=True) - hrefs = [anchor['href'] for anchor in anchors] - join_links = [up.urljoin(url, href) for href in hrefs] - defrag_links = [up.urldefrag(url)[0] for url in join_links] - root_links = [url for url in defrag_links if url.startswith(root)] - return root_links - - -def main1(): - "Single-process, ephemeral." - root = 'http://www.grantjenks.com' - - results = {} - urls = [root] - - while urls: - url = urls.pop() - - if url in results: - continue - - print(url) - content = get(url) - - for link in links(root, url, content): - urls.append(link) - - results[url] = content - - print(len(results)) - - -import diskcache as dc - -def main2(): - "Multi-process, persistent." - root = 'http://www.grantjenks.com' - - results = dc.Index('data/results') - urls = dc.Queue('data/urls') - - while urls: - url = urls.pull() - - if url is None or url in results: - continue - - print(url) - content = get(url) - - for link in links(root, url, content): - urls.push(link) - - results[url] = content - - print(len(results)) - - -import diskcache as dc - -def main3(): - "Multi-process, persistent, reliable." - root = 'http://www.grantjenks.com' - - results = dc.Index('data/results') - urls = dc.Queue('data/urls') - - # TODO - - state = dc.Index('data/state') - - state.setdefault(root, None) - - while True: - for key in state: - if state[key] is None: - urls.push(key) - - while urls: - url = urls.pull() - - if url is None or url in results: - continue - - print(url) - content = get(url) - - for link in links(root, url, content): - results.setdefault(link, None) - urls.push(link) - - results[url] = content - - print(len(results)) - - -if __name__ == '__main__': - main() From aa179ce5e1900a78287d3ac09c2618ed81d4491e Mon Sep 17 00:00:00 2001 From: Tamirlan Date: Sat, 5 Aug 2017 19:08:36 +0300 Subject: [PATCH 149/550] Removed linked list, replaced Cache to FanoutCache --- diskcache/decorators.py | 155 ++++++--------------------------------- tests/test_decorators.py | 35 +-------- 2 files changed, 25 insertions(+), 165 deletions(-) diff --git a/diskcache/decorators.py b/diskcache/decorators.py index acadf4e..8b1a8ba 100644 --- a/diskcache/decorators.py +++ b/diskcache/decorators.py @@ -1,8 +1,11 @@ from collections import namedtuple -from .core import Cache +from diskcache import EVICTION_POLICY +from .fanout import FanoutCache -import uuid +from tempfile import mkdtemp + +_CacheInfo = namedtuple("CacheInfo", ["hits", "misses"]) try: from _thread import RLock @@ -14,7 +17,6 @@ def __enter__(self): pass def __exit__(self, exctype, excinst, exctb): pass -_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') @@ -52,45 +54,26 @@ def update_wrapper(wrapper, return wrapper -def _make_key(args, kwds, typed, +def _make_key(args, kwds, kwd_mark=(object(),), - fasttypes={int, str, frozenset, type(None)}, sorted=sorted, tuple=tuple, type=type, len=len): - """Make a cache key from optionally typed positional and keyword arguments - - The key is constructed in a way that is flat as possible rather than - as a nested structure that would take more memory. + # Make a cache key from optionally typed positional and keyword arguments - If there is only a single argument and its data type is known to cache - its hash value, then that argument is returned without a wrapper. This - saves space and improves lookup speed. - - """ key = args if kwds: sorted_items = sorted(kwds.items()) key += kwd_mark for item in sorted_items: key += item - if typed: - key += tuple(type(v) for v in args) - if kwds: - key += tuple(type(v) for k, v in sorted_items) return key -def lru_cache(maxsize=128, typed=False, cache_path=None): +def lru_cache(directory=None, use_statistics=False): """Least-recently-used cache decorator. If *maxsize* is set to None, the LRU features are disabled and the cache can grow without bound. - If *typed* is True, arguments of different types will be cached separately. - For example, f(3.0) and f(3) will be treated as distinct calls with - distinct results. - - Arguments to the cached function must be hashable. - View the cache statistics named tuple (hits, misses, maxsize, currsize) with f.cache_info(). Clear the cache and statistics with f.cache_clear(). Access the underlying function with f.__wrapped__. @@ -104,132 +87,42 @@ def lru_cache(maxsize=128, typed=False, cache_path=None): # The internals of the lru_cache are encapsulated for thread safety and # to allow the implementation to change (including a possible C version). - # Early detection of an erroneous call to @lru_cache without any arguments - # resulting in the inner function being passed to maxsize instead of an - # integer or None. - if maxsize is not None and not isinstance(maxsize, int): - raise TypeError('Expected maxsize to be an integer or None') - def decorating_function(user_function): - wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo, cache_path) + wrapper = _lru_cache_wrapper(user_function, directory, use_statistics) return update_wrapper(wrapper, user_function) return decorating_function -def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo, cache_path): +def _lru_cache_wrapper(user_function, directory=None, use_statistics=False): # Constants shared by all lru cache instances: sentinel = object() # unique object used to signal cache misses make_key = _make_key # build a key from the function arguments - PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields - if cache_path is None: - cache_path = str(uuid.uuid4()) - with Cache(cache_path) as cache: - cache_get = cache.get # bound method to lookup a key or return None - lock = RLock() # because linkedlist updates aren't threadsafe - root = [] # root of the circular doubly linked list - root[:] = [root, root, None, None] # initialize by pointing to self - nonlocal_data = { - 'hits': 0, - 'misses': 0, - 'full': False, - 'root': root, - } - - if maxsize == 0: - def wrapper(*args, **kwds): - # No caching -- just a statistics update after a successful call - result = user_function(*args, **kwds) - nonlocal_data['misses'] += 1 - return result + if directory is None: + directory = mkdtemp() - elif maxsize is None: - def wrapper(*args, **kwds): - # Simple caching without ordering or size limit - key = make_key(args, kwds, typed) - result = cache_get(key, sentinel) - if result is not sentinel: - nonlocal_data['hits'] += 1 - return result - result = user_function(*args, **kwds) - cache[key] = result - nonlocal_data['misses'] += 1 - return result + with FanoutCache(directory, statistics=use_statistics, eviction_policy='least-recently-used') as cache: + cache_get = cache.get # bound method to lookup a key or return None - else: - def wrapper(*args, **kwds): - # Size limited caching that tracks accesses by recency - root = nonlocal_data['root'] - key = make_key(args, kwds, typed) - with lock: - link = cache_get(key) - if link is not None: - # Move the link to the front of the circular queue - link_prev, link_next, _key, result = link - link_prev[NEXT] = link_next - link_next[PREV] = link_prev - last = root[PREV] - last[NEXT] = root[PREV] = link - link[PREV] = last - link[NEXT] = root - nonlocal_data['hits'] += 1 - return result - result = user_function(*args, **kwds) - with lock: - if key in cache: - # Getting here means that this same key was added to the - # cache while the lock was released. Since the link - # update is already done, we need only return the - # computed result and update the count of misses. - pass - elif nonlocal_data['full']: - # Use the old root to store the new key and result. - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - # Empty the oldest link and make it the new root. - # Keep a reference to the old key and old result to - # prevent their ref counts from going to zero during the - # update. That will prevent potentially arbitrary object - # clean-up code (i.e. __del__) from running while we're - # still adjusting the links. - root = oldroot[NEXT] - oldkey = root[KEY] - oldresult = root[RESULT] - root[KEY] = root[RESULT] = None - # Now update the cache dictionary. - del cache[oldkey] - # Save the potentially reentrant cache[key] assignment - # for last, after the root and links have been put in - # a consistent state. - cache[key] = oldroot - nonlocal_data['root'] = root - else: - # Put result in a new link at the front of the queue. - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - # Use the __len__() method instead of the len() function - # which could potentially be wrapped in an lru_cache itself. - nonlocal_data['full'] = (cache.__len__() >= maxsize) - nonlocal_data['misses'] += 1 + def wrapper(*args, **kwds): + key = make_key(args, kwds) + result = cache_get(key) + if result: return result + result = user_function(*args, **kwds) + cache[key] = result + return result def cache_info(): """Report cache statistics""" - with lock: - return _CacheInfo(nonlocal_data['hits'], nonlocal_data['misses'], maxsize, cache.__len__()) + return _CacheInfo(*cache.stats()) if use_statistics else _CacheInfo(0, 0) def cache_clear(): """Clear the cache and cache statistics""" - - with lock: + if use_statistics: cache.clear() - root[:] = [root, root, None, None] - nonlocal_data['root'] = root - nonlocal_data['hits'] = nonlocal_data['misses'] = 0 - nonlocal_data['full'] = False + cache.stats(reset=True) wrapper.cache_info = cache_info wrapper.cache_clear = cache_clear diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 4ec4cdb..9bc6107 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -8,7 +8,7 @@ def test_lru_cache_decorator_with_infinite_cache_size(): # settings cache_size to infinite (fib function will cache every value) - @lru_cache(maxsize=None) + @lru_cache(use_statistics=True) def fib(num): if num <= 2: return 1 @@ -26,36 +26,3 @@ def fib(num): assert fib.cache_info().hits == hist_counter + 1000 # ensuring that no miss were made during second for loop assert fib.cache_info().misses == initial_miss_counter - - -def test_lru_cache_decorator_with_empty_cache_size(): - # settings cache_size to zero (this function won't cache any value at all) - @lru_cache(maxsize=0) - def fib(num): - if num <= 2: - return 1 - return fib(num - 1) + fib(num - 2) - - for i in range(20): - fib(i) - - assert fib.cache_info().hits == 0 - - -def test_lru_cache_decorator(): - @lru_cache(maxsize=10) - def printer(x): - return x - - for i in range(100): - printer(i) - - hist_counter = printer.cache_info().hits - initial_miss_counter = printer.cache_info().misses - - for i in range(90, 100): - printer(i) - - # ensuring that no miss were made during second for loop - assert printer.cache_info().misses == initial_miss_counter - assert printer.cache_info().hits == hist_counter + 10 \ No newline at end of file From 6d2f1d9ad8bd9cd51c1ea07c32987d2423a4307f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 09:36:48 -0700 Subject: [PATCH 150/550] Rename decorators submodule to memo --- diskcache/{decorators.py => memo.py} | 0 tests/{test_decorators.py => test_memo.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename diskcache/{decorators.py => memo.py} (100%) rename tests/{test_decorators.py => test_memo.py} (100%) diff --git a/diskcache/decorators.py b/diskcache/memo.py similarity index 100% rename from diskcache/decorators.py rename to diskcache/memo.py diff --git a/tests/test_decorators.py b/tests/test_memo.py similarity index 100% rename from tests/test_decorators.py rename to tests/test_memo.py From fa950b8b17529b44acbb1c853da9b4851cadcbd4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 11:11:45 -0700 Subject: [PATCH 151/550] Move memoize test to fanout --- tests/test_memo.py | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 tests/test_memo.py diff --git a/tests/test_memo.py b/tests/test_memo.py deleted file mode 100644 index 9bc6107..0000000 --- a/tests/test_memo.py +++ /dev/null @@ -1,28 +0,0 @@ -import sys - -from diskcache.decorators import lru_cache - -if sys.hexversion < 0x03000000: - range = xrange - - -def test_lru_cache_decorator_with_infinite_cache_size(): - # settings cache_size to infinite (fib function will cache every value) - @lru_cache(use_statistics=True) - def fib(num): - if num <= 2: - return 1 - return fib(num-1) + fib(num-2) - - for i in range(1000): - fib(i) - - hist_counter = fib.cache_info().hits - initial_miss_counter = fib.cache_info().misses - - for i in range(1000): - fib(i) - # ensuring that every value was cached during invoking fib function in second for loop - assert fib.cache_info().hits == hist_counter + 1000 - # ensuring that no miss were made during second for loop - assert fib.cache_info().misses == initial_miss_counter From b55a73dcbb849c4b9ee3d199a1fd94bd7a82a032 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 11:58:06 -0700 Subject: [PATCH 152/550] Add memoize method to FanoutCache and DjangoCache --- diskcache/djangocache.py | 13 +++ diskcache/fanout.py | 4 + diskcache/memo.py | 200 +++++++++++++++++--------------------- tests/test_djangocache.py | 35 +++++++ tests/test_doctest.py | 7 ++ tests/test_fanout.py | 37 +++++++ 6 files changed, 183 insertions(+), 113 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 9ceb4ac..42f320e 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -9,6 +9,7 @@ DEFAULT_TIMEOUT = 300 from .fanout import FanoutCache +from .memo import memoize class DjangoCache(BaseCache): @@ -26,6 +27,7 @@ def __init__(self, directory, params): options = params.get('OPTIONS', {}) self._directory = directory self._cache = FanoutCache(directory, shards, timeout, **options) + self.memoize = self._cache.memoize @property @@ -252,6 +254,17 @@ def expire(self): return self._cache.expire() + def stats(self, enable=True, reset=False): + """Return cache statistics hits and misses. + + :param bool enable: enable collecting statistics (default True) + :param bool reset: reset hits and misses to 0 (default False) + :return: (hits, misses) + + """ + return self._cache.stats(enable=enable, reset=reset) + + def create_tag_index(self): """Create tag index on cache database. diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 646dfc0..6967358 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -6,6 +6,7 @@ import time from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout +from .memo import memoize from .persistent import Deque, Index @@ -336,6 +337,9 @@ def __delitem__(self, key): continue + memoize = memoize + + def check(self, fix=False): """Check database and file system consistency. diff --git a/diskcache/memo.py b/diskcache/memo.py index 8b1a8ba..fc8d8be 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -1,130 +1,104 @@ -from collections import namedtuple - -from diskcache import EVICTION_POLICY -from .fanout import FanoutCache - -from tempfile import mkdtemp - -_CacheInfo = namedtuple("CacheInfo", ["hits", "misses"]) - -try: - from _thread import RLock -except ImportError: - class RLock: - 'Dummy reentrant lock for builds without threads' - - def __enter__(self): pass - - def __exit__(self, exctype, excinst, exctb): pass +"""Memoization utilities. + +""" + +from functools import wraps + +from .core import ENOVAL + +def memoize(cache, name=None, typed=False, expire=None, tag=None): + """Memoizing cache decorator. + + 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. + + 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, or + for rewrapping the function with a different cache. + + >>> from diskcache import FanoutCache + >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> @cache.memoize(typed=True, expire=1, tag='fib') + ... def fibonacci(number): + ... if number == 0: + ... return 0 + ... elif number == 1: + ... return 1 + ... else: + ... return fibonacci(number - 1) + fibonacci(number - 2) + >>> print(sum(fibonacci(number=value) for value in range(100))) + 573147844013817084100 + + 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 and return 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) + :return: callable decorator + """ + if callable(name): + raise TypeError('name cannot be callable') -WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', - '__annotations__') -WRAPPER_UPDATES = ('__dict__',) + def decorator(function): + if name is None: + try: + reference = function.__qualname__ + except AttributeError: + reference = function.__name__ + reference = function.__module__ + reference -def update_wrapper(wrapper, - wrapped, - assigned=WRAPPER_ASSIGNMENTS, - updated=WRAPPER_UPDATES): - """Update a wrapper function to look like the wrapped function + reference = (reference,) - wrapper is the function to be updated - wrapped is the original function - assigned is a tuple naming the attributes assigned directly - from the wrapped function to the wrapper function (defaults to - functools.WRAPPER_ASSIGNMENTS) - updated is a tuple naming the attributes of the wrapper that - are updated with the corresponding attribute from the wrapped - function (defaults to functools.WRAPPER_UPDATES) - """ - for attr in assigned: - try: - value = getattr(wrapped, attr) - except AttributeError: - pass - else: - setattr(wrapper, attr, value) - for attr in updated: - getattr(wrapper, attr).update(getattr(wrapped, attr, {})) - # Issue #17482: set __wrapped__ last so we don't inadvertently copy it - # from the wrapped function when updating __dict__ - wrapper.__wrapped__ = wrapped - # Return the wrapper so this can be used as a decorator via partial() - return wrapper - - -def _make_key(args, kwds, - kwd_mark=(object(),), - sorted=sorted, tuple=tuple, type=type, len=len): - # Make a cache key from optionally typed positional and keyword arguments - - key = args - if kwds: - sorted_items = sorted(kwds.items()) - key += kwd_mark - for item in sorted_items: - key += item - return key - - -def lru_cache(directory=None, use_statistics=False): - """Least-recently-used cache decorator. - - If *maxsize* is set to None, the LRU features are disabled and the cache - can grow without bound. - - View the cache statistics named tuple (hits, misses, maxsize, currsize) - with f.cache_info(). Clear the cache and statistics with f.cache_clear(). - Access the underlying function with f.__wrapped__. - - See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used + @wraps(function) + def wrapper(*args, **kwargs): + # Create key from reference, args and kwargs. - """ + key = reference + args - # Users should only access the lru_cache through its public API: - # cache_info, cache_clear, and f.__wrapped__ - # The internals of the lru_cache are encapsulated for thread safety and - # to allow the implementation to change (including a possible C version). + if kwargs: + key += (ENOVAL,) + sorted_items = sorted(kwargs.items()) - def decorating_function(user_function): - wrapper = _lru_cache_wrapper(user_function, directory, use_statistics) - return update_wrapper(wrapper, user_function) + for item in sorted_items: + key += item - return decorating_function + if typed: + key += tuple(type(arg) for arg in args) + if kwargs: + key += tuple(type(value) for _, value in sorted_items) -def _lru_cache_wrapper(user_function, directory=None, use_statistics=False): - # Constants shared by all lru cache instances: - sentinel = object() # unique object used to signal cache misses - make_key = _make_key # build a key from the function arguments + # Lookup result. - if directory is None: - directory = mkdtemp() + result = cache.get(key, default=ENOVAL, retry=True) - with FanoutCache(directory, statistics=use_statistics, eviction_policy='least-recently-used') as cache: - cache_get = cache.get # bound method to lookup a key or return None + if result is ENOVAL: + result = function(*args, **kwargs) + cache.set(key, result, expire=expire, tag=tag, retry=True) - def wrapper(*args, **kwds): - key = make_key(args, kwds) - result = cache_get(key) - if result: - return result - result = user_function(*args, **kwds) - cache[key] = result return result - def cache_info(): - """Report cache statistics""" - return _CacheInfo(*cache.stats()) if use_statistics else _CacheInfo(0, 0) - - def cache_clear(): - """Clear the cache and cache statistics""" - if use_statistics: - cache.clear() - cache.stats(reset=True) - - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return wrapper + return wrapper + return decorator diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 36738a1..bc11ced 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -941,3 +941,38 @@ def test_index(self): index = cache.index('test') directory = os.path.join(cache.directory, 'index', 'test') self.assertEqual(index.directory, directory) + + def test_memoize(self): + count = 1000 + + def fibiter(num): + alpha, beta = 0, 1 + + for _ in range(num): + alpha, beta = beta, alpha + beta + + return alpha + + @cache.memoize() + def fibrec(num): + if num == 0: + return 0 + elif num == 1: + return 1 + else: + return fibrec(num - 1) + fibrec(num - 2) + + cache.stats(enable=True) + + for value in range(count): + self.assertEqual(fibrec(value), fibiter(value)) + + hits1, misses1 = cache.stats() + + for value in range(count): + self.assertEqual(fibrec(value), fibiter(value)) + + hits2, misses2 = cache.stats() + + self.assertEqual(hits2, hits1 + count) + self.assertEqual(misses2, misses1) diff --git a/tests/test_doctest.py b/tests/test_doctest.py index 3560d28..ba8eb6b 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -4,6 +4,7 @@ import diskcache.core import diskcache.djangocache import diskcache.fanout +import diskcache.memo import diskcache.persistent @@ -32,6 +33,12 @@ def test_fanout(): assert failures == 0 +def test_memo(): + rmdir('/tmp/diskcache') + failures, _ = doctest.testmod(diskcache.memo) + assert failures == 0 + + def test_persistent(): rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.persistent) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index e77e6c1..c90c2c3 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -513,6 +513,43 @@ def test_pickle(cache): assert other[key] == cache[key] +@setup_cache +def test_memoize(cache): + count = 1000 + + def fibiter(num): + alpha, beta = 0, 1 + + for _ in range(num): + alpha, beta = beta, alpha + beta + + return alpha + + @cache.memoize() + def fibrec(num): + if num == 0: + return 0 + elif num == 1: + return 1 + else: + return fibrec(num - 1) + fibrec(num - 2) + + cache.stats(enable=True) + + for value in range(count): + assert fibrec(value) == fibiter(value) + + hits1, misses1 = cache.stats() + + for value in range(count): + assert fibrec(value) == fibiter(value) + + hits2, misses2 = cache.stats() + + assert hits2 == hits1 + count + assert misses2 == misses1 + + if __name__ == '__main__': import nose nose.runmodule() From 845f7625b04fb2dbb31018eee828d6861f49fa72 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 11:58:37 -0700 Subject: [PATCH 153/550] Bump version to 2.8.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index f2af20c..94229a8 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -27,8 +27,8 @@ __title__ = 'diskcache' -__version__ = '2.7.0' -__build__ = 0x020700 +__version__ = '2.8.0' +__build__ = 0x020800 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From ca4a0db3d6d2008d7962578da6f1b0da00347fd4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 12:00:54 -0700 Subject: [PATCH 154/550] Add docstring to internal functions --- diskcache/memo.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diskcache/memo.py b/diskcache/memo.py index fc8d8be..7c445ac 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -60,6 +60,7 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): raise TypeError('name cannot be callable') def decorator(function): + "Decorator created by memoize call for callable." if name is None: try: reference = function.__qualname__ @@ -72,6 +73,7 @@ def decorator(function): @wraps(function) def wrapper(*args, **kwargs): + "Wrapper for callable to cache arguments and return values." # Create key from reference, args and kwargs. key = reference + args From 5cfa1ff7d0b93f0eb841940329b289a53ca4719b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 12:01:08 -0700 Subject: [PATCH 155/550] Bump version to 2.8.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 94229a8..c141b4c 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -27,8 +27,8 @@ __title__ = 'diskcache' -__version__ = '2.8.0' -__build__ = 0x020800 +__version__ = '2.8.1' +__build__ = 0x020801 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 4d8ddeda90a993e76daad4c07be49a53270d2a9a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 12:01:55 -0700 Subject: [PATCH 156/550] Remove unused import from memo --- diskcache/djangocache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 42f320e..e5e3ddc 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -9,7 +9,6 @@ DEFAULT_TIMEOUT = 300 from .fanout import FanoutCache -from .memo import memoize class DjangoCache(BaseCache): From f5bd40afd515ea8e36851a2331558173f3aac37f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 12:02:05 -0700 Subject: [PATCH 157/550] Bump version to 2.8.2 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index c141b4c..da9d789 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -27,8 +27,8 @@ __title__ = 'diskcache' -__version__ = '2.8.1' -__build__ = 0x020801 +__version__ = '2.8.2' +__build__ = 0x020802 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From bea0e3a7fe0035bf5fab7ec2395165cbadb071de Mon Sep 17 00:00:00 2001 From: Tamirlan Date: Tue, 8 Aug 2017 00:38:27 +0300 Subject: [PATCH 158/550] fix to UnboundLocalError when callable name passed to memoize decorator --- diskcache/memo.py | 2 ++ tests/test_fanout.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/diskcache/memo.py b/diskcache/memo.py index 7c445ac..db593c2 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -61,6 +61,8 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): def decorator(function): "Decorator created by memoize call for callable." + reference = name + if name is None: try: reference = function.__qualname__ diff --git a/tests/test_fanout.py b/tests/test_fanout.py index c90c2c3..5f80574 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -525,7 +525,7 @@ def fibiter(num): return alpha - @cache.memoize() + @cache.memoize(name='callable_name') def fibrec(num): if num == 0: return 0 From 5802a2a2324f283f3c043a0c13ee8b7d6db06b16 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 14:40:15 -0700 Subject: [PATCH 159/550] Update development docs re: lru_cache --- docs/development.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/development.rst b/docs/development.rst index 406bf1a..a37c3a7 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -24,7 +24,6 @@ Requests for Contributions #. Cache stampede barrier (source prototype in repo). #. API Compatibility - #. `lru_cache-like decorator `_ #. `Shelf interface `_ #. `DBM interface `_ From ba364886fcfa25b0870b30e266137ea8ed3bbc73 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 15:00:50 -0700 Subject: [PATCH 160/550] Add docs about FanoutCache.memoize --- docs/tutorial.rst | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 6989328..d091d74 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -296,10 +296,42 @@ timeout. :class:`FanoutCache ` will never raise a The example above creates a cache in the local ``/tmp/mycachedir`` directory with four shards and a one second timeout. Operations will attempt to abort if -they take longer than one second. +they take longer than one second. The remaining API of :class:`FanoutCache +` matches :class:`Cache ` as described +above. -The remaining API of :class:`FanoutCache ` matches -:class:`Cache ` as described above. +:class:`FanoutCache ` adds an additional feature: +:meth:`memoizing ` cache decorator. The +decorator wraps a callable and caches arguments and return values. + + >>> from diskcache import FanoutCache + >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> @cache.memoize(typed=True, expire=1, tag='fib') + ... def fibonacci(number): + ... if number == 0: + ... return 0 + ... elif number == 1: + ... return 1 + ... else: + ... return fibonacci(number - 1) + fibonacci(number - 2) + >>> print(sum(fibonacci(number=value) for value in range(100))) + 573147844013817084100 + +The arguments to memoize are like those for `functools.lru_cache +`_ and +:meth:`FanoutCache.set `. Remember to call +:meth:`memoize ` when decorating a callable. If +you forget, then a TypeError will occur. + + >>> @cache.memoize + ... def test(): + ... pass + Traceback (most recent call last): + ... + TypeError: name cannot be callable + +Observe the lack of parenthenses after :meth:`memoize +` above. .. _`Sharding`: https://en.wikipedia.org/wiki/Shard_(database_architecture) From 39a6e7b478227b07115929abcd1ce76254ce3200 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 15:01:49 -0700 Subject: [PATCH 161/550] Improve readability, remove development comments --- diskcache/memo.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/diskcache/memo.py b/diskcache/memo.py index 7c445ac..bb81db8 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -74,7 +74,6 @@ def decorator(function): @wraps(function) def wrapper(*args, **kwargs): "Wrapper for callable to cache arguments and return values." - # Create key from reference, args and kwargs. key = reference + args @@ -91,8 +90,6 @@ def wrapper(*args, **kwargs): if kwargs: key += tuple(type(value) for _, value in sorted_items) - # Lookup result. - result = cache.get(key, default=ENOVAL, retry=True) if result is ENOVAL: From c7474b97a803c2a68ce4f2e0d3c940cc1175e04a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 15:05:08 -0700 Subject: [PATCH 162/550] Refactor memoize name parameter --- diskcache/memo.py | 4 ++-- tests/test_fanout.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/diskcache/memo.py b/diskcache/memo.py index 4d575f8..3a2243a 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -61,8 +61,6 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): def decorator(function): "Decorator created by memoize call for callable." - reference = name - if name is None: try: reference = function.__qualname__ @@ -70,6 +68,8 @@ def decorator(function): reference = function.__name__ reference = function.__module__ + reference + else: + reference = name reference = (reference,) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 5f80574..9db7e32 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -525,7 +525,7 @@ def fibiter(num): return alpha - @cache.memoize(name='callable_name') + @cache.memoize(name='fib') def fibrec(num): if num == 0: return 0 From 96cfc5e1ed53348b380113f80e9230f01c07c519 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 15:05:39 -0700 Subject: [PATCH 163/550] Bump version to 2.8.3 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index da9d789..9c44f28 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -27,8 +27,8 @@ __title__ = 'diskcache' -__version__ = '2.8.2' -__build__ = 0x020802 +__version__ = '2.8.3' +__build__ = 0x020803 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 86fe08716d907a4e8f56d1938b2d74e413ea00e7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 15:28:57 -0700 Subject: [PATCH 164/550] Add none eviction policy to docs --- docs/api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api.rst b/docs/api.rst index 12fbba3..0d826c4 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -66,6 +66,7 @@ Read the :ref:`Settings tutorial ` for details. * `least-recently-stored` (default) - evict least recently stored keys first. * `least-recently-used` - evict least recently used keys first. * `least-frequently-used` - evict least frequently used keys first. + * `none` - never evict keys. Disk ---- From 4f9655a9a3ecf1a30863958c66c1907d6b1804b9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 19:44:45 -0700 Subject: [PATCH 165/550] Add Case Study: Web Crawler docs --- README.rst | 2 + docs/case-study-web-crawler.rst | 144 ++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 3 files changed, 147 insertions(+) create mode 100644 docs/case-study-web-crawler.rst diff --git a/README.rst b/README.rst index dbb5811..085f83f 100644 --- a/README.rst +++ b/README.rst @@ -98,12 +98,14 @@ introduction, benchmarks, development, and API. * `DiskCache Tutorial`_ * `DiskCache Cache Benchmarks`_ * `DiskCache DjangoCache Benchmarks`_ +* `Case Study: Web Crawler`_ * `DiskCache API Reference`_ * `DiskCache Development`_ .. _`DiskCache Tutorial`: http://www.grantjenks.com/docs/diskcache/tutorial.html .. _`DiskCache Cache Benchmarks`: http://www.grantjenks.com/docs/diskcache/cache-benchmarks.html .. _`DiskCache DjangoCache Benchmarks`: http://www.grantjenks.com/docs/diskcache/djangocache-benchmarks.html +.. _`Case Study: Web Crawler`: http://www.grantjenks.com/docs/diskcache/case-study-web-crawler.html .. _`DiskCache API Reference`: http://www.grantjenks.com/docs/diskcache/api.html .. _`DiskCache Development`: http://www.grantjenks.com/docs/diskcache/development.html diff --git a/docs/case-study-web-crawler.rst b/docs/case-study-web-crawler.rst new file mode 100644 index 0000000..2885c1c --- /dev/null +++ b/docs/case-study-web-crawler.rst @@ -0,0 +1,144 @@ +Case Study: Web Crawler +======================= + +:doc:`DiskCache ` version 2.7 added a couple persistent data +structures. Let's see how they're useful with a case study in crawling the +web. Easy enough, right? Let's start with code to retrieve urls: + + >>> from time import sleep + >>> def get(url): + ... "Get data for url." + ... sleep(url / 1000.0) + ... return str(url) + +No, we're not actually crawling the web. Our urls are numbers and we'll simply +go to sleep to simulate downloading a web page. + + >>> get(20) + '20' + +Once we download some data, we'll need to parse it and extract the links. + + >>> from random import randrange, seed + >>> def parse(data): + ... "Parse data and return list of links." + ... seed(int(data)) + ... count = randrange(1, 10) + ... return [randrange(100) for _ in range(count)] + +Again, we're not really parsing data. We're just returning a list of one to ten +integers between zero and one hundred. In our imaginary web, urls are just +integers. + + >>> parse('20') + [68, 76, 90, 25, 63, 90, 87, 57, 16] + +Alright, this is a pretty basic pattern. The ``get`` function returns data and +the ``parse`` function returns a list of more data to go get. We can use the +deque data type from the standard library's collection module to crawl our web. + + >>> from collections import deque + >>> def crawl(): + ... urls = deque([0]) + ... results = dict() + ... + ... while True: + ... try: + ... url = urls.popleft() + ... except IndexError: + ... break + ... + ... if url in results: + ... continue + ... + ... data = get(url) + ... + ... for link in parse(data): + ... urls.append(link) + ... + ... results[url] = data + ... + ... print('Results: %s' % len(results)) + +We're doing a breadth-first search crawl of the web. Our initial seed is zero +and we use that to initialize our queue. All the results are stored in a +dictionary mapping url to data. We then iterate by repeatedly popping the first +url from our queue. If we've already visited the url then we continue, +otherwise we get the corresponding data and parse it. The parsed results are +appended to our queue. Finally we store the data in our results +dictionary. + + >>> crawl() + Results: 99 + +The results of our current code are ephemeral. All results are lost once the +program terminates. To make the results persistent, we can use :doc:`DiskCache +` data structures and store the results in the local file +system. :doc:`DiskCache ` provides both :class:`Deque ` +and :class:`Index ` data structures which can replace our urls +and results variables. + + >>> from diskcache import Deque, Index + >>> def crawl(): + ... urls = Deque([0], 'data/urls') + ... results = Index('data/results') + ... + ... while True: + ... try: + ... url = urls.popleft() + ... except IndexError: + ... break + ... + ... if url in results: + ... continue + ... + ... data = get(url) + ... + ... for link in parse(data): + ... urls.append(link) + ... + ... results[url] = data + ... + ... print('Results: %s' % len(results)) + +Look familiar? Only three lines changed. The import at the top changed so now +we're using ``diskcache`` rather than the ``collections`` module. Then, when we +initialize the urls and results objects, we pass relative paths to directories +where we want the data stored. Again, let's try it out: + + >>> crawl() + Results: 99 + +Our results are now persistent. We can initialize our results index outside of +the crawl function and query it. + + >>> results = Index('data/results') + >>> len(results) + 99 + +As an added benefit, our code also now works in parallel. For free! + + >>> results.clear() + >>> from multiprocessing import Process + >>> processes = [Process(target=crawl) for _ in range(4)] + >>> for process in processes: + ... process.start() + >>> for process in processes: + ... process.join() + >>> len(results) + 99 + +Each of the processes uses the same deque and index to crawl our web. Work is +automatically divided among the processes as they pop urls from the queue. If +this were run as a script then multiple Python processes could be started and +stopped as desired. + +Interesting, no? Three simple changes and our code goes from ephemeral and +single-process to persistent and multi-process. Nothing truly new has happened +here but the API is convenient and that makes a huge difference. We're also no +longer constrained by memory. :doc:`DiskCache ` makes efficient use of +your disk and you can customize how much memory is used. By default the maximum +memory consumption of deque and index objects is only a few dozen +megabytes. Now our simple script can efficiently process terabytes of data. + +Go forth and build and share! diff --git a/docs/index.rst b/docs/index.rst index 4266d03..deb1d27 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -101,6 +101,7 @@ introduction, benchmarks, development, and API. tutorial cache-benchmarks djangocache-benchmarks + case-study-web-crawler api development From fde72a188357dc08053999126638688e8de2aec6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 7 Aug 2017 19:46:53 -0700 Subject: [PATCH 166/550] Small change to wording --- docs/case-study-web-crawler.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/case-study-web-crawler.rst b/docs/case-study-web-crawler.rst index 2885c1c..982a3c8 100644 --- a/docs/case-study-web-crawler.rst +++ b/docs/case-study-web-crawler.rst @@ -3,7 +3,7 @@ Case Study: Web Crawler :doc:`DiskCache ` version 2.7 added a couple persistent data structures. Let's see how they're useful with a case study in crawling the -web. Easy enough, right? Let's start with code to retrieve urls: +web. Easy enough, right? We'll start with code to retrieve urls: >>> from time import sleep >>> def get(url): From d554e77e84a15e3cbd5fc2f683c1b913f34ca549 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 12 Aug 2017 15:40:27 -0700 Subject: [PATCH 167/550] Fix secs display when value is None --- tests/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 7b0839f..f2370da 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -22,7 +22,9 @@ def secs(value): units = ['s ', 'ms', 'us', 'ns'] pos = 0 - if value == 0: + if value is None: + return ' 0.000ns' + elif value == 0: return ' 0.000ns' else: for unit in units: From 830449b466b4492bcadb7fcbed64ad8ce37c45a3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 12 Aug 2017 17:39:43 -0700 Subject: [PATCH 168/550] Initial code for Case Study: Fuzz Delays --- docs/case-study-fuzz-delays.rst | 111 ++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/case-study-fuzz-delays.rst diff --git a/docs/case-study-fuzz-delays.rst b/docs/case-study-fuzz-delays.rst new file mode 100644 index 0000000..e2a6035 --- /dev/null +++ b/docs/case-study-fuzz-delays.rst @@ -0,0 +1,111 @@ +Raymond keynote: +https://dl.dropboxusercontent.com/u/3967849/pybay2017_keynote/_build/html/index.html + +Fuzzing technique: +https://dl.dropboxusercontent.com/u/3967849/pybay2017_keynote/_build/html/threading.html#fuzzing + +Code below is simple on purpose. Not something to use in production. Ok for +testing. + +// discuss sys.settrace + + >>> def fuzzdelays(function): + ... """Insert random delays into function using `sys.settrace`. + ... + ... WARNING: The use of `sys.settrace` will affect other Python tools like + ... `pdb` and `coverage`. Use only in testing scenarios! + ... + ... Decorator to insert random delays into a function to encourage race + ... conditions in multi-threaded code. + ... + ... """ + ... from functools import wraps + ... from sys import settrace + ... from time import sleep + ... from random import random + ... + ... def sleeper(frame, event, arg): + ... "Sleep for random period between 0 and 100 milliseconds." + ... sleep(random() / 10) + ... + ... try: + ... code = function.__code__ + ... except AttributeError: # Python 2 compatibility. + ... code = function.co_code + ... + ... def tracer(frame, event, arg): + ... "Tracer looking for call events with matching function code." + ... if event == 'call' and frame.f_code is code: + ... sleep(random() / 10) + ... return sleeper + ... + ... @wraps(function) + ... def wrapper(*args, **kwargs): + ... """Wrap function to set tracer before calling function. + ... + ... Tracing is thread-local so set the tracer before every function + ... call. + ... + ... """ + ... settrace(tracer) + ... return function(*args, **kwargs) + ... + ... return wrapper + +Create a test: + + >>> import diskcache + >>> import multiprocessing + >>> import threading + +Increment all numbers in a range: + + >>> def task(cache): + ... for num in range(100): + ... cache.incr(num, retry=True) + +Process worker to start many tasks in separate threads. + + >>> def worker(): + ... # WARNING: Monkey-patch incr method to use fuzzdelays. + ... diskcache.FanoutCache.incr = fuzzdelays(diskcache.FanoutCache.incr) + ... + ... cache = diskcache.FanoutCache('tmp') + ... threads = [] + ... + ... for num in range(8): + ... thread = threading.Thread(target=task, args=(cache,)) + ... threads.append(thread) + ... + ... for thread in threads: + ... thread.start() + ... + ... for thread in threads: + ... thread.join() + +Start many worker processes: + + >>> def main(): + ... cache = diskcache.FanoutCache('tmp') + ... cache.clear() + ... + ... processes = [] + ... + ... for num in range(8): + ... process = multiprocessing.Process(target=worker) + ... processes.append(process) + ... + ... for process in processes: + ... process.start() + ... + ... for process in processes: + ... process.join() + ... + ... assert sorted(cache) == list(range(100)) + ... assert all(cache[key] == 64 for key in cache) + +Ok, here goes: + + >>> main() + +Yaay! It worked. From 5a25731e02ca013591c3292cc673beb9ac7fe11f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 10:01:34 -0700 Subject: [PATCH 169/550] Improve benchmark plot display --- tests/plot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/plot.py b/tests/plot.py index 3096510..ea1c0c1 100644 --- a/tests/plot.py +++ b/tests/plot.py @@ -16,7 +16,7 @@ def parse_timing(timing, limit): else: assert timing.endswith('s') value = float(timing[:-1]) - return 0.0 if value > limit else value + return 0.0 if value > limit else value * 1e6 def parse_row(row, line): @@ -95,7 +95,7 @@ def make_plot(data, action, save=False, show=False, limit=0.005): color=color, )) - ax.set_ylabel('Time (s)') + ax.set_ylabel('Time (microseconds)') ax.set_title('"%s" Time vs Percentile' % action) ax.set_xticks([val + width * (len(data) / 2) for val in index]) ax.set_xticklabels(ticks) @@ -106,14 +106,14 @@ def make_plot(data, action, save=False, show=False, limit=0.005): [bar[0] for bar in bars], names, loc='lower center', - bbox_to_anchor=(0.5, -0.3) + bbox_to_anchor=(0.5, -0.25) ) if show: plt.show() if save: - plt.savefig('%s-%s.png' % (save, action), dpi=80, bbox_inches='tight') + plt.savefig('%s-%s.png' % (save, action), dpi=120, bbox_inches='tight') plt.close() From 5601a1e4ad43118d5097f5d7d3989d3a21e6fd7e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 10:02:25 -0700 Subject: [PATCH 170/550] Add custom stylesheet for table display --- docs/_static/custom.css | 8 ++++++++ docs/conf.py | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 docs/_static/custom.css diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..50b1356 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,8 @@ +table { + font-size: 80%; + width: 100%; +} + +th.head { + text-align: center; +} diff --git a/docs/conf.py b/docs/conf.py index 14fd923..5f04890 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -225,6 +225,10 @@ # Output file base name for HTML help builder. htmlhelp_basename = 'DiskCacheDoc' +def setup(app): + app.add_stylesheet('custom.css') + + # -- Options for LaTeX output --------------------------------------------- latex_elements = { From 06fd8dad1a0c920e1f3deb6133a61294fe9112d4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 10:03:08 -0700 Subject: [PATCH 171/550] Change default timeout to 10 milliseconds for djangocache and fanoutcache objects --- diskcache/djangocache.py | 2 +- diskcache/fanout.py | 2 +- docs/_static/core-p1-delete.png | Bin 39928 -> 47490 bytes docs/_static/core-p1-get.png | Bin 39661 -> 46498 bytes docs/_static/core-p1-set.png | Bin 40461 -> 48068 bytes docs/_static/core-p8-delete.png | Bin 38922 -> 48371 bytes docs/_static/core-p8-get.png | Bin 37352 -> 46900 bytes docs/_static/core-p8-set.png | Bin 39548 -> 48147 bytes docs/_static/djangocache-delete.png | Bin 27115 -> 33677 bytes docs/_static/djangocache-get.png | Bin 27696 -> 32506 bytes docs/_static/djangocache-set.png | Bin 26325 -> 33306 bytes docs/cache-benchmarks.rst | 25 +++++++++++++------------ docs/djangocache-benchmarks.rst | 16 +++++++++------- docs/tutorial.rst | 4 ++-- tests/benchmark_core.py | 4 ++-- tests/timings_core_p1.txt | 10 +++++----- tests/timings_core_p8.txt | 10 +++++----- tests/timings_djangocache.txt | 8 ++++---- 18 files changed, 42 insertions(+), 39 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index e5e3ddc..99cd739 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -22,7 +22,7 @@ def __init__(self, directory, params): """ super(DjangoCache, self).__init__(params) shards = params.get('SHARDS', 8) - timeout = params.get('DATABASE_TIMEOUT', 0.025) + timeout = params.get('DATABASE_TIMEOUT', 0.010) options = params.get('OPTIONS', {}) self._directory = directory self._cache = FanoutCache(directory, shards, timeout, **options) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 6967358..8733116 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -12,7 +12,7 @@ class FanoutCache(object): "Cache that shards keys and values." - def __init__(self, directory, shards=8, timeout=0.025, disk=Disk, + def __init__(self, directory, shards=8, timeout=0.010, disk=Disk, **settings): """Initialize cache instance. diff --git a/docs/_static/core-p1-delete.png b/docs/_static/core-p1-delete.png index 455d47362a8bc46cc17c06d2d0bca72b2d7dc830..cd3726f47ddda48fc9e671dc3dcee5b52124ab9f 100644 GIT binary patch literal 47490 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 literal 39928 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%0As}NJvQd z`s&xuz2uEXubFktZHz?Bdiby#0ftIkv5TT2ckAvJfRg(}oL(lU7G2ghG!zxc(~NE7 zu*CQxIN1TS$9sJq>b{3TiKP4kEn|&hbAh@#%P09De6q-&8t?N#&DG#CkuJ^ zEL;$&w%bM(pS;068$v%t;SjliN)U(3se?0nGuc%4`ASo+_I!Qj_9Tx+iO-vZZB)A4 zxKrZkt@Zul$B$~Nsz!r*M5d=(>S(Pr6$tI>s)&yb*Ch85xtXlKSzNO-?mjfYM`XyPEgaQCyfD5*nKO zT`@FtdE$Jwtzfyl9>}*T4b|4y7|=a(p*)q^`|^zAeNKIW$s5lshS>TkJAWK)wVpFa zfiKp1@AOO2(W}U(qL2ktk5pdIVU3K94J0!a1}+bKxj*z}RP6S!6MFu*TN<0|>+5rh zi+f>z`bQ!4``AeXsS^;8_8mBI*y6Nao`D>wCmVN8FaN|V`_JrPLPXW1(sMU4tT6hj zQ~Bnd&?^id?-sT6uHv@C`r2AkRx*oZsjbPz5R<{~-8)Jc&*D!$!iPSv&#K;+j#du3 zF>uNCI70irprD#qyLIdK0l93z_%0gPUe&n1e|2jL98}%s3Dlc?DFt0#osNFxj-Y39 z{;|VerDF8v|3zkXeV7$8Y{ZCM9tznc-9y9O8gXOVoH+;0S}C?F0Mv;$wWchYy;bT4 zQ^Bp)Z<+O(9-!G7!y}i9@$x8t|iVtj@)2o~J>vs(wHf-A5 zxm`#SFddyEG`HVkV>F!T5aC>K!ep?~^5uO;8s54Gom2PObLq$M=Eg=J=&G_LltgLV z8J|DCJU-{|c5Bm*#cI$pz`}Uj966m2pne#Iz1OaNvS?{UHk$^Tt-Gy~L+N6(apS%F zF^Ah29UveDM z!m0+JI3ajN5Cxw*CqKVqbaXU&`Yw_KDy$w93|3NO){Xx>y}84nOP4P33mc3Hb`Ao1e^04 zh)Ypa(2t$1V4grvuJ(%S^3b%kN%Z53<2m>Ah1?H!{MsZavf~;>_9`%u7)|d~Cb< zuerM8jeZ)aZZmht6L>+z)!EsGZF89)*vLM!d|Goi+W)*Ydz(vZ2K{X^SV*-b#sn&b z1Xy)vPp!S@k9dB5+x=QXf-OoUrQ7j2uK=%1+E@6usK{nj!cngG3TbCrNm(=NI2vQ0 zkOT#@g&q2#-<+4!-J8_dltSN|J-Ek(D_0)CJwF39rB+{UN4c(z{ZnDk4rG68FHF>~ zn>X7cKBDUiff*Z+IbDI4;V_vl)uvqoV+w#b;U`b_Lz=O>aN)vx*daR(my5>Pz<>Z{ z|Cw-_M-(cBc*|U%qd`GkmzbHYrDS*yE$&i}7Bd>u$uH)CfS!@k%o$&0x$Y&E+E~UE z3<)t&k)-Ng$E&)LF1ULS9s~(Z(#_4S1dZwo%?t@(I_EZ8#*Ttmd+wh;cjd~{{B!~g z;9bA>_5leflJOW0n{L`cSC+XfH?ISqmU|WZy(mw0{nM2JO*?`hbkZ9%>;mX*9Zejt zeol=W&Z#%+gZUJBlI=&|gD8(~-hI z73TEo-Ft}H5W8NrkBPy8JYgX>RuzQ2Ct9Z4wE>cap2{;1HyOMNLi{pkP#e%^8{Q%$ zk=}j#-r?9Cg(T(|@0FH@;dne178f5MEOkkL%qY8nml@l_nE!U1HE1%jHS2REjbQC! zKXjs5w81G3_*$9)R-tjMhA6#PR20mu9`Ov;N!<=8UPWV=z1VYb78^N1E-!X|ZPXC@ zK9FYaf(3_Y1#`V+8hWb|=3IOH_@hD_?j*C@!FXwDDF)|oKR?+erlyb-YqXi=wUYONg>*cJt#&gIPG{g;lf+}t?$jP%Mo{y2!LxyJ5=;y%QPfsxJ;Io zmgXBTub;fw+@V=VCLkl%T*^pQZ~2qB=XvctjZ_ao25@dQhYvsapuKf9Ah5BuHJ|sh zwX^%Ts956{sd~1GzJoI3Pd4oMs^7k8NW<=$Mj`FKWe9jbgsR&6W*QAcMvPDd0-Vk3 z8YL{jy7qVfS)pd&n?Ag*LgQTLw1d;-HGF^EDEhUgS?AK;y+R81ReHQ`(VqV=e{7x) zk%rqawUyk1UV*1rF06!chHyKzr{l^xxwl%g?k`<7z114Cf@nR1s6YS2_+8MVWGz|=1S-eTvaTqN4b+D-3Kg>_BV)&& z52R(_#y(pZ&lXiPt4xk)?k{ybyZ2YEPc7Z#q?&l2K%?%7=?wbt*r5`n09-kui%lv2Y?d z!&CW?`iw~#7I1cB)F*C{NBAwi5(i$gxBoW#4_@h~BdZd8C}882J77h@<3r?(S^roD zFRlYDKqaKjBd%u2x+(9F`q;g~{YRKx*#Ro!-`IgnXfE%bkgt*&fAx_65VMOW;|+si4d*PR?RcK^s(FNQArAFp5jgjA1Q9o`2&0G zFL!9(xolZ)I;m6L7)M)Z^<L6!2*j%p~$D&We7*ok^2{II$*!qg%g-u4CYvYeViTJw%7ihzXD>r^DR2 zvM#;Cmi%fHV9TE^k1ZTJ9W?1I45K3`Tq(tcYWE^;*y=5tH|t5#OUqBPGE|)E$YVD$A*(v7zcLPaGQ$n3`Vo`_CcIPlXik)@S%Tbfo{kE)KRC5YaPOaqns2z zV##ad)jW!_l>Ym^Q2_tFVGERm#HVcg0K}j2ZTv^!jSt@&Y7fzjL9K8UqD{}Ux|3KIdHkh22miC{Y=27riBY8<%ez2!`>TV5A)!?g~+08oV}&qin>R4$OLqq^im%jR=s9#qVdV@%-AV zDj&VP4dxsjjBvYpWsR6!&Af9~hU}fPS1<`Ul&V z;7rhV$rux$XSnl6$^-j51vT?c0~==&go5i9;fxv(h14Q6JK?hU`Qy`q!Gj0e@DS2# z8z3b51M!{$rI56n@Xy|C`J%0@jofE`V)71E&Q}iNV-t!tPK|dv-glKm5dQ?UhiMa( zr-%*gJ|Ork5>0ni)y@&Cc8Y5{JQPDv>ngF-33*p2k)nVhFM0gD z4uaM3>gZv1rqtY{;3?26Psj!Cf5c7n1}<`Vt0>{nKd=eADtoJapeftn`y5 zMW^z*cl}BSfuS+M?M`5DM1vrRGV_(Q}WCV98dAIItyvo+AW+2 z%X5pTiTO%t6jk$E_`Zz74iah^;ae8gW7vD-`U8ZZ|G*_tyL=u6m>YSlw)G+Vz0A~a z(*;Cy@72qXQgsYTtK*%dO|Mdx1T%} z3A@0%hvND{z6G8?e@yuD@#R6lmnGyJy8ZgMTE8DxdM^jSRTYz;?brdBIlj^|Qtoh* zK*q1%lEQ~~uCwZ&b9u7=9Zrci;@|z>^VU^m2u=ghl!%=&hGqPd-8OrfE2x3cQVAUML7{?CoXYpjA{5JoMxpQ5f&b(v-Oal^U(Navo1p4m#VfkTmfm&h2(FHuDMobhrEUdOE+Ef(YBGvHx>8QvAP-~M|Si6(bQ2WpBBq3Eq|1}@B}dd!iB))=GM}> z$h1GP7;oW;ZLffJPoF-uVMy$N^emK`=4pk0^5Q>-uQO587!c0bCt{{0W^KNnKuVZh zBof$yTpuZEVZNa>`|b2-{TMpD5=~urlPF~vov@4MMXmSq7LM8b=a3l_@A z*ixQP5VsTgLZzX0YV_D&l;mh3KpPDg;XFmD*WFge?1y8Z20p+$q}mIYAFEP?mj^3F zR!K>3K~5IBQ|b$fY;cdP%uLB|pVqCNBf=IuQa=S6%EYk!ALcHh#UxMRnV8tk5bX=+ zFgwW;7Pd2%(I%imeCMrO{Xut3-s==%;!9T>FDpC_xEmPS*xDcvAmgd4n{huw<(w^X zyuv~X9)X9b)VsI8;NaOT!i7rH91y5Xg}gaQ3V zH&*A)!Mp0{R_M4w3i`*gJ zyI`^dBJSDHsJ;BE3O-QeVo!4wuG%!F*b)e-srP z6Or$Vdd(1%M)(Y2mweD7Vu-3VspB`Eum1Vq&K)#++MN8T!Ww#Y?=B_rfQXR1#;nqL zn=9-Hi;Vsv7*bg8?6NX-;6D&}O5IX1AIT12k>cLaAAm_sq$r$#$H#R8X1i%gzix1M zTkX1$(erhdS2Q6KbFkElULL29Pgt{vbDw`r$zWRv`jY!BtApA}98!&kGGO#A&&KHF zPo@hA!=ga!B5u)1H;@|+PJTJ`d-FzUICAKdOA$*-J8p8 zq{11?lMR%_2Q^5yQWGAvOwWX$YLbjr%8@#AS1JjE%l>H-|pZ_R9TLKL{eT} zE)0*HyGL9-jBi!)^_Lx8yNwRDe=@i2%gOpxtPSQuQ72PH-R7(&tp9h2jGWvmW`s3Z zPX3+igsWGcPDI5B&3_W!cgUDX2q}2*;K-bZ_wHT(Hwh1tT(|xK0${OhqR|CAf-a3o zUVHtKuJs9}n;Xg}>BLSpeh@uKK`S^UWKm_)rG_LnxWFjlJP^)Ex5B>)$we1p-In*G zM~%u1VCIG%rJT{&$C z9VOoxzo^E%#y&8=xfVB}sa7O^5`LC#bUTrmeDwUOI3$@Yr06lDLug;2J8i*+-ajp` zq(h}uAjG9>SmEI!Kqi))AHukcVYHkGu9qU;H~Dh&c8zD(4o25R1yNSvu%1K5yDSh# zN<#Me-0*4uy)0;b!4?%R3 zF+GVdAZlsR>BppvASeZ+B}=ZA>|n5}I$Xc#*~3sE4!iJSqE=Eo)VpmL6~`~-|FW_v zS_O6+qN%xC%0|d^g;0FUK|`YbM^Kvu1^bw(yce#hshN@1yP(knMT-*9Ac>yK-hKMy zOX8_qT-%Vl^2C2LR?>y&>gHyTp^w7OVXju_Rl1@RxDi`t0V=w5Ce!>pUeGV3`W;hF z_^DI<8Irw*hqRVBz8X2Tx|kmAwHtxHqU}m@@Ahq9vyap!)n6=<2wH(*80c<|rhVl? zZ0vmw*@hQMBMzck91*QE$m*ksRJ6{RSD#)g(dHrzCyM|*_|IX<864$!ZlNy0UUA{f znQV&Jk3a6T4{)wC4bzal-!}i|?POK#gt|U;;5yn`ehno9EEZiRgbQcl z3)1)4EpB4j4=uywhL#|De`#GM02O#EPxAODSPKbK7{s|Pvx7u9oLKL)YrpAQV_tjY z$XX{umNFZ26)&o2Il6h}bYh<}P>ci$V3~;%PjS=l(#ID1gPjA5gzxr(+M%l0|AF9r pq)x;XkHF;Y@8J3W_Kz*z5wcMs>)k&2ih1v|XUv~|#mHv&e*%*7~jcekd&|LcWD&3yDM`7ZbfCLn5t_ z#Q({gHsCw`^7qc*pLG_3Vse}CpW~*R5AgM7Gf`y=5^0+R@jqErc^v*?KS}J;MLFAm zUo8%na?`o1tBNLPnO>K;eDGaF>kEWojJG{g`t}GXxoIJ$rMtR1Y zwxU=?`B3n3>*(^EK;`lnhtY=f1xY8wab?_*1?@Ove#vGd3N{S zz3<~x)9s7DJf-ccpFPzsUP#YJM(R7cfBywCGO|{e#p*n##T2~nQ~L7?4<9~k%cu}x zbgKC^dsH^!$kC%>;;&9zt9G&-w$5hO@A&9`JG-ULR#jc>!i77J9zP~tJzT`oV0y6j z+t;ts>gs_>Nl85R_H9266LKTc+niM?1(wW`#|`5%FEct#y|_#%q1(7l~^ zIF(=ZcvGhR2p8d6Y%xhce)#a^qeqXj8u&kpP(N|mlOe94K$eAMlG(F|oSsKcN?>_v z{l>`q_cyHHXcNEsUGsLyA=cKd7Zg86@q|1!IR4$JEl>SM@iTc(20nJJ0#Z*;kN%G^ z;a1AN!%v?+{Z?99T^TC0n}#NOZqD|Wu5L_xyku2W^)ZP^>8h%#UAU2uko}|jdtB(} zzkT~=uqS9{v@M~a-~!+3(xjh&OWwlNz(J3q(C^=`*4EWU=j5pEJbYE!);8n*wtZED zM~gfKR+PM1q!cVIQ$BwDI5lkQqRQwvcF})wu8VQ?Jzi(HF*W9MlB#L0eE8+36Dun* zn#)&;?vu+ZDZLI04}a<9750>tD`?h`a?2K(+qaW&y}38~7S0Fec`C%IyenpKxH>*Q zzJ;9pWoeI><=|Is9i0eVQg~&hG&v3X`_Df@X4_me_4V~{bJJyYdx^7ae_Z$SpWoftAbI0UeQc~5UCr?r?KiR8o{4=qzF~dBZ!=#zp zu;31vf|iz6a^tAes6*W$U$yIexRjHDB`<=Xu%=MQ(K1v)M9MrrOpFUkWe)eo~?JZ?x<+_5qYjg8Qo6;+`Y}%Aa zwR2}2(q$+vy)`Mwc|rd;GxI0E(>Ly0)ygX>SMW|gnfA?J`s~MP?6-T@uA5E^1Cg!M zt1Gkls(cPJDOdx;+=aoIy52JXxDsy`RqO1QMufg{pOUJoq44F)DxQ2ZO3C91c?;jR zY~K7qAy!FS`PD7tPWcuJinJix&do!)v#rUTrmaTH6TbN=8jjmk*T>asy0p-o)p@de_wHNtJeFTo zxv<(XQo;On4|W`kRn)NieC*hg?={&0iiiRS*+< z*kj2<{0NV^V#~H|DXl0EI((*C1BXXP@kFwgCQ9ST)^1QDemxG+(qupNLnIDKG1QRNHuB6(iuw7%x@*$ zgMVf2Mro3yQ|0<~>y%9Mm(!&B<|XdQs;HtC;l+vZ!D z>uUYrZ<^OY!|#|;TUpsof8K85EfIx=gEdjc<&&)rqp1tiL-j^RpC9d35EXqOf9)Fa zp!miL*4A+#!k;NN%gCBV$HXMl(9pydc~Gi8raqarX3d&d5eBXOIa{92;kuDB4U70n=LVus;p){V(_=-H4n)<|Z7||A6-_!(%HuefNCXxN zi(N75oXRh*(lc8nk@*?5D1tgoiLtUSkAMFBx#!|eJ*j}`wOMbxyr|S&mZt}+BRy?S zu(QkIkr*f_%E>Wjx5VTAqdt82v1n1sn*KE@)*|T2z$Vlby_v)J{ zHMIcdqlXR&OGe64^3R2|IxiXra+`mg<4}I}4h6&A&Fu&qn`Hjn=aW-k4|*Osc~Yus zel^e}dpK=R)6b+^kMkx2M{WBYA|@toJFU$3Efi8ZR~pfVhK6RXEDWg{-T3;PsVr}L zFD-41_{m(kjTCgJoLh{K9y=yuU=Y1w+ulke{;pzAMS1y`{L53a!A2YB4)fH@yOP*` z=C4ejR7bk9eyLchlbv9vC-tJ$;KdJbb6PqN1Z^ z1O-1T_nFO&X^U00*tErEk7UJs=3JN^Z8JK-!;>XUncXtd(nmcw)=@;H+HNYUs_V+S zPNtom==@o4_?;GP1vW`3Uc2_=*Q`daeUykN!$P|&Wls89ZuPc2r`*+%Z<^GePoBtW zX@xM@wv&|woJ+2)w`a3yHurFzj?vVwjh6pbQK5*&5^?oSRctT9y%uHXxNqsZ^j3Ru zPu__Kd8^B#LoO?QF69j_HSe$KlAHBS+IXp~s($|TssHiGJno?xhv~sXVrU^mrakVF zAWO}i8mtvJH#c9HPH<@%G|cOKbiC1uzhGdKc_ ztXL2w*1qSZq9vmAI6HevZR3Lno0b>zSF`yS`VZ!gecVcY#!9T$B{J;^qkzWx_3QEb z2`YT*HlKGZ9yxvb+O1o+h`Mvq@|%}ru4&$YgU&~{&Gs`5y0r3t!>@FQA2hTVC|mQ^K1l+$10xI(P7o}ONE_ztdC+wkAZG}B~SUqmVJE7!NOHP<24 zZc>MDzT2CI*EW$Yf9dx6jT?tPQ)|irK>f1Lj?d`aq7G0&KXVyn=$k;><7)>d|crizYP}qyY_|eF_>%S5eoi@fxPWp*ggoC=rBENosCLnPeSj|ek<32J;nezL zs4WLCKY9Mvf??WWuES%xZFRYAVJ1C4&V$ieNm0$*J#S^f&K}U9aIVoYT8z zj;e5TzmEO%;5(aEI}xH%=#&RUA#4Zo7DuXI92Ci6^pdDc&`i(`vb&5T6NcNqmEQF7 z_3QUjQ8~#Hzh7W^_>_!ar}Wb1UG%;G^U5l~V)N6`A>@{K5ALldNhPm4m?QRwajgm56{# zHLQUG6%p8OulI8$yPdj9WRA3g!o%H|;o~o+W2k7yIKC ztE!x`a@6S?UyktcsTX@Pnx^s}JSZgcjP3_+L{38^{?Vf?s4vyoId9)yLZ3iAAQn?n z^4Xa)XHuLNt%Ii%Pl%%>$;ilfQ84ny<~qzC}4Y*QxU`rrAaGoPdnJ&7Js-=91A>U z*jFCd+VdL??b$Q)bcU>53Rjb|fs(<<-Q8W8Ky*Zk4G}d*L~1K3E5}-(v)ej5)wC^7 zhjPZqp0oNH{dtv|^MR5L!`7q1?q2M-yG7B;&jm`Fmc5~TdR0tpd}dPE^Xpg979b(} z4tHwH&QF{1qzudaI08&XA_-D7+?uPZ>9U-DC1+H>EhsIvH|A&c;MVWwtYT5oK@)+2mc+@%)zZl`B`OP%Tua_)tR# z0OjuCVS|t)k<3niI3W(?fq)uS9*wITDJSgV8XU8d)P+Q?8?=va{BsFAdiOv?X7*P2Wp<|JF8N|a_CSL*ip9i@I;V7Q>I1a z;Ls206{7_wjX-TxK*0#3VLoUd!6tt z?I~@kW0G_^##6a(Ea;N%r^O_PQ}atBNVS`~y2p+U^!A^;YT z@$#yD{`|Rh0ks`%Nv|^GBF&kb4}iB*K&eJ2YHH+wP7GUf>_Es=61DCUb@W>J<@s2V z)s^K`^RCOebDfWgmvwZ^Avi$pe6N3NwG)XH;;=O_)^zNXcuL8z_3;A-1lfFJN`s@1 zA3HWta^oOLvxDzp`GeV8vsx4L5@r*wBfA1fqzt9kj?Ok;qj*T9JMjmQF~ zgtI`@;L$KPj^kzU)J)e-ZrDXbGgxmtM_ag=bmy@eez4&%^ZT&qZwXBUEit|`3_LaO zigy0~VL)-$HDUReD|_lyJIGctA4PuQiTa@_|Fvs;J1+P_JvGDj_hT0X)K?s<1dN+# zsXf+`NP7=(ll@=zaWJtxMeoM}bf)8on658RWtEj1lnrBIVqU&@5&Hi95n&M#&|Ngd z809j^BkW*0)4v*I!CMqBUc4*iK*wt<-OZl2kEAAX1K|zE*v=-9ToMWX?SMksvo=9P8LZy2pYjNyHGc-t~pVf)aVfIt8V>< z!LEA;LAbo$qCZU!RErSM>+B4Np?7cJ+AhwA6~75`S+OJEdn!a& z4?13pqQ^QGF{m9zoD92n_o!RzEWb&W7~-l;SFo_p_w)1HI%1v$J!%W7< z8R^38&gNEHBc3UFb@djbC4l5BqM~}KE8iby_}6(pcraF%QkS5wn%8fhC4h1j9Uo70 zL%X{AqhctNzGXVz%&2w8`S^0n>`A1`yho270iQC9P1y4G#g0C2_W}BZ$j?uGt3GPBKnTxn5pgIUImoWpzzy#{OR3-cEI0mN5!(RkL}se8R%Qsq-op78d7+ z?bPrtO9l6_Xy0BQZ^B!c)|S0FDP6>%rG# z5OKZbficiij`Q|3=#wv%w1hs^gFBj zEO?d*5oN@~%nc>(@0HlzTum9&+jM?F;?t*3)3ED3htL+5k-;6Oo?VyC%fTT(KR@rs zWg0|B;j!n0QV0|QLy#f*;GDUcL!eft7YIey8wN>5qJpVCzBZQ36jjRy@2cxGjo4@_J~($ zPgP7p-k`c^V~Reiny*5FMjUW3A@oz%ghxbNc3PaNWn3BChz$BrR^Mlxy?e(FCg0LS z9z`AAP8&#f^4XnCmcwl{-8U}uy}nXb;!UG6ek*dth<$#^WcHw>NxCWss4P@Qn-fQl zh?bO;G=8`t^4LDI`Y3b>a^8Q2anU9G<9tyBhx;Bg0ICqcihuFfS#{_6p23#vbd%g^ zh2iGRNkQw4$VEZqqL|Fhn>Ulk(UeA$&JIRdu!MR4}A=BTj$jqbPv2G{{%;|Hc9sKHwr{6JJ^c z9{WF0_}(`p>n_HMKn0GF42*7WR-@;4R3dnNLxpn$xU>ueuRDetl0~3YN56kx`Tfn=+*0u? zSA3uq=j8Basv4^VR(*YO@K$m|wPIF9`t*dfEVs!2CnVWJ9J&w77Tb8m2-quLB1U`acyB7Pj*ZRCduZu3nIsj;jr5 zO^Ar-z2_h*qoEOGJ=z+F%DCJ?=^}mY+6z3UAd5U56fVAX>9ZUBDVzxHhgJad4MN+B z6PP?O5;1-7z=0f&xM{=2S10X7s{o4omS^P^)!$)k+y6?{r2Uir2$9)>!7!nmz<(vqc;{UY>@Sm{_YHW}P}i z#!YDwXde7SKm73U#i|s>+;sn6tYp56R$HvTdQ&T$(O-@c?loo!GN+#oPUI5n(`p{)Do33t5HOwuQ zm6d&oqG>nT3-+K_cyGN*zH{#7r!+mu45_K9Dj8;>3p2yVG1!fgjXZ|Z3uSG5l>%He zP|&0KSx9!5^*W@SThoq!>l5=WBlRGtlJuWBGq+C*{xsrbJ4AAP0=hjMo0g`qb|;nG zHTjs8E6D;{yiG^b!W{Vv!!@ZsDLhD#a z{rfw}?mA}&U1C|T-jngu>TCKSfq)MDZ-KJ^E-)L~4ogENgMp!8&$#VUU7JqwQFitk z(`7PJ;M&5?ErclW7w&J`)c=S3U$R|u$K+YNn@L62I7ERfE{})e#z<|!f$<+C4W<-# zgTfGZ+q+3JL(gsFx?{YRl)lq}Q%>Tf3w@tHci^7yZ^Co@=O3T2QMBbcq(iahc=_tp zAW+StXV2osCvQ0RxUH9JcO(SXOD(~}!W&~T!=BnvYIf<6*Mu6lKwKthy3AY$mF zkDr3~nkLMH-dVoJpCG;liUn3aXgJT`f?}JdG;%wWP;j1NIsx81-o#A2xGSe?b!4z} zbYfy+G8;%-r7I=>dxw$C;=Y{-m42f}Uio?jJwmRMS5+lzn!v&j&s|hhx9j{C7VMW7>htS7U6yVHOHq(2 zb77MB)-^t7Mav=;B&QYP60C3fM?FekS!qSDX)?{t_Q$H~20KL%UYBR*N2KlTvw*Ba zffRAWs3w>MiV`f3U0=qbjG9y_QBzlEptWi%Pj_yxet6w_kM|!!TN9f4chg1+ZJe1u z#EUmIxR6iAqh|NBFV}pfahv4HoUwZWl99xk5_+@p0dh*pdgJt#2nhBYh2t6F^rd2vP)mO z5?aXT*sAFjZRn|AlI=PF&c?=~+%N!?AO)Q{kk?LzkY-_S*-cL$$0T%rZn=_$6tZaX zbD27zdDW$Dzr*W$!+guhFRZC(nuYiTmv#mi3`xzsGqt(tXl{%7;A}aO64VDN_8|oK z@+36vz~iW!AM)3C?25BsLM1XIljOABwVf`+d3nKVO0JTE=L9z~3(KD`GldpzRmM@T z*YoWqM(MUOsQ~3!px>#;1We=gs@{qbV>J+vj<|+IZCB@+86Y z#2ZtUr+ZHedQi|qRN$j7Sz93d-N5L1t=3WFX^AZqlgq^h#`1Bk4r2-YqmNB{bNI(+ zM_OWlbqP`3>upQ6%@E`2T(O<06{F1X9%Dhy!yno8V0FwxLmG`=FSEYsVycWEbN|?oLu1$PG}&_nHIzV z29p6GAxM(&{)7k*BMwtAAtXW@f<94Zl5UkwA*qMp9P)bG7wn<|MXbi>sO&B}x>(Hb z^)Z`N0fJ!GM<=9i4lfB_UDAb!Esg;RA8~1>S~f<*kHhmohk=lOFp z4O&ZXIxk7}Sf?yFu2LIIJItWZrS46LkDt-_IShGyxITqdb82pR)FlD4ntq%7#X#ng zq*$HRCcpW~zEYiJF)tw2mq9_!bt<^>mA|sQ%ak&l6p}V=PU(4zEu(`aEWtGpm!=Ch zGWP!vVXUQenJb#~3n}W5zJ5InR)?2>{_~E6^|MoqqB(h8Gdt-8X}$ zkAMad#Rrm-7@?P)D3uB3cSJJfwE^*5d5)P-8a$D(29r-$Fl%N5{@ z0GQ&i8Bvo6;#G#L%j>OjvJS#Yf-qCTv?3=h9rE#`U|A4flP=%yb}$*dT7!9HEa5UE zl6%+o?IFkn;u-`-F*6`#F?6*P9yZttV<5j^l*j|b3C*K7w{^hr|DQR=rAuzWPD9HN zpFVwQIk-#5lVQcK{{Y&dTHcQ`CbrzEs;#YKut%lZj(6_qLITK2NJu1qz`zZrs`xVJ z>PV@2VxXGUOwA{0gJF9#>@e^5pfOYSO~+*sRyoMSUS&FlGib7Ar%&;B5)1}1G7M=d zh^nbROcF>uhmc|ek->xL$z!cc5Ek37-*AjJ`Sf51TGC2SMJ9d;nApQp!?t5RA#Gw7 z3IW6>`4;BXt`#h3X_XCoH%2BUoyoKIq zJ3T$!%IMwXjVrR;_(dcDm5Bj9KbLZ}*$~;Y{GFD_^52)z2Vs@O!_jq6l5p@4sCb|{ zk}z(Sy&_yr{J|hL7j$pX1@O;z?^MT(AHYNriDt9p&l^L5S37=&LK0LgUZ4;4zj9!o zW*3^Z*$2bP2i0zycLEC^5u<@`N?EGZxFxH0_AG%!ZldQAv*_72M7wkFHsUSnbj%h? zq@2S#JFgRpLsgjY<3>XQX9ZTBOEAxxu2XHg!9aZ3dd}7Rmh$jvO7t1Y(SX8-i|lyk zfZOGZ+?Xe*akV*G_;V0Oql&}0l1k;kv)nLn45+p(C^&roegEfm&61jiF$C22{IlxK z=jceJg6(b5xVTf6bf*e-N=m_sww;&nJ^IgKjOGHR1`rN|li)v!qXqvlP<=|`EE zF7DW|0|S9d!dwI@s?kpPhSh~3|GX3THNfP#5&kQpJm`Gii#fp1+;~@9o$k$>{h+Xo z$-15x;=}M3`sS>zy|1s2&k>A{?Ck6?Q^BHN!C~qe-JeUb@7XrZREv&G|PMwloh4E)-v<0!j^U#{|AM2O@ zxZ{PFEX_}1Zd((hc#>e0@bs;0a6ouk3PW_fnBK(tj|VkBn7OzVGcz*@;a9_c;1D?j zZ$?@D){vp7xVV=Sv3JMCwOzUcVYDsZMy-SZs zb(UzF5cWKaX_fTqjOl6wF>K?p6orqB)c`h>lW5msP^lH|?d@Z7NtOi5q^UH$L!rnp z(jP_A+xS=Z9==)xh2bb>WuPEeBqUxEg7de3P2TOKQlZkyqVB*wFi{7y2&bwkMmj+G zGs>QIw!934s)#|}j~_o+U-k9&265Pu^#@>Ji?>v0WzCn-Go#Em3eTE({VHeiwWj@A zZh=U~pJv%Du-@MCpQgWUJ{NG#VyZ?~Dk?ViD&hY6=)M(d3A@!`&7M@q*Y%$s?tBS0 z4wZvY;YXS?{dnz?I=i|KiNWkt1+Q2N#@QU~>_AZ$V9xO4b4aVO{{`m+0-qQsGA|ST zE8LSm8fZA3#t%XJr#*Z;{Ih|J(wtKMQRMrAC+Pj!=NlQ zRBdTnhy(OS>W7VuD&)Ty6CNF0aXMu+`crS1Z)|wNqfSQ7^1mg4JJ+7EOkE}rBjyI>a9y);?BC^$5~Y}5Yr>(&z< z3g@QF2|u~%c@jOx)`bY+Pv@RQUQ42(_4!R-wE0Kdlx)<>BhSh!D-WdiU09gZ+jl44 zvwdAb-@RYjTheBy?ApA^8P>1cI5+)Z4=KgHeO*XtA`R_L4y7xqPTu#5mL`J}8lzQm zXZL$5uBqgKZJ&*W`-BpcHk0!}i&;+LNhdN(O5WQ@@q+Cb{Qn0(aurU*|Ad2zY#7jQhTSMnPBAh zf5x9q;=4uvmYVRd^@aa)zJm!2LEZ?PKS*kZd6%4mf^}Wv;Gn^+n>PVhuOP$0hkQzV z3>1>Ji#8COB7lNw;6;^}mTs?Rfc5K|g2F4d>|bY)2I|-;LTp?>C7_%E#_2!S$j8n4 z-)J72%U@j-ApC6hFgf*DXA|ywVbi?%2=tFW_{WG=gQEZUTg?^b|5B?d22&-{ohZXz zmJPRj2QYuD${x*YV#o&Qv7{QN31~J+hpQnHvc;&9wDfa~z*9k1_DsVS4P_m6$+2hu zmJvY2u+@Hu^(hUVoZd*i>p4bL~a7tA9FZ9QU~m}M1n?YHMb zDtqI9_aw|;J;N!K50daS+@Bi3E-RUY`4=gIv6=-t;bgj+9TaVtWIKKx7SoK+w)xJ> z1W+|1?Dx1h!{j#b6b`3_sbseNN(O>8(9qI0sX=?QbotMgK536;(&Y6H>+I#mzi6rn zki1E&l!YapOUqZYTR;40bZFoD5ypgJxAtz4;PMxp%TwIvZ31gdKVSPT8vo@d9o8k< zqy5VGW6h3EGyFqn$^XHR&7|bVle3d!l0m#t1s7I(3|%U4H<|~yt*)h#%UqC7Yzy|F z-bMBH%fjV+6NGa7z4jVmJDNi=A1;bf_8kAp(2IT*GgT7Jc2D6M6z{Vp}M-Du^mQ&`kUBr)iMJHnLHG0sQ-$<^GTQof_>z zVprKCPfx<5kL@9#jY-LM3W|!yF;M#>8!5nnpvI<{>_<+^sn=f_I@pjLd7qLo{@%Sc z!LxJ3@E1nF5|ea7aLp*-!!yhB`)_tT5F=l>9hS#D1g45K0FwjNK{4cG6!Zb#XgG~t zV=x9qt$W;7g|6epTe!CLCVR_N@*Fc~^?v;4R4*Yf!Zj}Defa3%!x8i|zl}ZUH2-)g z2c5Trp7z}BwQ_|N6dg*3iGsG}<(- zcgh)KO6q11%KaI+T>bw6ux@(2WKkw#xF)}Pqu79JzWH%T1b@Tfe`ks&hW&yVlw)hz z<;M$e-!elv)vu3NZ2WDWa*4jsL$960PjvaY5wO?@+*lmLCP8-N+-a@1%MkX)~PqRr$NXP?hqm{E@I0lov z9y$c!*Ozi=hNm95To$@4|I(PybpH<_yry%jDjHO>-mI*Z#GaEHu~4GY_m0*l1REF_ z_&vq=!T_6-jA#7$ow6dNf}QJn^7_ZC#7_>q^VKj&MjhzSv+c%q;QwdF`d?R|V70i_ z&O_eZ*k$#+tEF_bjJ%wm=sMax-!5)XJiv9)bRGML_2QRYFOMI%=U16?>=8|ZQW#a~ zdZuGr44#DiydlQE`O3BJTjiz4Mb9lRyUu>U#qHxN^Qmoxw}Q9goMBz7!|<82hSs!4 zL!a$*7Yvm?vg?tLbn(OApKTGo^1ka5@x3kX2M*PkhyDIc@CDT%_ch{sf}x_Dtpfgh zCjHipNAmagq>so5(z6gh#jhV7I8{vSQmL%3_W*fZw}xz_CA@e9(nj@DTeGI^ksi6#fV`q+@Up zqVb~*)44f0(n?AXV4u0Eqtgk`&Fj-QHrTAdfHgKgeo;^F2yBdr80p#C*>xbEhiNwc zzC0gKe+u&e=q1mfa)biIJooi2#?1JdvhvX%MQceA3%IQYcX1m1ESR2J0&@6*tqEgu z<2OhJ*jaE2ZnvvJy!+I%EDMS4XBnB9+VEzP?qJ7PsyN-B#o#w-Y%zzdR}44rX&8GY z4<9_Z*3i(fIm=31a24Q`c*{FvYq=ZXnO_4ZZgZZKfAv02qrE4ap&w<=H6EwW$|MF&rV?!9+$XK0Y3<%LGkLMLLbxw&#*9a~gS$4Q%EQnA}LDpgbRGQ;tvOim#}{ zFXweImCwGLxV4#dLFvRLD`6}Dw@fE?tRZP^5@(9FuRg5rlj}rAvQ?3Nsu))1qG&62 ziBzezxcq#3ldmR)PieXYjd2T`zQ0dUc_o{`ea_7(PT6mbla{wP9d>@kbt(J2B>V;I zrO*G!VuOrv>eMMc>HN^mKTm4pTu3)*;SRjtxihl1&isMZryNIRhj$c@Ur3u;t^DLq zFRSLx?-%rs+%YjU!6|jaQv0I+7gIgGpAqBfdzRBvo&NsY+RjfL>SL^l6rziSTd)f* zr^F8*$VfFcHSJi<*QhOS`@j8wApYlEfV`J4cemy_J;osJcG1oJ)s^!ouUfZmeI8KD zcMhZ;K}AhWOEanG{?tD(z;yB?CGq3Ey)9cG|Bl})*%vnzminFyjf|{+E0eu*-##&T zMTAFMbH!o6_zJu1!@GCM>@a(CUBJ%G{dg*G-@kvbYtJ4kYHI2;b`v*8mR=rLd=M6P zKYzU^McoWC@&RcXehjG3K%mx+l9z21)BKd0Yl6~Mm+d8+j4b9o>( z-YO<&T$GpJ4gcfU@^a_Cxgkk2%1xUt+`c_=zF0Vv-R;8uJ1`VwSr2bPPj%eIZv zZhqp{&!#3Jb#?lztSqH0%cE!ZIx7 z@VX<$lvpDw=%5eKh+AK6Auh?$$;r+hYvmpuelR5^MeF8GH~5eqTsknagiX))JBmEQ z(Ny;$ubo$yZ8YXEz5m(JV7{7e+_XVlT-ngklDH6`>q>Bd* zhQf9Gn$LlA);Jf1IxHuL4y?Jy`a;>)uLY>Ar?58%5qsW{l|g?03Nihdq&ur!d9&`t_^XvvLdpPw7`N z+uGaT)84z6hUPkUzcyzqqrUG)q}IkL3MnY;!Is3a$w|XA5|osbQ$zKX7lus|Q4ESP z#j)cd);8(p<}AyBH8*bD;68V*1D(TIdW}!{)tfhwRqP=#F^}P@yMcX&iK(gX-rhU! z-Mc3(Eq$jxL9+`XxBiZffUlgQhoBvGYpO(6W+oFC7c~-F80#|9mhTA@ZV<)0NkRHZ z&rMEFPTV{^2hNi0jEp`$K4YzEgQEq}v9aHsUfmbmk&JmKzH25_-rCwqx&xGm zN~k^7UU=&C>AMjThp$RVguZ*XK4iS&(8G#rM@GM8rj7 z{c`0scOdic{>;S2MuGag{o$pr-@XZ6xw3_%h1vf5sHj2=#HE#$ zAEJ5C!nmW)9)~=?yL}B(5x?09ZF&J*X{mO|O=*w)44o&u)YRwEKSPM4uOFCgoa}a-~jrV`8um!vgLa8Rsby&R7!`_B{W%GZ zHzX>mK`VVGN8J8oTh#~<0DKj&(78(qiiO}WC8zN<0ezLN#Y#bRG z>2FFu`Hb$|Jsg%%)Yg_?666D~O<_w5J@!ri%&|L53W<*1QeIyE;Mub;sQNE_eB9ui zK+RtZtWCOuZG3=y2Y7ku;CxofcjiOg(Ka+J^?&X%+{p2S?i>}$<%36$KEkcifyaP$ zJ^2ggG<>IP(c`h}K`?kau&5XsET8}PNH zzZ$$ZxqrM|v}N6z11u~ekUF@P z@%$muGoj)I7@BUQqa!a@Y~6MQ!e!(q zCZXe~suSgb=PWj$*XcJ#J3Y0INmP*6aK3j4CDSE4VU+Ez6EDA#Us1HQhOp3c?X z;~%7_V`TIM4b^NFb7BG(clY+Ld9Qxx&8t^mUWx$=+2ARpFGQz$o-;+hRJDShS4HgF zy*m`4!Dm19oV)({)vJJ9FTA|g2yTM1LV-T94W@(6Lh((7nA@MyDj)%$h<(DTFx#A* zmf^=vx`izwN+fU;gg`q;REGWZu}w_dx44t;0CzpO6sFU)h6H74^BbUhYDF=z%}73i zLZju~CO{`ntis6j@#U_%ni@CI8$gYkSo%QL-`BV=Bxdl4@uOF-_T0R6YkXW(xbaZ!+usz7PEr&n!)Bx!i{f<$7S-@8|n5=>#lpk03Y%EkPxqq8$73{N+pc z3;PRj?9pQsr-Ljk9yYDu8>m6W*zDa7sR3=@yiZL{ZR}UWS+rOV8W+$qx3x(QvnH)K ztw1q;Ha2!*DrLJk6DMcQbsyP@#&6bFPLK1YZeV*jm56Mt(uXy0vV+{{yY?~iJ3atA z3774C3bx5_spq-#T^S}scL7kETUy3z6*WUcLP&*$g_X6nJp;AFf!IsB5haz79spXy z5)-#WcZ0an_o}h}^Y`!DkXPF`tuwQ*5W0Q)4AIbsho1(Xx9Oa$5Ga1M`?%JP8?I0g z0{ES1aH!kehYvSnskiUgaTnW)x6yKL!-)&tW>X|B2z_{&z5I^cOUujJT3UCYtPsM4 z>Sq>KI9u+xx~|1KoWfSm{=va86j(0PRyR@RkHCDCA=dBO;rllmX+8&rLq=equ~C;* z+Le`+IUIO0hSOF;(1?$)vVKN^{9stmhW*XkU%h(8X!$z}&jRpDUYcLD+F+@aMKqtM zPlb_U#NJT?gF|0OLF=4$tXbwWYrnwE!g2%pDgjhbo{9IWmJ%30e5#)O2GR;f0<_YP zwY9rZAF(%sKuz1JsP17&u0vC;tf~0~i$F$49=Mv%%a__Pv>*j=>)cKY>|h#2kBJjr zM5Uxeq@}m-VO4QUH*E_;7*Or3qHWa9Ua{UdmFoIHsc$o`WtAW>JB9qh3J~15-XH? zBZ;7KWUe9>wUqb3geFtGgN|<7t6NM|4^_(IXNC7~FKBJelc=7<(Lsetv7ea0xIjiz z^&8`aB_%1amS6@=?el;UP(yBDPp`DBtSfE}Uc<+TnJz3{oQV`FF+>V%^H$+#h1(dS zNf+YJm*=XM)_$e58>VF&Hf(r(-o_+5LK&a|&?aAz53<`<5NDis)mLI@U{H``6uDYp z2^&1#RHY`!kBB8RD=T+YF1$9*8>|TfY93EaO1g&{_vFcw?KCvU#*NERwX_Jwh>nGb zki#Evb%f24BIffV)4_zAk%9W0xPoB`{`ax5MyVQ+3yhEHd2LVq4PiaI&(G8JBE^?) zThV^7j)jEEZT$p1wnFyN|agzY(+-B`0 zq7VTiQY7WvTDM^X5#zy*6Ax>&`Q5Brb_{s9yNAZc{>rV&vzt7Onw@I9$fU%+)PT3WJW4&a6jwq2-XtXqPCI0$N1Ur&w9n;TAV zOT_6!s4T=u9RV6hOzfmtFCZYWci+C;^cH$@K+PRzCa&}!NNE0{ld>5q8AJ~y5&s`i zd%4Z_Bd4l==;Z8ldw!YmIp!aqfM(1(KCTDedhOlR(4Y<1_uPD6a8v2`*-n5Kf?-PH z9K0V9qM%@#3A)7TO02-9ty_hFQ*v^0TF(VVG}acIxkR;jPv#NqKGy{uLIb z;vpxm#SsB!LL3kXPn|tmgqZicii5BA;cKN7y&Wtu@81`o#8%>vDpaEjii*@AWk*h& zSVuyyhKfTS5fLG+pl}(xpMeV9u_;3IlM8x-t*z|^kTpCCysQu=if*m&0o=nq608<= zk(~e6Xmh43#%mXW`|x{E6Fvj+`+pBk+2k&GLL+A{yiIp-)X&t^RQt%tv&Grb$th?) z;1edz8FyhT4#fhXOB8z@5+?w2wbMlR=A7Kz(6~5qv@IoNLAztUDy>JH>AQ){WW!2H5GTTsC_i~xg;jKf$!qz~_ zf>z1F?U;R|n1bLUxLl&jq9B+9kp@XxS#hB2uP3xEJv}YVw`>=tqAsZs650j|x=pwm z%h6VPR0Cn$ycGHbAO748+KH4i5CIUamtlQxZ!Z}4OVNYp&(nfbxZ=){nPc!%j+dvW zJUgkL>@Fq}|8OeeCl&%;c9 z$g*v{rluygnRn#|_(Rw>FM5o>?QN`unSq8bdMD*We%mn;a4_*Gq3oRN>wqiU4cvhs z?x4@u*LLOYQHk>?F$DRBaJ&)j%-3(;Xk)s{OK(nJo8MQH8w8-Lb^ZF9``Sc5G@CaZ zs8lf=AL|fm&UK&$f8)*c^rUnXWP)SGZo*|{G8o~U|Hj8CIgWpXwiWK2e@;I>e$-KfEW>m1c{SD0forG?S8dn`%-HP znJ!uNV1G#0pe}|o8Mk}3uxaK!!Tyd;9H+USX&s2DPxLliBQZpRhiX40)w4R^3viy? zb~?K6)vBlw7!_@#rKN>9qz!Qr!6dAq!60Rdeh0DTGsX`e;CfWcFe3vi?i;|VT)S`< z!be~<^QAd+s3n9(1}eZSb@I@m^*H08`z5hRyu7?x1_qDtbvu5Q&=7In+UKpn>c6K* z85zX4UvrxuAmB0NILXL&R=mcI?5|(HZbu#k>`X&$%`Gk#q2xmibOn!q3FndTNzI90 zZy>TcFAj6zs0A`C%xBy|C}c>$Gc%q8FGRy6I4x`X5U`&>u4pY8R6ll?c<I@4 zH3CxkeHaL|a2}`Vl`HoiKi-N+_-+_prN#xBbPn)utKcSplZ+*3Uu5r%&yLEhjDAC6 z6`b03;=~Et34*p`ZCzI8S)@!%O$qH+jX1H%sQ=Wa9#@p@C#3!0nNNdgb9jS(t~->z zc-q#^F+7c<7-PVP?(XZQtfvR%Kc3oVZfV(l6PTUlM5z~q6pT+W8gaX&Jb}~vE_Z=? z9L5j8BdHh|B)hTk2lryfgJZ4^;M)iZQDX}*u9DKygWHOp;}}{=6J1>qXa-_#g4*HQ z>(`J0aUBE=42TU0xr1Kw9KczU(|$_ty0-R39MyI1jVSX0)Td8HMd4zLGb1hHd+)#C z1}ydaUX7)D2ukqq*|Xkm9BAUEy?25rSyL^|&9>8Nh$N7bOv?eUOU~0%QyHr`81)j^ z;{hi`|8*my@5=%XGba-AK0D5~*x-h|0s~ua?qk1mKOKP1bairO-Qac zk#J8VBsMVtfRU$_WWz-6i=}*KgY;p`9PPlH0iCw>_|66{QyU#GVvI@OLhuAm>S-X3IqD?yfDBL)Jm=C zco98T3^1-@Brq_TI~3EgWBn_lBh85V#j4xpQc! z)H~N{@fBVQ1FCnA@;UT=O91C2eK>`fsXBnzn^i+zV{3S*&1kE{{Z!yU>_{fk4NT%A zst?wke2@(-7*F(pr)M$j@V0%>!q?*EAGY3Ez=_nv$+i~&R_kLvpW0S`cBZ$CRxQhN zGy0H6PL89u`950O$zBG1L&I>~Z1a{gq0$e)Tb6K2;~LTk>+|hvpdv?QweGur@%r@x zrnfTyi>3PTjDBXAAH-NL!)}|^q#Rc~wD$3vfD2$NeJh$MO4vE`5eG2Y+M}NU2eaXK zQFj_+TjI)4uXgO(<%WMOgw9+r4nmg>2@SoA^HWG9Ah2p9NAj0zrL%b?0Zh=~_q%Dq zzn7CfiydhfAO!%^6Fnc3s_~g&)rXvh#>P+C)U&j{`5c`Ch1WJUrNp2z%4pvyEz-)$ zqG?daaE@JZn-9HPW&L#eEH=G#_w-yu3Yy`_&TV^7UAWG+nS`kqmqR!X z&fSVmoCswNCn8_K9P_}7O`M#Z&3m>X)xZU1S}ZXbV!nyPY1GuPw~?4GU~2RMyG1vv z>-&bODk-t^oa05K0~0L*iv~LX$jV`DZmw^p`f!1>K2^~u88hZf+z77`N5lOZ(^#c& z9mO_G8gZH`amWjr=>4duqhN{Ksi{4%Q>A{ORiJQ~zIYXCBq_`tJR2B$>+?O(L-oB1uTo&e$MBlxQ%OqM|aZq$EYVO;j>P zhMlC8(mCjD?b+B6?o;{{7DHoacGgIpv~`B_xrkT zDZw9&nhEk^uBAOl54I!^)UjdhZiwK^f^K@J^bfizbK;b*L;VTs|Dc*CMAW_h->9Ys zwIi1MKAn6hI@$^5B<4N+cmSP(Nd@s7Ci=8zT|Y8qvy9|fZGYMzSRwwa$TMmk=-hqU zv}t3y?tt|*9w?`MW;3!8^okuc~5aAR8ui*z%-_%@t zYRIU)ff^Q*pfw&mkcZS4>;x5W;a=6fNNqE8XPG@H);nK0ZlHHu?b0Ji0zJL)n+(>| z8-RHnaa135d?Mi*EO_LU9L0gL|&e@rz<;b$IHXHWwKr`;8lv5Gb+NR&umJ-Ob!OW(WO&4o$4 zcK2>~3TeMfm%dIaDj{9;d0V`5`^Gl9-s>|Lorci@L5dos;%2D9~Pu4Phfb=6U09NKOB`PI529i^1& zzyUDE*4}aa`0;rgqtER6X`{tRduDe9MEgUorId!iAL(jG* zWn@GtuIO7b@7L4Kr|(|BE{hin4(?UA4~WvLM6RC3`)J80YS!*k`}q)S#-BP&zmg^_ zx%>DOSufqX6RJkKW5)@)@8_nySq?;fHd)Hp*cf4S(8!TpVLP6+PalQ0jzC5*s~`BVx)lHe?~^u5P1bio;`cEfSHU22nY;xe0;${*XN@W3W9pE z5I%6MP~3H=7*SAA7)W6VQ+HtDl4nZ)1zq{#fEo7+^xzQWa=7eQt{m#s%Az#W?k#w_ zJ<5~%q6Ow*6{y(w)``3*;ydVF(BMKNrzQ|K6W-dyEP#Sx0dmv3_FA!gxzy>b*t+gF zI04rlKUNSf`ip5MlNVoRmfMESn}xvwe5-l!oL0q2Wf7P;%|-N~2`>vaz#8=D$7yR- zMcI%s04jP@)5mVCuORdh(S%U~?0Zj!(?J>)1cq?2rTB%Bx7OveU1Fzh56k)WJtSf$+X-B`@PfS*1K0g&3>(Nc#R9Yc?V=3o zMukA<&!b1AbS(9#H7U(Zc)R|c-o%M&Iy!yf<(t2hSUiC7f%H(LJ;(9w(T{D!ok8$I z&}=3sQRI7eR?v}c--92O&z-O*DM?FcqHo{If>`3>;tbd|EAPe_!uxrDd=@-@^+SWX zb9-Uhc(Ex#_(jN;99P3(y^RLS(d1n6wj{pC=wAXgO_`#M)G4?pVQ?deR+G?NwN<^A zd%gPaX=zQByPt7s5M?ir@&rr;s4Py5PMI|BQQ6!%a|~^41~4!`jL2Vu9Y#=I?6Cak zX2w;9;fNl&=%O?ecnx7_k~V#HK@a(KK#fG>go}y-17DZ{mDH%kmwlPhXT*o(U5{W0 zrys#JKR>^8oA>pr5<&0p-rdgf$G5H2n!*nT+bT>aZvq}_8Xxse9Mr2e5flf`Hug6Je2M1w-q4zLaGz#TJNI;TdMO@a;wWO1F50fYtUS$Gm~`HWnAd0)smHk|IZR$XK{U#d1i2_n%5Eqm<| z-2=;oP9kVtm=mBfY|vDiE*~yS*Z!T{5BAP~<-vRBEM_G)Nlc3_IrAjdO%Z7rDKMsKfJXfKu4^kY{qTyQ3l7n_ z?K&%J)A{TkDcBvDtKnOe-s5D{)cXsdjpoD$8#zmjyfsKyR{@b5N&G~Q)~11Y$qt=T>iPf*yZfZBoBJCL>z!&7OJw2RqB}ov&*QF}K5JHlURYI?{e_Vu zzCNldZ|`St|FvpT>;I7Q5$DCRLX{MT0fyCHNKQ7kvMQcbP&V(%SZ!_Bw8@wH1jsR= zG3BlP6D4XDVze=&CPRs3+4ZelZ($V#7!ATi3t|%672r{kHXhnesD@N?MA?qywt{_X z#v+acUq-MmtIFbDAcx6W6z)O`tB-lpv{NWoG_}u}XDR*WaM;`9rN8Dcu$vQ_yl&g? z5Ig6->XlU*dgD5Sshb&+Em!%tNO#g7-!29@C;$>YRQL8N`J=IJI((fxf(7t7c<;7s zyi}l$%eVsJxq@hIF@!Ykp)wieqN9L^Z|+U{cqLR*ZOj-s{$BIT2#dJxpVd<8zRjEp z+eZXBNSGkV!eCv1Q)Aw7C(b7d^z4uqtIyieqtT9ovD)l&xiSZUVrNX(5|`1dgg1kb z!%KBozFaVLTexQkf9SmASO2A4B5_H4bK$}mE)D9}%$+4i}ho z2nPiOc>_{c1WkSo>qRtRS_UPQ_w|*FPuG+N^X58SoO$ui2yY`eQuT`A!_3AJ70C?o zC+=(*DytT%AJcEt;$7rN?&|8IfbKLjEjd|u25rCSJPW4|m&S#ePsOA6w09?#L~ZEM zM-Num;~;ojcCMeGRLpf4J%GL}#B5+_l#4aw-j>m}Q>ATw7^|Y>7ZH3e5cQi|TBIaO zN=oh&Fw)Y}+P1%VH}0OExkQHa3#==dKzL1#ojKD9I1J&|5F_RxonV9ETQY)fTDH99 z-gxH!b}C961r)`&WJ)X#(luXyI;{A$lHPh*=rrM)Jb7{m2z|(UuJ58rs5;OEAixz^ zfnIeF#~)fCdh2nxejn0xD1 z7mL`*;k(+96dM{Ee4!SE#fNW8;oK=}>9&>*i2p*59jU3QxaDzW`nrz)w-xBnRA>3l zJB6ym2z!YrE#AEoCLZpLdGn6Z@V?6zJ-h7Do|@hm+WTl?;@%;%4&dzB9sdJ9OJE^e zTSbAxFJ8<=_t~fi>(+CEp@f$a2ww1gdJz&+%5qZ5Zz?M?*T3tjGIiS?LAO?9Zk!x{ zW)wc6XZ6A&r8uEGCSr7MKVBBNe}Gc{G$@lbkK()HMxut&wRlN*!{(sP4Y>gBuZ*IZbNLFK*E)1d; zMsnfR*|tsJA|ACvATpwxaQP1&Jov9ASp!I=lgkUd=JiVoWg8d*SlFU)(i6-C#H^$h zH{|A-QAl+rUS5|1K3Bslajs^(JUNPbRdDG1&Yg%K2uZ_w3(RNWwpFLJWCE2f!WRicqCk-}~3rqhY;HnoWetMYb1ty>`ztdAu*oXTi~c=h<2%snNG(>*&Na1;`dK4Bv9m-A;3 z<;?jq%piy1D-af8glce;J+~*pfW3Z48?OS2+m6uCZWM_ZFolx`2aO)RH=ZM?hsVm1 ztsp2=0Z|E_pVuE4Ww%@S6yHBi5z)V=cuieS6 z0te%be6e##K$U7+b4O%(@!PXwbmci(CMME= zNWob09tTgIswx@^|7y^noOe#KE7J`6{%S0D<_H-UzuZBXdI9L!JAW-s@5x;uIpUHl zD(t+xyd5jIqx=hAo7DLNrbS>_`b684X-PBgn<)31n{wJh8B^uq_J#di1maRmiO-%r zGBP~e5HpvonrIn_%t5^Vqa^ZFYuz`SpD6h_D~)(R8{XM0TthLNB_p$jG7$_$Phxy0;{RKBrlK+-gx^;WC49KkkaS{ia5)o`N$Z^dYbC`pu zl(pcDG z-3706gs|k5zItAl4;485Pcu87PBw3h`q;6544t~MQJsU(ZHBqIhA3+nL3&a^s!?^v zUA$;DC>EVWd!w25*Tq{^-zuRh;CvExQXo>pON1_adVP3?fUK-3 zh+~SScEAXva>1(LGHUoYOKHz6sjpA6x1uFI_T%H8w+?YoIKT2e!ue$o0SCML#*Hl+ zHDh&jUf-Eg{P3YET5kG&eQFfcnr5b;b&x!>9jPXbi!!rCCG^^uuAr%n5qZ4eje5*> z%q2fzjq$@C7(BTwbTxow@*!XZBzk%qfKnL1$2jK8#>S#IbKye>3Ho4El+J3O)t@13 zsM4yzpwHZ8ETfr;-;*p<1>}_YG7e8$ajEeuN$L5plHy{~SV5#9940n7An=Ruz?S2O z2cc4AZvC;~_#GQt+Y@a1gJYL>7B?h`mN0xJ!)*aqzBz5bqKB@Yp-j3yG;}kSVC>|L z{$k@kD=C?KJkkxj9%{`rm?5atA)EU9K2^K^I(4>#f(%yKs$cQ(@p*Kc!g>@k4;F@+ z%+bK)xEBIQBqy&Po85%+L?@?5+m-e2{ITU8y?F5tAGbq?4*ALX69e`rXtiTIiOwUF zNf5UI|KM{3uNHUel)uA4nV?)@V2g_CJZ@a_f*T=0K^^}}NQf>wSFSN*@VeYxz1N+| z*VF9Qh6sO^$WL7YS@-6!;w0~pbWq*;*-utl@K|C@KfXxI<8};jo%Llv8uZigl>R6m z5U($u4=d1CKR4?ireA-c>g*%>$}BavD{xPW^xE;A)TG6l;%!oY{fESWdo=QICVzK* za}4W(=TL+sgd>5lWz%Wq2}E!rcb}h>l6M@ibp|J zjJ%awP;l~^+mPDw#gCRG-9ZlOS)D&(X@ys;(blfrx=EGlmxk`y6II$L*hDxY$Xmh_ zh&7&*Ej)0<-k6`LR}z;um$j#{U3^36RF{KfKi2K&H>pA9v;JCIc|%7_%MhebYQCI4 z5WY(2Rd*(}lMp;Q+15UsF{=(UX7HOeWjTE*K912M8^N_ZgJGwMcTf#GCn}N577do5M!8C%JVa@zkhR=BHvg?dik7QIi=N0thT&Ate6nOHl$9{B z0b|*I3op6!(U^VE7s1H0-;lg9&A9uo#Gb zzFsoN+8Q<9%>2NgeR*dWMusq+(vhynglsFB({~~W=a7&$Za^rOS1p!uzJLGzDtFKK zWEqJB{y-x6cO9#n&3jAjAi5oqrhuqwailbvFBZ@xhW}ObyMT= zdwl->XeH!%z;o1gIw4GfDxyJ-)fz}?&%wUR29-z@BNaJ_Lh?@D6_M} z?y}}=>L%#uq+W|%;88AD8PC}-%<8?X?NR77mXxigGRZkn@u+tXb>~g< zN${ZpyGvY_W<%$TUNzeSjo6&h(r+3sHd+#0YcyZtmdhpM49TTNr8w%u^XZZs>h+)Z zHv8(zg=yMPoyrf~zft|C4iaKlWxd`Ma+6!wRzs8`PW`uz^PADD>Yy=rl=11umS8r| zpLsz@T%+c(RsMt7yfk$}05SLfFq;?sz{24-%C5QocTDCrlh*dFMf(x{w7-l(LPK|k zg>?s#N=o%7&&Zl_k^)hfq(;XO)1dt*uS`&=buFmmR$o6pj8F0| zdnyPmh#q2cx77BZI$Lox&Rb`WLOiMZP@w+t+F!P@67`R^!tbys^;+x34I3aPxHDOJ zyDpCba_=N}t$iS6x!KNk(W3E-I#RVV)_b4q6{-ZVD8{P&5x8NlQd@!Pbl1J!V={W_ z-29><<6*rWn;WWozM;EJZ`p3i2Vyc49o2+o7z1JU_u3KY?gMpoFJE&wk}+j8*}Cd= z2yf)yo%*8LUp+SpJKOm2ecJg+5S*r)n~Mq-LuL{keOtJ0cVUua2MSutB}>9Y!xeYD zi)KuOG9n)iXcp#2(0z40^3$&7QfaI5X3^J>-mF!ayrH zB>J&Dm+=EbPN(H>AbT7fzq&8X$D`vdD*u`A)&457kt5tP^vG3K0YhUDk#jIlPfyJ2 zFX+xafQ7eXrzk>Jb8)W$35Y(qn<4kGC$a{QFRkteH6yC-apT4jM1?{idXT6xM2EvQ z-ZEfb93^Ux29Xz`PDnJ25)g)3NojUCHr6@0oe21a^m-vf1yc>!GfNjj-TM4u!FJFd zK-2b;G&{yJ(MhFB;gpAt*on49R&+3(wJYyFYX2KEk_IfDVPZHh<{IWqE$&B=_-18g z1)NC7bGVppI$i@=T?jXV-eVfUi#xL_)4~=V_#IhKu#DiDt0g520_YFce_19vV?^gO zzC$dE$4DB&1;nl1mtVC>24s$kuAy8N$^(5lVYHGd1<2t~>_;_Ugs!NY1wXHM{NCX28JV=*nfZZ7^i0nu%zFg`p{(BKVh#k3l`~0@&Rh*%9IFXU?~elZKxi zvA2D*PvDr(?nbe2kpBe3E=Wc`XB1UwNfvAAeO>7lcd6^pX3y_3oi{2aDY5JKx<~t^(&K z=wm`|s_vyzyTilf(NlK_nhrvxRq7y#MVE2KRyMR+Q*erwq}EGtTHs0tx*ugbb-G4< zk%Wcg_K~SaR68;8W+zMn8%OwltJ%aex9c|5J=#0GivJH(+g=wG->lPTKx!GB?XP;&51@RHTJWrNbO^@4APY`WD!(?)YzBov&U`q#a+g9F|j${5VjjbraH?XknKmYu) z#}1r+S_tli9!i`zC#S+a4%AL>N+=!2KN(WG!K`e}0aKoCBp!f6QBh-qO>+5Ij{!w& z;r=0X*OZL^_!QsMoSQjZfU40m_|NmtbG9Gh%Bt{6ua|G%p7wlX#tBiRYcyiI`t6E} zST0G87JvNY>C@pS0qagEyR+xcO}-I!{QLJ!-?*SRm}#8>H{YB@q(FhSeMdw=t2ykz>DXPgZL>Y%2_t z+suto@~uti+6|1QZ*VP;s>a{S(|LPf)3@GSCVP?z6H?9yM6n1^N~6# zh6VHH#g#Vkhi2csX!M@BwK=t|Fcjjth$car@A|Iu@>-6Q)Oz@d)YJpa0X_V+zVgV7 zlU5H;OzT0uFC+)dJMd@$O{B|+8nfZ-ztWA!NX9?K#XG;5;XvvoD?L(8?O0i3)Q@zl zw54JK*q80SobI%3VJN|$8#iql>*nSr9y}pI^T&&j(T^$TTC^GTDwbx^!i8hPr{_aF zFJjVpahOe^!U#>xFLNeJZAZL*)&&M8gf&a-n(MW zn!9aFax$Wu%eHO&>2okCa?(*{B_)L-kK50lW!~tXTbRB2=Twi*EDx z@5fp6$EYmH&Nkl{Z&lQu|EPnz65peELE87!q4aDT_EK}BXFB}g!Ciu$!(5r5;$#@* z^Zko6wex;|jdoS9q;k>FojoT{n|AE#nUE^ouy*~7G7dsS2wMrzrnTLsV(-KwSh~qFL||v_WY*URf%f9V-tSqRJ!EIx%Em?YM5kqMGbQw#8=rly*ikSuWByG zuLg^+CT6{L<@p+LI=J0X@l|g8gNf#LCQ!}FzBBms`mqB%YTGe`%YMy@?uTYSbRNBK z+`7`TGB1~@bb1JKbNhA(QuyH*{jD~?nbq_vsNNKsEd{iypwd!2{k_-M_J2y zVZb5RR5J6o8~b&*YHQ2+EsQ7^EiPB~Tga-}DTnHMNqMF#?e8%>{Ke;Pmn~BCGJj?C zyR`7n2^~GtGv`h_EZxqpcry_n6VC7{xMH>Iqp9>ohp-rpfoW5V%MkFVExzL2jd&&E zt#G4D%<6{>94O%!1SKVTaJ4c}u=&H){P+R$=aEotE%25z6dJtBNMTkhmV*ZFh3LLsFBumJeh=t2>Y;;olo zSyPeOiDo3xL&6ayvod?(aZ>wl-@fged9tCgF}LkkQ;vog3X=eLLiOg=SHF7~j3af$ zj~C2&5rY@x8S&j)JB#tv7oN02(ePMir)t46!nVQcz;gXd4nwK5U}j&wTn1De zgo%I>Z)QvH&K+pymLD*nGuz0eC6Ov57kiAHqGAXDEDw4VD}#_7BenoRaTQ!eJJMn< zGWHJSUx|Vq}0QOsTVLxS(b**Ilma$D2B_xj!2xo|_n5{^4?==iut^o9 zj_>4!up#^S1c5V)PtS!Zr_rg|RJ95{WM<1hB@E+_W7b|d zBYo}Oy5ZdgjD0!9sVkI-#8;qs_WXv{W-VOb?o10ofc|{Av}}XfsK^=&V+%6QpEz;i z%M$gTXEF;FYW6y9Xl`JN0$jSAOC;~nH*)ar^@wys6r6+?i9WiP$m!}v$Uv2MO)1Ie zZ5rJ-MCxeC-a`We_MHznRXaV1s=yaaQoLD$aMD2{baonLj4*)V6$`)&tTuUaYiQjj zJ9u4clP_PtR-V3X&Hj~=0Ptv9KH;s8H;_fCq0l)Eq z?XzFj{gzWQ;L5e9N^u*W$9AJpC!axI`@3gsnLn#YKJzZ87Ost~g3|5MxGCb&JjWyj zn)luAPLowr?J~e)(1$RsKx)iwKkpz@?K^ZRm@UO3eSU{KCOkH_I~G@P={dV^bs#=b z&>?*hRGd(V(WqT6u1id4x=-}#EXo)<4K3~LLb2=$QWZ5F$?VOeb&G6m^B9!D3)oUY zi-8!T2bs7lIQSax=%0kMn1+W@P*Y__1491@F+T@?5_6?xXz#h^p(Mf+7Ac`RAtJkF z+(TbHPRBX;VkZ!tJzep{(c4J2Mb45aUz+tcCfqkI!5SwTc02K-sLuvQ+z^I$bTE_O zo>Sa1AEV2m*)r3n6|K1C@@`6lr}ZL4Fwr~>7M3TMF(Aw*Vp`Jao9A3^<(y4Hc@fRg z!chQ{o37rotCFsq@V}K{;MsO=J5udn(PM=sWE&WJtnlEUj$Xr7x~q&(U; z2*}Z><&t=BzFwH`e}YKnRGA25a$KWxjQDC)YU0;ReBBkrNSaZ{&!7LeV~Qk#fBbIv zs{Auu{+^|fxMbgrWrx3Ad|%du@mhzU_xXFe2+0j=;NhHaD0z7|Z`LNO{sDjz?}U{vIZ)@tL$+S>l}?te;J({1k6OUIQw z(_6EYy|_EWz{~5aZDcPFj+hFy__7>_m%|}=)bQx&5PrfjZds8mk?BR5(~OO&{8H%l zwjY(@tRMY}XSY0DihY0`!R0hvgtkm35xYtJ9Ut$i=(8QAu}1rLcNr3}<9r}_V59Om6nIgY5fqb(iooFBil6Bv2}CdW-2c~EBvuFLPtmC zedRN$Hb^c=q7D+l0m2dr>n^tg`R&`cZ^D{&t6^!LCb$y8jy(8xBqRowF;t-AnR&b6 zMB7VXb%OAH8Vm}@AJkEFv1@A1zq!=bHU#+)$B7|vjvNdCTWxiZ8>;?n7r{#xU5V&Y z4ASyPNoDA+AI-Jn%O7&Yr6e%(wwXg|#ED_8pc!Aq-voO}TJ;hbC+|y+L;JGFlr_9u zRw6L!;>Ajc83YcD$nf`jU|9OI#1L3A68s^Jb3HXZJU!bv&33j)NSew`Rra(3h1Ce3 z1I9gJ-kbRj>R*e7ISx7~!IAuIRxJ5DJIu4gK!Rygr~>Xv9Vw|XJc{vJnyHa;Mi~P{ zTs`Ztdnlh#xH@qrp>V#qSGWbFQNa9p99!wzA*R?7j>#|50gGC6aX!1^ z(*uBwc|GRlnT$tFr%Hi7e2n}u09B}JPYF?h-p zW!eo`;<}$x9df1VnsKBRUs$&Qhz)5uKfAF~t1sOV*xa(pyI_t(`bL~0NdJYd}0xT3Sm7(6sAf1@)5EF*_@km2Y_Y9|ph5a|#S_6nH4 zYaG0A;3qZsQ3%|e(O`dCvLqlK&5Bt-PqrZG_2$wad8)teJE{=zcCR`cwtg4h_Fr|( zm*Pfl8JM=#plvuLVZ()08^*Q$h8Bn03dZj47pxWmvhSR4aefJl3U%zjs_5e0sjaIk zb9n>w@SuhNz33uXc`pz~9dCC-bY-jX4rSRaCk>ta;bu;^2o35s-QL<<#NEh2@vqSp zQVnmN0S8Bm|Eyrtfm+`XEylW>{PpeACn=}dXL9av(SyICpTW_GHox&Mkry3-+tf$3 zij_S`8}-zv%DK=0ZDf|7d$dF`he*>pi^fGDXX|mFK z3l*2KEn=9U=(-u72-hJ7V+wFA23^ut0kEWbtjV;gJO&0OmqywTW9J9s<%5IsV5koE z0G!b#&ZQkUJgLMF7-TYNPVHJKCq8htg|HCr-+In``^6m_Bh?&0EEkIQ0-YeVl-M)5V(q#R za_^F3@H6h$x7olK6(6DvLXHTjDd2@l$-Y3cBXFI_{|s$kSDJ}NM0#$ zj-wPfyZH8Bmc!g(2~W;Zr>2GWyHtL3w;(ca-SXgUtti=5;3%=YavoidrCs>${%p21 z^g%T{WcZJA+HoXfmR**5&b7I1VKe zc2CUJkOH_rRzu8 z(C94uK`hnhx+DLrvqkeC1Jsa;L}OX%n&-Poy^K-~y@x8cH16a{yrmtvG{JA)@SO<> zZqeTzVA`;n7%W_5t0BNU;{y(ynVZiD#{+(V#`x@!3)B&uuDM4b;cEVsqfyLr-c^! zEz$sg4zv+IMbSNV?pz3>&@pnj#21Gbb8`k`smD|H#^=HK11Ha}jW>%9q`5@2=x>Sk zq3~y$55mnRlHbCSfmpE^IjYG z`aEO*cBQ*qh`T$d_0?#|hgFv#qT)u#Uw8;<*PZLD|AD5@YV5tZYRdw<^Z{L>%R|D? zkJ?@TeDhk>bU~eEo;`W5EX{DRxt*lQV_3FN52&h=r#Kk8*f8#2++RWUe@6oT+e4jdYd`GG6Ps--9;9%ogJj0< L<|gNj?EU{2#>L1T diff --git a/docs/_static/core-p1-set.png b/docs/_static/core-p1-set.png index a245615a5c9b4c996dd8bec229fa8f4b88346c9c..fa524f949b062ce587c0fbe8c5b061898084e6b7 100644 GIT binary patch literal 48068 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%0DK7wA)OxyjMCY43MaP}fJ>-=2^g<7T{Llmg^Oe6 zR>p!=y0^jHxIs*48;*;Oh5a$Hd`|@KKSpvB1sBM=qi82ZBbbay$Ep6GQ3qe0C6WO8 zh;>HOLe$m*y0P@~p8{&^4+_a70yR&P2726ki1`{Qj1;IX>~EgZ2vD<_X(0(0n@WVd znj-IC0i=>y9~C9C&+Sd6hfzwVLrj4=iVp)SIG%FwE6OJrqlP5`o*Hb({{w1Jt)}_! zvGtF+L11@Yhkt}-gPwr+sE+X(dJsCbN3Z~XARjM`5$s#7!NiT5 zWquTC+uKV};~GARRBmv&{0`xWsh1b7Er3tmt-=Es3(cuV_QW%TMkvobvWpd>1L6bL zn3#ER(4}?{(;!_@QLC(+Ba*nQdh@Nt4+V{sWgliJmMsU?zl2=^%Cfa0ejeNJ$yq8Eotk3YS_zuvjs1h!`oeGtp4yS2aoF0h+Wgz^cg}iNs8kZbbC<3N9+P z+OB->n2X=l|J#Z##`jB(lL|7%n99IJq8UH<}ApKcX&vf6<-TOgdAV@KEE*K|H?3aj=1@;2k#PtW8jH@9lrO ziadYl<@7kenu6PW5%Z7XBv$wXClYWvKQHU?=<&CnS`eqh{Qn_vZ)!a!rI z;>-u~>IQ;Fn@*lOTelV2>x5^vT-oxNp4`Np{-Vp?--JyChh8pwP@rtNidSWyU3aFU z)T4IG*X_gUrfKc6&y33Jo!o`I>w;l~?Tq=P^dLtYS`p;+w$JF>R4(-UPU8kZ04^4# z`6i#{hcxzLLCnvquDJwZ&KM5J#7)Z%ROaWl4@&d-1&xa7BBTje8usG&0~XzmX}ek^=oi@ijfcA$)p1CAp47=nmeCXc%Q z%l7>Ru33r2n)E0^eXX~y8T@mdAaTgKk=c*76GHzZH}-nUB7v=0IaoZ)Z z#CDdwTgM}!7J>;APzcb&L3R3Cj%nw}gY&1OWWgD><>8{)6Yg_nOu;y=DD2RhlkE>F z3rBg-^MPY$zNldc5OV45&66=VC8R+L&L$0H33X5u=zVK|&B7F$C9eHiOv=~z^u$Os z_yGj(Ku-yozepj>#t^7BSs}CC<{yJ!u4=(CF@XzXJ+!z4cc?X%pPyX9jHlh16L1lr z`+B>sZiPs#U(DfC@X%1ZIVOwI+zxr1zpSzTFJx>6!r28P%D$!OL*01)Myz2mb)A6= zaz(;EhS?QI<=}g~gCYwMq6c*AHTc|Nw4;$}{O-N}mCqcPn0<6ofC5BJDt16Inn8-F z#6rgmb&un{eCjz@m9eW8XeVa<-i}#uG*%7Ge-i_hr*H#g-ob8(EE{VnxZwjU>F!Jp z_53(w`iev&?$c1rbnqx5T)we_yMBk&$Dki!n25eX(3nz>87_BWh$S1)Xs74IOsf`* zr)vP|-Gtr`exy{E+lUi}c5fDt*BSXbeEi(?A{~7@Bb(~v3siZ)pY4xHQ_G)pz+P?- z`DPvHm_#&8lzZA1Eacf6$I~?2{>U8M7!s{gMJn_(lr&p0E2*pxL&)Yp`k#-sA9Qeg z5!;3mt_QsP`94^a2+>kBsB6^~~ksQiF<34M*re43(D*5PkMjfr4ys4}?D(rj#?rvs7(Fr*iaS59 zmXp9fhufa49gP4-vz~qkmGp)T>XID(Fn}l!TdWin zBlU5k$LLgmxk{M{TtN)-d;df*sj8}SbVr1nK4d}m?8*wxDD*DqI`GvmzQ`$<<1uLfEoHbMYH)%J zcqB4&WH0gNJU` zl5#5=N&{{vqa*c;@~av3aQRCop7h|qLi8^IVifwQDi(YEsDT?bsgjrz5Dkin?&`xz z5I(7c3VZl((-dL$+eqAZ8w|Q~&c9;t_{s4GN3VT8uXEQs;Yh}LujhQ@Hd`B7x<+%9 zX8Yf8{9*dW$q3axp^dFq7QI3cdJRFc5H`40G}_>K_uW)1#B3NDfO8gnfq{@w8^2hDS2yix$W!KmB4zWyRR}Z;1}K7Tt{|wyJh$ zN-#6#d}iYk4YO8I&+qs196g8Q5+JO_SEMjth@Kr~`=;Rz$yacPufvdxU(W{)AVITk z!tk`k(60!~;JCh=a8I{$Sya@CIW!v+{9<;CtxmKUv=TMJZb)ytdyDZlTy7)9a8mVf z`Ee631FaVM1`1->*af;EsvPkV+USRKbcek}1tKKm^a5LMPj(SuL6AhGwbS!Q-vf-% z%PCZ2zyn71d;>{kGKRV;yv=?4@J=vwl3}YItFWWCkXp?8IPoc3PBN(Lnws0?O!i&0p3XwVd~hRnlKUY&0n!_x+eCKiCi z4TixiSi=_gi-4#BKVM&_A*S#{I2+c$X&%qY9bZl5I{!J%>dT4uaP$KW`2MLLrzYRHc5Tj*pq^96Eu4TF(pFEm%PQt)CVDMIZy6QKhr-F4< z>V*vCi7SgGy)ZWAM-N0v-W1B8XP;I0wk z4YALv5t;6F0K}4ii}VXPk7PB~G7~2d#3Fy;7ORL1m-lN#L{KJV+4Y}S97;9=rBQ?p)2e)f+*6(XgYdcon60;&^jK*1gbLylUl2mFMg=zc}#5Y1cxooNg4 z$R4)n-*ITG(&dkc5nQR4v=n@6BU}^>*n7>`s&OFmYLML&}4RUY2AZ*UVV zhYJf<$sX)Xxw>ryTQ$FDQbKk85g$jH2;3#jfyo;A^~}Vt|5LTdoY65Ceu9#+8rUZI z{Xc7J6O;aG+u@A81q{lC)rB|EUliuM6wRe`mSRQ#>=xIB{cLQUezR<@`=Bn2xhKD| zc?gt2Umd%!1~n4(&tRVtW{0N?^pd}*y7|hwcj}?!prv%>=Z9X1L*o)bLLw|^RYhsD z3=RrVE4x95YzvN&40P~~*-?dhfB}MI8p=WejAF)MtQn(q*&oW@p+z#3CMV*-HrfJy z3;gWL&c~C!=qMt4ZZWXEsG74fbA}dI=w`<1jJb8t68eYm0hzG z?{~d8Z+A)x(nnX-VLvkXVUFc=NZa@JK3oDQ8bfV$d3jqiRiJ$?!Jz8U)FU63M1FRj z%bXk*u&iHsO+1e@P9XcQXZS+p5!&D`<#?^_68R@>Yn={mQbS+E$8UkP*U zCa5%wGNbMjpz;>-1s}WsR@Z&>-o7$znhAyJm~$|p7<-9!d|BU6V^}oG6miU&g>PmP zHapM?S@=j^zc;cU#~Pv{G~x50$ajs9Yr7_W-Bh;uw?Vqmv?KKTdIyPtl|oG4g-KIr zr53@qCOR96L0dnKXfAzxSPfZ-q97pccN}evN*jc8tS|DxGMK%oJqApkdZi7V*_5`4 z-I_0=j29s?HER3Kt4G(8AWTLmpuDF>=MoQuiX*AlVP(Cdrh zry*HO06nsC0iN0+c=X_*#I~o{)G1Em;M;)&x22^&;?ys4B<9O+#nP^ae($J)a2|t% zEF>f(j@idBj=TkyltCi&%lt}{s}$X&sW~2Jk%qgHKb`!1NM8cEPDHYjzKbY|DLqGy zCVqf*h7u&17O~JVptJ}qfKvd_=VDN?fC2yxS1~L+jn+DZLYFEXgg3ALZb-hQnf`A6 zpaI#HJVP{68}0u1eN93H{MkB~{Y+CC2^(Xnwn4AJ;OQtuc!nF#Oy|3eVzu{Y^>tLx zl-_Al7fz&z?!6Mu;!==pG^z`uzQ#)@=8R{D zE}~~@7odL$`s=|fs{OGoFQJ}A6zeelu$=~{T8i(c;f^E|!~9NK79bHNNZeUt`te{` zTR3M7Z^$WAA3@zQiLOB6m#vr5wFV?#GsF*kt`|z4NnL-N>jh3yxlZ{2h6_SZhJAG& zgF2zecmQ94S`EVgTrjV+J0OlwmMO!Mktwr8{5h;Lm}K~l#w(#a55+6%Lw@#YTeRM5 z?A*C?p#ChG$FSYIv6;M%+hA`a&6y*SM5QWOTcAKELnsaJqJ1|^(;d+pCe}M2F0~O~ zq^|CgEgUe5-rkjsa?TE}EJ-BKr2aZ0F+lVhI(*oW&nqq=n+M@M8Eq6&mQ}XOEf3%% z#K@@-n*U4Vu}&k2?8c~Zsy$G8O&dM2F98{rUV;w9^QnC}Hs}@R$D5p}Y0wPN#BAJW zYk1G){tz--3Fw5~T8d&SX(=@xVEfrsIL%zJ>NTcuQqP8OM&yN=TxBRrFp0(0J8 zJdXjM5Eh3AcjM2iiswCN4tH+JMOjqcIMKImn`y;_#( zWPu?22As&->%yb^${OSZ@`p7eXapNsNr=ie_83}jS=~@?JGgVA+atS}f3hxs_L(}b z=ml%^t^ovavQLwgS7KoG6~3j?@ZguIMf@HtB#mwzh7QBHaL&;o5+i+(>Xd%) zzT=sAECzKbMpc3wYQ%YhS9Mvg6+nGodOMvC=w5+MbW*X(WaXv_n5g3#ag4qrAjF|3 zHp&BZoFRHi-(%(ijYS8(3h#P>lS18`f) z@_1$-e9%^8S~YOg*cNRgo5`$-rZe9MLC9gYP+)cgi4!wH(?H?gBIX_e@4Gs0<6~bA z54k+jbY9QXGObEOn~ZRlF{~+)x&ATiydF0Vwr**p8Flabd=SZj)L|%}j#(x&&STjY zuk#n8K`v1PFGki9SAcR5E=6;fM^KR_oy;fJ(O|2Dt3bF~F zsO>NV;Tx<%=2hLJcD0QdZ6HVF6hL-41*Y7hSJcT0r76cNm&O}?FOG~vg7^(aDfSCB zktpY1D_yrjShxp?@4`a|`IdGSst5qSdm!}RH3$Nch;w{0=Sv_&@SY<77{MhNN<)2q zFf{Z+V#0Jg8h#5MaIVzGCO^^4RsqCSWuRbcX=2V*xVy6v?Ta6hJfTd}=-A{!(P#uX z3P{)4AQsQlBh9wLDcyXg!Ijd{w#38pbO2)^8>Nrr?+D;bBWB>XW# z@j48uqMyrv?L*dBT&@{fv9my2V36;I-db&{Pw?DGp~CZ_Nw%f}+ zMc{gcKAELdm`=xB6Q0qrM}A25(=jpwcm<^WbewzcHK;ztL4fN49M@xF`&kTB;pl6_ zmEoswjGBymhW(-o%{y$&k^hJ`7d`{*eK-iJ;vD9z!Phl&8odwozHTW5u;jJGX%ojR z85gvOO~H)z=6d+p7EU>fwq2bnMHJr)sG>cGu2HtU*RQ9bAvC8Rvz+Jb_ZX}~_E?zu z6|BVkDQEF9JM~6Ti1AsXmzu8T6)5QGs}X;*Ab+04Pzm+44|w8t<+2f~XJMk)hgaa= zrqE9f05qI&06!(+6_~Iopo_Rq%!Y}C?+OxckjU;8(uZDOtfzUp>=v@0hK_^gPJ-6k zcAV}z2_V+Qik86_eMoxX4i94NB&Cj5fEQB%Lze8&)YMFyrG7akrJ>F&e7{Gre;}(z z;?xx9`Uv5 z1hSG<8Nrl>8V#l1+GwK*OsRD=93CJYFdl0jHL1|ZQ3O12{DS-QVfy>c@g(P8fw4a54^L z9vDhAg%lo3Sl>fFd78xvr**`(VEe5@$OTkwtp7hMi$%zFqC2~2@XW}rAa!WNVF zlkANmr&7XzlP#TVsy|?@R~lumf5{s8fWQbwE?veX_Hg{pMpA)2TG#9$U6f;Ml}tA! zZ4v`K8e#ov0NUTSk=>x+7gc+l|GL_f_&Ja-qee^!(k*dcAO~R~*p6rnquRLkKpGln zX>@5>3@h?>91C+Rp$%`+&|5RZms#50;jfET5p zMJAfB<(?Y9jWa0FBTIC@oXime-wEJ4$r(|VFcPrM z8lj)8fjb=Sd*u0{%a=TLG?NwwX*RAF`AsBu<5x&rGf}P!!cqqIt`uj6ec}LxnTpRa@gk?3Z6iS(8; zjGoN1WXl7OdFb$AvImm!ST7Q9GZzz+sT~^?PkQSeAB@pIg6H(oCbw!fzcZjJRn-{i zzkOdZrgCjUFqQ#pP4@+s0M_In>@P*5y6YdaJv}j*@mfROnR)0aWknjvr4&2-?8IQP zK(ve+mW~D@AU~f)vI~G1Lp?Tt{gd?2ISXsgGR$;C>p~B3jc~2vMyt;%D9NZf7a$ew zTQ|@-5Ys+@%x_n|bK`}?a$wso@;)aN`!}nO*@^1G67C8<0zEV{dCf63b7IYit+*@{ zw)dpt0JMlzESxcgs$?`m^MX6o)|(){7$`LfLFM|_xcMTe-2Pgxb#BA1 zgTa{#0u|uGEE;iw-!FW`P*XK>HG@4Jf{;o>C$Zb91-HT+KfOu5>*ZtucDUSR6k((& z!Q7|^P;9sJr55twK$kZhP{O?vn8>pRR5G>EQ|P3b9caY^p@uqVwt)>1x^Q3Neb9*f z8Wce*Py@t@W|9{@o{iWcq9h~e(U4)t1n-f0&``U9x|7LTe&j>;0GgPYJQNbpCCFHl z-~^h)ph2PxYhbd*&!136q5V&ch$qr|!J880jMK!C1=m!R2aVLgHzHintOm5kf54WO z42!cvmx(rfJeb;{O|D29MvT10-QR*aiM#Lvst(#<~zbL^h=#se>6d@4pTfoL4zwWyD7I>u+CzDJsAO;Sy`XDRF1x950 zz!&8b(ov4)VlpTX1YSQj7hMQX<4O5f@=y zZ2H*7^P=}+;8e~@&df!bO==A6I8BHx$kK}%_%#;elE_?VynJ76xSnO=`4_zX`udN|r6TyDT==Hx$=6uZ20w2On0|ex63p{*cq;B9}rER}YyG%3J zW))9a@WKAy0l72PeKH2}>)r3aQ}$erDzl^D+^2%AQ)gu@iab^(B_)xzNpO8Ue0+gT zO$Tw^Ptm@4L0(>ed}|jnCN6@KqQ--_tXj1y`Ps8)Wf#`+E}c=HE99H;i;i9?TZApp zF^K}(hG!fyfrULOS?8dk0b9tNnGUG5XqyJmvM4BP;KaD&VLSL`|;DKYkMy!UzSE%drf7-JT(5& z<=40^3z=jQzJF?R((Xcmh2cAt`s0yiTDL}r0ViVjuC-HgqW3mgSXji~yLT_qQSn_5 z7<@r$yF)FXm8i9~^+#MQ@!i|XO4QiM$d)j6eRVZ8Hg-9&MD4$f2O#|}o{`m2u z|7f%HtA5vSLqoMxHd+Z~M)CeRDDrM0*!DMZiR>udwLRR#M<*^eHv7Q?3HnQNyWNlJ z>4JA@ggs}lAs72i(!6H>#Y`so)I+r}pdLK2TF_ovN$E)TXjhlbdj7CzQO3$5dgB>j zX;)EQPEOA4@T%^NY3=XpTlcHpHe)WAks`O`$QEHI-~(<)1E-Eta>2GAK5;jK4d3yI zYbQQ?#*4HQc>n&InI8fao=JCKUl7i`M{CTRWBJbAOD?#$*q%CN=0yMTxMidu zwN|!2{_L;`C2sUgF^C~(#5$)=ZNfH&ZeeI*5(M#W%Hv9SLW?2kbb@j%^BnJX zojPlvIP>)3L#DpIzVY#KbvL&i=(?>{?PKx9qLx=y?lFwPPH;X2DCm6te79;}v|dWA zin~I2MMc!zyPU$p2`s~31aS4Ch3rB5jk~UAW@g%ii%Uu}u3x`C-*ZMLa*bN#_1$x9 zSkdmMooz(cQu~U-)A;dYf3PVhc-70I2KxG>%>dW+xI}%scI}#2OdWIkjW@??>g%mL&TUK$c}A`ZvrTC%t=`7R6a-~Weed2~ zjZzb@?(XvY&fU8VzORnpq@ocmu(VYDkcr7eCa0{dEKn^b(lEY#`xf(5{BU3i)zfE$ zyT{Nv!XqFMicqrDW5KE#*j%XPW^3b%Ha=J8SB73Pt)gk0d9RAyd^5O#r>2BgF_KMu zNl;Lb+u}k&Q*gwK7p?&j$;cIU04v$gp7{p^fKwOn#cLA;5R;XW!LfVyZs&^^wY|J( zHWuDjuI%;=PR?7&$47=0qNQuiwXiVvI`r{T3uF5a*h`kRII4de9%g(Z+9VOJ7nP8( ztjhFmgqot_3!QV15lE+5j_ts`g|OK81Jbkit3=OXprk}iddn8{NV1~Wn%mFM&wuC8;9w~}nc@A<^M!yNnA_0}FErQ_=9tWp z_=Eh4W55P-^74K!Uv8Hzx+JLwP)JBA&}*!cl9EWWjHp;N4Gp=Seq|sijf{>mx2GP! zGa+|`66xe}#Nrp&g(OdI8~*l<3|;r%Ms*aBJt?YoB^;HfJxCP2x?-O{yM2dx&S5{b z{nOx?rd&`^pm@eTA}J*$2wV+&)5?f#4iz&3_I%-f3sOcsq1;U)9bdjg-M@bw$cnfU zplFfnBC}y?q&uBCdGcgwd%G}*`#r$>Anu!c&zT%PT>jz13OL1IqdU>Y-k$NQwm%lY z7S)OWo9_|p5Of|%6^k4(G0|AdE3T+$a*E#n%dZ`iVt{(Ne*XMP$fris+1TNj7~!LR z@#1rvPN6gx?vvm&J6l#!!3e?0 zHz!9Pvj|brg&~@k6=v;vyjEJ84F0N=o_MPkMsVa+Al-#8eQ^04PQ*<=n zD5lCZoIk+O!j&bI8=0Bun5(FdhZcd`GufI}ikEd_R-e^Qdkzdorp?|ATrfAp;z%Th z_TElNkTU()wy%YSg@u%mO75S&d|A!I!$Yfz+Pk(KflUs)JhU?oh?9tVUALv~^`CHX zXi?T)yLIadqy~aQ#_|rF)~z}NGUUi%mm76`HsNI*9W=V_`7C?TI&QO*>&VqE^ONrl zn@i>pjW?@?#K>B+jF*HSz9w6M5cQSQ+W03QEmPLJxVX5j$WsY4Q6#GAX7jA@aE{)- zK3hm<7KxmRI}h3#C}v&lN8~XwGh13<@zzq(m9Tr5N^nc&%=-t-7%1k8@~I2@le3G9 zmbSKba&U4Bro^yeVKMo2A~+TkJyPF0;AwVa&O}F}Mo{5*%Vo=!1qKC)W05|6`ZS0^ z%Vf~K%;t6!ZP7Za8BcKyuf=nkzyFeBCr(JIKDyR@<@y>m$4RZglJpJbZEXU)SD8#q zO#BA}Utoxc_=XL-jm8H%RZWbIzi*g(SIm{nLZbjz{eP>lw74jn{1Z9ZT|4P484Q)ex&z=;cfgIq+jndM57HTiDE(1s6 zHBwqVweapR<}rygVh`^Ydm|k+qSLAEN8zB;;j%XKOQ4Q*~Y4kRvn5 zUR|gXIg3v(9&dqp>jsJ&&pkRiODGuEufxB1WN12V_~;o&*Wvk8=VTfVN+nIJbd|yo(t=Dy8BlkoIONDR;F~iP-?GI(M%ZdeDSm z`|wApHKkcHKRc@-%|A6%D^LMljM3QG*zxV{a(K0tQZ(foR&twpP0-*dc zRGFQXm342*LD+ZM;Slq`d2oCLFNeTT0h0`gYy}pP+1YX(g|5pErt+b5 z)(cU?^&S-s4V+}u5QxepE-o%4=7922$Nb8hk=HF`s!S|VO6#~w9R;*yOx3_l;J!rheG@H;y zWLM(@iQjy~y=qkmK)D!fES`2>P-5&*R<=bUgl!P0zrH8vL*|?J?|JZE*_JH}NKTf- zYIu2hC1z$uLZ^1~@F;I-S&2CszUk>w$fZ%y(Lp#_imNF-+ z5$lCaf|1#A_Z(RPsBp}{=x`qJdA%=on~)&b1+cT??c3#cIRf~!7;?)%nk4!>qm^~9 zv1k}vvywP*;sg@R`ERI$0n~b?Raz?XXb}5CuU+%EkO|4jIe+te>La4lIsX_mx`-i2 zSb*TCPq&Ef_6k+|v#rFpNDQLdb4cdfrf4N-;1n zkdfsA_M`&6g4XjCjb(gE*(d5_{m_-Efl_PPukkb&(lZr?+p?$@tyv6A%I_4MnwskA zuNkEm36r&lMv~8RJ1$NTHtnk@pM_+tXueoP0Bz@mU+4d_9fm#Xu49)VIc^G9n!P;d z>Hn2yvh#;EmmsF%81)Yh)&S<4;me;OdlwkCDz+@|D}p5sJ6dV|%{Sy~=G>edTS!PF zAet=h#V65LimJGkj*N`NA_vzsH1svijJ8*kN)F$9Q!fQ>JI4`RWj=7*8bvPYAp15r zm`&UUpfGa5KFd>Q5vXbBjyX8U`}p{fS9$bkeeTPb{hFLxf0h{1fkSE2d`Wo_Ol$Wa zfPKW#(NSL_O75z5IiN)wGKSfuOD_}afe19*6)ToXf#wwL@9HW=dXFtAP@aeEXsWLt zE>`~%OWow$ghIf-u^M1XT1?Crv*_dvx)ful{>uwsU_k0=1Q({^{{H@FP}<~BXdOR$ zwjO}wHPkky?ofUq>{x7+EQ) zu<36fLiVlYJ$~|J4dC-9j6+z*=29_4n8;SM=etv#^Ev*3Hy5ox4{f6u9TV|S|cgJOB%G`W&>?}qdk03(O z?A`w?dh^4OoALB%oVJ9mhlj^sqldV-#;Lfh^h`rK5)=m|y!E(#fI~;IcLJHXjRhSq z4{2Rl`wDuqzrw9AQm(7t;e$VeD>?_8QbAEskvu=Z=4#B=k*>kK`yC(Q(Rq}a`4%3^ z8seoadx^`o-!1(99fv2lRZh+Zo`a4PCJkq35bc5j1Id`{^vJr4FBW5-MeCswzm3O-iana+Yo3Lim7+- z{~G9z$IN{dzWEv8jy%hilS0_HOi_CS5vvqEKOxUmV>n&>^S2l^hH5arNX8&4CWaN? zfCiD077yA|GRqFc5=<<4+W!A@ENH>ssk{_4mp;*}D-2ji?xg%No z2)aaL%+8pYTeoarvLD2yvM6U0ky$|mfhuLrM&MCar@-r&!l0BJ^9o=IoeJv4#w!jVK8*Vt zER$QeNJ&+|88{A(R2{_{44l=dB_wJv1TYxbwUka51)|E%&VG_xpO&^6wB`~V{l?~I zZ4jBbr%<*A^ppw`O)|dc{q(wClhoo-cJ4{*pLEgtd6vv0Q@h5i@(Ag$a;3+|$<1(= zu_Lux#Rmjbn5Q+QlM8MqCnw2lm9l3vnhL3$;1wwcW&#s`=t)}IR}@3=ewBgEI**Du z&v5w26m-#wy1M<&Z$ld~OCl6lj)|Gs7PDQp9*KgZ^!@vsH-8@nhzt!> zow7xMDyZC*5&z=T=bT#5Sb$*xVVHQUWF7}eUhCw^jnGMgks3vmFED}m+=vGRqgiv; z94cT!ltUBZ1bo310}cT>3Y%jOs)l?o6xh1R(d3RV^ko#%6$Tm-LO@?FDJv@@)jz(5 z<^cwzOkzu`TgA=o3m1w#RD}i)eKwykQKdomqC%2Pw8zQE6dyAW7ZCzdW*w}Rd z?FAU_XtIVBuHg)V#JaGhCG#8LNdSDhFbU0`JF+Jg3j~&oH$Fbz>D6XZ>yTa?+{?m? zMAO&AxMYcqg9AY&#Rq$y$Y5Fjg#KQ9>;aAnybv)xWu7>Wev>Cpo~R!?wiY95m{e7Z zcDAZrSs=F}Laf=FxZ1^n3$fvEcFmO62VAE!ABGU@MA%xrZKsIxtp8X0Ns-~G)oXr>Ltai|%PJVi}j(jeA`{W;2FPuLw@9*!Q z`1mnvoPIDITbqhx$Q-;~*CDKwBMrK_qddNj+7IUtEs}WbV8%qJk)MS6<0^^= zv+TS0*hDxFh>Tp$!O1C)AV9db$qb7EUQoqya&o=^Ip`w426Sw74|4=UwI#^*AtE3k zQDRb(A0(-z)cAO}mSf5E^t3$go4IxSHv5pv{LgdYt9S8(izJJMQ`tQ*5Q2JFgHCP; zX9i#G2S?0AQ$$1r9vMPqORqxv^HTvMBWD+2-067nXveDBei5LUB*e~|ni_Rf9!XE1 zasr#cx6D8Wi*{p^ZP~J=?epi&t0gYssl#os9F(?ya4-w73W|XZHRvT#C5?Hn4j|-) zgNvG&Y*v&_`)QF4gG?7XV>A$g^X{jmWz9&>qenmoPZx7t-EgiCRHNE}rT*yH96ZwKchf5ML0)I8a>({=SA3wA~ zjzLlpl1{kMs)3xzodPZ5J!D@2At4C~i6saqrq7OD?W&7Z09OKan3-HdiwKnT{5ju= zw|B|_ryZ(o~kTa6haDd9N682`Xc2TMQ9I{`oW}i z3sT4q*C9UCg8}fy*g=Wz*#7pu+I4(g)4+feL%lRW&))#ChSb9Y^h=wAQY(TaLEa0L zHCedkNf|_>iAgYqIPoGlZ`F7phNh;WP|ixh^Wf#rH8U+hC(#%WK`^=ClVx2ZidaI#IFNaJ5Z@OMl6=dS@U~%lYd1=4L)TRq{BTzrJ3IsTQ0tw4n}sjSHBWt2;V7*MLz2)M3HZ z8RRM90!TKb`oPrGO;i`&x}NZp7pdv#F4cNRj;umNBqkux@7?|V!B9dZP`{G)OYb+1 z8CYTG`PA5$*2e6o$Yan=?9jVQrjwv_3c(c|HYloMRXw-EJ{^n;$9mA$Qw@d|pp0|x zT3)JU=2~v@2u5QI>H{2>`&wH4q-EXM-z2nk(-13-NDZcz;l4Y;c- z5s?#~-v?d>idr5iqFg>**dp>0C0KZEZ7oY3h{cY9K?oZR>W%wH5fXsrP$Jnuk#)W8 zip2q3V)Gv)1#GxHZjRaJCUYx#Q?wp5#&sKwb7#9 z%y4My-o49mDR6o=^ZzTF|DRMeLB3`neb7`;C`xbG@R&Z@QvLb!(d^Y#x2$6jUO1?* z>(>XGWGwPaDLF*pOE{%6zv1Si*;klOpST5$Now=vujhzCTHo6G{(Qz9s%-)4B?jTk zyaOo|2X+iBzz7bL>&GbhDu|2B?aP-hKW>*}`c2-jW0ilDfhX#xw9|< z>cwm_H8-EiR15mc4@hs{e)yY*tM~Sew_6rP>@()3*#4W!=`s8n<&~z8CdnRt$nEIv z;s$@#@xh+Te(@qGCWf?$4Ov>RzX{{fvYLj5pp+DcBKHr-lEcGch*tLS_!u5JGH`qK zZZ83l%~gQp%u`EGM=ED2YY!mZ5lD?gRVq+{vt)bv^ff5N1g~No48PO;(=jP>!1DBY zgj2kn5-l^-0-aY_U*wbDeI7!g;#o9f-lQ|!(+vkjy&XpTQM;e zkTSrAxdExp+!Cfx7qfv0(6Gexo$?e+W-P6&44TnCygAwqtuKIrx|c3(2mc|IaoHY- z;zH5hh@=sf!otD}2OdGSbOsd$ol%6eagxq-^OBqa%=htmjgz1F^l1ZU4l!Misl>;| zCL-kSJ#b(^;sX{~Lnda~OAv)bv7ymu9+EB$+&_K-WlVS|oe^cN5`aY`5RW&%pkQh6 zM}`(ZHohW%b_6T(=+5oOXGJ-&17JF4ebin8mQflzvZX4Z`{3lECyS^y$Laf14z{(o zCnFn@7H!AQ;s}yVuvkR$6 ztP4kMC2D5`M`qB#q&h;$L<(A2Ycn%JkfmLNgQ3W*W%+Xp3tkkbu9MWpjiu<|&4HH4 z2Wsawq)h5pXUx4S9sl$%zD=#ItcI!&_iT=qP*PeIp0iWfCqmGL>kyZCs;pQAET-AV z-tgXm7eH77bu^-VlqEwG~3443eM^&r|iDaBzTW8>i9 zYc>K6-YE9O`-<;ScPQ}A4qUuPEEFkHuO8jn)9~ub@11BoB^zmf)|XIV`*K*-{LzX~ z1`vd*g0QzJpK zh`O%^;-tSgWDH6EPsP+53;Bk)~J$e+9s^iVtW8p?}ER$9LR|2U8BdQBGPfvRc z0ff=70v{os_7r;^y!Tho#bh!R4xCujUgzPDTL7j1k^5AX^V%+_>0)I}56V)2eIN#}A2HW*b-Y$s?7QuFW`KW01SjC`c{s^f z1|$KIOCiWYIV8gw@p^d35m;*&uqGnz>E@Uc198f{Meg}#G)!q|v5?zJ8(z^d_=dEj zNxaCw+f#`}-)POkqysHV5?-u?1K0pURoHI(DzvsbTnp)_!d zkOZ88xrp4rP3Bnx1tN-;Z8@US@UFHt%_aZf@#E{E`(@+KV!G4)=E(3?7&A#)waJs+ zsx@XeTuEq969y$x4U9eX^a$>u0LhaLJDf}-3#GyLG`>}vsIyNpG6F4R1~Q`x0kqH! z+*cy}5le@ni($oj{ZiCW1W$&BhU#$Yrf+tF%4&)~Vu)BVF zg+vW)M$6Px;8||&d)z@s`YS9By+neNitsQshX7n~(j65HW8y-XkGnfMfi?>jsVV-p zEMftDc4O~a&`rGB8!{T|>p6ga{jDPAzNf~=UpT;^j)^KvQGC%PCJ`>jRzm(l4*ZMwE;#=uZRaproHe^OHZDa1GyX@@jhGu4O zrfYa69~o|R(hT)Al@3N4CZm>@ee?X*v<@jcyKFk_+BLT^*NYczY;BV+ng$mZ?u1@J z=n*_%t6v|HnY94EVbJ6b&pD#fZKA@EIs4#RefByYim+;K{XrxplH?Cd!X)wGO|PJO zt(wh}rxg_ybP65>A5{vf)_oT6VSRp}}hH{oy@AcZyw zelG&W7}`-%-p?BwTeco`F^1X;#S8Yu=*7L~ZEeM8@*vwf(rIsoKS{~TR%0NdShOA- z;0LG{HFvzl@>h?kjjLa7h}uh5X?`DL5K1P+A+P3;QC3zLZC5crkrqkK+0rr{?Gd@KumHKv-PJC<8>E3BDkvo`{cdo0Ocb4_ zr%7iTQkDqZxKdE?D!4o}{H{Q~LOwL!n2nRu^{Ih(cucT4si>$RgR(I)GU8ORfLE{> zZ2t^yhuoq;s3-I&zefGk-PJ|G@dJCjE!aHg3m3>tq{OVO>p+a%z-PoJL4tKm9UEemvz-CG@qgj~N;V=2egx4sHv-F5p&pfb@0D3=xiI)T4g;v_ZBmEUF2wL{rEQNuZPoD(g zUaA7GFaf*re^KQ)8%fE@0hk1I-W?SOkY&}dds-OY4{{pM%9YFo7R&AJd)9w3HkiQh zw{B35#6}LffWzF}T*d1bx6k7228j2y4a)JDFa&=pvCa`G8TaSl)`e`0{n_!~ZT3%_(LO%2dm187P*SBw1 zan+y=4k;PWB`Z*kF<22#o@^9>`L+Evv^+p9a!ow^WULnoM9|k)2J-`R$-u3sC_jiV zj87cf0Qy}@PJD!5j{QVE`EHbeZBx8BcLu}amVkr60+l0nO~AuGULM2PqS_B>=CK^T zw_*Oj)ZFEL4FdK^Ru1@feM`%`FJQbT7j7(bwKN_zq$3`lK=om-5EqV_R`SBa!u%M} zOe`LKse-lU-B^qIw{If_g@tqBnUnEGN~uTjLB1EhR5WZ?kvmr%X8|zHFpN1C>?jk{C-TxECZ|6I&Hj-=dip>lC0KmX)M<3M^bwxO&l#w??>kcW~BhX)asK`Nd=P3o_J zx{SH6g=0eo=v2>G_zgcaHrjx18$nB?4Q34a%e8|#)vMU|s<4n3Zeu z2H98N*!ZUFV56TmjPJ*2#b3YLu_Tr`H-*c?9&m0uV*Vp!pk-^Uj%*x*{I;~#@KRQ@s|vtz9XPBi zAlf=u?#N?BgVUOJntQHTCz>}*vHo#L;=?G3FbQUish!vur)FkO;KA0Hmij`=b-r(+ zm^A^qoG8s0!S_a6!sxDTQNn3x5%smT*Fd>#g+UB6M7Go<=U+Y@BI5INL~tJ68gY0z zBCnI}3kHrqBP+l9R?+%@?$p$oqpg)(KL}?!d3`Kpm-oxo`V+^Fy#`Jc>d=M9%PJ-` z=ZHXvIvc}y3EN4|1{1Ns1tkRW3LtJ^p^KeLH@)9%Ov#}r{`h^`8i(e8mVqOL!z?>D z*R#Dv{Es@qsQ?;bAXx}6h+mj1(YIRFfW*eFDU=E(os7zIy29zMipNeSb1H`Ky^D*R zuA=2-dz+lZLuFz_%JREsNF?MxB5-BXWJq58k0MOq?tc_v>i^B(PAc(U)dt+`qF4Xb zX}b|yEslcUmP3bUNy!DL8hE_Kc&|bps80Ehj9gTtN8Yjarj|TQ2F=B~VnN#Pb4M7; zJgaG=n-@;3hKh(zDpK~t`@$Bd&jRxd(Ewm~@4m8BJn)8(5Lt`neez2EFkuN`j0)^em{#G3tdj(bGu4=WL>8?| z`@YS=P6Sg`TCSIaOC%qPSkfA(1=a4-rAvs<2MfQWRU;UyUf$Tq4c!5pK2a)3;ggss zHaI-21z`+Gh1ov*hz>Xtw2(&Nrx4K^UJsHTal@+d*!iVgTp`Ft#lL@=TUG2WtE)?= zyBoVUD5Lj!m&oeXFak3JC=u}!mWr;viScn?gi0AJS}3BHmbtkwV&vaJ0xG(ZB7UIa zC>!*8a>L^5et>K`o(EMt;tw^TROq5EBlC@&oScZE3cNg(7E*=SCW@+>=89{EB4GIq z7yR%;`2a#TfyFEsWQ5SAj`xoWvjXKEvpcaYA!lGozCt(C!m2}cB?KGFXfHHm5Jn#& zQY6utKHf!(5QOFkSVZj56i@111aHDpHJj%lNmcv@dl}I43W=n$aU~Ese zAii=72(Ts2{eruebaN7iB6`t-V9&LKj{pg!T1wH7Z05q07eo!}S>omNX! zRaH?U3E;{o)z&8{R9~YJlL9HQckRFHoyO1==7*wU#tPUOPalL`B>H`5+iEPFts?ueJS~UCl zb8T2~WJ>@bvdWTSGK8Wx@$)(ff(E*9$LtM&H{X4%?}9Q|k^BNpqIZByL$0GJ;zL1V ztDZi)EgHx<(a#Qy9OXZ|5Aru{2HG*@i+#o3gso99Gdnx$hWVa0Ha3k93qg`mr0Pz1 zZsiR+8YBQnS;)KYS$MWYx1ue;Nx=i^)Z3fBNaEhZZzoMxi$8pG+DCq7a#9oQ719KTCarcD?2e>~e^oV$kKI0=DRlHYk#K)&Sr^6FTn{gd= zaT!@d!qp!^(rCe13o;s@eP*|;EEnV|VgmFXk{yOxtqIRE$sh3BlFA}WN#|XJ%Fr`Dgl@>3q+r_nshd*TO!W$Y=z~2#rSY81%mIr@$l%LrmCukI=TrPfHocL zf{u8i0aghR4|Cn=pQWfYx)3mFeNj=8&c}_DKFPzp+}sqHD^yGx zV_fzexHKd}X1|7fYx<>!yU1H=jv&c^vk;(=4D%tZD)v|@wzxcc;OS$+n9|4$)qrTW zIM5T=_r;SxGcq#da7zY}d?9L=qk%9CT9qvTNJGvp$`>I}QcjK=i3x0fB_;xpZX@&<~W_~$-=^a%Da4Fsa#2ciXK?XK!e0&RCP01 zzZi%#3xOf&KOI3NQuupedlTQ26sw&fj>c;&jT~+zh5^2!qeBFj&TAE^ZJghXD?k0w zCt!!uPI4m(^+sHu2NGgi{&%dxd%#o@!4bRMfND{#y+?-}5tgBXTHPE{KRxoxqakn!8Rd<)9msuRBtU2Qjy#qfp1OXBE8`}Y1I=#~S3y87;|JsEPLO(}qt_Fk~G-zE(nU(+h)jv%5 zxi?zX?Gyf{?&bd*6eI4s{}}-FUskri5Gg(+gca-r{pekxVOUa-MbS-C1DBeV(cwSP zag#uxI6eIb@2*CTnH=mt$_PwE9U?3)QZ<@`h{%eCtAJfM-wSK~8U^C-yyi{o)-mIO z>cw2umZ0z79Q^=hXbf*C?L4SNj>jQm@Aj2qy(J^J_qD@r7TZxc@iM(}BgEL#w=WR4 zF|2S8;Di93=nFC#eQM5&3uKZ21A2OidBu0A%INT6ek9v3gOgv-jsWTgeGE#eR#0w; zNA51?S}+Unm-)#D1J0A(gYJesn5qsDDE|4=N1!5CAd=Asx09vyVI2#=~T7)^^}4AVGy>Xc%B1KRKj?jV*ba$(-H zX9{S55JgiAdCo%?@4yR3M@M7gjxa`An4c(yQ|tlSFyV`Mjb1VeceFkxbSUYA0Y-wy z{7G)>H&_|Tcv8ak11*=KLkDDXIg%+c#{tR{TgeWuUvkLFAG?Yf)%WdN3TKWu8XCWW ztJcE501eziLhR6if!plR4hCyOUohKlpG6S}lzTKZ7>KPMx&bSKRt5ZXeb*Vs}}66U_)x&ywULT zQUTCpp-2lX#!NML$*W+P2Pzn_eQY_V9X406je1M`gIjnsg|8^r`LfI^6Uy^f5RAkGlV!`4SeE3Xc#cjJ z#1F;yYT~R-z!PH+wgo^0dR)>Yy1-`mvi*1({Wwn1X`N}Owhe>T1~zv<)0 zDgb+-j4=K66(BdxgDcl*y^j+!E1USYwgG3<(TP$Io+|L1crjWlCS_C9%vdKY1rM6# zDcLB}-`ai$@*c=;4C+yW1=9|+5z)DXO**k=BpwoT4I~$y)vGzNM=@zk0EgJ|kRat# zGW;NRMQ>MEX?j&?!e~E3`GWy~#4xUv?v~>-(M zAp3$T1HMv2V8YDVP|`OF%#W;m_K+YVCw?aB)dhD96b;P0Tt9wWs?o60amPb0FuifY zj?YSs|NN-~z(h<%1TTr4Xmj7f6dQpL6bp24ay6p z%Gu@|2z*QI!}CU~L_LvW;JG5(1qd3)x(tnA0cfB+erkc2yhH768IjygYY}bnho$&p z#hH^Eq0e0KfjA9Zu~-Vw<5qWhr0spzB_vR{v6hw=+Y=|?cz`>Zfg){PNGcOuWsEYN zpo%Acd^6T?hksEMr^U$>y%HLjz7^n_`VP=^8O-0r5bTDVrk%_hW(}@&2B+P9EQ194 z3l~f|L;E6tjDlbyj|r~W=Mqv<`?LQUwf6z_hAwXBQ+Pd8&-wxFncgEND1JmYIV6?- zt>f4UNwMt{XxJ{uhA0(DWk3dFAv}e^58j1nM&x?R21kiRankKXc=IG0BtHOyS&zKG z)=FXF3UqT3$UKA!0M4$lF)kz~g#8{AslRZi zc2a8URXn-a_F&NPWRNf!N)9Q;ABe-t+dJW+G|Dz4EEcTH1F#*$&qfji>RB@0fmna@ zwopVBLAW8$0&vQsP85L^Gs40#DTk!UUnM`kccw&b;pq8w^YsoPp4a!6zBe^JC2nUw z;QJuD?UU(iog6m1=$-x%-e>tTq#M@Qi0j**VV^g$le{V?#wM|2%JKZC4&e*k&w|&z zx?FN!MtOq^eMPyxXt0Y;)0;O}Ro;DQYWf##h})Eu9*eOZdV1K^#U)vcZSC(DTfcn! zmO43016_cCchTKF_0Z<&`!1iqd@I%;P2zPI<`=@;>}ea|4uZrinMUaOOfqDodW zjyn{OC5+8~k?DC1AKm&lh+LmPdv)e{?RNTh@$!ma`jV&(HEm~g` z7w@h+o_op5>n!;H^>1(uZNhJhR>fA=gsgXO-=1}M|Aehue_kXZE-uF|=VVq^R>SUl z195AmqV=})tPI;@Zej7fyW1B10$HJ*xR|IqvxQKD(u^?s`}+ZM|yht zOLrfqDV40g-|g-AKJ(nNG7Vt?f$Z5?cd3}VhM(`=9oV&FM>dGouu8k1?>=O$mEvPR zXCD`b7T6aNN3vR<8g6WBZoakd=Dz)pKfEj~JPjJrgJ#>k$H99oJ(oJ)9Nh_yR@dYce?g<)jR z#aAK=Rc>t3jeZ-ip{RN|Gjn+z?^67)(k{ml^-<}Xp83_uavUw4omptp{37Wk*v&R* z$hRl0b$AYw>iPD3kzu8L)&5Ztjp^9|UzM0Zl4PJHdAr z^ZWeowf9=*tbO(wXYX;(9rvC!)?y({=KSJ)pXXQ4_efgu3guS%tt1kOQtax*nWn)^I;Brxvu3;~{O3kYE;^TA5oj>BH)$ z#p8S9O!cd~i3?s@jh9N2*d#B~y`BxZd}=q(?{}tGg&DuBEERoSu-h;ZZkDy+n9!q? zHs??<*Jo%QD(~R(OGLz9OK}~E^y$dLhglpOscD0$f~qV`RDH{qEj|&C zcCrsH4kgbuz0Xd-onceR|8>+}yosFx3P`bXo;29?sCSpGr8|`dnDq!CI zaCxPt07wDj|L;o&?^i+8!e`<=+4zLQ;Ydb89= z>k*&i#1xrRhe*E}lXed`uSyZU71FwDQ_)u~|-NPM+ zb}=y}b#&ZTwJ76h$}-dR?V7JISY^dkGHT7qD9m9n&N{cAoWgW%dAeFE-lRQ$D;1TT zt!k7C$r%TMRaz|_9fRJVuOEAP$>HGR;^J8LeKddd z`gN>vYtE6whp+BcF+JxVe~!4%W9uX zg2k^h#rFMgJrk%!=0Dl@zSc>osE{7=S2j-NEKOsi@|+odU#^s&pI=uMDW15z?8qT1 z+8&pbwEDH{ske9Lsne$!RQb$)U*tG&pm1gCSBT&-W@cs%5s|h)ezSKoZ4P6KQo=JY zYhz{D4VLF8;Z%h6%ZX#!S*cZC-%F!^X zKlknHmoEz8SG+&8+4b!+C=2BAuO3Kup6}emaq3hR_F1O?F=cJ1>B=j0k9TtehuyTax+Be*<$1&7okeoDZoRx>+LWT<-qY2knYTPy%G8{!l3hhylKj<0gVHpw zyvl|7c?D+2@pJFJrmnlmV5?neeRWck6MJIFPhQRJxS(l=YtPu2iST@f`>0mEx=Wtb z#MiybSG*YUJNxOF+Gr_egX#WC39l(H!O@gEnwrA?#Ql+rmJCVo@u4YA<23m3@<^Rl zfU5Ol`4(v>)415!R4>Lecl`YQm8Sa2Gf}G&9LDnM(>SNvWmZn+;5ZqE*-xLYPHQwA zo#5^LyzO{8F5+NnLQYN-icor#L{P7qzmLzIjT<&(UGULTl+)9TMS0NC($dNfkO<~a zEi`-2`MzTsmr5x_&}O#5Q)}Ons^v&i#<_|ON;H{+u2gKIqUyLqkvUdFy=r@Q?TTP9 z$*m0M;^UJ?rK3Ns{S<4a#RVUGv@}i9jw6yMB0Ab(xdppXtSUvLsHOD$g$q`r*?nf)BqJjuwIFc%^yv(nF*T?8PR~)T;|0rU*#5ZZtYX(BCFL|U z-bP7=#9;%T^`vIzb(ZkgB8o+bfYnXhG$y<59j&+|KYsl1v0N;OesSmuZ$t6o%#c;J z1YdTs!@GCy4mz!^%q@ zP*D6ick&}#T)#AhiaaP~E?s(n{nxDBNNvj2RF`m$RM6lpx*D^(vc#w0{5wb+R}-yb z7Y$9-o4VrSD>!I1hOXCJw{4r6KeK;-M?`6eWHgH&_H|r*eD&uC+giOP`P&{%YR(WoyH7+QEi5dI7Vp4fy5Q{WJUSD-Cs3og%h2OQyYOJkwWo~xaYyw4)=;m0gfhRQ1hbPb6GR~Wk6JD9F{_*RV zoTQ|eA#c#-XY@TcEQ+iU`R-&jycA9@Zk9yH*PGJs;^e{8;sV*%Rlkj#LrZ? zrT=zP*6*#t6JK^VWtxO7NzC9n+0V7w2oF|OSI;zPddH@v-MrvIaYnBI=dI{(rIZ=g z9NeihY!b8HgT!u^rP=(h%61C z&Kv){t>A9I&rcUA2|8ZGS8~Z}iDAx*v6anMdG<3m9UXH&7&S+s-i!rMDOROU^s1g zy{b*fw`UB|%EsB3s-q+UaRH8D3(w<=AXrx-3>zC4IdRE09MYwzA@ z(Uk$IXr9r0rzQQyG%b771j~h<01oWtmBks~2pP4RAYP-u)j5|n`Fa(LXZWiqwLJR) zbp6G0m(_47(Un^!xf9>?o^8~XBrfBlhYu@oX<}k!qV+%D-|`s$>@M+@m6Pjr_D#q% z@42=zlk8Ia_MT~Ev$zWfUe^`Uykt1Tj4@v;2tMkR8KrPo)KU&vf<#O9i-VLA7*47@RcA0m- zbLS4d%0hN;pux1a%kl-His7|2c@8$um_^l4yhXLteVgB~6+}nb6B{2zm0D=r6?iCo za8Uo!l`9S{#<`sy4C$zj!pq}DIbB|Ymh}?KC&Yw9mb;^l9eq8eobXgfv zmm6!#OT@MzkX{`SqO+2)T!Mmzs?$s@f%^+tM>v_Sx~gi3)j(tb?>xUKsbe~QzH9TI<7x>#r2z(50!NP@ua}iqjZ9DH z-X~xgQ@Qx`DTSY(pI))ozN1Hv-V_si#CH8vhgw)zSn|)G*U`daaW}T@<+=sL&TGW*QBKCvEYQhoic9WKf_2RD=%N#X__U9(f6ws zm}MFmqCcZvt@;^XTZ~Em!sXN2KXpgmlAT?us}ef~=(aedzBahJ@cqRhx%wnk2BDTR zbZUI(@9ph91K^ky;IuMRPy9<(Ma2;4q}96Fs1Ddb=mUeWeL4^BSZUho+J(-*!OB+z zfW!xxt_gu)_=+S@>l%QK7^vEIK9DmtO(8%Y(W2@IAk|c~&eb~kQZjMiBQ4q0UE;z+ z1px!`$@%)y>Iw=mL@mF6UM#2EM|{`z?QcGRK5tX@NS*d|aPaV`PF2@MbO{05@i$y~ z(>J|)ObS*$$6|3>R4gqfS z90zM+?EwghuCI@q7bs+xcKIpoTZtfEo_@3G{u@Na;SEYu6n|qpHs>MMc0^RH$bnwROgs9}60nY_S#wt0JF#j7oK$SpxiWlE_1QC(2yc zw`bLE8@CD7?OR(8z)Ae!ap%vwfql?B?y_yqcR~p^N|7RwDrR@@-5WNow{(mt_;**A zth6*3C%KrwY~GCr4<68qIy<^Ae))0{oJx17E^$=ZNcn@|4I`sOnw>jac~A(Lnwwg*z+S+vh}GfBo)^MQ`wAMvcpKoqo3+3kqme>Ux(u{01i2-4;J|T1dwpSmZVA=niN9PwK2OHZr+_T%^GkmRvh0V2{*zoXV<%cf69SdCjuK6#U&t7-~$ew*Jl>Boqyf|47UDU(q}M z?`!_gT!FPZCfq|^C&_3Tu7J|`CCF&f5E10(Izi}2q+jOv#g>yNkH*G~4?6#P^00#1 zVK#ke{35XkIQz`AZ{57{wmkfoZ&|V6d)v6gaQbWz+h+$#$) zHccP6**vk@XbOKjgOmZX=?)| z{0P^sjguX$j`lu({=6m1i^0rbZFKD+f*b$`9Rv)|FpyVuS($+b<$HxXpfs|5ZFO$2 zDI-3|a^T&LLzi{Ismn~cNu;w5mA?!1CVqca%6H7JOIA;Y&Y@~mD+}0D4f%;avHbgY zX+_1?*24{NQ9Fqy3FI_7gHG||=g(UJ-8i0f)6Vl+ckk)~Fn$wuUR}_Ez##R}W=swp z__7ycED8w0oK;U7HF2X15dEkM$bC1?3CCjFxtWaQ`r$`$8fQGKjV*Zl5TJkU%Of%@ zVgOo$b%{Ztj+0{e0Na|;wmfBkvZgexC*W-CV#GJ7v>E8%P#yYbn@v!o{L~yqc;>7% zzdgNi@#1}`4N*W~M-Ci#Tf+2g3natIo>Ct3?yD%-hlPYx)|UH33GyfA1I$1`UqYJ5 zSsaWP{$Rz=Nc0b!;oZ#UpuSIJqz=}ZG37NIH5=T23yH|Z{u?t#8tbFl86?^Yl&2s2 z7IjuYwedqgRqO5Ri;jq30pgLT9Ne~T8}x&IDj^@s64rpj`}dzk^B4_;(r0fN@K3t0 zSd~q?6MJ;^z{ydN8%Uzu0>G)Ar1>%tV23!I#sGI*r_@G9=T2?mKL5sz8KYI9Z1==6WzM!sKfD;p4 z#e6YR*RS7_kaz}EICxSt_QMCcM~@zj2D-Ve^B&2c|GkZsm6dKTM%_O&H0g*;ly=zxiA3wn~(40nBxS>Ry0kwI~5*ZnJ{=ETkL+B-5^~tJClK;g`lKPDe)v zt$%jPLw^^ee>0nBz8~_=GVteAe1}s}g3bwrvcne7JGLQXy74yMMze9@EJX-}!lMWjH8@~oe zN3E%us$13JZF<{+M_w^fkw~{6c5L|n?bn!Ew`%OIUHkSOgMR=;*Qoi!NJIu;`VVv& z1%+3ii;Dc9@5jX*^$wJcmm@^@ty{KKLUK-kh7%^|eN6d-?)-S?U|Zft_!cujr@XXR zu3dW!RU7)h)?eGB#iD%W^C8rZrYCY+B%ka(UWbaG;k;sxQZp16pLA16DUfQPK#i^w zcp!v8@L?(_aj_8*7k9Etz3Q5&09!$(^ z^YQkU3hx@9n5axpeBW)#e@j*Mm}k-35?^+{>KpiMqYxK^VQxV|WspN@@CjBi5YL+S z)mgqowLB#hC8i8?$U3Mr8Cs=#&<(_VLT!IK}BRm@*2#Bx|75in$*@)LD4bAHCAY>Q}EE3Ku=%gZlc zzKlrDUyM8F1Zfl(sy10&bS_PtEBhiD8QD=zPRn`Gs=#q`-lPd}I{1iqg)`ekva!BA#$vPnYkyfciZ=wXe6@R~pk z|u^KFL+YPqQT(E8EVR5N8zY_B0%}kCFsVskw~`cXbkEyQX7o6WD}}fe`PpB%`8+_ z{Hz`wLH5Rtw?I^7uTEZo`66v;na(l*sjZ68#0a+%T7TtWZG47tt3WH%v*=;7W!a>_ zKzdXjv72C#P<3y~%d?9eJ$8&}f=j+G0EN|IYs)&%7|+~1ckY~;up0WikuIU){<+lc*jYVh^+2&+wrSJ z^Lkss!jIkPQx^z`{-680nkY@6ML|dG}QSN<0w$hT(Q!lOXLHiy|L=O^F98#jVPn*k{6V}mD z$m~P)aPT|uiYA(_j`TZUw}Og@`%I4)Q5#ggyF^&m)1TH;+ZvhTB}R`X0+!`uWsh)k z-va+4{0r#&aI*vk@|Q-ZdgqC^q3G(s8vNt$OqKCnyLRm+UH}`&YWlD`CF;;Gs&9ov z44`_qyvkc_72(+iO`izhO(s{6vtRW1EiB&}=yUO|t|V25kqn;*bak7DIPw1er|!O% z!}neli%Uux*A?RP(zPU<Az|k#fyl(?3e`{rel>V~R67 zO}{|D=mtHMlZ(RkS9q^eE(h6Yb*X*rLyax2osxua=AfEw=SGkloO@^$R6TTumLbC7 z02LzcDQ=<~^I^QdfnX0~G{mE0>iH^HyqRjTNHSd3oF904#_N=aPR{T+JG0?sA9aMz zw7_Ft>BdELeZM2%DUO>05aYl58dB$5;zF3BN~w1qun?`UI$G-AZX~xw@AX8L?3-hGbD_&C-l{Q#b(mo{rJYO( z5cB5PNh(P~0>nGBiXl(o69Lv!I$&w9a~B6?^wi9h=+IcDLjEg#qH8x$>fci%?DFQ= zDJn_AVwetUU%ot6u$J$|BpmmQfo~AZtsc_#K-mPb0O`Jk#SzjzTrewmE8ja!4<9=w z;ma;n2UeJSMg*6Ew<|qA-gtF+AyCvMA8d6uJ$)SRw}}@eK1Sk9Vjq94;9QGEkOL!e zL~VLbaibm##?l~!>(;FkTE!28DW4|ugHRu3W4lbmI1muvD+V~LV58ggXLm608C!}j z(~uwwX$HH2Z$=|4a3eJ}wT%($kpEuwz{}E}NhKH0qRx!Aeb3BH?K>arwJaB&JAC@I zOkapiXmO4~a^8o=O$haPJ&4L`;P>+n_%JK|rh2m^QO2(@J4HaPFV@UPVMU(`bh?eZ z4Fq}g*m2pqis;?l?Ts8uF_ugfZrmr}R-X;_zK5hb54B+oWRw9B(W+KyibfRSpE+TgYx zRl!-2h*ItPUKIob4jR$N#H!%GNZBhJ!w)SJMUcsrH>#w=vel|Sdo46DaB${db{+X2 z`Ss```Blg&g+!x71=@>tuvLJQ=@$(w8EVe{w2R3Y#SDGF#p!|Y=g+sRI!=65g8isB(*Ws&=;ouX9}!@YXGTJU?iudC zr3w(#f2GNC2x;oPVZAS>oZIExq|^*~z(a?LPoJcTgA z&`V+axzR+3OyWMArTQndeX*Kcy%N9v%^Hy@DJdD%f)|bkQ-#%1`zAjnMH&GGBH-iD z$Qq#X4%GxjJjZ~Xim^Xc)!aPoW0;t?jGh-0R+W>1Uk8iW+qZ9-N{c>!rgsWVD(cjg z{zoZ>+ShEY>aQ115G{>$Uy!a-E_}1B`VfWoh7H4T%Ycl&s#f1Bh_dJYQ11I|DS!ym zjVM1UHv^(VR!08ZxqIZ4^ekeigff8d-@KXI--w(%d4q?CrzyuOeu=jc`qnLF<#@Ca z8wtF+*jn9j;sI>xrw+kCUQfR|t>cA)$0DYur-v$ex@j-=7+rRDHu5umEa77@hJGcT zsFU7Ih(W=UyGX?BnC$xxAONC^xB#WT!*u^!f`(uLDP@~Sa+X#-r8~`i=gVdYmEu05 zIYV5;pAia)aqh(BJlKFrnZ_X^@b=;KIN!7Zba%L8`xln&9veF@Dk|E=RIHq?yF)%4 z$5P}~kkV<&pKdu&#WtkeW3~Z(sQ$~NoqmCVaY?ElVev`9SCN6!_4vsX5Ikh>`jX0F zGT*%$!IjmqZf3Yq1|Z2ECSl9aCQAAUKwP3%L$I1_cPWrdQi&y7Pfk16kYNxYU^AM4 zJ%C&eK_a0F)fG{T8nzIN-Y{`i)e5d_{qn*T1E)9qcPp!m;xtHYM7T#{mO;=u;hWb! zhpL(A!5y%s5me&M#vLYB)stiuRE-macKm<(Yj=AS&gM;?g+K`Uh}yu2DSvL&ULB8) z4Wv)UJ1hluGwh9@teUK2+Nwl?is7pMsmDAvH(e#R)Z@Eo)MA4yv z!wElS{+~nY|Ge7%r&^ObB>pl83|970dDh#9j~sxEFTja&c*QgkcX%l_!_$q*4Og!} z1rpKGAI>*_hnaWpr5z4&;y*eqF&m;b@a%&p@%!=Rb?!aPw7>n@tnPW@nj7u(EyAk! zCot`Q1aAM|_(_J6X&|3TC_<*hHudp&2X!YT{R09uganJ&2}lnTjv#_PH{QcyoEfOT za^S#$9E4Ibpa^S^y};kVp>7IG^r5r}l) zOMm|;r~^DoVJ}|1O-nm9(vW(AKrBa&RNmjRvt?*%YKoAP5)wG^p?aO4A3$cR0MrP| z@C4%0E(>IZVuLgVD5^xLP~)~MU}5Q((EiX!cr?FJ%#1V>nKh*>)3+*I^6nf$K15I; zQT3yoxId?v`Z4?8kJ!^8x{fryzv45!JXLOhvO>?J|8jX{g-81*ZDe#b2NWQmboim_ zE#aYBifSSP*q({{6CEH0=@xyGtnBO`17M~cL2N|bX-3w45vKe`a`HI!0_Rb!02T8` zsNN7T`jhC0u&_1Jww%i^92U|-XSwOBEm0rMFAbVyNp>8oGFT4tJU{&Xva zf<-5uE7W2HFKuxdYcD7u>>0{^0=LkV0JRJ&T>eqWPdo=qt}FbvrjxCbBvY4p&Gdhz zy~#EId+jYS@rowmjVvLKZPo8CJrQyv8k_PzG!rfJc`TvuVrCZ=0&b&;@z{*2{IkY} zDtI*(xNiQzD5W@ zxQONSXUsuD-IRW(z|4}XJ6foRhDpSBp^4POjl+`FLy($?JtO@9HVA>HWd00OWfF-?f#OQUNSxWM8Cg(6+6ClwhO4Q1RECs&+ z|IAtG3JL&S8vBd!5r!@X)EiG+HeFX?oCQ54z@Njaar?q1hrY1mIuB%+rB-S<@;Xb< zd?E}B9!HDKdbBIma?T1w-I=aVi zst4!&Kd+^KFUSDh(lq;r#!9-5(#_5A_w8aGN$niY+VCIRMa%NazTJy#OTV-sSVrNp z1%{~KPFY@mAu!KGwd1Y!*cMW>``@GczcBt^UvaL%mvR@LvOenLBTH3_>yx%}G?cAzecpTTJgV5S_iY1|BqOrIE&Qbo>+|*oGpY@4 zBN=V{&orh9KMq}=e;~dj5;vdQPRSoQBwjALp2(g=VQ42!8;GC%9k^%^jDGz7bF6o? z^Ppebzx+*!XoGr)o1k6f+=~lW&@)LSliTKCdjDh%{=0)EOsHM+pT`~Dmp+^=Vd@WQqdGnVR>@xc7kT`DXXE}) zHe$H};1+?T|5EG3xRM}3nqW;Y5o4u0tsO&*fc!getL;RV1btDp=OvmUb`m+oa50dwkMdfFf-guky>6?k!>dmCV z6(1JVw<}cpCVq$nFNOF-T)!!Fhwzjqvs|2HMxw7O#r$o6p3tjRp-1=H+`6dv3;wb6z*9e$LEEF;;%5_}e%83>S-mrG&Y; z&XFk}iz*cdB)LAHIbye#+Z}9p26oPMoJm$%4Og$p4$Rr@sQc_&p_mg@L`t(VEt=OC# zB35dW7W4I(T5bc#9T6&!8biqYk9M zWk?hm>v%X5bujZa;N&PJ7p&&&W@I#ULP+=+!Gpl+Xo4|#1IVXCIO%G(vb=1V{!5uk9=<4%N|A^e0pR!eavLssZANMch*N+f%y4<+zH_6(5T< zxKRTG14LYyFvk$N(8Hz=JX5{oz0-AWN3g&{04_y%draQ9Y>@{gQq$f!7?mPah8Hhh zT)TR;q9xnH3aQY!MWm*0%Bz??7`FZhl_m!6u&rUi?ZS1$)YHn;`*M+Z#3JI6a3SPq zXeY^#q{(M68uBK`W-RIZ_p1c#*9`kKfs=g+4uPxfC??cQq z5}BrL$8$fOX(ZboEU+1Ie2GBrl9g6_$jmR18=~ZE>2O{*2$v@s!K=YghtV6zT@fFL zti8Cm$YM-{CiQX-BHx(5OM3ixYobEx`Errw@e<48_8e#=LBxOov|l(h20-_L0#y?8AT8bL(`=V{5x@Sb!(1yyxliZ4;HMgn3Pg70`Lr;Ws?Yuz+{Rl!j@3L zgGY0xBtdJk-G32x0igQNSOhLJ;;iwg|5;ch5KAx*(+^BMK(c|b1VF87F^jV@C|^*8lN)_-$aU5b`L(Uo)bg3etjeX`&kA43~# zcPd>#Xw{E}@LO=7>M_n0^@JW&>dU=0TxJamXPWO>MEu%1k3@OpU(5qTn_*=k^m zknSZiTg;z~W_3}Cnb*HrImmByg0M-zh`1f+O+kk35|ERcnr?1im4oqUvj#S7>9R@v z`t_%%ej_{V0~fj5aOr0VL&jyfi#bE5oCy;VIVOb@wkNm)ut!$9nAfDiS!PFD?k6m;)k?ZoBgsvr|>rrt9~ z?ZJ zOaI5}+FFxf?ZD-nic2&(v6s!a_(_WO1m>SU8Gge3K)?-=laKYco>rZwi0!Q2{8sz7 zjLe}MwBgsgPq{D99?FqE#PUMSi%Vx^wZDEfT~JjrJYpfCuPc?=+oEkLug*TX6#rLm zRQ~FohX=V~Q~H*SjEpR+p*@f@ny<&_Ye>jGm|h)yeV))4kih7`HYmc$?q_GO_l=TSjk2s^ zyLY)S@56^Z=&JnI!?Z+L&anAIV)XSW%L6rLiwqT$7d}%_QRQ}~MomHBF2vMI3T{sS z;2@%C>zSCC4j(x}R>7ZOwmff!VWG45A;xV!VL+kjdVNC!EfRvSL|sJSTX~{-Y=*cw z=D~P#bu@kM1Z{6RGvWDC|;NF_-h-nI3?`L|cMjF`Rq>-!fp zgZwxC_EW3izrK@>xGy02=atfjZ=T=BPW%mCe&ns!&cD8U{8GC3(Vw4fy>u<6>mu>n ztq%^e*8J<+`iKY{k$MUqUOZS+S2vqZ_vhV~k3IMGbw9hmzluM$YubN5&7<45Z@Xdy za5nwOzr3KLs_ML%nOT5j0N3-?8ZN)u0zk<(MTk|q9GMdf~D_#%e7K;rj}lmn9`7Go!6KKW`jvNL??wGQAUm!JBvQ)^Tuf*xK0@mX^ksmeRdqsQ6nJ zpj)E%87^d-TufKZwm+v^>~~mo{j{IU)PShV#(tyZ;YrpIiXzj3#Ds+B0Rf(<)0Rga zr}*2*+g=J|2*@=ojG0X;^eL)9|Ikp^Ky@^~-6RXbmiJK3G%>B98ra(`SBDE%wNtt{ zc5kJ8z7AtF-#v6EK&tv`?dh%PFvKoEd`lBeDc1r(;*03&1a+?M#5>P~aOuM@3;0Zm z=gE6aq;B4%xh^4bVEg9V8XETy@M&9sz3!%^*mgo1+s@F?@J(FYR%BLQy?R9~#gQoA z*RQwW-wA4Yfxbxderj)L#trp6yWjQ02VNl|q47UE^U|e_xp{dShK3Z#qEavk(T0YG z9_HZieYl3LVQORJi4Bi@g~pvbZs`A8^5BG#L00j=NFygF=QzqxCVf4UF>j)x$Wc%m znwoxN>_ER@KiPdxOcto-)moB$`t*s^+uNIW$^_S9^23FF?AJ0a7N_-W?d?BfD)N8yg!VuKo#dlZ=Go zU}|oD{`z$ajN#pfwdaVoA}u3R1e=761o)3s``L>ZHz2H*V${sLYuBz%b#?xH2i7pH z_KM%^AaM1L-Mb%j6?+$zms7hePi!jpLRMmaaq$jd*HsCL9>xshx+VJn)$ToeMhO+l z10(H@nBH zAIcZppO?2S-{|q?HS+bq^Brg~sT(>B;r6};?eC6`vzrfe1Loe>QuGN4k=T5I<;DF+EHy53_3zPb0Uvy?SaW*+Vv!U_H>ruWeIqsy=$|tE5BA!z0>&?JN zh2XH>>EOsn*1K9-Cn3K@L`0Yt=_hs$4LyOQgnxB}dyD)I5wsfIm7bn{0yM#Tq$SUR zkMr0umN;b`}U%#*fzG9rt zY~uH&?dMT6NhE|WA1H8+p5P*3o%ud5DJ``y10s0$_AMC_J02i5Hs4E14zRJ2gPR+i zHpXFK__YHdVqa5b^WwOd=N$a^$ z)lf;yNEfDYjo-`~ zfoS>no;=wG-jr>DYi2t?egI6d>1hEN@Mp5dOcQ34v>a?JLnEUX&!4+ugi=h)M)~m3 zqo05MqN-0;F9u<~t)<0kmhM?lAQ~!U=LTo{gZ(PJ_jydJR&z`dSItB)z4%ep$bAow z&tJc8L={l@U`PkryMu;i9k@lP+Em*kC|TGVw=vgIQC(exZM}nuiAq&f)i~D40)XRr zNC@Ry>CN);@^`hh&!fGosi`%**8$_{SU1^-|nA`SW(Z&k3iDk!o%et{^%SZ80+ zQBr}O9hXKjQ&LiFR)9`6|fsBscd(Nl7Yn){fu5-LP;`_Pc6g zWj0e%enp{6!SpLIUD3C19t@+~sHiSu6(J)>_TuUWoIG~So&Jiqx%0(O>D+;rfa^5j z0ocwA?w+2RVP%WR{7__FIkmLU4smnuLJ}$_F3v3~iY*}_p<`sEY+%Rv{d=CU zdgP+=-!?LO2DGRN6QG4%9*o!>6L6*Kpu8| zt4Zx~_&pZxN8PdPMS%*YXf@Zf`_Gz{IJJ2^FrkNoFvv}StR5F8TsE2w>&UEB2*7lH zK%+lS;C};M{XLxdJJ2=?WV8LzMa%Wq|GcL?Z4*ERen87@yBUqNUbFa3;RO_s98M|h zA}=qmmbKww!xYXZFyv#qHgDNNR+@$tqN-g#ba2VZ@svL-SAwKS7ODLQ&ygi7hwO%e- zJM^Z{kbsU_y@H9DVp!f5h&aG~Oql0aat{p-p1AYnVSSuuynOOO+$!vXz7hs{`cG+w z$!njc(M@;I(>KM=*6d*5HFU*Z=jP`Al*XCA&>JNAKuS#P9!fu&D*-9dRvwuc{CahA zJ@gz^Z40Rj4#D^E)`5sLx$in|K!8J|0J2Z~gNMHg3R-K*qAdFoClFhOxZr zL@x^uwO(=$9SZZM;2wI@J*J3akl zaDaz%MyKG#V&L4J=+XFiRE|7J$Lam&t+i z)|;4^V9#}eaeo75L1S=7`pCi_I5j0T^`5skH3bEQTJvZqnp56pI=f5LFXRXHPUmcd z)%cWwFDbgLU-l{E85;Dt5xvu^H20tLGCz-(OZ*I6Xg1n%<|&hKpl@?yqXsgg^RT19=zECGpbG;Cd)e z9z!9e<>kc?Q3Q62?B7o!7!DFX8%ZgdnM9+2%(xYr40ir{61L%b3kwUJng-^MRRcXk zU8!ns6<@w=0OzN%7hN7-f7zRf5^EYfGkq}y&%65D+l6uqZ;2v9kxw0kt3?3L zRyJ{&LnUwD-Vb!cIy^Bs>Gt;R0oQM7X4cl^75wRq+&^tsm+g=iWxjg#>L*N7*h$J* zw6wHjwF7g!;x(eTw6uJakU)VkH(bgL0Y?S~hHYRaINI~r!zcz>mIE|W(fRpI$gEdX zR^CH!SGzp)8DyoaU!X~?Yy%3uj}A&sLF0Jc%wK}-jRGPrt3t$+Lr_3*q5K64oR+L9=uU0e zx^*k9sIw54O4fdjQq+tSE~}0NmEOPoti1eQG+W!n>Dc?D>V?hC44Bk#Jl3a;*@~9& z-_H+R$g&3hsT-75t$yB*wbua@Oi&5BQ}->Hs2KT4J9qAkiHW(5IR0<%9lb zp#{W{4$wS${`z%^FM8$@Kc>CEI7RY-FJH?v|96PR@wd&(5~--4I2&4CLs~ou$PJ^H z^9u`|U!UwXiXt?ytnBOq`}ezkJc-IuSW??sZC9!7WkbOxeK32r&0i2#t#XVVLij z`b=|v++}Ufg}_w!@kxlDsPzX!a6<;(MU2m?PmCok_FP(`9E;FX;{ zJr8gQzsp6}x*JbpdUzRJY93h33~OY#C3|O6Qd( zK#xN>lSIPB`<4-pm|S{X`z5q9tgsRUcya^OA-JNr(5 ztrGYxjoK|}R-m3$6%}W(U>*XN5WrnZ2iqJ!HXm*{8Ea&$tGgfG1L&#`u1U$r@bGt} znIBzc(LbFaeV7IPCn6=~xi1FEcA}Be?Ai0JgmK@#N3UPg#U>~B-My3g3T=bN#%9iX z0-T@ro#Tz$XeEIUlbP|2*^9hOt+$p=oq@<)=`{sNnK@CO!U{M#f zg9WJKEo(q9n5E))kslDS7bv!SHB7`QP`+Kl5!2B)SipoDW4iQ9V6|H)DOpj-gE>V- z!*JS=$D1kSp$X7>9FU2+-dR#qRJ5futs9N#{`T`IJCAUWwop^|4fJ$(Z}-r+ivE5e zB9&|7Ix;**p>qIrZHK>>n1qBx&5eUAH{OK^9ma_CM2kv5|DDgvS?^Q4g~J9BD)z;QgLwK>l)2=!Yrh+d(m3f(+$Pyg{NBjEh6Rn2h)76y zy8WP&ASf##0U&bf5j}_cNdxtHa(X}}w6&}>s^nX5y^KT8PFmW-vJ08HbwPxPt3iAD zU2DcyJh23Clk1?mQ%OnbOLKD&y93ZXj@}SMy>as&-Tv_ZkgkgZykG8|Oj61IoVStp zB%T9YH zCUvx}+j0Qh-zRP;M1vNB`2?FM9@sT*iEwWUx>|Q%pT!hyU|+1A3Le4IRRXcy7`V>>~M zNUp?0ThY7{$EG)CR>6=Spemi+OdtqblQEQISF~Il-3g5G3)xL>NLI`9(S47nWuC|C zJb`DCIASt@P>q3|BhtP?b$}uByF@^y7&=~W3GT5c3|xq2Q{AXBGl*CPS&N^LdYtacpW}a&YVZ4j)~$IpFo^(H7F1 z^z>aQayK*Gv7tvr>KGxb5F*;%<7)SxJP{A**2E+mRK79k#8*< zBpP#6R`vGI@-`+UCLY@%DK7p*uzfdoqCH$pHP3hxELLnzFLtTW{h-O;zJ1%idBasn z$;-I5I%|NxByO(Mv4?d($VhwZAsL+8d=Jd!qJoD;R(jyHvOpu6%lDr@FW$Y&!ngMe zBmxKmxpr4bkXw&xh))Ti1!X1_L}e=RJ!9o1l=o z4%Df`5)bY27awyV4`ht>uh)UEd%qP+?ct+G z=RoI-+w-5{gbtlJu^qz)U!d8Lkx;YtsWDR&5xWNshp3TX?xfx?`hJ3fQ1|ihoj;I5 zwcKP|)ra-q9e7mFRTb()RX!4n{cuE{F=;RMzaL45n2XaPWyyXo1#iEkYp9aNq} zO&5Ue(0a2#ouJUn1a#?ngL6y|6cyxBGxCoI@NLTg9aK`HN8IC!^1{nkuQne$b_|Lb znXaQP&3V(QUJ1~u^}3FS%1TSST2CMsaBlMp+a*SB-R-^QVcsb1UBE%bh*Gi|E`YLj z_V#+<6G$CB@9_|fJ24h`+9Ammg2ZA5`+n$PxSzm{>4LeKMkK*sYIAe-hmKlUSV)F= z@K-3!^?JnvRznKv>dd$${N~-8O|)w8e9ES0IN{B3^Dx4&7f*F7EQzFxTff>|L`0Mc zIGbxRH`eYM9nFsB-#IwA740x~vg%^BRby)&J#L?ag9Bk0p+O1ti5xgU2Be*pn~a0l*j7S)AfrTY(ERgkIrO5$(0G z=c*F_nMzqTm?AY58UT&bIMg*Xl!-~y`6-NY&@6N6?q1k7xDE^uhX8s|Z|HOw@1qQ$45tD{!p#haSYEI^xeawP({K@hj;OBxB2UJ4 z*;rY9#hket%ybPrh>T#Wn6IA+@Dw2|7bxYx?3#GA4Y*t(LtH3`AsRI4F5H7AhYJGe08)6@6SagiT#$D(S2=#V>}VX$>(W=4pC z5F4Nmpl`9wz%3AoBtmBZI&g1)h=sKHg!!?YL^r2F$&uaF$wpxqtould%2$ zc9*pfUsNXzly8M>b5;;RcMkca{X#;F*oJ=niV)n_7oBKb)q223W)*C|gECp)UNr|x z@-mPv(jDWVVRt}T9zK429?viPQgLo-4dTT`ZEZG$bzI?}15On}_iuNcqD;<=wxhjp z;Q}=KPuS>OTwI{kZdf&#d42$)4{q2d47$1j*}&h-Qj5n*!b2wrF$&TumN?^xQ8_6ZEPAsz#<-R3dTI{e*m5pfqW3Cdmw%IBK$efb5CeBg!dr2I)4cHs28tZ zJwy@zR!#*u7S6+=qer)%?P{I_!JI&x>^8FM@M!w}TYG07)$`i-`=2?4grt2ZQ`%H2 zN=cGLC6zIx(j*NC4alrg(Lik>84}tWjFl-ukxFQi5J^cz3YmxJby??}^*rm`&wa0T z&iV7Kb+2XLd#K;<`@OEu_5Qqv&lP|Fm9NS5$mekGKJkU+q(T1y{lT6r^pVJ{QOQH1VYsat4kTf;vQGAe*aun81FTpeNc zE#@v3^U7M0M`uD}GBJ9PmKNnjPSji0Le9;EzyA(pY@QR_?ZWD}0|ha#bZIz*Ag%q} zWMBQsUYWY;58dAcr;M|)v5`VHu(SIIUz3wwSP9q*nU`u8#d&3@+j~5M^A^a;$VfuV z*;1QNTOvP*?+^7^Q$r}b7opiR3?5apy8uV4F#nue57yt>Z@FjGb}-&!2xm5A$=|0= z2|82L`)QVc;jszrJ9H2|#Hc8WirvPzPlGa_yzA)xTE}4hXHCXhhEtuKnAi9D^{SG4 z$Nr}m;M~1oavj@6Tn>lGu~UGpMFTYq!P8Rc}$J_V?y~P5Zx9L8|gj>_ORh)p_slANxTw9j^m_<8acc5WsBP!-S+$ zU3Qt3Ef$WB6~4Z;KbI_761aE)yWULE6h3Kw?C!5#E#B=DW;}eObjDyswS>Q;$I?P@ z+qc~Ea*80-fYb29wi6bCT^T}8(Pr{fzxG=sXw>UWLRhiYh?n!PC0yU8VfLjL}F zI8?aokhV?u7y@97U)@}dr?38IzNR!;p@d_{Zsv1?!&-e#rQ8Q;3n`xuhaOs)5II}p zNR+eE?U4oksSmrOAH=RnIh=lv5e!-+;SY{|p)pCyBcLd<=BI-Y0QU1PF4$pn*}swzdwFK!|2lszGTB_h?fV9E%yIu%$j$hu@86=$;2h;!LEyA`CLTDOjlD6@z7-Da$cr_v- zB6#=ie(airlP7z!TkS6Sh9%8jv`9>~fA#8>Seg(s0(=dt`O4JtV44j<{BuvoHE}-9 zoqMyiGJDg+vhs2*RQQA6X~Tr4sNy8_AZOx&Hlgii&!OlF!@NdGMeRScF%uC>`chiZ+40=E&xjD0tff z4LEPFWA+u#OyFY{=M&3p$R&Eb(9lrfzM&3pqdP{wcsIOu(IwxmR4bE}E45f8EBJCk z9fJ0&u4DsB&=OPaFUz5{#3ypaMQ($zbs+FwXPwCO?;nB#qbFz_Qc?)F$3{MeBYax? zebW1Pbr!fWgMxxYt1kLfK8Pj+q@WG{y2vsY-jukwZXgJuN&qM09sK-sFpUS%Mgi=E z$f`MJOdwsX_etZ0In)2Ydh<`z8y}j-ZD4KON3zxj4JKi26r~u_NC`w4o!a70W54{I zy^cmmY~5LM37C8btE>0mbh`nx?=2&ud2O{9!@vBDxwZp_4 z#%V8T6KpfWXxOkeAfSecV=Bx9xsy?>arJY1EEpc;2V>6;zlfN?7?q;2h2I!}M0>@nevuFN)svy({yFWhp+o`Fw^4dp zYJ+5;O%rhkiE2sgwe@x7n6MU0jpLR*AN>(L<7qc4i{^JedDPPc`GT?!jfSN@nuP(rmq1(yB7nfd%@7ql3-*RT&)va&)E=Rf^| z{LA{fyawo?f|c0T9V*=%tWsQ7R_jXq0;#Cx?b~N=7bpNoPWwhL?q*FLwVEHR z9HlgIc#zDj`SZ=Fc4lkV=vixEIMkG$3`Ue19N{-YMhdN8%35Q@h%f{i_Vc53WlhbU z{56#B+Ev`dG8&iT*c`t(d7Pko?pKtTE3?jO@=?Rl;%e#ZU%FC;T+v^b9mnm7jm$H{ ze(XekIeb-pu!Xg?3a>Ig5}G-`pukf%Ez>>4UZ=9cz}m8|Bx@9&olxpaZ>f&8qCnL? z+we*8)Q^dqe#X<>%TP%i6tLr(0EC1m7Ih1Q+Em^-^G`ZipK5s8#;r*2U*9b%s%rpy zv=*dN@cDkQ>dNmb{yG#H`Ev1TbetoUoZb~_3(sQ*9YzYj#;y-=XqZ~zyJkO20IvC8 zmOkQ{U>2oT8+_s1$>+>719f~s_|9jchjD%kp#MI>`4zf^c+?w*K5r(>Thv7#rfeBH z0QJ8U=*Zv;9y?9f+dKVp5TFF_Nqp@lvwRqRwiTdh z;%#I>Tq6Qzt*@Nisw=h}QxjZJ&2zuJuNMtFB;ENx_OLFzn|EP9!$ol|?L+S{6VcPd z@wMB$ZijNvwfy`r-DipFc#hyby;og;ow8OLF`_GA#dPCFL+Zh(T}dxjlF5otId}TB zpzlBZLhia>QIW?$g*5@3f%7ldI%DxEjAOmd#J+dlifRW2V&ONb_xz3@T z#1OF3^MzO1KQ`YgUkSW|`e;A6BW2)4o8k1mn(bKr;`60@l#0$cE1+%Kk)lWioo+0v z>rmeo%;%a_N)J(njqydP`+Xm6jXNB3HkA1$qHVO+qb>bkm4{rmUl_-(z* zw61#vSHv9OE7B;whI?OviD78$t>pz+|OeRsN3L>whN&JP|u zcp@kLuN}X)Snb1nkJ)cu?&kL3Wo=7Y*_=B#5^&9mwg}t=#)WHLSY>nMSW(fNWoH*a zF$}RQE!dN-Z=IkV5D*|yP-^5Fy*G{9Z#qvuWT+^PE^147LME`0nriM81-^ z$G<4)owmD!WYo#LD5XG|AzJC}v<;2oN<2%v8(jYwFkmo_E6~FeI~gx8FY#?(Dz@89 z6$w%=YC3w(@5$MpaW9u=WXXX@g$8#}O4J z-U)|6Jz?X0>GI{tgJh~9Ze=55sL|dRK3h%_79T!xJ50+qdm<-t15b{F3iQ>($Bz>M z@RKTRVA zEfO_cRFkZ4zU$H}&ducwxHTU@oO7o1Tvs{#UuH6W)=q7k*TaNrw!-e>#Hh_r0#j z_zm4UYbI>2J0mCw32fc9tM}$xAu1kPC(`K|V9K*rpd1VeGRso$sh}+)V|l3dD`_@u zmU-AFJk)M?eyt%JPkEdE{@6LX6Vx2Kuw7UJaB((GX${X4|CzSMbxAt>n2He#9nMF> z;PY|jwx;H8%mT< z1gu_IVsp9C6PG=6dT|SGwRt?LwA(E9#>shEXAMH2Qc7HrB|*V)wE z+^iV;iytY|w{LFnK7GHDAf7cldTzee^r)odY?VIt!Oe`yGkJ(Rb&`X_K=>k&A^8XG z6+*h6U*lgU8jIF(CL0Ztb>iMd*?L&db*|ebzmphW_>3DES^A>#EkKUYf>**oS`~d-@!yDGx)M9=frKFOsYc?^nrv#? zC8VdKF@ZI)5*sYpu6+9WXI;B)4Ga{NIu3~sZyxXIe+a6D2Ynigf;hN%uIxQL+Qz=V z+QKXN>Q(6C!dT%!gkt$qP0a*L^G`1`qa6WKH%LYbE4c)V zN_0@-MXw6Xpex$(Tsg}pQ`K+WxN$fssiL9CV$u1?1+hYEVXjUj;8PyZd>c|NxgqJ0U@AoD=gIfuFu&Jt-RcbWI1Uksg}H?lopGtdS0;T5DgoJ1 z>LY!w8 zIimqxSXI{z>&F@YH?}8LUctW#<07Z$85|R&p7wmIS?cC?Yni!-!}3doKY5UhAo<0b z1lR_015(RtqR1vrPJ>xD<9FAOO0YflZX(&=^Qt7I>Rroq@)8XX{pPtd6L9k%1NI|u%PP@HCvrvyGvnuo! zaP}6LmJWsesI{rRvZi&bkTSsHgH15xK!?fn>eXxcu`-rUJ`3gc=5`&-uomPK{JV`- zMmOe)%Ze;By9*j{hj77QZjaX z7Dhr4XNgpyxDTZ?*V`zi{{;kXwBP000Fh1l(gmSEdRdj@yO0RVF@TpT1iSMNse+-ZbE${fKm3@gSy$N+OItCtOI z2KsBW6)RSRv}Tl_JgVMbxS6kn?aX{^3Jbq-#fm{_X3Er|+}pQ9)fSu$65cv}{0^@C z=HF|C=MKZUlLhs3yREU5NFkD38wBz+S-5aNKBJ6BvG)#)Hzhj}$|_VVoR%8;Mh!pc z(x+Mb$j zNJfgElAd$yYE>BiI={F-|FQz^!`;is$Viyx+VlLG!|9d~r?5lQ4-D_N`BsmhO(AjO0wBUNLITOkn-U@{^q+naW`c+HXv;Zd zKLvL@V$NmtjUtHkMufJEmMv2+P%Y>^R=L|&!*1RN8)p8n=otTwiQ~(nKI&^p=X?X| zyOYgny*l#Qtomgl@+jmK&|fH*ns>xTPryBgW9^LhGee9ZT_*neK1PzC#m?`*<{(r%VUa777A6LMh-?yJMNX5OFfuW2{26-|wX-0=CCJF65&M;TF8k9NAtF_Uz;Vcr|YDnzhz_T$+39*1H>FH=SQUKHp35 z{2~MA^t}ktp(km-(@erXdi=Ps!H9o|8(sMZcd2&~aaVp`6eFx0$vO#VJ>CHV4Pkr- zs!mR6Y0REmB1}M)qsQyaz>RF-puiZPG5*&pSU*Sk&VZlS7pfoK_A31T_1m}K4p)Ew zsR04{g6!w2OCblTPn^7uEGpR01-T$}RY7HhMAF;V++@<6s3=puHRpMcGxMb-W)>DX zd?%b^+v(|tXlV2zqq8aBhL!u}vT;b`pH)3`GzIMg`spGOX>_!`-+@j-5~?<-Vl6-d zI|*nd23g?1=;iJG)+%Wp79&oWaK-U*5(1d*p@9)xE%dBK#J0X~1nL@7?{6|SCIU9c zn2#h8E5Li*qyLY>+yHFZHnBZvP*N)JZ3f7EDH=x-#UK&_1+iWcF9Q_I#hZ{x3Ax$ z&D};ZZRyGTI>UIM=8N%I`?3A-EyJfCfS$1cUmtAj+}zOM;d>Bp%bEHTRb_4g(%y+@ z`*pmwQL4p-hB3p1U{jr)tAed=%FNImJ5v0X-s2)gQ&~=eWox z^vIuFKB53nX-h2kH_&c(*H|@SxntJSPX6A9)#s~FX2j`|3PmMRZGP`duWg?}?`{UbZ){wAB z+z4~Ybs`_crZ0Ba4p*A^^z|jB`q!_gbB2fx9M%?&!G|$KBdwduKPrsme$MT5{up&d zfJw9swzjKka=WsE%G&InK7E?Lv@#k|xa|q7wyc7}G=hCRD&MzH71L}AVT{c!ED`}K zkJP&kBr>lGzc&?ag2`3dnVN1aaL*?#5 ziA{>-hLjmfZKrA*8w)_13$XCfyNmb<=LvDBDM%UIWRk2EYs&RKp*T5P5qpJ)Qd^-1 zlRqe+dF~3uPnwuB*cEi=LdIJ-0+5oi7$iqb;(IA8+$_s}U2~BYK!86IbSfWlwuuKjy0>Wo~UfPbsFqvD9D%g?-6hgBrceX7XRG=~rH{VQ%J7 z8U8_#X3Q`v@u!SibgsGh^TzaOJ+_>T@m_OT74fIk81tH8;!mgHm3b$2al6I_58Z2h z#ZT=%V5MhAC^V0T=UWaOsOtVL{X+{j@efm0cap$8vcyjlfopq6*u|aIbD(Lgyfo(j ztvDK;pro*y;N%i{UYySs>gO30Wccg*d)x9K+FG;$J9%O;n-CXfOhnhv8#hj5`B4A7 z%BNbAgn$U@1RSx6Vvc__Z+*$5NEu`8&ZBF}q~xVLpL}}Zde{E*KE%}@YLZj!*HO;y zrlY3z!vtOJ^ozUc^b7*cW!^5>n35dprSiMk}d&BS}Xck>O?_k<)f5ODe{@N?PIUybV zNo4Q4LT-Vs0>OHQS`V1((Vi1})*BsK5Rs@~ReQU)UverA{cD>_Z8?yf`D_r-rQ8)1 zqt*V&<<(JKIKYo8ZtzWaFWySB%7^d+G$8)$&ezBk9f(eeBo4j(h`eRW+>d#JPJRD= za#ATg(p_9ow_yt+3do?{ho$GphC*|~Fh93II9~=>mP2rgEs0Ny-@x9W%83+wO2j;t z=pO@C%C;r1+OC4(niD=6c*MH#dXOrzslbg`>^3v)?Cv1*ysA8>BDf!Dkj^v1Q^-@7u@rzP(a;-+lQE?6{}w-dunkBdP$US5S%4J~7dHmI}8*zaMBRuPQ{rde_pQ{<+$BJ>|Z zeu2>!IV0*)YRTOfKPEEi4cd18)%D}xsj%;fnMFRt8WmjRj6-jk$g0eIOYuXavq{SS z`Qy_f3{GPB39{|HLe5_jJUJZW`$%rlM=*=%fU-rbreEV634I>UL^mb{m1nLcAo=y< z8!zp;2V_({@8J><&|Y0Sp^D+v!48WVjA83NeRAZvi?_8X8O5sm060X1=l zrlH^pkJcUJM?qf`q&rvh8bxK#D0^ohw(#9UMbq}JZAv>YX4%|Ho}5@LZ~r!iSUvFC z*2Q?<0jO9Pq$aB*EQ_JM=`&1}PaJUthMoZ-ML1u>zmCV`IAvz|*T-O$KL?b#YrCcG z?j|M01kqslH`3n?NmmB~pk57IDSMC0fYi(0f|UQC<$UZY6x9s5CmN?HNhF(+u6BhHlO_o zk#t-l=bE$ypDl#m&n-@Dbj;{F3T<0Ler)*la|1h=&&vpkh^dJm^Jz?7&UzO$yoG{i zt(lpdC$KGuQJjOQ)VJSHNjDiiSLNUi(~4XYO%idSfPUs3OLaKa<)?)q9X53MtX~cU z&t2AWPGG&>4RpY*aGR;40I~Konea&!BR?V;u__C(UEeXRZ`Cn6P6N3LPTTh8y9>~8 zq>_6XtWQ443?|M`af`9l?s0m0_m7(Q>8WJ6bk$M(0hVcnWdmpI?z+^EEH8KE^!x43 zzPUqCH&hY`^gArtrd46aQpJaFhsLaF=3?`04yWp&&l##SkNZSEOL|opcxVe#diY&{ zu&%1siCQ2WrwToDxz*6BE9)w1onNB3LpnQZZ+Bg`%o4h;Qfsf^$(XTK0L6D`{w=I* z*Is9m=Ck6xhL0^`q(UOGa}@ghuf5UtBozH=^e(6*Gw#jpdu-aT3XYi&NKj{QZ;eua zm!ZQF7X1{Y*+oWQ?1l=_AXk)0({?cnyq0K~@7CubKI zBGu-Vlthywn0UqtFbdNV78O;`RkzL%sWgy%`t=iODN|$Pt(Wc_U|Qjmx8fCe&cGe! zEQwAPtBEt+HM}{F6a{v8%9TiJI^|6G)})2{LV9JX6|N6HXOSD{ zr+WBb_H*>~b!Y8owch#z3@=(r&PAG(Br3LVW0|=+;a2?*0TYDQ;HW9O0lfSTyv2++ zd`L#q<)cehruVbE zoDk(_*}nDMSe1M8rV#94$ctT1ChLva|~K)^V4GxKphjF#c$O47{+!Uu5j#l zUQDjfOxPL_ASd04cO!QctS-0LadGn5u+neEp!$N{oxc_I1AW%aEU%i^L_eg^?tqo) z-QBA4(%bsW1=^(SJ_=gRRkfB)nrfU>YF;x0gqr2}Zq9+#t31(lpdGDxr} z1VjYM8I=qIDqIi{n0>DG-s|=H_3Q5GnKf%>y4I>HlzadChwnRQpS|}v_tK$*3Jd42 zn9s(>woplNuR0ssZ2I?)AAi8#ge{-1hyQ)=xLZl{NBraUqv>z>^E`V+Jx4aSB~JA3 zcdcf8ZftD)Y)X4~X}aD2(BkP;?mScUsoS;w%!#>;&##KEQN34nUwhFF3$Y?YPKQ?+ z_b>5eU0yAsxoF^V!sd&N;?+xZ&aFS1AoWLr?*13M)514xX&M_-NcZ;jZh9dnSHSHz zIgqxpZESMNJI^E}amd-KynlH7nLJl)jU^|}hV5bTeOuS>=qLNg)r$1P>0RFo)1QC& zjeidP|A)n4^uK)mvmEsQWqbW+(f@z_5tquwcI4+>bop#Qt@+n)&gflt@c#NMg^%M6 z(pv}B?E~wxoMSg~UBTa;tZR+a+oHGec9RCjY0vpMhV9w*BEP072KUO9o+`iYJ(_3Q zHEt!FLeN$x*l@PeE5(*yZm)YiBfQIuw`>bQmglvkr7_$66mJV zIc4|T(9diNTAy9G+1N51NBU}1%ICrnIDqN zW7NZy(_^ZoYo8n~=;-Z@zJH%bP*AWpxm>R5^#kct_hGYm!_3bvf!)9S{`>C%{{9a? zcb2vdsLb+&fZ>O9UYyTsKa|SA`TqZ*4DmZdF{8~{0oo0 znVy+J*9)KIXU6@eN7M64ii#A(#Kh+R^pob~=s>DPRfLzLZF9z9HCblB%Rq5)Zfo>{O|9LHQRzA~yKi1>Tg)1f2Z{DnaqV%>_w#xv=&p$t`uGSP35Lmx% z-TtNP4m`Q0s-~uMg-eu!k1uhDb6=ENh}_GvvYHpyR@S6i>8#q}q=*}LLja3nS0!KG zX$hOg>Ot;K`H)_Fj^5s0%gV4zgV{rJvSZDT%Bre0LLpv{YvT>9x?ex&Ys<@1*U+d* zvoSbfZf=D~?9p}CoMYLtC@hA?vYJRWK2hB#J2^LKYvR>-P|-tqGscO}K3|rwYmH0H zolq83F`kt7mh~L1#@VEve6yzPzSI_zncvSZ5S*H6$P5VJs7W+2;TAKj&`^^N$13l~ znpRd;u6$p3Wt-c_Beyo_yskD6;NCp##n*%nt&Y|Z!m0Gz`A!DKXvfENbeLjKsGE3w zI-h3U@EBL$5oA^W&3r>2!=t(Tpe zoSa;f&iAc zxz^g;6Qx|S0{!RaEwxo1m{XH>T!a~0Z(e9HHvB_JV!t5?tWcdT&$yVR75Ai`j)Axvhq`_ znRgfY;%segad5lb@fVh<`ti}x(b{TiYF3#JopHF!PgnYWe7nmEK_V{IsxHl{e9|@M z(4(`=`lRFIwQU)vLbVv%wr{V$cmMwV#F>tgn`wEzQ_MOX4|`d!std%9OjN~*6R**tfQb7V+2;=tWm zO^KZkTwCA#acL>@<%Yy4&#}Q6lf2x`Tej3B9aoSYtv}8P+joUaSXS2P&6_tKU*26D zXf;VTQ)TwoseStNX@}Pr``mF8k6yv16OO(T=C65_Dqr2*!uj*h{)u_V3NNSSl?Tfj zFh-^)hf1res%#AtjdM6wtkCZ<$7-|AF8>2D3|CTTmp7&TR`62c^8-0-hn9&A%s$-| zW{_^HpXm_3V(ZC==E3g!%Yr0zZmv`P;B)j=7~Z^INGP&ma@W)3WI?5Q^X8>L{%}ku zUVmGQYn!)=4OWeRVqzkN^trrl{B54IX3a{msuOGKr>rqEJtbn2`wWZj!K}Jdr%nlf zHm{D=5zm>Kp488 z9&&PVX}`^C^PR~-wpg=fO@cu?ZR!|JVNsk@{$x#pVdi7U?$^Cv$A?`4w|Tg^MOoIw zjK7%s!w<@g%F4=w?9po(Z#=$xtB-7JALP_mt%UQb!cAvlCsqFYU4>rXaWi{A zI?rEPymo)FWh&Q1GO|*W`}%aPR&z}}Ei0e>wekZ$xys6Vj2zO|zI|KNU|sKw zPjgpnvpm`G^u!L|>1XfXzaN?CPuw6Wse|jNn#<>P=qH7f;zMp@Lv6lg$3>%Re}Bzz z_wW!A6;(w<+^64GTH2o#;@tW2R@thPn=-5V=GdF_D}Hx+_Ie-*{gjiB(px=LfZOVj zFh)89O_V3v)i(+W2{pOA-FG8>X;ayR@0V5Ava_>y@80dyQ?WWcB7&>*K2FmA=FOX2 zVumN1M4xz#16xf@X(6O*rP~@^TfXH*n&?ztP3)EMVENLf_=;lJ$Lk&S3Dg~u%!>U43dLx-YKro%q(i4ozCnL8~kEG7^{3_8YV&7NKJ zc;5o=nW?dR+pKP5(MBd1WyfHbxU`QBw#2siOp3Nm&P)vH-uD z+10he$E@Ce^&SBw@!T(u4?fs&D{-dWX6^o~{sI(8BGp1(OO7JfPGH3>jGxglHa32$ zyRq=|s$5Rytc)>xKMuC$b$c!@s)^Q!tj5-xGiOdoLxUcIRZX3VZ#-gNe}j2Q>a_>b zt_jz9Wn*+qytS@yi)*{NWq5geJ5P*Q0H>TpDALbzinftV!`+>b$TO#3j zf9bKLp7HLGJS%aNzN$z+x}ZCEmJxmeSl13$z7=rqUQAe+0Ohg1mh5!EOPvlW1xV@j3Gd*Fq20FC*J7HRL_f8pT$?T>7l(u8GX zo++BA&u<4j{$b&YlSub{wedkQTCovJ1eK$JH1Xr@N13+!a@YoCQRrl-zVPEq)QZ)n zWr94$z`_*KiU|XvxBBw&-RIArpI#F+NQ+CeX^I$Y^;F~Y{hA~<`C*Guu9qGVwa3Tj zbD5(}c1;qI{?i#75rMQ;NV~)VNhcuW8EMCA;pVv7_%p%~r*(i_-aK<3PO+$37mnZn zoP0|}tFq@!Xibi%>z-j}#I|35SXg;}0kF>cE`u zzwg1buel-NT2Y5~iRz~u0z6z;F6I8&6wyjC z*}S|Kk<#hY`+ehIzC;=4c=Wvqz1UK}>G+E%rJL*S$$F-_e5il4&8oJzpkU8;-+g!a z^5v&Z4IlT58b8y;+N}T(OmXT}2Y%Ly(|u#_pQVMxnywqSfAATrQlw>zf0ph04_AnI z^$(wRwi*Z^-6FE}D~c-@QVZ6sickf}wfHjieYAloUe;Ifs9->B zHoYb!&NxRO303l|+}Uc;CCiqr@AdPI!ZU8|vc&ICdm0=&_IQU=&yh*lmi2fA!&S-z z&xmu$5{IBzs&@H%FW6-tO`eAz{})ueILQBex%_|T%~2f#VkrK{EVdtQ9}rs|G8Agi zHn@b1&2{nXe_hf4%Tv6#NFC|*-Y-YbBCB@{42Yu8udlDS$`z89HbC-xf_!iG>duWs zd88Xa)rhmd{@U5shbqjHs@6HLZ=sa_?YAE@v$JU@l*`XNLyXnoxk%*6+eKX!yZR98 z06cC|&8@)aouW8&cZ>OTpXu@L-IFFh<0p_R_Q&g|R^FF#L{t_e@JJD^B?IM&?DUs* zkk%#&6$iaZezDyh=Mh=al=)(Z>>MPo3YioPt)j` z+hK%gEksWNrK3lW(gz+pew>q&Q|pz&0=lY$?<&m0z7_U59iNUDUDsmv)nN0F&6=!> z(+mCZ!FnOj_KI4wB;64|->-X0N=h0t9V6qXz(r^f23W1k&`-0D#({egX>-nm(h{~$ zx9{1azW+mOjsZ|yHNvH7lJ0z-r6@}`pLiJsRPQo?vq_u&_|g8s4rgV+b}gXs&t;3b zxFV2_Op;9SHkbTPye&*xj zLyY4Xpo?K^KE>maeCaVB=mi3s1g>#0bqMjd?%&_21UOiQpKY3MO-@es2b%cYsIg29 zT05kkJD*de{>4=0RO0cRoSX%+D=Eso9dyM>g!oSD0V*Jq9k#cBf|bpzKklbAaeTq> z+qXwn$$9BAfcj$qzJ*t>er|gmd8c7QP#7tAtj#wMFzj=Dy62c}({?(&u?T57MLFwVKZwwKDd zG*k@z#W&WD5Bk#GvbQ_+#WSqy^@CW{#ZA{etm+a{jj|+7i+={HP(ZFp@>FY0@Ls)o zwaZEw%{bk|1sB=tMtZAmJ$MjLY!y}}mGdaA);1Nm2~y72&-Kc@Cux^(wBUm9+O>O7 z%Ga&+lX={l|0Ks4)r@w6p|Db4YfiR|jLc7EV?dw4#44bL zbU?XK4yPgww2GaEp%O8yn`>L6?;F&FZ^2dTOK6{u3x{t z?ZOgwpy#c&&D&Ax4Q364VLI9V;@X9}x%67s?DnA>bQ4Y+T>G!Gm^jM+Z8817%-sI> zHRBHaXQ=Lfa>ICbD4sAiJ^A~&d6z+-rl+SPFCr@pyy)oa5|NP523#iG1I!x=JV)3! z%WK@>%&-rjC81zLBO~V9U)e1|Ys|SWja)Q6J(dS@L4$}%;9KVp^-1sL4$uj^E<;kz zo1TbWr+P2!+_~>%CwfBXT+ejsWuQWdN0DujfBHL*?gy?Mi2R`DqL9T9?= zt05TNLyASo81`t-#lCBjtxr}4-nj7y=-&$YXNA1aGf>Q&$Se`6L4By$B?s=`)1I!EabE)_S z6uu}Eo?I&fg^Fl~q$!9>TuJ@FfvZ&EfMsZ^wE-PvSsO<|!LqM9T5|Fu>%-Sylv~^R z{{0$M-B#C6xwxb)IPAW|<->X=R2H0ggt1EtffdVzLI_0+3Em_W; zz!?4IIM*|?=YGC+GwjKeU#9TpiK(%+1xAw}8j=Az{ae(4K6{U|a@R7RiAuRGtzN^b zw?qxJQ3)ia0(nG%CH?&AQ_{+Q+$S*cy$HaJF!0PLPMj!dYm;w#=5B*(Q5(T{fuS^r zXOL2b&gr&Udu-%8dI{U}45Dp%^{yJFJs?G zh1u%_SyX3`7Sen`F35f^UOWDxGk!RKHhWL*oM9&VMmzOZUcP!YB~=y=tPxa^(VYp9kxXWtNq6=P6$1G= zh3|%L+(T_$Syg2t-4}0^tqmx~2wT19LNR0~s*u4@?)T%h8QZvCiE3Z$7=d2D$$RV6 zg5!V_qmlV7Te4C~&jFv=lUiqd6FYyvd-mK#)ssVciN(c=*H&y(22!jvu*(rNF)_*c z^8QMCPY-KBuer#szQycS1qdhWM4be~XwWOlj4an7F{K^upTh(!u;s^F+mg-I4E!0> zKPu8;h!8_Tzu}i(4q?Bgp;j{+qno1!TCwK0#cQ{oc*y}`A_nZ8$K*h|GiVL&$y2m6 zGaR>j`S0-g`o+0OH(q;><0y9N)C4noFyk`k2Fvl!ofo<&x7&|pZy5YDsb2c;r23zi zLvauP8%X@`O{xocg8ora8E9u{Ynwo^ch20or8PBLsg^aB5FS*^XuH2{Rfkw?FZ!3n z35KlqjRIc{^q>Vif%osn74i8R^78WD+u_nO6N5F90Yg0j2p17HFU*;8?jbHY#epQYBuy)v@aU(elXUw*+!N{=NXbcQgV~ zyqHlI<&q0s-_uqdI@O~x`K``$Z8;Uv)5elH~i@4o<5%4{;MWHL72SQ?5`WB-j0Y60kH!?R#{2u22_iU zTq?Pz7)~H93mmxwzU4ScpF?;v8&BgZzAzvJ!RY)$?R|ZgE8E|{KQ=k6-+uMAo&7XFO{EI4% zIe?=3(W6Icb@)#faMqF~OFp~+qARVnb2>;LF&2#CPDvd^T&iu$jt4UCm9=Rg_E$+c z{4r=84l_b5sEf;&E+wUcRzm@&2HuT44ez8V6Eu7BO!G!Vs1T8rZAlAlZA`Udm8?{k zqo5*@1}cgn-d6|nLI98&D)Rmt3cQDhT61h1LDT&^RUN1Mx3lE^H!}`9JNuj;etl_;B|IfLV6AFAr~T)O!r3yz$zW%|f__ z)1Frf=0-y#;I=S?$w0!k*{$W&!Gi~h#6=COWBu*crZ;nru?VO9`2V=W1c(2>JfuE6 zwm=>1H|!PKr&OtSWm^>6aV_^0(2IVu*|prMQ3IkAz>zS*5V2yx=Fsv0x_k(Qlv5|3 z-tzY6pXNk^JR&mQE{A#hI-k6lot+)k`M}MA_$N*~7LrmxKtLGa$VrG}G0?P(y*}+@ z4z-y8K4j??(RyP`zxs$D(NT*O5wZ`o%}l%EFx~gs$3Q=0FIfS1-vg0?kFbr#7Cgv`O~=kjSl2(SGxjWVxt|I2Q>cSnmC1Cy8Wj>x3K#o#^LxpfH7`7{k`m7_d;V~m64j2#B zC+$Iqt0Ej5EbG}A%;(JP(;`R8@fTMQTU%S7s$resqUY6SOGEcxQ%5YSNw+hZRD!af z=QA~uH#4OMfw&q$;s7VbY_{uv;H7gH@jNklt>aG&nv7e;E0BeFe)b3EM1zMT2{Q|h zkK9}csh0QySsLyN(o6E+;T8{t2O3vGh9;$G;j&+LBFWtnSS9PB2`f`ouJJuckYJKKDM=lQ5F=PkRh_e9#%z-8y>;z3@Y-miY zm1yHYPBU*RWM{o-T_M9s`S!+W0PYy@VuhPl(O+**IW|xIB0FDg(($4Ih{~}w_Y(6I z{CEqKSczx*_ZqwUB13%Cv8z%+8PA#>AfUIAi{-F8oqai)zb4Pumq5nQv*GK!o*y>3 zySsDh7}1g6KEVy4ikf6sNQ{?bdFik1-6D944RXEQ-QCQqAcjl`bq*)6m?4Z}Zy?#_gaU1H{j6V9~Y3U1ipE+GRsuuwzR4%mz?DB?& z8l=~p-m_^{SS^_D8*3H%5Hnl`okLD+lqPn_z_oRk~ z27b0QV=Ynp?bz-x`fRnT-8j&kxy7Un%XceG9jVq0m498UEo+%wL3W^R_RnW$*8x;Q zJu9X}^(7v&Ehy<8a4PF>|Ltrzd3fUR@h2f^6A%6LL}`k3!{+eta0{|i<@x5pL3lY@ zlg`clr-+cXo$c+R>1{p+SOG4ls_CAxEh?{G9*tem2Yc7~QIib&U0{#5B@+;2+s=cS zf@33RoO(2ci2Me+;mN;UQ8kwB2!bPb;|%_51^8-qo8HHE@wkX0nXLdv&X4LqPQ-2B z{I_Fb7dX&WEf`Q!R6K<3ShkV1xE2*@`h(`G05t@b2!>Pa^~6@sAz9e?XLJ-nSI#&2ThIx;uBJ&V$ZSuxJWMWqAVYh()jG$Rd#!&9$K`aRo zqpTEUfUQnFhd|7A1c@WqQ{=*jfRc-VY}^ArBTKM|Rz!Z%T_66$yu3WP71qbr!~l3J zF$VKye6U@s3OQ>aV#DFG4k(Eh987E^*sMEn=OIKLOWe*Ck!M@#%>xzZVqPH6+oEwU>$bRg?Eh z;To76A(*vQ{w%0Og1?7H7Bt(V+0T9v5m81igD2w#5`6~{86*v&+EMWF^G zZJJq57TC}S0S0dhOo7X{Y|G6_vu&|-xzwL!|L&|nNnKrCLJQC{@STH;OD&Y_ks2M7 zXoNTy>3O3)Z}toh4id>`V?&;ROG^YfI5|&P!4js`mg~)O1IW04UdOkXK3c&{M6t8Q z&S|9Qu=7x>5z#-nuqpOq`B!0msdBr^;s)PjMQHG#RpckY-kIt*nW;oMpcQdoIfYPc z29mozv=YTuQE@2y!QA%tb}hsUFN5@p=g-5-wB)%JtPOE+`>zs(3!6JL?KuUv#ssWEHg%xo97PQ?Dm2gRTfBJjBYZO9B61Mf42b7_eTvn&Mv2Os zks`gAyw77}bwK(yi5lwa^H&K?ed2#_g_`BClT*s4*{r8aks6>Z6rR?S!ou+U2SYG$ zji9i+qxZNN5n$v?yI(Ap9(;iJbHBV_p!#5kmXD9m3K_R2R2`AKb-cA7v8q2X?A5DR zyNf!RCZ&JR_kBF|=Sn(DPOlGQ-IK8hsUX$EHii?W6FZdC1J$-zOv!5o6MX4~t z&6*@L+MESkQMe|v+v^!{m((CEw{3|<5fe$u7<`s4dd97SKEAgZjK`$48AE{M-f@u8 zqD#+zj8|h9tk3#TW{Izpy+}qjy8rQTG4wTOI(l^8(zzU7VFkPJRR;%$ uZgni`d9X?XrrdZn#A63o)%=#DgJBcqa81kn$Yu_lPjZ@wLX zgn*kjL%~|33fC`G?@5J3r%EnBhmPNjCIQF60*67TBjf~=NHrX0BS1w>FE+w40dd@A zY%7tBFH1_^cn;;5Ta~_iS$THOg6fC6W}~KR9r?x#XB?3CPX|O}WeK}BIYKR1>;%8y zUGXgCeI@NNa?*ReGtlEhQqk6L_S}PHf`e8W4ch*>i*Tz|Von4Z*>JZ3Xw)4Xo|0q@ zQ?WW0XxccL3PBN&aJmC4ojknQo0Nhlpe@*zY+@}9Lj;-Nii(ORmy)J-Klua-m`jBY zYJ!6&3mbr0&S;~RM{jtbbvxF3D41spED=K8WMw9(1g}P^WcTBJS4QBjJo)K;A+nkV z;Z4$Xf^~2x?S}!MO4EJ0mii3FY)_28Nkdx;U5KNO@UNmxCS{*)K2ajJ9 z&`L=iKLp&lbMkKmKmalWYJP#KPfr>V@%ylgQkdqZrl5(d|AJp_B;MqDdzRhZqJTmV z2`;6p;z~L;d7LQpAx|&`u!nXM(HixCgKVEf~hxe9$a z-57)P1W0B=N<{U+(M216H^J4@*^LY5M$-jhm2zDP-`-DalkK9pWnC_;FgB>{Gc4(^ zPD`J~BE%NzfuQdJ#|zzUGH<|VG44a}d24&jGWn9f|IbR5?+PxGotk@xn2=D>tIe$6 z9>Q9JEAWRTlsuP#MgZo&EF;2> zg_k)kv8NwRQI`b-JF|a*9bwst15wG*$OjOMIPtf;&)+#X=u`F(e1ials4d@seZeNq zFa+iZP(}TK_2Uy%caoXd!}IY9l^gZEmJK&ca#*W zv-Gd+^EGzG0KJn*;lfYL9|IPky9D>44(gj$=>CNob6Be_6dx-PjQ~!)H$GEL9Ylu% zj1h=OQLvsyTw!5o1=Aghox&!$GVciN=gU4n#tRKOOZt;#I>5dSdxUQFIZt1FlyTw-g)C2w<~N zi&d#^--suLfJi+hgrI4+$FQmvo_mOm2>f9%!Y+PHM{3r<6UXB0NB2{q7U^$n&5Q9@ zNTMeRTZ7v{@5z*t+P+xl-lqI0CNmT z7gdxCsCalE{dpHP{jq(5VKAp=4JoNl)+Th+u^%*gyWv4aRu|YyHzy@iY z0M)iZA|Ni8nvMXe$@tdUDTwMJC1oqMmR;oYIRu%S*S%wL87yz8VY2m#=tJ4kv9!R+ zRL2`gg3cnWp^v^bIO|z0K9I2k1qgt%%Z~WJxVk))_+ymplHvCB<_sfSx4K*e$5`y+ zLcKqqZG9?>sQ5<>dU(&@)2B`aO(8Q4ApA#Qr|!W{0?hIy5dlIM5p(5J(d}zZV&)ea^z0* zZNPgCLLeO=c88Tu`{hXc`SrHWY@g=6WV+@?{gQ>pq9536^zYkfGhm=4Xqfu_VgunE zWs6&1I8!x+>$DTXPx|iFvJmz-Y!H39wsK81LoxoAqTu3^uhSn(4XZA7Dw^&#E-zZ^ z=h^L<+kL;>b;N(nE3f<2y={M{dItthi5@a6=@nQX=;Jt&j$)y&cDV?hvYWms%3z4s zz<_z(E+8Ti`^~#p%!7YBen%BOYLT(#Y2YP-oOh*I3$9~3NK6EpYM8Z1*{}*KF=YtY zv_8|LUYJ9N4t0r8l0D6{4j5NP#t?~k{*ON%0`xT9e0M8~W8g!$ShZt}bF#4IA84gg zEZceAAM^xjJL>m>Bli%1*(Ju0}@UO4$74L9%bgYsddKQYVBcTl} z4(K$GxW*QBx`L^H}CpDYA<%6sIEf`m5}V<5$FC)LVjhObh7fwLg{xS)`b6+k66#2p|jP23^a zO9Zno(9_|cTUvdUctT@uW)07#rPMi!2CReSvSatLS=WLipbQQNN39&1Ix5M*jVMS+ z7D=}ju5hQ%9ZJ9eZ@S!I#334m%2)tT^2ot-Pz%h27P>nv1@ut-f5jDVvu)M|*Y-L_ zpI6Vz>zT($&m-{B>({K=jWs0mMgnMtbEEE|rgXc&$jC?m@;>x6r6-d6201{5K^?JB zDG#6!tbt))P>FgofN8K9FoZ;<_xW7ZYG+!qZN z!V=!5mp6s{roRrWfw^iJxdZ?RtUyuB>Nm#V_kf@5fR8U5xfU;84F|{h^XK)NuyAqZ zp@f>TgZzA1f}cYiHY6}b4;A@QheJEs+t+@h{~N!W7Cp_wU3dxW)22OF0!gUy6I?5G^mAXmlgK0jaLj0yu~%i7-8OP)`XD++9p z->KsnuAPQ}1!9?H|`bJ5e> z-z6X*(1S-ny`D^AZweK-wynO?Uf1~Kjlm8u2svd>m6;_wXA20fTi2RwGBG;29GQrE ziW^azRzKP^_xNnqQdoNeR0#)5De(Q;&L=T|;>^g35e8nU)O53+iM%3$(N!I@onY38g5ea^ua1zF zef1RxO9TxO0(ya8T0|lF0U;*mjxw2A2M!##FjZMjQ~>`NNp7{w0Z`M3$|sW1o;h2hVZgueQCbPriA)Pl7ty0-(>|+iI}} zMlvwens74J!G{*-EjQk=6b&-<%~immLJ*`apP!vmn}(=Dtqw@Mw?abNj`I==b^89R zimSwzjG$fsK=k@NI;xF7^6D5}kU{HiA9`b^r-pp{u}(;sBgYSRhgEgd;o5LF0GLBy z6>2h09mkp%2ARR<_3;c@Xt6!?{d5pN(Ar~x7ZXE?U=WEat`Cdm$|@#$Aim87a5=gV zC$e+!IPAQ^zSHiAIGv}e8N#G&WSnYG0cAk$;Ttz@P+R1-Q}6d$g&@D4L^%bbrV7Gv z3K~$U1sqg>OA)C1yB8L%ib1!{-aQw7It*=|Br^z$C%T{DgV;KDUI=M4PtxU8SatWd zq@6KUb&!X0?D7y1A$U)oDcTZLMs5hW&+cgnQtWe6UKae#8`L~_<7_~Q>C3)v>cFK> zin~w_*@B<(h~hKvj(jie%fd^2H`EWJu+Vz6`+QsiD`y7(=*JP}fes^-$Y#zg3FG75 zw@1l{@UO=VTo}3mpzO)xOux-v`s;FG`r*Gmvj7MAdr0*ElN|m3v#V)D^M`2QjAGNx za9+0AHUaBU!#aw@-n-_(Z=kiI5#>l7aV-G2q&SdVK)t7>w?!Kc%VFu9<{6A$ppFcG z4?Ie9OUpnSjRBw@K-<=5gnco3LKB)e0HD878;W<;5IG|>?8kttgB~XYkBB}9sh8J} z(rPY#et(tGPVJSLBSD_u4coS9K`Kvb_%CPmp@($%j`eCaSF6#N776MBru_n213(Ds z=SQ)qwXjn; zIgNil5tIhMQvr(|_1_}9ZI>lfS)wgbgWY>K3V}q40pC!I1gJ4caa9pRITI|3Y|x+- zy+9niyweq_lo#Ee%5d)LgIenWL1UZQt7WjCn%4_$+H{!F0kS;3%v!=AJ#s7u3DM74 z95P&r=jnZ#ngMWE*w37yd*rr;j(~>o!-!L$Uf+6-o^o`&>C~On<$Eh2z_cZ}bR5`;bQNrAtFmGA>-cdGCpr0ZG#zKx3n`;t)!3vi|%nYd&e1 zfizE+W9SBPHo!eQPQ5?(zP;UMVi0}vS)@tt9Ay46R~QHvcGlX#NGQM}dQZbZ7sSeW zPZUEbLLpoO5bWIkVqyO@88AR>S`S(PG2H|sKv!aA0A86(+&B)4zIE&pt4mLaRk6VmbRw_QI zYab-I?DQ^555;eSzbqGC3+4F{K|ZwjP{T5D=U96C_U^4`8vUDTOzNQnj=&~WvQ&Pp zvp4`-`4d^VV&T{Gc{^>EtVdWEc|SRW#gwUK8E#w4%0GH%{#UwM%@sh+6o;K!r=a71 zM)f*+>`oV5{(mb=;Sx_u1G8{oDF=B)!O zk$_nn_RgUpaV7F!fEOS^8vL`!>eagd`l>+c(Yz$o1-gFYV3qpOHlYKu7lU?e zu-^}&e7RL(s*Y(XJlj~_2dR8~91ZWm*qO?8KY}^XLtbK8kWk;KFfdXoN`B_61`+&l z;|R0z3X#p^eSwI?fd3~H=ER-UUk$oLPzm%t;dPcN7~S z1l>>HAB?_p~%`6W_O8|g?8A$j7JL0ou07j??+bvBpsT_C`0{X&_FcZwT^H zETp)Xrkfj>H^Wd6HKStbdUe>%%}x6!N{~Aj5$M6d2`$_gbr4_G=qXTAhm%d9q^71O zA+NzYZS)idwoDIAO|+DQ-sI%qFs~)7coou@l z{CCidm6Q)2OluMn7A}eK3kqu5!_4OX)|e?X!H;*cb(~ZyELc}rSXdYhjF9Csm4y`< z2K87kJzTt0#pJzfI+8saM~ZhW=tPF3mU^x$g$W@7LG2W6ioJvH@gog~f$!`Eeb@Np z=&u;ia4b2;?bCa#>RMQYKa943MW?iwID6JCr{ONuAfaicN?5|;nrK;f7_05`#{ecR zvgo2*jz>c|x@}6I5E1YgGoZll&G_8>3_>Cn8V71d8e=1D*ZPb)rBcg|C7lF15ix$2 z0Ymr9mmlc1@)vnx4NY{*&qTuwpiX8v5J7P!`9TRNg&1M*eo%WQ={iiB$AMjeq(rWC zn#*D}18I^BK~!P^GGM@m4fQV*w}+0|=YgN-+B2K6Bk&Lel=Sm_a>$XLE-R}zK0a=N zNedqB*Cfeo0yDur8ajaddt@vi7tN<8kmRe0889tbatrgmP!rRF;hQ;)W6}fJ=MO<; za?cp}nwZ@2uAsT2rJ!~wS;N$H;_JN@FN2hr% zmy7QE+6%E2oBfx9+&`Exhm-zyvW9iEz@594%X;n%?1(g^1^tBmqB{*!s$Pkt_2{w}uzo7rSC@35-q|HyQ`e{eQ` zSE+uxa{I2llxD0{>^I5!N~&8f{mxR;mw>5&B?K$IylfGAfZ@dfCwWDx*_!w&;Siolsty3>BS01 z_xJJA+yod%&^l-p1j;%@&N~GG2y#Q~nHmaB_5x?CW%qvLE(Qwa2e6u!Qm ziiDku@+)&ae**=HYRJgbqX`LrFmvdyMHEOIB?i*Q(`Rg&%ZpV@mlM2sk-74^ z*z?!tznr8$xhY=`Re9n#)pJ-O(0?i;fp2~tv*2CU51TLcPrU0m*S}+G>@&_&p)rKt znH|g|{K=_Sl0jv)17n*p01Ng2^Ne^5tI^YVs}Y_Ty1;FXb=^UQz>lUM$6TG}`2XQN zWsid{soVB(q-0ke6Y?*qvjvlj!v>$1m5CNO-J?Rvhw3j))CHqf%Ah&nU@DNckoJ_V*eM#Q{&h` zlX=S`+-iQ;HHs~J;DMyEI0&J-O&AnI^KWY64RqkdS`Sx!zWW)f06;B~QuYCnVh z-eys`7rwdcVsmzK;knR-|MC(u9yVSVqu|39EYSR|g88>2;ZBM5a6N*NjY~|&h?tut zVW`Wc<4EL{*>RS`;TCWb5g(?TEszadnlJ|s7D=LSF@$ig=8yc+EmN*%pAh@ZrN$AkOF7NJNP+>MS6n(vNqjeFj~n!eIW9zZGdn7*I^ImEz8XvF!={ z1QP*su#~jm5n{erY1acc!K;MTt1rG^;C^Gn-7*@wsSfQ`VgS=655X!2Jj2&Nz7nm; z7zzR8C{sJk=C&O!Xi}u9vjL!y(CY*;s&hy2Vxt@nny!I)J~s*E0a#h#qd+BOQd0#c zS`>TS$Lv8GZGrJiMA)F-r9J@ax4ClF1`P^c0q=`ZfgK!&|V3L=B4~zl^VuT?N!vv;GUMQchpA`B}{W~akg?{0q*)P|D`G}$7w9A!o(WFOADS;#`ZkQy1O0CH`N4PmO5)Exo)zdN z;RkbIn3#N|=i9&t!>WpgLPz65;1{v5!7#pS)XcWGj#j@mf)Ru*>Y_9n{1pJ5%tE46sn{7iKig5jHxCEcbr4MKnjI*SLzRg?hSLTHI> zJ6W-Fi}`C!m^sL7g}Ir#(F#%P0~=fdsqHkGl`vVb_o&`ByVkFdY7gV3`jU37)#yAR zV>rD9lbfg)`oly-Lj7K2{j^r?G zi`4Q4bohu&fCzgMQ^3UGNC@QMkx15}@j5g*26b92{B;;HKql44Q~^Su*nIZ@BMf)m z00`IXh%9ukwDBuDP&Z-nTNTFKpx%-SQp>o>U{u4EPjhz|)p@?YG#nN&jruM&GjrK8 z4@3N<2`iWzMvZaUn}IkhJT2HmCX_L&q#AcUK;GAC)7r1wUZu)t;3S*2VUEZPMha9|_;-s)7-uy|dqbD!tsVpXE4o97R)2wyT{IjZLHSoCIRb z)+wJpv$}|1akWl=V`}Wg=m4v7!)K_UnKcKdf$(se#-5n%pkSTblv~(NV8~%{MY!E>8mmkhICK6(+4V z#F)+w6ZgK<)`B4$hz_f7GrwzCzaha&iE1?36r}2+64F7!r3RNM)^72i{5i}&mimDx z8nE2nLo?HM7rbBI7LCRFi9uKY*wofcNP&#OS4YVARXd8}N!~u92f{hgt+&ODEQjqc zWrv9Tn4bjlpJGjaKi*69C7hf7PThf`cpUwe53>i_@=S2f4cZ${UmSkeKVggHn4Yjh z3dw~gMlD&wFbZEY7e!WwC3PNS6an_NYx&TbA2IIE(lTbc+jqLiwh2kx8A`O0YwfXa z4Cq7XVZf|5dKV+zX7?+G9L@5S$5bwi%UH33{q%<<^KxEq)SNW%5AKb)0oKT1Xn(Ln z8Z=Q>g#B|j!uR#{ocVjQ>YS%^kOpW8AkLe+=52Q1HVgCbNAt{ZZW9KB#$n%3cLnjz zq-kOu_hEDgIhM$Lj7O!HS4uCS!M^(Gw(;OMU~f3Q?=nZ`kQ7bXL%R!sWIX8`uqVJ5 zR3NLibCErIjnSwbi?C0Fk4b$dHx)uxy62j1gd=j3$ubMkOG|#-!sHX~*O+YYk}rHf9fwJfF*F@5|x; z>$1#*5YIs=8l*&R62UU=nvn-rk^fo3Z;!&D>C@(%+br4s%WJM zRMehs?tr3A2_K>t|CPe`Js{PKwGOPiNqHT!!GTM`btwd%6S`CKyPf0CH1rpeMuWHv zSG<6^xf~pA{L=7)5I4rq#7=}+>gR}$#a~nx6|a-DZ9W8=3Cj??RjElc+_U-ED%9(a z^n|7aBdq)DV)NA2W9GzDC6ojLpL)TlFM@e`4OyaRrqY+YaeOb#9~6eR?S_WCf{-J& z-??(-%IB`KSfo^iz_UWjcFiwdht78Q&-NwXvH35(b6_3s*K-HnrSIThnH0tjX6}sd z%^Q0En!}LM#xSX5Jvr#|7U$2MTef4rqN3t;FBzMOdL+2_J?gwcn6ZPpuIY zxO==?`vpT(;FlxU2NQiV)6>%sS#DFjN@3Tr#;s(1M}TCTmj=LA4FWok5zu?)54+~? zOaqK|cABSlA0tUsjk25$lU*TX=4;W!J_cj!$qIhB6?>6_9gds_#Utmn@hAS$4~?lR z2)BP)rMAI$k}c<(qacE3MJ-Yo%?p1HffH!{WH`v}rdHsMgL;9K|e*J&$oy6&c`msh1b+gztij7Qr=+Q$Z=0 zf`&`d(+SN13dX6-!SD$BMg=4unhT3^tab`@F0awl9N}dQ3%Q~2Ji{*8KMc_DX{kAFAq?wz5vpX3OtnR;N#(=T;2QH z+5lsG$^8#UsJs zu5DOqJxH3E9MmzpJQ>;$RVn02kem7H7&IAAK?>`+CqBLP>AX1%G}*MR`h~oFGz0)E z1OE5Jrlw&6y+8pI=<}%t;0yZnm8shcTe#hD7M3N|kWiZim4K)-0oXXu5CF@kt}hLb zB$^qdKz8??D!&`@zSEPrlCIgFSJBp$<~We(Z2jU3_P1iCchQe*`8R)~%O1dKQQH>a+&~(&) zDi9G&VQve@0Hp+Agr*UoSlW3Qva1SEs{HzOny+_wd5TPrv>)JnB#r8YQWlPOCH5ej z-qmYm`DH$g2~BOYCNE{>1Hn_6wMB!NFuCNyke@(jSw;T5(xzmGHjxl>!bYxn1$hzmIKrYg{p={9bl_aQT)Xa`vdm6y97GWV@>n{d1m1g z2KR$T^ijp*_6`m&pU8Sk9oN^thjfm49CwbkO?o#x0Rlrvj=+kS(G&&M#WJX37>)%= zgu}x(ky=Tn^2QVdzpXFkUQ^53@&enPe!YB@i7pr~V`x*G&vKkTpyC^2w7+y1yLUQ& zY5}K67={hgOiQ$T9fBH2V;h6`Pi($RWp)m>2EM1G5;2x?OaCZ9p>mdE+hGtVn`O!& z6*|BFW1gr{mL|NZPts@E8&RoOD&p0JIg~%u%)5e zFv@&-w+Qua+2C3phTeI60Y_Ct9#RRMd13epls1X>@%~))SVT`)u%1WQ)p>`){APokj`Vc)T9guV}=$w`SeGd(CK7G{*?F#v+k(=ak0&hW!jvS&F z*!dFB^$E8NHA1_LDJxZCif$mvN#*zg|uQXK&c*s~E`>)?lu+hR+P6OV=F1)#x3?@tFaVn{d~%qKws)(&+M z?T2|tG-d!1O$}e5Fo8Y|YNm(4Xr+pTw)_oda8oqEj1oF9^=yHfV$C2%Dx`J@%H|g^ zWN7Dgv+w*x5t~j(VH{&j>^3`291+weM3#>Tv<(IC}Hj>g>wM^)K9@(}T@qp|*sMDcu50z|Z4Mhqa1-9}P00N-dydNapH-3oPtXBBJ= zL89rFVUkPcyS5WD#$J9L{;FSc(V^|IXxeDM*{MILj{lwb^-sQ+>K~Lm|N71O$G0F# zp~MMF!zFAG5cs3w6k;gS(~a}DPFx%LL=WjrIR8?7t;Q<6%qY)Cw$u-#jU)8`{0ccF zat*K@PoHtEg#-g8Jq1%eFV=qAj_HwF>uxFm7&$lzXBuP4Z3XC12>eb4p_8q{9t|Ql z;Gg~2jeHEX;KD`>c;Z7>f^2$GVIfWaqn;3Qz5^Ch_yzq3!$#Q)f0 z-L4e*TnF#BL5)|gz?}m>5j$_)_;qS(m1bF}9tH^GZfwO6;%pm82QLGS^1khp4!F(G0gJYU0fSG`pia^U36vf~lP0*V6c&qL(A5%*un~X{0}@A&CK^ zj;ZkM>$}(M*zr9G!3qq+U;?a|HEa?BPKRUwrDp7gT>dg#0nxGGE~xsSK+ir!j?R*s z3b+SPNBirOLa~y=Fm%hzVrr)gzFVSLTdh=F{CY)<)&^8@rEgBiaDf;0KvJQ>H#97P zrdF1X(w7~9u3HrIIs;J!y1Nof@DW~4lRZ$}f>2mxbom;63k6Lf!q=9y6pdbSMtSeh zoZ)y6n1P0YF-*VY>~I~tkJ;&uXsjH%V*CZpU${UF6h%KE&y!QXlwl4f(KkTg5ny}j zMd!?Q*dfRDgTEk^jF1vattDcCO2KaUT$q~n@S_=Q=zXSk5{N5QV_=hG$V91kxht&p za7M=Bfz!m)G&^uKAZoSIQ-?}F8V9oi4_rF{0+$3j^4FkBC945ulmL3ANEe~}ia~og zjrRoEh3OmBXrQ*K8$#tkeVo_` z_-q)D(ky2BbJpWBCoq!*UmPRcU-<`kO(h26csO9rN%XF}z|2GN95YJvOfdblC2OP@ z0|HT4RLf)d(@*A`k@g}W=Wz%3km#jp z9_;KGv{EW<fjZ!tO~}619VW*+!Smm5%sX&AnQb7ic9UF4BGm1P>^)Kd$$_& zAgR!x-6Xt;DaQSaJ@jZdeEh5?dY1wHAUOzlw@XZk#inr=^pzc`bsO>3dhpTqRu&mO zIkF2$PX#rp3)2wOU}$;~lrL-!5fCTzb!1>|llv#5h`@UIKZ<+vsGiq0?E9B7B{C&N zRM;UYl_Ep~+1O@6rNJDEqzOfXp`zF{p}|b05)Dd8CDP823Q3V+i%O;_c|Vu?e%JfH z>s`+u&w8G~af&{GhcKp{{VZbx> zez>1$zz`N@iYPkZzl$z-!5|(wMIJcMN)s2x+V~>ahK6Ifp2XXx{X%@1x3@QqOZkb< z!773zu6I>}jic=#|4eCi!^&6g8)4 zoD=$jm_G<<`uvPHB&T?njB%(7E$wC~LnGVR4^Xfo#S2M^s4bd+09K2*kiqGVKypzR zi!DUX7R~9{g0^x*mZ*bo?%3H<+f`IVxNjanx%{4W@lUpy=ox3XQzvWm)0jJjT3OVi zncF^$oFNEKfpw%hb~)!25{jlj$7m5kSe2rnMjRwLLjtTmkMl-|KUn} znlJxljQEbD`rLnubgmtMV2nBk!YN&ygOqa)O+er@Sai|#juSyZ7|}U(%INY)DCxP# z=UsZL#IuJ)UlN_(!NL#>^Jkp&`dT*QV?8dXXT7{IU}0v0N7-Oaz$rhkHM0E9s>NSV6dBTD(03%hV z;4_3UgIKoCrGbA?&*2M~udg8@h-1l~q&Zz;X{=|EHlg5KK-AvxXE{n~~xAC*A?v3aWw%zXGu;Zg+_!Q!$0teDQH{yNf@y zd~`UIkWeyk(4fb3HoDhWl$AMqLt0oRwgG^2c1_8>m~-*ssE6g{uTMW3BfwO5NtE8Y z-eV3%M-P5jQSqjlyFcE|-vp0jL2=me<0XF|Ir8d3{nP6^j5@|NEeeatotrLj0!z8lL@8{!-o%_HS)-oTlVyGb(dW8 z_%z(fh<^3%5~H1MhGDP&{rC2%`RA=Bk(}=ut)4wE^%9bSZHHYx#MZ!>98YM^>7iR( zFM6pk_x!FT36Jl`W||d}sByM5%F4)uA3D^V!%_9c5K4tUGXCVkY%ksE)2C-lc=Gsh zGVG3irW2cDm84R%+s*FC(6bT2!E(xaYvschFI{?DqRsP$9nUL{4i7Jq zB(bUXhdsZ2d!Ljo7q>DKiB z`yccR@7(!wlaXt2qL=jqBO_gjrmE_$nb+Jus&&l8=~0QGmHwI9@Z$kFhbzzL6&4o8 zpE~82mH_V6wzjrzxb}_=Vk@b{Z- zEOhU_GWyFI8=l(QCc_Ko&nHud7^G)Srq0c199XzM$4HXI=5Ko7xPE>3T#c11{PkbQ zEt~1^E3DhP9`8cc{IWu6CV?$hMDeMzUMERPPTqC)>>q3mW$P7cp*2)joG`L^3VYWPk|@<3I1d*;iA`f67ikO$agxl7R#q+RF?89{SM zOnSN^Gm1ws_44M^vp-hdo)ta$__ynT7K<4(rh0gc&$rh>hn=@zfiDT;qvzj2K|!{5 zcDon|wc1}&O-!q@uFibdu$goLPgHKS=-L`2*+f6E}0T?|46=HjlS>&=zwz%GvuFDg z=A4#u?2NRWTmh_dx?ZS7Ck_r+xq9`Mo0k2SrmmfLS;Z;l(xpCWdD-ieBO`}VE0q-qVf-32SSbocINvuDdSK56>=+05QvjluUd zZ{O}^EI+sDK*43D!q?nAF-aM4X$Wkxn)4!xQwQf<_LPpwR9GGtJ4Gd>@zsx;t2%d( z8rr+tq-nd`)aRM*mv5fNNGhZ92g~vTGq%R)(XTipYgEUkoWT|)!F!kF zB%i)`Q8s3qWw52@w4FOAD>}%H>$_;-!pJMfiKY5V8(uK+$@1!k0RW+42GcLb#|Kq} zTv)MYX?DHJ;kY=p&u}AJ(QIl-4Rl;Wc}VZTf^^PkyZni$Hi(c~KzdW2c(5dU_(~G3I zCkfxo+`Ja#=l;EUSXkoQ$oYLIHSvwwv#t#9-#={e=u6}E^z@t?t0!e_UF!SS zeR-2Y#m)tZ{G7nc7pCYCVk(Umk+SYb* zoVdYo%$S}yACfL#zRB<&f9_$*qDB2^YEWmI(W|cs=O?6@nHu{<>Kd39K6#?_`0?X( zRF1h?|-n}*Yr#f|z;-me^V|Gk#u$Vd1H!4aw&8mDRXHY(@(LTc5WNK48xRb0k#bpQO zD>3>d#+MH{^5+dj--w8%Crv+9=Tk8>y?Zoj;6(S3Z>Fcuzugl|@5Ma#9+#{#I@EZF zX0J5fJJ@*VU1gKAZyP*KjpcW2-<|{u&2g^|Q~|-|Y*JY|y(_Oc_U~`j>0EH7<&tNP zarzdg=Pz8c#FX@2VPQ&e-ApC!U;O#Y7aa-qzJa=xn=xIp07}9&++yx8N`=3sp%)e>e4{fUqk!0 zA0y`ZnW!?$ESWxa8^t;T-pi)iGlTW4=FiXbxj1x zj-nbJDPFQ@(P{0^nHED2wQiP{V9-gras~g~sJcA$j#pgHtu+4J_k$yuf!*j)uQ*`9 zjoY`oahBPZ*vBj0Ok}$AK#HM~2?v=(Fm?0h;U3LZjJ4>TQ_gk z`IZ(I)}OUdP_GjoAD_=|R89$m4o}{&Y%Bc#~F#nzzG_>du!_wukL;%Hm^w6 z$x82LIQ|J%5RgtbjQJL_;Jb^X9J(Vi$}#LAef;XNzZiWWLe+YQ6R0@!@>)Ux%^47fvl z$Zn95a{J77vn8ssJo-QqRvmBRgX37#;l|#RN&c!p57najEzZtU=FjiVMy;u-ktADo zhxj>n;ey(*VV&{4IB(lFZORl0bJ9hqWAYMb`zZ$m1XyNl8Ajf-UA;Ph$<5N(I_^Dq zU;;ofFu0(#bnka&cevQSzr>xOwYwPCL>51I&>Q9g^mdDdma%3c(NsOWT_pJLq)I^yFUl9rFJZ^x~!uA7-!M8EOD<(GzmLV67v6in)WiT;>RLQVG>N;R=z z@c@iWZ=YQ=;69vL)Rs$^sxpYBVfuFfiZ+JhASHd7t5>hGFL^V0T$Bz}x7PFeyP=2l zRo)%?GI{)j33()>nU^kH*u8(h%%$lUReSX4K}!Q|*nN3V#H3}PBi8T30y>ZgZ3>vz z=A=c=nXDn_v~W^yAf?G>ms4lvL?2)~t7&V?Qv(N_KR*HsfHORiD0!7_&^EimM~T%G zq_g#XWiQbvnCH_yrKwgFve&K|QGKP`DqJ#}zMjlj^v^$C<{BN-9yKb^>p;|{OJfYq zIx)puynAMG-PSLhHG8%Wv!AzPKobyV;J|?)$BqR-xzx~uS>wN^>w2#e&gwFYExm30 za7CYiA@a>mJuiE{uhe;6QPZ2pe#t-k`w|n>Q-QD~5LqXVg5~8A` zOBqa_%arYK?r3?+%5F2~%n6gNwX(7K2M`W;|=gqcdM&UtXsc+EF3{O zT>V$3Y$oB5arusPS_wHAZZXupOP4O1hKAukQDwKp#tv|Jj{vX%emct$nrT(lmZdG9 z9z2*|?J4K)`uv*P7?*9^ULU_jrCse>r08H{V{`Z6!^2@$(P+xS@rhdF#*Nzmp%kBz za;R8$+k4A1Cr_%o1+nNg`@fzre0TsMT326Rf3<_dO#+c>*c^rS?P!@>R*hZUx>U(% znfZbOkXTL}pX7DaO>^)>_gn;|@FPe1_1R}OYx?v(OyG>d7&TH8>v`Yytkl%M zr34A%m=Z+2oWx;M+Pp@aqAvk?(QX_lli1xn`bfZmH|)R4paHs;NT%WJ8QrmC?ZJ#2 zuvl#Q4#AiUPkB?UFj)0UtPFpoedyX#MVpjx=KT5FpFe-Lw6Ku=X1(Z}5}VGRK~wQ? zd$Wef>nX})4^xtLSmE|GrPShQsTHw%IRQskN5>9$Dc-@iNmd5Yv9YRwd-uA(oRD+C zyY<(q=C9opy@Ef=`GqJPHs9^Iep6@Fy6398x_yXaOcgj;x?|LM=NbtSjHtIRHGVgD;75=*HDcH6#SLk2!2$?s7_6)h{G1#`{{%0oA>r@4z{QYwaSy8b4=g-UbD*b;yI!m8X zI`Yz;vQo)Z_V}bnk6gq56OGpwL|*CfxP8+0mL}^Z{#gPjO&KCz1IkUX`3g;{&7^UK z1R*T~G8SK&ITOzxzc6I-p zy)TB+ojUkm!Vq%3{@oP|7&`!ovX#x^#Q})nvD9f?$!=l;I#Q5Gb0h|DyV=jHAl#Zz zf^8978Z$?FdjZrcQ`hx@)s|tY2|)Aosj@dCo*ct6&_p3D%j{XLB{pesaUGaNbQ5&mRZdRsbHg)b(fwm}opVP8hTXPp8z^IUMuvy) zs`9cjTeg+9uC8xp<^;ZXzo>1uGZJOW)~$Nv$0+L+CN^Qs*AgYFomy<8zgs_$%Y63sKvtb7woq6{_k2B6H2Vcl)u5 z=iRswTd-b4FH{qUdt~2Xe=Wy_EJ_3B;{RQ7J-qgAieG^C_+-C^^3r)F_c7@iTZaBhq@tHUfZmkZMVyuMM z@&^c_9(RU)luPu+!NbW;qG%2`a?_FsLYQS?nPe*^ye?XeG!Y%(`I>^USZmX!PTiF^ zHgNy`CJyCuAgs2~)0HuSlMU51HM;}!tK=rc&fSYO=;gJ>>!!#BRZUw=mi2k2Fx)S7 zKQ34~2tPj-7zhIUj+F^P#icI?>y6x83F@Bv zkIy5*P8>ZNfI2*N`Eq6ZWJNwHpY4J@P_)XH8?b@}{Y_Q75k5xrVU4b@8Q7(Ya$A2CJC+nIO`u4TW-`nfnqeuSGB^a=L z*=sf@P@M@3m`MV|Y`nGDO|sM^K~N-{*2Znnm}6qnfrKfqqM}u?w}WKRph59g8Hc>Q zvDXJF>U2Qn^NEa90@ybpZk~*bGuiZvNFs{nwGYq8!H(E&+?dexm+QK9BN#!)9d!ii zGOt=AOG_*cql1LD8@xmh4}7qDp+wWKUHkNa$~PIAGMtje+IkS8_*CXR2s>->#y4oe zEsP(Jp$Md$)3i*I@MNX!O!WM`_o)-i@2q2ny$~9L5X;4NE}PmElJH_rJCv*2d7XUv%e=36~vv{(Luh zEy~o65}1*REh?VYim8Q-@!!#uroUD~E+ zNpNX4Cjwq?eiVHTV&^m|D|C@l@8L?5aN=`lNqMDERX-EdPXSsUx< z^cp#GWcb)sGGS+UCSobkuJuyx{toO?F>!GZRvxDU+5;hKa~#40@4zl1Q4OqFRvU>J z?#hh8+=o~7X&!!VIy__{b8K~RhAS#64pCF{xcRU!n_4MtzygDV0JydX@P0sSvc#wNHsB4Pk~rN8)c zP^Gq>UOx8CjEO`qQPD6SQkz39IT*wN-?Fo-YXNUPvY%B?xrwVTJVfT;$V#&BkDJdQ1KE3$j{rezBw%a4} zvD3`0tOimEg&sPzn?WKmw$!%PR?-g>^5^LmTx6mT z23RMD*8b{vBk`18aC^dqxL}ou?mncfV-s9uPyl@`HM`99T2g)Qo;+em)<(^xDV?zS z{eG~#DI};``ppd88U_jm=oP$gK3JsY#f#nefID#J!wmv(4~Q7NHb;tCs1sURTOE-9 zL@wCYSS?5GMAw#GQ_YR6jf2N;>E!kGeeHpWefzoz@{_ie8fYZWSMLIc75ewzeyq5h{ssb8T!6pfz1x zYiw#_;{2__ntc9ueaS_mt(mE*eF0CikE-|Hm)>}vpd{sE)#wIGp(-ye^qL@*NtriN z*biOdC^95d*-IT9x^qtb>=Ux)tM(Vz1E2038L_+?V}zDyNh7Ip4v-Ibs0q}I=m<86bgEoSQ zv^2MqugE$lsND(`9WeAMQy$)Z`ZNj=z}Z6uiV{jKf?CE40ORzNc;C>7JX3rmhdn=B zUfTVOo2%=pCr_SarT$ojDLqKa7CY;US06upa`^G%M^!5Xt}ZkXCNB5y#E=LW6)Vpj zM9wvmzSrN)Pj#Y;f29y_^$5LPe8iC{R!?v?MNp|MC*6lBC@I~MBmu0ptRwH<&CmCn zae97(C9b$5Qns|x)@}SlUENp8R*;$xh%^zXLl@Vu#yxJukJ$g!6*`V$C5dRec^x5* zI%<02CfCxJh-?ES7=ZL_R4@lHDtCFqj;T|nIC90FeDeAgXK&b>#D;{42mbo)Ta6{t zA*HalCQbXck%it}(#o+k9{(bPg^s%1zF8!R%gM=h`0!smKrI^};dwuXPjvs@r%#^& zlCKL>utmvuUY-A?u6r>^8R79{P;l^4R=d-3oWQo3nVD5? zkBBojBuRi0XAcye0>)+DM)vc9tnqbS8N|tU+cP%8uJMk}H7OQNuAiMPM#hYqGkf-I zI}bi$gW%f3Zo|}kOf-(5@egs8=|Q!gaytx)jz0RO;t>C}RazoS@-f+G&IF)jk0X*y ztAA4YYn4f)FJ+gi)2Yl%C1(2tK#aI8QIc@vTg|`!?j!3>s|d3gnvPD3UmOcU44}Bqg-;FAIcDV6gAk#irBw)E z-A~GC`g(rn(y_D&yvc5Snl=V|HCwYsvSkqFZjo%kURf%`)Ya83EiH%W>Rw)2>q&c( zhTHV3iJ7g?90BOboB^sjapFL>=*xx^9j8zo*U+c7B#hOuWgWJ8eDN=y{o60FK(IVG zwqeaq7ji=i+~pxmh^d)<)~eNoFyPjB*^U9Zx$ zP?0?vB(PI@dXZdX$MwFNMV`v5kdkAGlTVM5!?0Cs22N|u9@={<>4gHhN_(+6tsZ6Z zx0FS^tPU)&2Rp`+Totu#B_iQ4;<-EGG*YD@s45b|VaCwjHupLJqSmfkH+AHZX3sTP z9;AF)<9VDz#*ZIBp(5awwK6pl9IYAN0Dd7fGE#<{ht}I)3_{#ZIj#=l#$FAppRv6U zSz+Dco_5b1KVtdHYAnyBjOf?HE#cyS^w2ZqPQhdUtB6MK&T{=Nt%Y);YnJ;*6FYT4 z)`i;vWwdkX&@o1Q%&V?e5!HaL?GTE#yy7ME=1IYmk|>5SF=>;^6gxXrpED;b(PHiB z?KgyGz-j5hPQN2*YwSATMg7Girw#wUd)MLIpvCG6fuYgSJqs;;;DaFl+o~$@ws|G2`-PHX9jPRN@f218RT)23#V9-&m^D%8^e8!*_Lr!Vo)*bOf%o0Aj+4JPBX~~qXzm19%EKVl_vp`>8yzKAZasx7!+>GDAVHi* zRZtJiZAv_y2?mWkcDelCkCZOY>hF3yymv1bHfq_z;SNC^W{0F5fF=oXyTeOIs}^%GlxBl3>&&U(^VJ!@!B%2Te#VczPi*F<{T0t|H^3&*!3Rs)b5paulUg z-SB3EFiaZZTCJ+q3l?kKdaUYK#?8FNz&D;LULxvj5ZBYX`U+nCaR)? zslnx0z~Af{tCL+N^f{GYt9G!rZ=zL4ZQQt{6^9=l`*b)Wq8o-v;))&ghxXX9g2ASH zJ2X68Bdwvn%_m_L)hZvs-0H!i5Mxs~Kz7*XuCo5BZ=rVxjQLn#3 zVOupvkNyZ6$eEWNbureV`dEDj38lS`Iq~uPu>%}5MvZz|qB^W@V+`b2 zU2u`w6Gn9ukA~yjxOjl4r)Rj~W;Fwyr@TmbuB?E+rwqxZ?$l;m1`QnF8+WR+i_6QS z#h%@$CzS?{J#zeba8}kt(x~sZ4G}+jb%6TLKk`V4@>|gXuE%lf+Uh8=J;g<`qnmdw z)a&;%NrxaWQ(8+>l}Nz9lJ*K0;lHYupJ47*q5NalTZ}R+FSW^&Rd~RTsW!kTK(e@$ z^dV}Jy10FhvWGA8T{sf+m_Fa-7Oa=g>eQjLI#%GyV97jF%F5SnnTirOv+Ix|cA8fXnS}$H|U7TJy^~#u>UAv_4Z+^rncgKI*&f_Y|6a&)!CKMxg5=s?c4)O&Gyr2O`=H^+0Y4@)m zIeN7DMDfGQsK~EUCGMTjvh#9h;dM`Atzef{)g#-(L1%h6{n~z<~o*SMi8;hkETkV65NF z!#h?y`rtp1E@S{Flw$E|<`{Os(uy9qGeKnp!G`B7ein1)Ooy-N)w}m@ZW;+p1-1sK^Y%WK<4Gb&~`5X~NIGL>)v&rjeS_IpPnE0Bi$ za+BnOu1xiJ%2|3bWG?MiO`v}V_N>>dM;|L|eG0-n2*L@pIugp#Zc( zn*;=hkK3q(o+y!EpPvF@2Xyk7v}ra?PkzK#DsUY{Es?WuU=Va)G_<6Z(UKby5+cE} zCrnwr_jTf-L8=W0D5e|L!v2^G{g~ca$O2Qf&?b|RidXomuv4HN(?BN$(TBDRZE61J zpE5Mp)KL0keK#~RGUct)7+9I@WzbztE*YH;Y<1)A-R^?@BWplj`O{spY12aM6!}l= z?>BjsE4ULj#p|6L^utb{9>)1cy%=U7fM%&XoVOCYuNP&|Bp;GiEaIqO{k3s?*bETj zud}mHVxpGdOE5-muD{}!mSzCIuY;btoyNO#w?>uzA$2bbKHmvQ;VF5{Fq^tCuw>-T z($b!+)@j5oEPOK58AFE+yZuumXY0Sc@puQ}3A|BWu3!=?kG|*L(xf`3?^lG_EnBym zp?D_K7d&xin<7e5`i@q_uSyBa64GE#@pAGdImPDSVpDWZd8lrtbMpp~3$DD?ebgJf z`*>uK0D`+6Rzcj4H(;Kdp8sm^53~L9wFN`Y2N3|w!ExM`92G0(gc>aImh?;+Wc@W~ zPER;pvCb47-c}~7i(yy(H~OrMy}qU-vNL^X@aN7qG;P)#_}gM=p1_AV-*GJG-n`k7 zlvlvYOkyI4O-v}|f)ebIz`1E+phwI1?{;V%n54AG51h?#fa|zDi4$K($+Flx&GqUN zgY4#IW5q$twI4pn#aunR34giZn?E+cnkk?rU|0ZsI{I^EzxOEnn(y|mY@bQ=;k3p} z-FjF^e>d1;ye0@?<$w-#*BMYcO)2^GROk73y>xMB%O}!X3Xeu-pIIIps*qD5w(SwJ zUo@A=d24-Ysx@8h=va)>jV*O@*iOuGuj}ga#qVNFhBvTh?CGSmHrfd8K6`eYdx=U2aZ6MdhE-VINlD{&1SKG`$&SeZ{ySb4abYL806q_f(|sgx=Jg=Ii1tr(Ux4_mF>0_^u! zO+SA8_^UkgH*enPk_6C1Z-VdzoiKCu>?6w!jE$pde)FN2(xDc+N`gCj;)GWJyo{`@ zW5FtuA}Kn$Owp?P$nvP9t3NzDd(y+QvXw}~@xavghv!VTBdxw^W`IZ%*i}2!BENh0 z?v~EZ+N}`AuS`sJ3o8-5kQr{eSKz)YPJfls^oFqnSznZ7>aRmMzJCAyuF*jxm#q2_ zokB|PaMIzJjc8Z#Bv~tKO4UlbIW1SM8Wy&O_$BCr2E{R)`k{5#6*HW!fx&M{yRa}o zj55-S)@=KZg}(02%J{FB(LgMPIUJ%>*?8wfBd@QTjh2($n|v5vfmL9XP|FV-=uwX$ zVkQmgmaAtH?}O;4)uzsZw!OhW{Pgjo9Z>(xD-21iB$3^3lqfp5HB{N*leAm5Y*|)F zy_~;sBU6*C2JD|aN^Vdo_V;AG_(^yHwb_NMSFX%ww$Nk9>$>LI;o7@6jV$2&g$pV4 zjwgxpIyXJ-sWQ=)JT(i;Ey7MRbJrTKU1-p^S+uAC{n-+zA3kNsTJ64`Ga2mRzjWEM zvCzM(H*NYycnq71CL4XZnTq56*iYuy+F=2lWo><&hN3`x%0&rdT7yP-ay)ZHSlWdP z`-aK2WzCWaV3OaC``P+Z`BlR|aoBAv4c2u0o|E$9Mze7B3 zPE(hcmhaW;M)twLKwmI4xh~_kVoccg-tnsX5Nw($;;)w0fthIPK2wIcyB!fXYT^6$ zbJxz&s<{R?Up#m1Cz0G}y_H=*vTYP+!}g`iN&qYBrB3_IIOlZ)Ox?svn#wAM zDg?Ktxh?CDeq9&sCrBG_FG_kLTVh>7+FK@E5kh4biqOD;Vk(6#PDKGY>DxX)9y3U9 zV2?JztEDZ=!H^)_PylczS0gU4C`ay#P#eF3wfszKuO&_k#K|BuVnyJKV2oL-n;tW& zK-fg7OUI>Ohr9Uu$iI$}JlgL@;*At3A1--Cm%j|{-ktG4p~;bOy7Dh2J_vBJL@k+$ zw3L+HXV0$jeKjgleV7Xb^49utIU)wmJaf92@?5=X4i{M;0|>kd^RWs^v`y3zkoi-W zEa?yFCA?RP4q0Qt<-$BDe93$QYF#eMzbQjPqR##{ z8Wae_!8(0p4Phih*60j)G@CwM;3LS0H=GwG1`?DM14!q0CEik#mI5^}Z^j4=&t<#Vf=re7?UhNZw>w zFjnqXd6M)r+6&i|80iLZlR!l!Gt2g*7xb9z^tYU~od#XV!j=kePumK2Kgc zP}CvF*fGvaNPYBB?ne0+jLfA+;ltP(?Kxs?TU?Cb1{;53GmuC~i1+T_zd_n+V$i*d zTnJTL8)r2M>!HQa^&h`dt4{)}Y;G*D*Ws##fB?aGFwBkPPP6Q`P$}33+3{zqNSxdZ zmlS3@t<9~t{45|1ISY~uc(%?t3KX;^yt-x!NQtj(^Kw)x?h(57w>#8PtQ1>^xlI9 z_t0^LXl}Q1WhXbcZ)rAR0degk{&d<`( zn6_3awNA&*)8;)LkB`O0+qB`Z0KZ+2i4jt>0GmEf27g7ko{G9B3`Rh10b(x=dXmNO z1nJoLjz%Svq;I@F0>cIl#+OK+qu(7#Nlo>`$t8#^jM%sJx7eg<1?hm9HcxVLx{zGE zk9(0`rzmbFHIn7kTz!cuOd?7vtJ%%iH!C$f+)h6|`E%p*!GJNz`ZwOFYC#pb-DohR%?J5NtZ`A!APt(MaXfhVP?!Q7z~Lo{ zfeS|#Kqw?rm|M|JC!4U7t7XiaEwPeP)A4>gnvuoO-sG^(gf01fW~hdBUo?BOp3PJ+ zd3{%wuKv|1ZS)hrsx8#82zc;)6kYpi`Yn1z+glzs;uS_kenI~Jov|n%WE!BVfkA(u zFc&pgX-9fveI-465YM~Cb|*Kog{QLjPz}oJG6|ZZRF=mVOVkmILP*4TM3eCti3dCS zLNN|RyLIdKLD_A|UWvUT7mjC{m4bwF#+J&+63txLRs}7AJM*{2%%V@Hpw)x=G>3)w zA8RN(|IjFLMm@+t)WPUW_n~Nqf4a0ht%oQ+nLeu3fA&Fj{|jzv!hCAAxyL*!1e07z5RVn8w_m zHxD>qv*z{dum8L&vm&SZgP(ZHKo^_9xF2)EwWU~5+t;`XxYYiaNUq<#FVZ2$`4+pV zv4mY^?b^MfCgp5D0c*mXSX-j7`*mmnE>wIDCbeVf=k(fFU{BvuPh__rGv7@U7xQc0 z&We6R{3L0?Mp^Y71RLb^e0#;BwGWu6wCwaTE=HIHG}x;cJ21rr(?N;7U5XxA#supM zJ4?(})gy-w`(j8KcWpZ=KMF?&HtOgLOV+O+iSwh<)`8iy9SO6xLA5kKRc3kU(uzwX zP?tG#N)OC8F9w2AZF%m&V&NRMsoNLr|9R#VQmY70r%*n!YDH;tP$VIf70!446I^=N!Ir7^R=S6U{tGJc-|4l zMPCH|;!WOS5qRs0hKAHgUOIGC2hoeB=peLBZ25K@HUv?K(h&^+k%HpK-oBeEMp8o$ zq(IfY>-6mqRQK-PTe~F+(xCtVqJwMizW`Sl66u%mXLN^N`U%|X^fxAhnSurzU%#=P z&f{s&PGkXpf=365LEW6y=-z^!g5ANp^w`#E@w91OIL2{5g0mfv5hg5;(SDm5YaqS? zPK&<19-P4p68mTPw?Ps_vAN5Z$DypoIl!^k_L0`DvJa3}y-K zPLUR6p@`t9%wp7cWroWi4B!hJo_0JmbQesBJZnIBaik>37KPYZ4j|I%U2Olon)(Ts zZNeGQd)A2|us5`>)^JToKOH50etx+H1;{*_HA?fr>C<2+#B*SuN(q&tr>tz?@H7vI zLRdCC!s+M>^=z^v24Bo#%SZ3{IzWVlckg6Kl{9TSe}}BI}37;PY5Mmq#aNwCUw1i}isH#33#Ku;(sRoG?teNnoM5wnoySz8Cw7KWJG zQ6eI=xMPG6K944hE}+!ak?lWw<(r;+n}Z>2`k z=j`Hb?D<(5Ypg8wj{hHXPSgO;1QAEmkkHT3?yIivP@b~U$DdW?609K+6$(ReqN>iA zsokKUbH)><@QCA`t{Qr`PZU?iZHpu*_AAR;Nd5YoA{}UNpZ)a&prQw+kef7mc<^Df zdpy7Bh`~mf6!~3_3f={CHvkL3E${YEgSC1(bcYzE_e#nk$mlEGQ$k5#)ihxZ>Tqt- zS8nm8em>ipo|@F*_4Lp-Vfz$M^4jnBp40jFdoQFLp!M2s?1UxsW^S&}H%2K8kWguL zmH4pPHQw*QG_rn66pO$In8s<_oiWwjJvP2i4g(7=zkAA%F429+kN|Ufj$tM2Y;Ao2 z+4fw0$;gg1jc%qZUBWT{Mp-AS(KcnuRv?n(m(2E}F9=WSH&+`gs~}Wqb^MbJm2;?% zCBG^eXI8BLI@0AmW*+)&_hGP)0q%vTWqZMKGH4n153g0c$S{aUxwme01xM5Ab%V*YzH|>QUZ>HAgyp?NFZG~& z^J2tmp*Dz)u}kyR7BB$d=Z9CX{OP{wh}QLb^mE3?9exVyUz<(uoqs|C6!5wD_)Zex z92A*{r;xFoUYR_mY}VZ|+VMk=-QsTg6qSv&85uv*0ff3kw0PBD$|)2jYVo^&ZXKHB zb#dp303mbIfnG>p6tGO$VuVADPM8eWcY|96v7w_AI#aA1Hg;sywx14{kZ!(g z9U&9_7b~JIYVJYttRJUuRwp0cBR^o0>&L99JmF~L#BSkegVO6Nxf^Xfn9s{+sSAc$ zt1W}T&McIBZ^toFsuPqbdcsL|Z~FG{V+F&GnSm}>O4fnQ^c+kfXKH5FNn(qiNHmJl zG9jA7c+ysk-ud<9npfECL)t+%-GX#_2k%`mH2+}UpR&Eo-*?wo?~p%DKJZ+O<=*Q< zDlO01p1iVKW39umA6+%1*KL@sDm`wVz52*bou>K?EmbI9y~JwU_)$?=4eg0q5w|1O z{IxltHFwrlXXhA#3B}Hv!ng0=m(i-D(7!)H@pN;)4#=?J7y8}>9J5?yc_=k0DdxtFsTt2U1qTQJe04>fXPS}{$BZXTwibuzsOcM6 zQ1CE+qn$0YyQ67+vx1S z|E{Ad{qpteh3nUCI1>&4!}l+tJ@RC0M@ep)gQS^IWT_Uav30<&{furpyt1^kH2Mi- zxcSL>vYk4-{^+iWbqiYZ__1T#-wJbjXV2auC70BHS?1z$871T~2V^GB(Nxf#aWymZhg}wXoys=}><(nTYOB5WBLx29<;xwl!iw8l!jG;*oQy6UtIOZE>QytQu%x79si~>2$3zuwsl1|M4egPG zcSg+`zR}&ia{J`yM`dM|82{eGVLkkUuD->^Ko@sfGU6>$m@k7+9XD)9Wx$%3yL-z+ zC8?S<%`0reLI-QCvDf!cnlA>2*Zg;i-oXE!qh$J@ zN!iYO|8r7ik6x#ICcX%nC6RCd&h3_wyZeUxK~o~<%}q>~l4xk_E*O3|wD{G?ts`0r zrRPsM;ht@?YU}`bv|eW<5{;0p6B6)!@RK71#ms@*9okgKg#5t?GvB^_^YrpM{Pe@o zrypL=R+LCQ>{s7eefiI`4-XBEE2Dpdu-2Cr< d^bFqi-}}q*`!G#|{^GZ0nEz##Xkxqj{{pl}41oXu literal 39548 zcmeFZ2{@K*+b(=-pvX{BLR7|*sR7B5DWs?*p-4g*LdZ93uRAzc*06nQ)yeR-zE0)$5N3uA(swzOYK<;iYp9;^;}|f#%HzkORCJa@ z$!`OrJN`?O_2p37_UEhXw#eh(?uILhnMh1m%U-_yjgxdbMC|2IQo*XH_eACKF+uZo z@jJsmYK+@B11=YRrj^=q!rJ;smH5>6%}a6$3SpB&R$)slB;A!k4feIwBS9kOIvy*k zDk@yZj|Z(Sgslw>#yDRyX^iiB${l(meL0|DnX{&*CU3UI#MeN-#B0Q-DY1U*wH$|a zel0r~800TpxX{zFTd%}x_xB(X@3^G=!0G8}@3y|Cf>j%jG49i^PMkbRKJ~le?ZQH` zrKKh5`|HDQ7NW}^O2h)>QaqsWf=k7Jn>LP!mVrePJDg z>3c$y^PIRszN*k)l3X5_U7gWgd+8yN|B6+@!J_NY&fDG3L?)x9R`#E_>M7m2RYu-}C#Kh3=*l}K0mzE{BzQ##Y|7VsZZgO3Mc4p~2Z!a;mqc(%}I|}Mrq)wjFE# zQc`l*@H?Z9>&ndY%5GdLH(tc7H6vZiw(g9G(Cwbm(z&$>wzb0QaM`Ece}3e;fx!pE ziom+|xm#^HzOP>^+1S`zZ;o+MDPenSQ0{Np;XLOX7-wdRD2i;0ajK6m)e@TJ83MPnV81%t%A$x{xq z!{2%Xblb1#7A!U-rQGg4)$Tmkb}`_juJcU8#ek}o6!Q*A!|$)8wX~Qd9Hy_9`s@{- zsaK137Jqk`-^aRgVX>)z^|D>pqe5?&>9?I41&iwAzdQuU<>lq2@jLQ&M(ZsqT)=Xt zuLA34ylZdgH}0@44l{!cKOKe=r0F(Di?YzH4i&8OKJoIR@#nw7gvGdlfq|j0@BxL? z#Byz2-Glr0?+Z7b2@yM*)#InUuVd`T2lglK?v%toVU>|ftu0hN;8gbC(3WdU%cUC1 zav5(^IF~>7vGZMk4kg!_Kzi$;M!xyBF_)dqtCA+4c_9D zisN_{@}Z_A+We)qF>y`;q?3)Kja7W^rpTb?X0#4Q|oqn&xH@VTm_eXafhn_xE2d_ve$QV_9NH$zdQ~$jI7-jhp@% zvq++RTcpx+;(I!?`$9bqwidX$(rw?qU3_``v#&vuuB)@4>5t?$F){m7hOuJ{v2pJ| z-Oe3q9a?W*Eit;gCWexw?yVpUnpwb@c==-Bt}pUq1DefEN`F58i# zM>mcYthtO=%hLY2x{q`1%p0VaTvaMcZq&a&mr#t&ylLCEZTqIo%CT~oe&Po5`R^9D z6GT1QJc>&zP5Gc-LjK{y2VWBX2I=wDr7?=O92+VW6FN;{REdive4AQ!y0f~>cXj4X zRhkH$#OKwV9%;=~fAT`?c|cX9)!B;|cko_}zgl>IT(!1kRxOK=m2 zHPbWnnEBS8P50Gw+WRT9v$Of~*K!M%j+1WXO%#`==zMgrEZn#=*`$e|G&neDHW_}Li!(zd1#3$MR%0LMQmz)R>zsB~$^3a+IZjB}^v5T(kls=! zso0#HKw`fwPf4v=c?da7_ZRLgnE!fFe+#R4*4NziOS2>88+Ts*R2g!-^g-^{_PJ=mPTQH`P^BOju-hIXv2&S8hY?hAF~KY!SM^7`P=(8Ac- zs<}U(_Q}(yw_wX0)X95=o-rS}@-`yk{rmS%@;F^PKiwnhN6M^d26dnB&QJ*-EQ60UR|wcLKdB$c9c8Jjkd)qT)up{=auVutT;*6)v2(7_K%Kn zHbz4xYGJz{?;BvDXJe~+>n&ME{7$@*ixRpsYb<~1iHC`1-`nE4m11EKyt_OgZ8UMrDZ2Y-$soXXG04)sGZ@7B7$G91odP*0&mtu z37NGVF*Y$-Sg3NX#Bwhm$?T|HTPawZd$FS6#bRG%aYsyw`^J*dOFzFNyrg5%#M1P@ z`cL;aQZb6$x*RuC(UqD?T#L)f;zSsbh1uwoLBN&9m?>W`BObp>%k|`2Izt^tyPA!{ zp5;A1AR-b_!s_U`o`S}r&88{DqBSc^^!^^v1Z5ec=kxP+MCZvcZI&@K{9Q75?(Er5 z#D^bjh_lDuBG&Lg&0B?_bR{LFq8>#cI@xoy<0{> zHk;bkf(tq6;2@^1uI{wd+OZm`M(*|TmgJ}j7p{Uk%*l^zJvnOX?UTRRL-rGQ{D{N! zjbd-MD)hTbwBagLyt2jw?S(;&0?JS6O*(&WPC{mTnZ(@B?e-Td(L#4NCh1n`drK6f z9F+A*t=V6@c5S|k)>ZNL?Lz=Um+v$i1iP*-T*-4A$+VlemiTd23!C(0{%m@ZWsUsK zb82dze^&$@bC~9t>Z_6(n0-J&`#Ux+@mgcNX5L)8<4?9e7W;4_{L3~GZs*%;eyMPqOxm#uB<@@*VC%ZaO z`|k8B!F!8~4yyxUt509NpkGB>o-AfICkppeg1Pfh+HVQ#{R>lq+*Yglt zcf9K}nTmbJDDebKq*jIWi#@xD|4%9B?*{1Xp>>^KPv8POyo{;|f9>n*c;?bgI*xo* zQfuPGKE7#e{EL91%Kitt5_3o0>*KZ3-N&5A9GA!4q^_?nFMRp>)i*R$1;2VcWyab+ zYUWt)Cw_3(@jC~x0=r&n=yU~|BreEJ9bet0z^&z)GNzib{PyGF!-r$yNp^ z=bl2P4@oh1@np(f&b1Xp+iktWgmM1&0oELkJ^D6mOI$W9bUoK&D z4QAlIn5_4i9FPPE;>fXM$5_Oy-&ZUyFFVbSww2G0w8kVR8lu{setn2DB|jnp=xZGs z+zB%?zOP@u_Vo3o+E4vftiX;qWoao?7p-b;<%thKVBE#IF;yFzqg@uSYHQE)=ZzN~ z&Yc1mil=ATz}7v&6jt3QId^RI$|YrM`!u@nL;^y7Ww6iTYFF+eV|8&Z$4I*m)0 zm6g4Zj~{+=#h*jTC|gBJjh@D>`GdZK2d%s5s`wj`4yROgXCC@}{QUgeo{&y@GD@48 z9t=OrbAGCZcJKGHGUL>ar+L%0%D~V(+oA}j!R{?yI~Z<<4^bCWXP!pm_W9F?J`WZF z+_sd_w=--BX6^ae`3ZsBXO07rGacaN#g6Pkb%~CRbqA^|$NE2a;X-}HEc!gL<+0I& z>#FR0IR;WWHm##!6<1taU1@c4xfJad0jy4YJKU!9#Y;(H|8S_Ve}K8ne*<@kEfY znOK5b_A{1aezCE!cN(}jI7lTlg996qQY(g=Q&ktX?2JiW$f<8GZt96v5M?p^beGJx zpqs6Mvuc0fF)J>ro%}sHLy6r)RRBWX1h8~ESoDMX2$u7=^76OI$v*0F7q@O813mC$ zlM1b`ztH*Pa|4Qp7^>pu3XzWQYpY{x=PzC?PI8%vQw)oW;tC85ykTmJ`-O&mPhZt= z<0~P3^0x}!zloxi+3vtwfTFgrI#u=hVR8&;2#cU3A7e9Mz-jQ$mGs`z6<z+R^bLtf3 z19IwXV06<%P2MkgHBL4f85yaaKVKBDnW}jDbPu43QA4b{C(E%z=MSUP;x#mruacSP zO_2A6*s@McO!&^QD|lU>8Eg=rEM@25))L-5J<#gicHEZHlfx#5pGw}Uud;U_I*f~j#;W(F zMvC>o`N!1EjfNxl)>C8+CFWkrytNzb=BLwCh4@0xb39ggV;#G$jYJ8$Gx}2rpoCcY zAZwoSuH%Ii3(Zw7C)L#%nwy&m35Gkk6#_;UwyQnRuHAf>Th&h$<-X*{kI838R(N-H z90=FG?BwQY8M#Uq&bjf(ks}b0a9x|_y`@$~3GVRuF*Qom6Vzef=g&Wftxj|Q>egN> z5tfn)D=t2Xrldl*;pd%JGnDX0=y_Z3f-U3MJP;gPKKNbgoe?dRT>F)ord`KwbC3db z9QLEOzj*ob1LTG6l_TD+%j_LX?Q@Jm*W^lu^Q^swcI;F=eYuKeH#-ZB?G0Cf8YN@p zfQJ$IZdA7~P%g-L`1yb1o<*gly;xpe-jROXcJw+(hiO~R%buR=q#b-(auAVnZO6Fy z_?|&Sc^(i@@^JHZ(yJpkw}Imj*fl!Tf$}auEh!nub$jIyN+$6MxI^vk1W$cF!KmSwlc_<&rpbQH+ z&5xI+Y-K-5hoyiffXe_IGRtghyp|gue-KiDvzAqml&d6Qf#LG}#N&N2R2vimHwCUM zmay3^G_iXsEILZ|Ox}?Z2IAwtQ5j}wyV(F=BTaYS3g0ZKmh>? z(O!1>91i+-(a~y}no1yDB*3e^|FPVca;8Dps{Qdp@#M*qkx?lrDPQlP=I=XvIQ5E^ z16Xr&h8Z)ut5HkZO;UYh<780B@?*5~cE~ltnYceLxeo$3lm?wj!qPUmeLi?4XZm6K zl}Wjw*N2T#EV^XSGzj=)_51r?Vd0Fg+WkS)ETTSndE%5*RLz;@Y)6kCrDtFea#^-J zuck(p1(KtQ?F6kdJSOHT-UrPM)%|3os+!t%$X-W;g|}@Wo0*wGw^AtnfeIad_6eVM zMzTTqj+4)rz5?yo0}hMzy;1awku*ckWi5ca+yR-0tC(}{)e=YB_ZZz-W z<2?nO2b4*`*n~=O_(t`fh=_>!rGhobP^ZaniUq^Nrl(GwdUe!fhweg|hN7w}QRICy zyVsu%5`Km@u#JIX<0pW2g$ozxiJN@jfIFUI<|};^7$7e~T?w&}<4`)U;X!$SPye7& zut`yuAZQJ4r^CYOuw4~NOT1&+ZF*JvV`;}Y(<1NwR2Q-S@vQNg8`J*sj?S^9>0Q>S z`7V3nAsPSwx{Uu_DgPhpQ#m~J6(Pq1fG9wL89^{W4Y}hbEILUG3kw|F+;zFO<|u>E zOwG?X9zH&^537X~Y}b2AeNikdD~>j$F};Ow08&&Pt;)6tVPmFA zw~E<3&(iSDML&*^?byWNh$?npp1oWUD?Eg17$q z>cMX0j>7u|G9%c52&p_kfLlPod-HZKsB51Aqf09)44P96d?nSQPIvtdGUwvtd{=R- z;+4p4!5Lxw3-)$j5+lkheYZc&k8{P zb9>$E2mhgruD%sf7cmb52adTJN0iXFP^|FKe2Je&!wgArnH_G%RjmWRmQ_)yyy?AP z$xlqh-u_r`Z!ZbAFgPuvPWBvn_{*0suNxVyw}4w96HfP>TlJQDn6Iz2v$I5NhMC7@ znbsT|-e=FAUB7-^&REi6tSahEQ0e#Y;tAPi=y^YXUM5HmDt23<+3%{bwGTx^)nxg% zRWrop?F{g{W|qZXtW*|Br&r*I=uYL(hqW8P1pY~7v$0^JnEA48o6YyYqj7Eb6#Y1y zb8y2C96We^us()x3BavW;Nqb6KJoT80(AgQNw(}RR&<8&2}VPp4e0T8NxD)O6EE-5 z3=0W4gMUditk@?cK*NGs$sBCkZVv-Qp-~E)o9HH=*3dl+GEiElOjtb2ghPFFHR5oot>WV_D;R_l@ikNi8E)Y!PU69x!urx(>24w z!m6N!Hs?DZZ)j)$rE~-2lWWwtaN#TzK`4)?&+oFbUO|u$Hg9K%$p#@up)_2a9x(H& z2_?3EU7WH^O^QJ|&w&HiAc+th`|{<}!1+N&h5+E-AZ2Uj*;)3Su8ENMxRiT`53rN` z>cV7iPj4^5z`(2j6?Pu1H9}=3zZ=kbol(axw#b@ozqs|rdPCz zG-aw=MYwPWHF1yZg*J0VC`97wwQKtHuwf+EFSf4 zXtxi!*HL470|NuplvEfXMDNqo+@-r%8$jZL@WTIm@zUS301)8}tKUe& zpq`j>U7I7Any(8e7+{~EN#nu%$(Kn!p`n#9rx=gjz8+}^yc>Tx+wc8*9ugdQaX>=C zi^+BTb0zO#$0UY+$p$5S{^Et4nOQps|NG$Ee|ICW7Ge_|E*`uY~mPjo|P z9F1Q%g$4ZNdFXy7m{c$i*NP8UiOUkaDj_K$!JxP7C1D$xcI1Ej%!Ni>U!%h0?lPC# zv6YR@wP*&L!>OCOqjLzEf?Z*+0J)9C26|JUZ;YUoTK0jns@4b%B!sGEWi*w7)sEo7 zBprtV&hzKbLqViv7B=lq{)X?c@xS91VH9frxST(C%UuOF=NAi8eW};KZDWPZN)r@J zyztE??a8hmXBuF@M8NR}Xq$pJ8a&#TI|a0s%ihP0%iG_w+a0L@d+-YTp@dA}fk?-> zQD_a8)BQDG!C`oz=o_{?i|`!EcIJ<7grH?V{Go&d*>bw;8A_X4cw2aXmmAw}h~gkn z{w9gpfw+^ePHaos8e3O^N_X#_&TqEm4_}Z1;$T3OnJUbyk5pJ{UA3sV_^D6rz z5jhcSRg$e?TOFt1&+gfVJi%9_FN#olZ{E0Z6;gcO;$R#ARY#YfGJlt<>!O-bg@zwT zjBN1qWHVfs^#DDP_4R5al^%ZXD$RNLaKo+a-W@KBbzu+-Yr3|-1fEfh&Wq+jU4f`< zG2MTz-DSy4a=vpt9Iy&-ucuzO=2F=>QQ?e7@@wFTi%rUzV2!9UtIquUR()dU01XgPc^4Zy_%NMpo9nXW#rR8w zPbwqhy=r$ z_R*t9(GvatSx1XYJx=?r%#W~ zs;r)Sdoo>=xsDZ*bz!S=5Lq^@BdlhE1nKCs-yFgE7ZXWeIwo&kup$g|nTiA*jYNP% zY>|-0yA`TmNB_bQE{*^W?B%O4qI_W1YOUw`Be^*Jkz87bU%qv`@6$W@FDpW)bfaa%Z8!#!bjb48my78@gdCuaMMW}jhZF$k z{#Cjsjpj|M;7UsZkDA;tHjaS$2eAc#p_)-v!jnU0LcgyvghXsMql&YcM}CeCkQf}kb_!jJhxqI26+BAaFaW8m%y}u002B5;~@|`N7Qm3E$qOX zW5mjwliMgwXFbDFNjO-43GyGEqN+T9H?6#!OW@*4G;;Gi$JVZVLy0Uq?$#Ser#YHk zZzs;O8!Rv{ue_9epfWR;+h*iW6v10L16%;CvZkh{9s;O%R#$S()A)IK_CR%n%;_E~ zas2n%HdMx(Q@OG=V=i;YiF{b;17>_l@=S~Kg6aHt*D`$iHL=_LM3s+fi*|Myf4~M| z?KCuutnnf$?f6`DiOTseM5$NHl|6??my|b2DYevGrFGET-TfE9pOMow{CG%ATR@e& zyZbB1o2aR8K70_Gw24(PRQ}T(oul5vUo}T3-F<8)E|$M;^su~0oAq|&5gimN5mt;T zPg^=&Lj*gpd=}hX=PBUaMeGW+=_o~x)$L%3U=&v}A zr2D#hBOvsdn;qR%9nC*~Dr5}Q^kn|@I{_iv(Vvxp&xxjbyjOy_WHqDjviM=;kVEtv zrK@-S^?uP?2*bJ;9cku55EB5<8$5Xl$JP{9eC)T|?>B3TY!F{9?rMj3$14^-UG`*ui&gq$M2* zIp(|nYB@Z4`jh}QJ->gam^L3GwL{5Oo4o`C1))QH_J=-9c&fY756FG}{A!gHu=;J@ zsnE**)ewDNzGTouy%RZf_^|y{g@|^8o6O!{zkV?y{C2(h$yRpw@tpLO^2YzZBPt7g z{PITn0YN&Q7pL`z6eavX79=XakL029n0&RKz1wBMxaYQTl;-W*x39M;3`uGI%}pqo zJ8n1Lx! z2NG)l4*{kaxJDHsT9Edy;tRfu$XJ0uFdn%<2dgLwYCm}6p%h7EukkPF!SbN;a~-Zy zaTgP=K~5eSg)*iHf2jg#0vTvq+qq76!teg2-LZJ_FZJu)|5d;KUn%|ngm3;|RKMy* zYfQiSaUwB$*r+mCHUBNPUvM>Nc>hERo6%Qy2IN)mArNxXoAnH&8uT3FR7o%yQ05fp zr8_{U5OQdtx<8FidjI|nRJM&&JI-orD^6zOl?iXx9>-ftYEaim z|G^K%tTstPf&|^W3N&z+l*{t#W=(L8$|nQEWS_ePNg&b2AH<xku=zW!@b z%WH8hNE-MB1Ssg~?M0&YZB*1_7(K`?su~*~Ab~A$!*bCFoF>FGEiFOP^^&ZHf>IM1dm7%VZSKVuuAlx`k0cFXlKt5B>>|jMoysd*JPUO3g7yvh!;U% zg|J${^zjA87=iu!_oCgS6$;snUnTOvWiNTzhf-5h(JA$i*5Tmdx{Wkks&T^s6!qpr zR?s9w?+U(Z{2M3quiEfz=kvfR;oU$VS*b+-v_SxNF5;8QYMFY7zK-;c4$GMwk9v@_>` zY1gyHe(1{K^dNWR?z74W9Mi+1c*YmTUs8~9>TTG>Fqkuw1KkLTB^v)-b z9yxM$%7wZ+G{@`JyqEp1BfE`%y}Oz8x2p`9!N;-`nh+x7(S|x7kP7>^>HpB+?#!*K zbm+2}4<6)&?p1cy+CSFh{`*@(M8qvFdLKru?uYjuY_QmHpZ&~rr?`i=EGLZeUG3P+ z`GQ=EmrnUvCmx~?>CO5272eU5=bXeg+kVfv7r;or|2#zNb3B2^ZTT_#1FK6AQ!&Ub zs9bVH(hk8GB^8w@Ozyy_MM-Xr`2A?74l(&MJ>2|U@AG5qI0r2WBm@{CtpLu2BSdx? z?2YidjP67ueW#gFCtK6)Emi5xzzbz!Ju(lTtP+6$R_Fsuw^dwRB%ul#U~2>40(P-R zoqoMdQ}^6CX-Hg{CU^rn226`9Y0k5^%60t5c5ss=g^XXM3SI_LywU0;NS=x96{?YS zWBau-Om~5(W73NLE?z9uW%1@IW#w;GRp#e%|K<<_$l{}tUxlOu7c&`=V${}4`A)}> zgomn2c30^+moI#02vfvlmmNZFD$PLVyZDM%ev1@*lcklAgBDCo2`I2_0}CbAM?mDe z4@MpNG`stXB%SBWN87Nh-^!K@@Psh^BS(c&9-$6I#fjk+P2p=_HlYq~ek2q5?%h_( zM_D5oty-o5FJFE|QoOaT`|W`P2j-Eq&Hm-iTUqL&>{tI1;QvReIa|Si?cUAjc3b)O zU$uAUIM3wA0$0JgL$$z(CG8Y63Eku9DX4o(NUd%A|8nZ>ZPic4bpx>=E9N9HM_0*zSnr&Y<(?u^l!19ziCL~`p^G!5h+kzL&FN=X1Adt z9X4;zQyZGxEdVw`lQbYBAhSzY^BLwjyR59jDIudTweW zi`9Gaa@f?du+%F}ON$5l_8d4s+1S`f&HuThgAKzp<8yOvIXPlzC^h-cjt?I`%nJ#T z*|hhe&&8$#-V2+yY>~Tq_08!VXev5+c6(3NRabvP9BAm^7$Wpv+}K<$4K2;Qv_zUC zhxat`V{@z$zaP-y6*kb*ZzW!cbmQIVVVO<#VM-i+4$-T^FYn)%3Hm6_x_9gVrTeD6 zk-KWT={McX<>A@)(8o~DTWwF|NW_~bbTV2b36*<38E)iS+liOCdw(DC2Oa6}FZ+i0 zCcF)4qx?hSe@MhD;D5+YNN>gW$!sF!sBX2Vt!8Jyj~$_)bXN>I>3w0(L%Q0__)aq> ziSzg9WE8E!-#p3N+3!nSt=H)Jh^qc+i%DIC1;sGmo-Uq);M~L z!Giy0caC>S+L1r!sHxMq{#IRmT~8P>KmMY-Gs@^^E&5gj;YWT7Z)d7VUQ_3 z{usBRw1bjYpU0?|Px$>y^0aNWcZhdi&^C^dPB`-BrB8;(fB!N5<(3J__we2Lw!iGi z%Im*<_B6)8MLf4iKHP`2SN@@kTdJpxa5pR5?(eJp)o(9}OKVxjbmI(h@7ECz3|}Cf z{qu3@*8NXBl>ZGc@rL0+1cGZt)zx$u8MCmk_!M&7*0^G6dBMug!9fO5J17O?v$H3q zrSI{mLQ_9@@Zg)+SW0DOWsDsjaok1RetXySQ)kl<9ffC40n#H4Vgmz~QbR)niN1zc z9d&ivp{^>PIU|qoK&Yg%J7S!h$8@uAZ)|UG-$X-$S0@7vrdCx|U4{Zzp+%0}P!NV4 z)tI=>URjHirv-BR4gnoc%(zKI-igylItjhw8Q)=q*hOsy=@DxZNSj<<>R2mAUSYhy zhMn{Ynl$FnZV>$nTF+-p&(LONXX^ozlkP&v7S7&+hvlkDPru@vH}mFwaxxu!gilDL zKO%-7U`f)7-1_PB&2{Sc8RQ8f(D!9pDA=4x*!e&6?A1z%d|=IOa1Hu06R$(W)17oN znaj|)bm`J260i;3I}>AL$}#mST?zBv$(IWzWoqepI|X!(WP8Rk>XYtr3mCT-|30gE zM@f&rjKO5|qN%&zPJ0qbDRF~=yg`MIBuR0SgLJpg&q?u=E;a4aVFIq4=%Dg0M*lHt##f*xhS3^Tlb{R`HPy8h}1{qeo}27k{2 zlpMk$^+#4ZB{lW6hX+-Vpx*uQRY-!b;Z}G&du9L+k(ew!EieCQWwKo0&(frmY1d8I zMlV$S1GMLLXeg_iy1K#a?CdaBoTpGRsN-ue{3V2ZALfCxf3K^1Zqr|V5ALd!6^+aK;Sv^a_zXksA|H_}gx>^lUiP@~1-gm_KBlnh=>q!?@eGW=pw& zg5q;UMFNA#pOVQ+>E3QhLqpSP&x$w--Ma#(g>ASmM1IZKc#t_j*XGSU5E>mD+gh=~ z*7MsbC@6@94E+kL^Ancx-7C3w$lkvqTf~%JVQJ|mjH9~6#qo}|f8^CdC}cfEEm>gE z8cfP8!%3IX)GR;U#O=yVSfr33yGO?y^^(O2yyP{TE4tES2yRf{R3VHD? z>w#@5A!3g>m9<<>N!-+=r%xv%KF7t2{{8-B9Zpl!PEJm83yGJr*C{9{V6ylV#;3{1 z$b3${D_X*26csrwt0x|$J|ZIXn2MAur`Wi$u(DER-WA-M2nUE+_mkGIUk{edfcHDJ zppl@p5q~FM3W2sX6`9yyqBNnQq1BCzCn4%V1g4{>-*D#486Ma?92^@|L&b^lA`g(%ciirss2qJUZo3MAxqc}7Gkx9WLN4{XLC>mjF+$W0SC%#; zz>H<1yxED|J0pbk({;>LYGrymveZJL^<9a<#4E$*5rTMtup&F&tHhg4;l>S~yY9^D z&zO!<-nw-QDbKI4{Xf{m4Tr48-iaO<8e-?-B1eJCy4_1T+EL(*w1dZ_?neg(tSjW5 zOCT_NVpz7|2ebj@5z5+P$iVs?`OY}ofiQo`PSDFKzdJ8j{E$MZ1M|;F+dqhkit;hS z%$nWI;LFptW`tFljI#+!%gaCi{K=A^pKp^`_xbaB=;x%f zmTt9JYn?EiBg6p$2#*iNghEYCP4w8YE^r3EWC}&?4D_uVH*ZQ~4jgIsB1BuSnwU@# z@4I0G5?)kDoD^YHf{ZjH=@MmUafKZR_L5 zk1edNiEORq@DCxl!&jyO`{sy`hBw-rge>PC-nd`>qp)oIe`cq?=Pdp51%WDrIp{IK zAsej{0vU=7r2?t6r!ITd3wYmygpM=P%N*d-$zuculMzV{2-ry?04X^+d4ETN6ynO8 z&CJYRy?!l?S^X0u-W@Sc-ab)wbyF$0eWI4XHXt@iLWas|x<+Asx=xksM5$~G7o~d( z$^)Ad20nNtClXz-EhXb(6pIkjT_6`aNzzF@ahYH|6hpY&I7Z2fC$o| zkZUY{f8PbMbUhXZQN{2Rn0%#q6A{;i5f03CKfsNPQVDU#!l=*D!R#hc=385tcZ*qn z!o2c2bMvtsKV&BJQY|ka*`yyo3i2kFf#q<{{J*M~){o48Dnudz3c)-H_Uog@pwr7UvVVp&t=z zvac$PsA8ffU(i|XZ?FE|YMqG@tWQS4cAutrH72sV-J)IQCMG7A z^PP4P0(kwTdb!=syo5ze9B#!zw77j+=F%lb#5L|C{2DCj%!F-l5A%=6J~qVEOGK4BVys7o;LUj+pbBC|~* zx1=O1Is?)J`92pfTzCitn>cFb?D_Ne^YbOSRDz#iWQIg!Qqgp;8XA(roU|VOxeMLV zZh8L0>4nT&ZTH>WD6FlmapK52D=Vvqj~|!yEFtGctZkIN2wciM#wkq>nqc&*y0-Q{ zQdXvBW)rXh8a}d3zA5p>T*xNaz_FK7RFz z5nY239U{wdF3~62LE{omuKC&4R)gtiBW6xq6kBD#8S& zN5b0T&K*2u5#zR#SaQV5{Q7kxD$&g7Qbw8?7&({VcM z7_Ae;Wn8>?kt5!S9AdWDph@+V9jI@MffU{FnLUT{bRiv?{4FQA<1K|wsE8|x{=B5p?qfA8`0=g&_c z17rlCi%<1}iD>sCAL&;$e24q4bVv}}^`nXG1Jt%NG^rrD_H*+75)2^}3)1R|f? zkKuOPWb9~eZT;Nd&I)3qci`h^@QjM7D z$hL~E6k`>$-h>{B60yCORore*+1r?y&7plTyqfPOBnClJwF@oDv!4>gyt802Q(io9 zJbn?)4}DTbM<>5>+3eHJzz3$%wtjvISx;i`W;+U0nfL$_L zT7mt}SUAB_uB>xo9RxMe$#>eH*N0mctr~`D{lvye4|n(L_dSS(O6~%C@pEJ2j-|OV z1#Hs?gI91Y3$Zee3X)Bwq);lns{Ypp*5zi3E6%YgO(=#&| z_kE!2y25Zih8}+$z3aZq2FBkfBxkQ08BvguQ&T@<6yAJ(VWA5R>kBeDp6GP!Qc`S~ z+xUwAwS>U|q}&(MPV7RoNe2ab;SmL=xP+!}-!>zR2|1I{ES~_S?hzMf>FVl=*x?cD zIycg~6Ye144G;k2-Mfv)j~{nJ$~mI&lX`&A(W6A>fo1pZuE9ZfYz`!8y8!byQBoFR zNGuut;0+TKeOMtfJ1?I&K|*08B3z0b+eAf03FO}2m=N@S83(rP*}s1?SQ?R2!!TUU zJLR3P3)_yiEo3k9QGh_Xp*ZZtAx)_7U|Dw)6Z!G1ySlsCd-Oe}54d6G!!}f3|qOdJUkhNWaihf=AZ$<3a930 z$5Iuql0EQJSo>pOSz#d=3}o{f8z&eJo%TAvIWcU58kpXn6Qqp(ha)^N>Y&rTglh2Q z$&_uBIE^1>;nAXi39SX_8lCL75HBh1|p`wZw8 zwE#z|HNEfkW;wPFyO;+X8MGWw{ZN%L3kEiE&PcVX%`LO~^Yvvg*nEOkwp^r|l~y^| zn~+qHm$gYm!~rvzkje&!pYup4AMVD_L*v7 z-TL)HrauJdv64X;zs=k-1C(a(p;xa~IhURuSF;a6Fu}i}zFH7;98Q&x9yf84Q9s>f z0b>b-p7{x#z6Q{0Z(C(04W_wEkVjg}h;8QZW|JyLxWEE~e~pRm1jfcjO1pD(11hfq zO58qn_O5|}h2q|N8=a4|VFDE^rgtEdhO3W&!xM7ATYp^`i(nyYW4Qtc;mjN$15q!onB1RL`^-;lK@< zxu@_~zE@NbhaQSGF|e`G0(}C>>MHUcI8cPyBb*CG3&2XScbuvqM@+XvGoX-^loULC z_(5#!0WsUrEg-bwF3XpqmcfJzp$=f&;Ox0`_p-B(0k#=TuD~-SDD(w&^%}Glrh-!Q zwwy;3-6gWh%2a58$?54|Ri#!g^vxc9A-50ni&WK^7e*y3{Pc-5Iy#zVI~K~Ld;K~G zRxt{*kU<%}b$>M(*d8G{06ogvvq`yZK_BQJ9E66p3qT0T=DR%J1qD)Qc?N^sy}cVD zOOP0rih=G<$lK~0GG7qm2M^28}q$#vOND7D;cgw|PW!p<2!| z>gHH)W^%sZoxH&Mb};OidQvo$0vPq%SxUO#!0*UN z`p|Zzq8DM^EaxTRyu3WQ0bHJsA_s<4cfMQ01kV=?zh0NrXxT@Az2pCgN}O(wEf|M8 z-ieQcozM-&C<-=?qb|lFF=;iWW7az>H&-9i)J1M8z>C2lA;4|-2I`{O`T1#Z8pJ(+ zfB*4&p#cFDP=mpL_U+wEhMg-5uLML#{y{6ssXHD74yK?++ZDB+$D_H9~Rd>g%I` zWk7tZj*bqdGY%2nZfwUdBn(qf_Njw%n0(jG)&*tWWNT|nygXj%GlA0t3YNEF(C15z%}_EX z8<%lEVaV;^>+qR6ao{BZ9?_smPz!M@Dex&!ZckWy3HRg(2@4k^$Eqt>Oh-qz$21I( z^sc+r?=!P&K{-}^G@@P)(Divf5_}y;wP4(7?q$TpxJtT& zw)JdaC4fawm~6s)rNQ-^FVj#19YWRySi2Evyc&suUfr+WzJ2R+2YjS>N<7!1Yn{I5 zhyMN>_>1GjFZl!m3Ucy8p?#WgeW<9Z4Nz@NF-{&KPRuZgSEb^S9m%RBs3(TTELlnr z^mt?qDA|~8WyIyHXnduyu<#y1NwJIf^6~O|fI@%h?7X_y3}*>^udVfiS+x1)`Ir}) zmo5p?(t3*xWg?pni&clWY=mz2Ng4mON4N``(?q+>)uA2;VT zmINk3=lHwrL;0@cq&4?G{$wn zs1qta;Pnn=k&4&?2zBql>wvm6Xzc{rz|Vr@YYBpiOiv6FD)#jBG!_?#%Ll86*j#i74I7slFw&MAAT(RhO%&3` z?8Fuvz=KEA45%nSfEskD&%W11(r*0Cdr3SQY?c&A8Jh=)_&oj&q2zEelM5^4FFf=g zz-`=Ra|pK*UY37Ym_V#;gR`1uNoDm+X~48?@CqBet??k$R?1y1S?Iz zLx?W=I0*EWq_Y@m5zUn=ZA?@AKp8}_yWO)1QZTQ>7-&W{oVNuA-8Kv)BxBzJAZ|)H z$5OJHPb*2cpcoA{8Izo_#2PjIQIT%MdxJNK-5KWjp5~{>fpfgR0D)~L6(xxjGoorU z*Rxbw5=l(?^$D0`mA8jr_5=rfbfSzEW!;Nyy^tP{?&wiVG-XT)b*;gqick*VXFPQM z2~QXVd!U#{m^d%`{yo0kK_rt~HFVFOJtSa`9XU#O-OZpgJn;A5feEYiBtoJe;!BEJ zAzk>sudb#>8q<%A`mYWdxc6(!VTciO+5NxKP$&vGW6A%R(GqwNL=IwxflxvOo#riI zN*#I65AmTGw2%F+Sn_2KG32^A##f#+d#a=g( zy@}9H&ToAT#}0Pd=C!4Tg>Wo`LkWWBY6i+wB=9y_I1Q))kKD`FxYD5st|#GOK4{P# zom23(&dsyL;S*7THGGBs)HyiVaH;K3fR#W7EwBo=7{}c|Dvz>@5&`3 z;bzRZY(h+W7pUN+v>RJ-+A7Wm-W+p*5-p`mcmsS#SbfxsCu&eMl+_wyS8)aaL9#(w9u8!HaUX5h3U>&JYs^fkgeGvP90`QY zVTA;nNaSJW!VG3O5 zLPpsD{wvr0dqq_)X&9^yl37En!C--RaYjQF2HJd`($A3q8OgR^^AhsXC3sb9srtkWnI9jQ540OQm=@d_-YwH z;-;_ua;YHZK+;Pb+w|Hb9g1Udl{EJKxd0PwdFie8?10uj0Rc&ti_<8VgJygwnVD{2 z0sz6jFJ3&vFz{7qAsAY@XHjU$0y$xx3^wmWbSA7Z&c#R3Z{I#ZL#WRV#kwkNZq_{V zIBN0DDSX6naqsWnzdK~)xFFL9E{a3vKjTD^Kab(~ICZWG7x3L_|hzz#53TaA0xY`(?lP-f!Qsi+X+DM0+sf?^ytV2dGbH zjoPtdyD+yaYTZwVe#{fecI>v?i=-4zE*ypRt?nL%dvWp5Ot|X~Y1mwOx%4*bUa6Ot z7u+K`JZg9sonvF(sMMX|kjLll)IM1`>yg%(6aB?=Kjv{(~Ek}b-TQbbWH zOA)e!QI>WkluSxw%k#cHbDrh@oYy($`Tt-4^E$6Hb7oS%zTeO1b6@v$UHA2Q`S3(5 z<|CC9FRA6Jk5sOEAc$jWP+~;1&*-C*LLIi34B${xp?{}|>tpt)=0f=47of*B?3FpO z8}`O(DfD~0siiJdaIBa(^-7S7wi~;fa}FL#Oiawk^|flW z$qnhXG?2$j^GANLZU4wBTn)UU;i)huJ`7_QL1!P_qwWC_nU@OG9THDlpU+By%nzH84z*AO4H;lpKl4{0^1L6GpxQY z;$`aX+vBiP&1MYTeaRB^g2le)j!t^+`Q$-N3$-EYuPZC%sswG8S?ffaoet8p#ld&=?skyJqF4N5aa5dwmw6q@m6rpj^Ip&!X;3OeSc*t**U~ zukR0ed3ngf>e!P}vE=%PIs(9l5$V?2(9n<(+^cjiab&tlKLvn-6E<~s@7qXC(~rf; zT?n>L?df2A`A)bgnxZ~wv}@PNz~CpNMFqP;XWNDS1OC!#&!7}qCgA%I2n)Uk4ovsG zwS38v^h@=%eMG?#m``ULhty*9W}83&!eE-JtE&q!ju;LV(LvWfQLHULe#DV19&1r= zArE=~$Y^_Bj$Ek_Es7%%X`>ZgWSZ4nxE?7Qph}_*8wl?^VDMm524|tZWb6=*csXrc z`hy28iN~7;h3vC=<`vk)?MdBy$e(ATB?#ZqGsG1FvbCWCXfB`=I!KeecQu<=kAU>z5$+ZRh$|u*eF?!zC&^I(3Nhmz6H>zjeORYtQ zwHh_XK|VeTLW;G*)_nR!6gB{b%rb_Th(N&b`xUh8va+)Ik7C=i#@C1Rnc4XK6oo~J zr%ecl?9HY5M~@s)L?#}LUbgf2kt3J+RAlU(I2#;srpY2s)vD_1^_=c#CYsi#o~GPf zA@YE*(DL&R8m~}BBGewXMw>&@+{DDk{iOJ1@N}hRWvO$xVLnP1TLfB9lrM)DoB+}1 zN@D1NN`cPRVl?G2{o>MjMj7;Ae0`Vr>ghO4co;C0t>+Bnyg{j`tH~PX9}mKpPDzeD zQpw!WgIKR(OV{r`g5Rvsb&uX_u75kfZhiQ2vlSke`|M8b+}Vm6*IYwmBS|04F@l(Q zc@0QQPoKNDCc3_mMwEmYN0>(f%GPsqzJC3>iV48xX=8j#if!#>{s#i(NWu9;|pYaF_HWmTMCZ`ukS))2C0DCC-obXJZ0-Jm|}03V?4| z2Y`3}a5@t3+w~ z@>dh~3eK{h5_EFT&5SyZ#a_IYrAe5u5LE+KP8F#eG)R~g*|sVUl5@d z?>V^G^3xA1d-Uq0)-`yHP@zPDORb(?Ssw2!t|_5RcJIzP66^Y4(2PrK)Sx8boNwg4 zjyswyO_tE<#2!`k_&VJ8Q-$4T&|pI|zy8WuH6r)$LW(LX#?CAo|Cfj0=?1rl^imFU z<~%$yDlug&Dv=88tEI(=(OUh$<;JVtHw6T@ZWOUIgD%#=b?UnQ>(J|A74ys02QAcn zxcdF>c(Z;T5H_lpXSXbje)qqYNnQ>$|Fgj+vk^v%y5jh-ZI{qGL4T8(a*1vRFxE6W zb?XHUNUi?4hMXe~K$Ns^)HFp_4O?b`dpl}i(Te)OurCEI(I#!btlNN&(c9}_$O=yW zV&vG~$#2SjN3ln^!145`ZVikbIE`yY`jVoBz#|Lzx;WJ+a89Qt5(Uj!4;I;%koWH0 zQaH+I=L^@kR_TN2iazFieQcnwFR9Q4naQltMrB_Qjil8YPA+EnQS@MQu4@$Pngnd|MCIh z2QqVqX=6Da@cC5`uhaQlPI&opL*mAU>CU{E7x}aQqxVG_Iq%Mur+aAQ7w}gJoZ|9& zXO7)i7nh!V;G&l=d(`;c;5e3JlhT!j4?GUiSsn(uhLlgbu&UQK6+=``>wWq9Rm4E` zp>>C@+)8QkqBhrdExHGefWF+kf$I)dYc{S^uHG?5LY45qk-<|Rt?+zy^~d59@7#i| zdYTgmcXp`T8aUO*DJ%L(ft)+nACX*&Ep~9L)X&4Pj zStTp`tWKRe)j>(Vg-(-01ie_Wgm@ldc*a$B(@^i>Q0}vl-)E6rpKnoyH%i*c=ojV=*A2K1U$2W`TNfP^)mxh(C?% zc{&qsqoDBrfX*fTF_i-eE&x!VGi*(-$@SpqqT*tu_U+qCp8zUl-cbJ|i6Sb@kaiIJ zAWdE1%u-ou&|ilUokiz=`czCSWXao)z z`VY-6q4*Izr|! zvLKueV`#*rYWa!wt1mZr6sMd!ecFTr;r1Y?Ntt^PfeJKJ*4C4YCqzu5g&A&Y`UB>q zw9f$7=E)NTx`nN+w>hSZCwblZ^G3)rhiQ~~CSYCM0W-TVa^$_;h*zev@}3gz=Rac& z`YZ@>c)lhHkFWqxekW79GnPt2g}9uk$G%8YB;2?&IuYtQ5*{BbjD@weKRb!U!L77I zmIsM+?ltqGHaOj%<_&AJ7FtL z7fwidQ{2PK7H`*;{ynRhpZYs6HDX&AG!uZO1jWM;(-r@!nYRWbp1?4=H<3p9-4Gcz zK+A6`-?4xHR_6Z&@NycpSbrMPx>FWZ>gHML0mbwh8@Fz~O5vB)zzqb5D-pv#pG!%x ze;4(sTlJ?}SC}&KMv+`0NDPQA(Z=x4H(oD6kA$-mzc*^>V-lp`nf$!_%)EFN3CrXC zYa5ZGi2V?C>fO86fohR46~|pZVNvlb;d%%|d(4W%zJ2M(0^LGC-eRjB@>1;Z&mHOW zFzr4jR~YNNxPb^lR_1sK_vZMyOl_Fu=;(_wz?DzxEJj$4=FjcUI;n}(7S5i41SO_q zuJW19SBOK7U(=B^K~dHCi0+gDa+*f&>Vr%MOY~D^5GinQikAU?jP=O;&uLmyaPg%0 zUXD$_q3#r67((R~@o8Gz?1uCQe#U`AGQ{Le>n_IZPM4n&JBq9ZO}Uvbm0<#e(hT8Q zYC}0HxH+yZ7sBa?T?Q>oPBP@PCM%9iI&piT%^%TG%tkk5*0(JmQK&yoRq$L(Sob|f ziw?e4I^`P}I6mfub@o?_)t3Z5GDwKUQhe=kpmmAM@J|8(cAMpBHRLuCbU*4*4U(G#CH1}5mx37FBR4gm%Fa8f%=;RU`#elW- z#@HS2F$H2d$gwF5fb^Ce6@rA(810=tW_CCbeC3hdwHtPFd6VL_z?eVs!$+R+(2FSi zbgQ?^9PEf2VTb+?JEG<2!F+SXj_HZ+>+~|F;q2!BqIMBX=mS^Yat_ivoMMamXr_;ua*}Q!> zGr}Uea6E38DPkvQ=bJz%zy2qzGI1X4*|1^Mf!bVhH%bOfD4RQP-qaU{(59PDL`I&* z_1S5NzW(n>Qe39*Ln)RdGxhzV6< zZZyAs;lb3ItFIxy@Q*y-_&$}J6o8h;U}lcwr>+V?Nn-Zawqh~x69vbRJQ&NBxGnhR ze2#t@8e@O^Z5H%iudZFqcJ(1rna{zl(s2Cg`XtOo4I4GuTun5_cD@gr+a+cT9SDCG z!fexn-o1J~hKP2V9(&O-g*6uX1xvyLT(sfufJ!(xKcg}D_oujk>Cf3dx})I;wPA2D z#Cj$&4`=lK#XyvK@U0_sP?~mc-)`|D*=2I>3TvB7h#g5YqjYX@h#!lHNT)_TMk4il z$Ny};`;9=jW3jP!WzeRAslU1S6&E0OVmGA)y2;x@j7aD)^ZC3-RG(j>mQR}WyBH{r z%nw7w)7w@u&B9{)?f2FTC$2sf_1OVA_;=@ZIstJf`+NXSCXyUH{*u zCXwm?!)@xR>-wK=Q}N1hFdmMMwsS1tOzsGhz=3)N-r4+bmQ(#p&!Umuw8SHbPI=g1 zbIE|i<`uAAGL^KumyNQy`L#i}^f@5|Yw!QN)6~D69*$4ETXl?=DVfyO-qc`b;_Q5H zbGHH8h>1Hn|4;(M`#rL7D#RgE=B2J`i+@o6|4D7q_?OkD$ZQEXz%DZ!v69f5K&Hu| zG;ZxbOPM#7;Unhk zZ6z+R4?^$aoNMc_aA85O+-Jqb>liW2-gl~e;7ylyzSlJSE<2$=(J=&bXkjr19VA}t zlbZ^(2pZ;siEdy1#Hz~oFXnErG|2NgcgMo8gys~$M*Zvhl}+3KpG zQSA)aG+_=GXD26bnwu*6e5c?s6}_7%IF#o+fEe*V!vfoxChA&*Gi}MTdHR*`G@r#_qYy49X=9;RrXnaLoHWBq*Gdlp5?jSc%2*-)(5f3Am{Lz0)y4Nt*5t%d>l~z z$VDsJW)5@n>4}S)h=9VA#b|vomJ(D%WyeVOSp5HUZxnEc_!k0I#4kX(FqF4+;RSY z@mN=Adp)-JC%TutW1+Vx-9z?(d-!ILD-_tc6h3$gj!gOmkn%G9y#BPmsb2q*(p6v* zJrvts(ST;~@H?GDqmyGZzgqrbG6#7Wic|zO@C6wN*C{eWzJ;1Z>Vm4WFZuMm6dC=0 zC3X31_V|U8kTmWh8!E@E!@lL_&))`a-W(O@A77mp`DOpgPw!4NubNsvGeMih*f(R2 z?NiN_`HG)D)H#oKeF-hYWt3l3Os=U%(^JUEn$s%lZEd3}+H?pm*~Ua#m%Xh??^{po zN$6aRti`3J4cN4FEtB32oF0;FdxV|j;^yr3TmO4|Q(7E+!M&X(Ecq=DPT41QZngBG zIc9n$ZtJ)_6nodNOtoz)I3FK>6^!cft$sDe(9Mlvk`oh)cm}Oagy1u4 zdzdj1{9JVMYdAoS65klWC9DIoFgfPW$#aE(sIqJ;O85Ln6TYk}V-#+6iPrR|xw$1Q z8~w8hz*Zr6V1$(;>t@f%ZZy9PzdRyu_;U_{D;hgL2G5%_rvY<=_&?NGqS0}>!Tmn= zR5?2(|1Jw5^v(55=WD?;8;ob>KzUoqcjvE1^q1LxU-7~ZYwTTj+S1eczlmO!pHMX` zdPM+)ajXZ>=*!UdkuZh@0K}(iJS(3XSl+VY6n8Pci($acB6@*m!hxxG?%>R)cjp$v z*tf=!`<zs1{=F~yw=9T>S~&0Yu}CzSAMda)7ETP$CuBP2IpK(-tyD^ zJ5GUdT4rVqf1TgJGV$|<4Q)pSbnmFKHf%|mc0ox2H|RFIyJ+Dm{m`bJcc}8?5>~z_ zKTAXoyZCZO#<83~IiYjsJSNQbhRPQDGnvBW4n!FqV$EW+wiwX5W?|58_!*3b!9zde zflec98CCHfbg=CTfSa%*w6SPVk`nxqi>t%mOFJ|of^R} zA31icGY(vi2K!{xsKeqe+< zNyE4f;My>*yYIAK8Sp`3-}&;TCQFi)WYiUg0wGA@^?Ceaa7co(JyVmSjZ^Nt-oI{l z@k{mCs*m0zd5Q=jM%D7v56cTfILKv!K!(ogwq#a>EOWAAhaCiVP&YRkZ*WWJ$=o{|v?SSQ)ad>scs@yu_Z+RDqs zh>DN;a(;e$3^FogElf=L!(XHyg%W*M)~cPRrd*F9y)e^DGMdg?>zNmN0n&kFdcSeq z1%(N3#^K1kUQ$7^t~sojGF^NF4nZCFG5mS*)1zjkcF&Xor7pT_MTuz%p6?q6Q^v3XYIbfqRxHNMAPR6mG9L zaA8GF_nl*oE&%uDWhS5x1opO1+}=)0tKVUZ8_e2-G8l9|Jd(z=VS@&JDYM6b8mMbe z_<`-hvgUEDFaA)?e4-wZQTiZg#>-%(0M@f<))VN#PBO7Mu4_v@TE;SZa0;r1Ip@<*zIxN+7fB$=FzrFKnG`W9EVh9Ftp)aNjAjvd5zPh0%y(HbLX6JYBj#)8pUhs zhg1Ihn_2$mw%q1Q578||ZYX@~W!y$br@&!_XX+`VDKE73N3bCW?n@H0AqHxZ}gg4>NElICM2wzjv?gzSONpMxs2- zuQ(=CENryI66ieY$^HQzBXq~#`QvzQG-?-aDNVF~AErzw{SLEct%LFrdd_|j85Q{O z7GLKx`{D;nQ!zic3puA5d(tg|Pg^>9=rbpd1YODvzd|J?<%II{Br%- z$t3RlSWr{YStsb{<;D(|6;!dzA+x@G?O%C^4cQm|ifK&+3q1qDvr}_bcN|b$H7b)W z+}5yG@$g~@lyC_|EB;s4gime|s&e6j-m=$^r(7Uf$vdh?50{_$ktXIJOH|A&ZIJg7 z(6V!~=l@Q}jW9>G}GT|YJowP;~8yhe6OmV^KRZR&&8er z5KwXRE!QLq2CZ8uW%V{L(KLg;NK2U=CpU34zBQ%4_Y@JlM3&+FeVL7wCzVxJke=nz=seizf-6B zi0ObSa@zPV{08z|f`Wqb3>bd%p`UO>#*mC0bY#YC?Edr)!@gDJW$gX&d5D^keftle z>c0lyjv;?ltQ$^F-T6CT7`bQMH{GOQ?Sf*I8V{TkBKLta3xEJjcY6_%NGR!e?XsD16u= zvrtVjnfYO)73c@)CG#CXJNp%M`?WdIu;U6bA(XC~bmY(>1^%Vslpvm7QVj*3iH{zn zus>gUkOR*HBO}z1l0t(XwMu58K|?b6L=G#p4hyE$1|1dKdnP33u*X(QwVqUVtrP z6k&JHEijiPY-{I;UahPU_Zmc78jl!u-A9BBDE;1xWA-kAF7>LbI-IwHvA871btZ?Z zv>mtR?k)nfD^NDmyJwH>8LaG;oBl^jUd3e%Q`QvH=Izu5%1Xox+|52a5*YA&oBxPT ziuf&+ZFm-wP15HbG3NwMhjV?Sp`a^KD1eP=OwEcMg=t;(AR{RCMXsrT(wa6`Z9p!= zr$hRMM=@F}_{{4=Lv12zZqCsc38`PdR&XhIVkLCb%UB1$jF?^gFa6AL>*C1c8{;?P zn@B2&kMECIB?wT_amVPQk`iC0w>mNPi#kRBQ=&3mUOS(*WE&B*acBY4?CjRMe=R&v z)NAL>%O!6m?xlM$sTUm?idBXS+Le3c?eyB-H`j*onCrZULiZ6W@y+&>dMJ7IjR6S- zrQSU8frk!uez0JxUROekYo?3L!VYF6Vg%S^Y}@9|eGqfmW^&ETeNqhZZr}=_jq|}i z22OWJfw%oNZ|_QVgK^`>x4u7{NeR`k?krz7~5% zRBBu%<-MXWIu=!yN8`Jbx{Y7ckN8RgV{9tkcGz6q)AoIA54UmSnu=12&kT}#?G|E z<{|R_ItGu?qqZd-PR%HoN*jw$L@om+2~}o2$SR0B-;B4FRHSD<^V)A)Q0GNu###P+ zwqAYZ?NOWvFeX)qpj97hgEFu`wP!m^!%3)a^}2*^K!);q zd$(rgzqmNkbu{#ED24azAk_xzr>uTsL(HH|Fr%D{pPpf_m}R}Gd!?q977v?kGz&Le ztKqt&fBy#YjJr|=uB4?Yqu}OE@{sxOA%~041|2a-{>TjY_AF~x@7M>wd;c#7!smyD zFT>aTOg;7cBzW~NhYp4tPO-F9YGrDQxS#Ok*42B=sr}@Z-+Dj(4(c$*Zub`#KbC3j z*}K>9!t2b;F(O%t9uGzlCj`!I?o_-no01OLSTv@ zmW#^D0%(zqUTLaH5^z}7b%eLXCDccPpWNJ0fSOHXB4qoH9qafJ5f}#)1&tch-qbObYMZc4 zB+VAk+Dt(BXKkygt@TIMq9*vF#!^1CDFQN8Xt&_3zPb9&d`g#_{utGduPrPdP8jXw zJzCT2kV|yr!U4Zcww^L&M91dw^F#UEcXTE00Ao=)hJhADogBc|-xZ(#hbzx>^8VcL zFwV*JW3u7yM~@zDU|%Y}>mKxj#5IXROegB?en5-4>cT0%?jzarSIwba?zC*tLJ2T2 zbIr$wWMg155Z98z0S;IuU$4&ABZ!BY&2=bZNngaMrK;+K34*4C%Il7(VfzSmd9(dJ z?&y)0y{|6$D3}_;65A4^Wf@puJib7rVPFsyHmcE*V*RzDu zBDG&ZgE?o;oQ|D4D+7qk;AB!#Q(e_wqzt<%N%E2^Ayy|b9Kfa8BN;dGZXI{}v>S)912 z%_LKr#txP25*liW2m;OFjeVGW_jBd2S!oT(LJ-^p6K6)+Wj$zJW>D2~h?E`r6NyDm zC2zgk;IPEn7mI_7axBw7)mrk7y z7r1hor`^2y+d_>6m?mn=T5P$7M5zMPphsubH$yB;Oo`$O2?uH^)~Fu@|B@Qe(SI)U z3FQIi=hEJNQOx#(}2s_*}-F% zAkM+Lci~S{v=s`ldt`L9BP4?i>&jFdPK~^atF;)?%3D2f@Zbo@Ip9LnhZ*FtkOU(E zuy68E%&iycfIJ^ci_rpTotGuH3l>aqW#ygKho)Lv%Zh_ilUzHW^Bv;1>fu+q75x0w zE1MVE`!szUsio1iim`&NqRFt|LaQO80yX_f1a)L@iVDcalqsuWUtdk6eejCNEPCs; z_=8;t!9~qF zG+b${bINkbP8owB(MM1=MdA(r~SIj<}JH8M~m zZdqN0m&Nyb8F!c%o+%GW1)4=}t1hjqZ9F?}T5PK4(xuegM8l-D-!7(|a~rqn`0?ZE ztT#}$m`y-^bT?<5@+BC>sK56}+p8CrSYYN?DBy*mq({ZXBU@kobDcR0nIj>okVR$W zH3Sl^fr?6(0RuGny8%qHVg(UtlFiUPJ0I)ngb5P{Fi{XulQQhZ$QDqKTzS}MeAKPa zJRHgVQY@MpMoM~@o;@2g*-#d6iXSS&j)3f6UeAKGdyzRQ{<`IOciRs9VOluxC`qt| z40&>%h_8;Nb|=YvgsQWV;LAkTr%C4>MI8Gt3oosW2?-5t=W~9^R=_BhqvzGfj~+GX z`7%;GR5NGJyfNp@$_>lt0S>#*X1vLO=vm!IpS%1!UVJ&I$1~Uu{N+XT-k!%up;z2{ z>DrsRM&i_Bt)YWh)C8Ha#wI^5K(<%-0FWa9t`L0oGcu;dykE4DAI}Yt%{Z?$$8_=g za@b;G3ug}=CXXK1wTzI-%px=|L@A1g18II3u>6Rt;5dY8zB}IKZf0g0BY&DH0gTSc zd07ATvnd`-VebXNLPjrk#?7=H?%|G6^0G=zG({1yQ3H5DB}nuVVurnUD+i_SB5vhi z-X{nkF1ZbS77mf|E*l^NB%~DVqx$2I%)J><))C={btK}1udtu^6oWbDDrMfaT)%Kv z-@J2+i(lW{ak|W=sS#O#0SoDP5gs&Y1M-ekGFZr@Qv(qzFmQ_JR~TY;lg!!6DTnL9o~a<9XRJXLihvh|L|hd8H?? zQPP2!PUcEIHMTxGLJ8gNK3A0M=2PxF?HDboB;0L08~-H{mFaYt<*C~M*q~KGwQxH! zw#)B)1uyrlqufWkTWdl&<+SYXfi2(|nb_lzjes|iZ_CvvO(+`AALz0Upp&mNm{|4f zS`&;41}>f7U-0|_^Unog{-nCmWOQa}Bj!q*D(NH&4T3jiFo72^_ttKY=sPZ%lOCQU z5F?$ZD>r$@-X*8VgveN^Br!?Sy5eYXWVSqSe=R~{70**X{NWlM{f})#$Rw+C(NSe^;wm` zJstCGmm2inzV^ey3u|txh-W`GdFa%AzAHCB-7~uDi2D2se*?_I_rWW@hgVVoax@fd zJz6UwdmevP?ex#%=51HMzfW;V@RY#$?GtCXdLNu*@oooAkwm8S3_j;+E&pPeh_%4; zHopcfi?81ZP^zz4w`!fByOZ>vQ@jP<-`;_uYH#wbtHsNmY5@ ztm#XpGZ>6nOoiR*48~9N-?LwS#&^OOPd|virr7RcYW{*hF25Z617A@SSBaRxp^ma&)Ns6X3LzUk4XZeKl% z82<%YN>NurQ})=u3{Mf~DOQaaj?mLuttj+isdHrXl$GYp%4hRdwhiv%%W&^mQ*`>g ztA+I@LsU~SaQv}khPmP zYlek|y`3Ta;Vhr(yW#$h)qy3EzP{Yf`ihEPC$|RTJGP~rlT|nW8qQe3myx&s3ZJYI zi+kb1wRqk4@86yE0(iCt9$2rJBAD_m^~9T}E8lT{3@EK1ta_!Q=2qHEuaWA#`m3i9*s{_W>knXOJe7e{-pzckc0FbH~^U>Ijo za{EPL;lAU={#s4BuHC(+M~>XxVb{JwR#tZC`}gRhg;!?}78r<0OB>>!S_7R`t=--6 zxGQn?U2lhbvU;a$wta0WZ0+oftxdIPvVJvRMQSCUS&s8yZC&ngr%39l20dJMUw^;m zP+u$0;>9uV-o2Y#;w&zf`~5Dz(Dv;Iqf|o9w2Ks(3hMNBN42rLy+l)i_rfj5Uvlr+y*v8Y z3s&FPuWBvNucV&+`a#OJ%_vjCZS-t+eTGJNU0Oh5Vxpq2U|3O+f@NcN&0&`lC!(c8 z(@xfuzqbt$$#V`r^?ZL0!%H^z`eeoolUFyx=C0mbgcXzHG{C~IzP*3&-kJlqqwPA% zhj{1DpMMWmUfk4VynKh9>Y+oSXL=js%_<_zaFc2u9`{!C6SgnT7#Zk_%CK#VJyPK1 z)EN`?v8yW%&z)(N{q^G8c>SnUi|UspB?soto$Gb=s`mcXqVHg%xXeOyCCC!tbBb}SsnKj>QA^G9OtuwtBcyt?P?<(tobIfM&4 zh})*fWWMng(x}X|x7cFwcK_JOV0EyxbBax?;&K@m-7JS*fA8Xn33so3SC^?_oBFa? zcd-KB#e0W@`yEav+DppG)&u#4E z`}bkSZbN&p1+?(W?xUr5B%`$B^@KKVRI~4_RKrVrZ``dlRNer0LbP+Lg#n|u2P3KkkiW9`;v*hb+h*D#%0{d7`XhHNa~J&?nyeUz>C zqWkTP&_&`5Mw-C(?KRd84%eG|1 zU%&poY@0)mfjsN$OOZs06rBWvnrMRzo7|&Wb{$6yvd_K=6Wg()_S&)RiSd!y-K@(+ zMMW;veOT&CdwP2I`Uz`YuB_Db%r;Zj5K$4nm;Qn ztgA~j%rZ$at9X9x*!Q8K#WNO)+DRlB=eb?vKZ@NoyBp_mvBX4tftbHhb;FTYXt~(Alx(*uRWS>msdso+48FTdP*|UrC#)mC}zhXb; zwz^zAyr`=>G4FlT*QHk3hZQ#Qx_psyuIt7-EiRQF47z^j&K=X>yzzlV9Pmr?ljpcI z%ZA4X^ClLzy}py2hY*~$^2OP-A0e+_OT$mU879yN^}T_s;+J+m%EV)UC-p<#y29z6cHcraR#`)|3l<>0c;bk|0B zcrbFXaEGpbMI2XGS3g(H%G$!dIBRK&>j+zFEK2!KITnCj^W#ZX@%m|x+j8`gnF6BX zU55HtzE(LISt${bk+pR;O}{m{jjV4vAZF9MUbtwa6S*XBv{h{Qdy7x^U_P__>T<~x zt0o!yj`BT|CQV|0m3HbcE4sdVp#7dt=PlDoe!``eDaxxRYe=fR&d zGBRRL);#R2O^w@oby*A|w8BI7;_0(yX&pLrD4317FS2&+zLLOgIxkoPR(dV?jy-~l zOK-)E@Xw!5GKcc2hsF%Eva;f^VP6&%rFUef6)WQQiE-8)&iDZ9^PtP9nyRX5@CtkO z=YxhhPF3UHJy(vIm^5xsIDYI{(T16KWL$^++k~Q%O-mJ7k;-?RiZg@M+y?KpHrClD z=hhp0L`+QZ-Z#wBwx~|Xd2hhwItQTVo{#Lf?zc}R$x~w-zkOn|oCbTkH=KF)$K2_^ z{GxjDoOuljK;TFbGFS}yx6h0ZOy@I&@D-L`F;Vb+;$f7_p^zqGFU#f`P! zq@S0Tsyy!v&~Ew?h2WbOl!nKhCgoF;I?ArI+9qx6)|AV$Fr)WL)5Q2-VD3~z;Pqug zw>O*J_2hHYHFh2FdyPG<2d(;@!|KLe3b~C zQj*;~Z}qI&#O=mRc&dwMNfi3(_)nG>|nX& zE$i`_%_nmod9c;YsxtaI5(HLBZe4ISkjwk`?`zYnLdq*DX1D}-dl%1|@Nvb);Abw~ zb}G9&(<@W*D{p^KgMNUR@mrRFQa{e9>(woBCw7f#Yfq0`{!u`w0c6|G79Uyd5E)nB z;NS#4x9?$P=B!f_;&$S*mTbP}?d|RM{ryzKbnCF4M|=C*N&rn*>)bW$(>l3SQ-E2U zw+^(wk;itwz1=o4-}Z!!jm>8BO2O8TA3tt4`TpfSD}2&VKYtuK8HtG9j(d~YTEBYl z*pB6_%M08Z?7?*q-Dt+}T#d!A8MT??&rJV#!?iw$Jn zn4Q7v_;Idl-;1@K)rlcb^YeKCW02>CB_;Lu`S4Z&31~e!d2kGgDra?XM6zzmiR(Dg zi1D$JuHWX!sN!T=0nw0)yBl-#kyfe@lKUD?u9RK5awP$8fVQ>RR#o>8iZ@LiIDhAbV#25lr0T-Dk+bLI%F zU29Qtd&_WVT;k_a(ZDSh{~sFxY-a;`A;ilUbYVnuV->|G|R? zbw)?=_z5i~9W6BPZOrNW_U#7$ShtnCxNAa|nwZC`$S+$NQ#?j0?(XrvjQ3iKB4Y2( zogP3xb6UQCZP|P5*vDCE{f+6T@4d-!c33ST@s2>wr21ABkb-g504iwA*hmJ(|UP$plfMj-gxG9U*C@#*Z|>M#)qr(mYh3xjvZff z@7}%T&p$Wj)U^WxsKuxY@dl9l{iHV7=-n_Z;ES~BWK+<{}i(Xm12ZzqNy5c4k zJG{c8I$_sSU-Qe18RFSFIkQl^tX{S1DWQ+)*{!1`!K>}XO=RS`w!52{L~b-I=f4N2 zdoe97?R`tjQO!i7hObQS=PL~#9?t*S zzNdc3D1ctlyrVckOb5AmKi|Gp=r- z6)!I@7D~j?v9W=A+tQByj*1IOoAKU^JvVW9ansi)p8h^fQBkq{sE@RE=uwl`K{5CC z`7}P5KxU|Je{-+uk)?h~&~^=A^77wD}cZJWPr+5ME1&B)C{2&D+Yc^%(1qLj_>wo9a)QW{;pl}L>t z@m1s020nO;nWL^`g|~&c8v(vA*<$XS>Ch{xqoczt|M=pD{rjg}6cBMXEdDJg%68%% zwNShNJbRgLQ5-Qes+tl5+tjhgQFjS{`M&Y!^JpyCPGsNQF%j+is%OqTx;S(35v$~y zlTPn1EcCs7I~Kt2U0vSy4>RoB%bxJw<&QZKxHVQUHCATib8?;TEF9G1!_%0&>zj_& zJ+j1Q2XD2k1me2i2?MbR14tm4%+m$wS4v!)-Jjdo=Zp#3UfJN742h&8bHiummz9OdbCSjBmggu_UpN@(>D_+mClavs1lQE5 zN-X6diNkyjt|NSfkx@}1A|gt-wxQSamoAOFdGltS;cPn8TAg+0HE|o*pKI=cpJ}wK z$N%@uk~K0iVvl!CWLLdg$`yl(Ga4a$wk1kqbs%@Eq?npVRUpy=kw0clIx39IhuF$Y)yf6vtdE4k#qRV!E z)%o~aCE_vCe%O~U))y{Z5L&lxKa#To9CX#FSKgaE|4Dp+^qcV;Hk`e z1HwU!L<8(9Dkv0*H$Aef``kk3Rv+T!a&5`hlQ(lm`;g#t2B& zNVx;O?h|$68;qDsww($>sbQOXvPK=}_HdK9_!j$4O=MFI6cRc4aYURpzY3C3z=}Ko zZXzkq=+Vhr$iZuvSkY060iu?jE(9h}l5aMBy{w?1paa=r>gOV-XoFh~i%ttx%7V z+f_}a-lNhSZqqvLAE?id{ecIt-waARQoa}kTq_Lak?ysh#!%(|a^*kEjpEJz7WDr& z1k(QjEBjyXj63k3p|SsOxM4P(EBpYbK{)T(v*!gUNs#3r0cRgYAp#aX3U#$G7+?V< ze@(1;kYoIYxh~tAzKwSoPpA{A3oetI=_Ae7%{?k=lpPJ)trID2iPYH#jqVe!l&nCtJAJsc z_Sm9}`QH(g+CjqUfEb~ii?Cqv?!jRv4n=g_#3U9JG@%}RKkDYqMFMNq2yfb?4lX9= zs0Ak~jQ;cFS}OiLKVM#U{QEsrrAMrmZaCxtD9*Edc|2G?N^Gofr2x?mBzGNb`Bb|O z^^tc$LCX#VNvQoab+&IvNC>~s)~(u8=kQiiIQ=H`_1XMbwR;y4G7?ZBH+8iBgsZkz zS020K8x<9G{`~n0=5$BeSzG?%%+rfTkkdO6kcNl5 z61y$&=32x%J*`{qBXt+$Ll;0rR{Q`C$`IrY+M#d|MaF@X`4)m_<>;%wv3O$K1>g zyR{_n-3a-|oLjPf3Qo*JL->rGIk5b2UDJ3Z6-zDuE@>>62A6OOq91VvR{xZ|9|Nv#025Q9Wywj#^oI>Q0FX59d#{YBGYz z1E5tkODxNxD%OwiCNj~E6!-7%J%!co{ti~c$_lkk;3KQ1o?j!~#)nMD+GHnQ*iO*1 z@)+^1Xj+YTSqBF5b?&`J03bgC5=(a2MH?6xfGv>(v2>y`T5Wdec~&?!Vl?m)*pNiC z56><+eJxm6h2jmYn=*)wO3Ht4^{ue=o80QJt(5QoTF4iNJ4HYhG)KZnh;vZvdX_pC(dlmedd$;{V?){I;jyTSLrPlwi zCAfbgFaMu;&M#tq(*%nE>K@FJfV;bU&B?6*p{DlulT9nGNMHZU3lOayq3HBEsgRqG zFQhjebkr8BMgx>MZ&4CbrYD`TG+4U!k!7Hil+@>=5DV;cz@TAQd4Ty!TV>CP2A|5%-tI@JG<>r6o=6?l{n7aF^$C7YD6_@yBi5Fs6{cCQjq zC?hlT_McbeqhVazhTt0-8mfPlGGS$y{8Z=W>)|LNqjB}aKgL6G%|hF`t=9Odjlqot65t= z04MC{=O>_|q5`Tj2IM~I1jpyha;WV4L&Rl~I9?_T6@U}jpD>diN3w9A9CEr6B#Y>; zeSHsopmwB8I3}MqDZLf)?wxMIvu8IAsebcVb+>4$5W+iCU0Yi_I2UD(KmlrCK9|?dUcMt7RA`gSM*I@P>-SZB;WlD<#W~A*osgFM1_#sJZMpG0z!4O0 znCV$OV&k*kI5)e)_S4!88&r`1(=t}kZ@x|_vf8SKf(^ol_Oxl!9ESl_BLDc~mys^~ zpJDb{HSiv@TR0`+HfLq}mBA3a8GlDiRB$=yPrJ|WHageA4nPu(r_yIYI*RAW=(Tky z$p5Y!?B7nwq24`cygc{6tNUWG2$|(5-L)WdU0J$KnZmZ-qmwZK#%>31-MV#qhkY!V zj2NK6+ET5gBYSfCUW(8+A0{Vj<+-`y!VD-=HGjipXTxVD zzhAuAiS#tY1}`cC!w8Ge&6PzPcLi>>WWm{ie45x%5k(@+>_QOh?ROj+-vn*9_4W6U zBMa{Mii8~!T~{f{g^c%c7F za&P#q8R`&hu(`Ak-{BBGLH>2!yo>nwIFSvKXC*)+WI_R^`lWdSnmJDPz#h8+rL#E1OUrtC8D#Nqg43d;aG6O1;h$DCCCs25iKWvGVQa+H8-cLlP@Qpi7&r zdaJ{+k#EE?0o6HtsN{UF^x$-LG7Mdu!`D-`VE+8oe0+RE!^5#CFJ3~u(Of~Vl31!F zXAa(Hc9Rs@KsIjNXb#;SzmfpTMB6H7z`u1B=WTD*vuzO_uyxfSAao}#T(1`nqmV0j za2s1&4S?WWu!9F~Z7Q${qXRduTj#o(VkyYhk*?m{5oHyrufNyVyOo;y$P7oTYiPu4 z{X#EPey)BEsjgzM$z6;PVv3W?k7LYjHRN-G@Irg}1@eG?s_nBj|iY$r>Zq0j+`VIvv<@9He3b z1Am^mC=>!~-ja<+ey1oN^Vg+s4SWat%$QB?=M`v6o`B%>WRAOGNw zfp`p4R@t!nMBw^x^<(+kPZ#FvjO51;a_okD>EH-PdvvkjO8Zeh0sMKJEgO}(k7R#%| z{*A%EfgNxuS}-bDhH-Q3n6ICfZ&Qbes~NACnsT~jFF5=1#>S+h`*NTxliGUZ$PwC> z8LIUD)(iY|uaixS><7QzwIPPzb-4cxOg!=X)eg~e0f@Zk#Um2C`_eq8kJtBYKKyhF zV8@%o^`A-tt5VD~`~=l@$;rtP1r1L3TaXy21*j_8SdL^E1S4yXo9%F%^9+IlBdo%+ zv$G+!yXBAf_uDx-Iv%`rcPCh?$e(8|A%C4yM?}EK-rhtg6@oi<=t-XWT!y#jXTq(c ziJDpq0Y3*ZUGcyHynBV+mu5NFZzbDiw|%aN^6?injMXu6+%rdZ^udP@AIM5+4h9C7 z-Lc&G2TyWbNCB0*qC2P*5{SV$N%n*K*5Y&da&EjoO7@`egB1(b>4qXw18Enfbg-cEeiGc3)m;Ve*H=`8NU!f%TGVgEOTgbHBGd~0v?7UPW1N3 zZx^x3BOvXxr&b$P;Fk4%zfCt@LQE-uP1QkPUmw-tRq3aX4s=xP!_B9aToulok6Y6L zpd)l&&#HZN=o>2>8*O{a)TvX8K~jL--r+u;P0R?)eYNtw{mwg4fV?pcaW%2h&$Np% zFf`l(Hs3iArN{6f=XSP3^>8@5hu%KuR>TS@Xps$B4n)OaN0z}Mk#g+CJfy|;)&OH1 zI14Jjo*(2kD&|jSRIxNGKE+`)+6Vll2{HA zhD?@}wDh-SU9GLhp}OE34b}#yCQ6>eq0waoA?WnT zMQ(?3csybeOgkT+`-$=ze+=Tv%NH-+KwliF&KuWiY;0t?HN7465jD;|*&D!bW!1a% zYH@eot$)p*aY_@7hju=dFk4XiYj}_c1DalrQ>vfPbbfG`Ab}ovMjI@bJX49<(hqk% zZ=KlSyu2}8c1%ctYz92Sn(2q# zv19E=zn>P-;4VTMpm^A7R{Hw2fB%TUA5hD#1qKGrn>TOSbHMODv>SKt{yk?sgsQ2t zmp$nH`KASSC)1UbaE8&~UL{h*U>XA2MuFlF zWhWjb;)(Z9FAT$LWXhL(=*eWz!aEGSs^j&^#&m$swf}m~dIT1-fx@W4GjEh`bC3 zX@pho&|V<8>@oH1v_>Z-tTr;4A^An4-XXrreYhfoLZz%R-jXP{E6XLd$?zsWM>-sy z6CnYCJri(0^n7=stCrXX2nK>~81|{EP`RJ5Rn#DlvBH4~%)!7-pEIoyn+oh05o7>35G*z1 zzf`#HABJf$YcA80mTcplw;}=#Gum_x%(M7a}MKsl^u$yZ>SdlO>e+`@x)? z7^EI1EmUoi{Gy^l174Wl1?e^v+>S6D;V1nYoCvX@FMWmX&04xufkk8o)JP#2na026 zHm(@B^WsQJKKmsU3?yMNXJatpZX?~>N^Xf4+JG2Amfw%0il7?0?Z}bZNm9uU}vJOiwAZ*N0E+g zy~>}js19IsZ1h_x*^SN$=sYOJnwcu$gupcj0Abmj5w#H958q#{EhM z3%VS!0&zrii)C%)xDyOyBpRR>0BAH8F~HHPUCAq6l^Ri+pSmBXa3>9C+(@G~Jh_(Nzx{4u;4#leD6haA`b0q_zF zkD;(mLJ(HT$Cj3RNW3wyS_AGX9IpI@?u2pyP!?{rJ0T#{p33CSz#ZxC{?=Anjao9* ztYQszWxT2vXZe{P5>g;h8X->+SrHFx`7pbuAr7j91<3Yc*zMNyOrs;eeg^EmbnD5o z2Z!_feivktAZ6s#A%IFl|6f+5-<><_DRHm^fWF|6`McVnD#1{HVzfBlpMIF2`(Al6Dc3j`btA^$etyFU@W;uf4eMFHC6bQqGvg2xhS6D&qpc2di zq10)lJ8k(e)?y06G}QcZ^>cLI%_rB%UQ2`MhU=>YGVLzC5^Iq05Te<-g`9i)y8Th_ z^-Q>m(fASvi;5BpKdZz}*>dvTwO{Ejh3aN?ub@l~8mQPLna6|^&6@kyb=NKgRD)u| z{dY*7`Y2{ElL&1Kc2)*YRE}8c2fIic=gqw4D+M0--P2Qv#qMoNgecKqU5_<>e?Mnu zC>N+-a{_40*hi50v1_6FwI#A{E-U>w)zco&2R z=w}`kN!;NCH`fNA{cv%NjE(;54jwtq!Qj?AP!S+@YegvfEoFONxX=N1>u+~oNCE9N z^_vChD)Hy#514O<0JmVh-U$Rr#HX{txm1LrQc#EE?_bI~b{j!^&<5PRyb0hz>iEha z5gL)}annqB{A``?F1m=v2T|)F-&O$MZn0}uErEwEED;ZP7@LYX6D2O%gC5Da3jP>m zL(*RbXhs4_Sv>1k?Uub{ai8F zGfn+JzMQj8`WgqG^z8F@=&nO`Ei?3SJ~gi_Uc7kI%>1w>lVl3P+EJY66@}1G6#p(K3j%HMi%~Ec?i9<6U+kbOHh~{Ph&YdE$B9$GpX0S(oVnL<(!BP9s-mrLbMmO zNIPV3Ps5{xX&B8+JII&<-r<54r;?D^q9jMkIvz&(&5cJj;Vq@=36I?7`}>ESTDaf} z3VxWr4&*?gi&YIfkC3_V1l@RRV-&n5rNQyY!WO_?HmBd$X6^mWkEt1yG#_A{fOK913COfAL!`n%24Qs^TVzKMPs+I}dDv+p*%(7bjVjc+LG z4yf3Sk2(@3^e0(z84nDbN(h%vjJJ!+=z(q5i(bEeOP~ed7Y1HMz3j6|tw%VEaq-Q} zedeIW^cCp9ip?dWjh8hyc=~S8T(*mg=XLKK_n@v0 ziS=@Go^}nL{qjdLyVlv2{l+r;pmQRf)}lYPt(8A~K!1H1w$x~RR2?z_p;lk`#1LZ$ zqN|=dm7p9fB?OZz8XtI`Z=kbR<>x-J_a!z2$V)Qv1-lKKU0JqW9h4^viuyXj67Y8X z85csM1ywmum1-|AG}N&MX@qU*=H})!`n7Q2#o7?=bPfe2s5qTFe4L$@7lYdkm6zNb z6>RVvb*9nu#-Zsr6rnaayIcyoQPv=rYsy`upLwi`+9@XtAp-v6lK99GcAf+utZEDcz_tufhr2!{IuHdLb%K6h+e?kR&@qpqpR zi8Q$`jsUj%Vb4`Aq!JHd0GiG^s7}C-=D4+Kotkt=?!#Qc*Ws=6SRiT;jTPPrEB0)@ zmm-D+E+1eC5ozC0onl@k3`?dQ;#eYL_F8bfV*mwUMX%+QB1jArt@epV4!^zC@-}Hp zRIz|L)kAYY{#Rhk2w-6|bdSUyOpYY_0xA*FE^x$VcC^u23vCxxy*7+)8e+iF&LD?h z@Ph?LBV<%-^i(8Vs`Q+RG)4f7RF4pqeY5qb(*sH`FeKhZBZ$$iZ4$4OdzRR5Tkac17n0-!(1+>F&n7!wV`Hv{|`AaHFwx<$Wba{Jr zo;%g;4Mh|+vz~5h{R32wbbpB*CnF{#M|0dkY7c?EH4NWYKoVgQ=^i!q+xhb;aB`1y z_4oISuy2He$m@C=$<=Yi7KNi^1xDHB~`#p(fhYAA3uJ)1Bq#g zgmtK^AGLxRz~@?#?f9)9=A$lPI2J!lKT%`x+HtN~{IDpoy~g^|j7yrI{Dyj_rR=tJ6owp_*Tw&%59*VQDVmK0N?*0j7%dfUmrB z+Np-ks0*o`cKpd>@MFp&rlzKKKG)r|MFz6$vPGuNoJllK4U^tro1C7)9-V+@e*SK@ zv2Ey1Idjw%1&lO<^~P?;K2qz zQA0gYa~gpApIwR0N4JJ#3LKyo*eHlyzWL|p>pwb~WJL&4)rRLpRW8JxPPl!~nEk;z zHb)K}nz`~L&b>KNAeFnqJb78zKd=L!-}uNF_5oW4Oc`Wh|9tTSq(>oKe<}^JvMNSs zAc+aR$1uJ0NA9DfSigho_I0SHw@@6Z(XzpDbxLJM?&pprS^nh(7{^`jqP2$1e?V64h3S{v$z*o5e!mN9OOw!j zdUHX?>6=#r7c7f6&NCtf3;iV1<06-CLg`EW_qYvjP$c^L`i4`R4`@!d4E9SI+SAdO z5JQG_)HyonnMyfXvni^@6~-qLFaTh~sW}^TrC!pJ3kX(DA}WNVcFsMPRCDst(uSd{ zl0B+MM!~LIB;%@_@X(Ra4n7d}K8L~-UTEqbQ^#&Ggx5Rtmz#s_N3dj$JvMDHG@R_Q z>v^nefKFLR=?puICh9i#29B4<(HV~|k#EeAJuYZ+z(T#u^#02nTld67R3M=Q_etb42g@`+dVGWfN)P?9f`TC9;-AeYMc)_4Ams(BajK&Ftv7%4 zrpW&}=;2RE`@g=Vd$!^KZ+Am;-EWl$_s5cSxfd+>lwt`Pf;wohq+bSzC1M{7iHH=( z7()`K;TMR_kh95|ODmg)Q+5^GetGv>k4=g{wOM-k~ z4YmzK6hxTR9tc<$MXV=K3@aQmF75PRV+GuPoP;BaJRWc zdM>S&J$gyEu48~h6*R{xbS8owDK#4bV6;Qq4$^d47}!Qb;8ye-Jxmi25P(Khxzd`c z4k9nyJM9@*|=qcK6 zIRh#MRF`GLMlm7$#Kh^uA4eLcMyZ}!PF6AU+H;LOoFp@oRR@i^0o}G}TxODyrUKVu z-W1$4s5mAn{nl?LuhcuUN#1}4(ZGK?vYZP`yMR_5YLiD5stisq=LjE8fVnVW%S1AI zJ4V=}xjLN=9Z3oNKa5J%T9UXO9(ARbeBp2dY;hlV0eRl=-aiYkz9Z{y1nrZiZ;U<4 z9@QTe1xJ&Br8Lq7`~#7~@KFfAC#UjN46bvl%0>4SuHy zh$4ytwE{Q1;?PaHpxi&`%{%dC6+qQ?sS4uBTR(j=2N%kI1U}aF%af__yk&QKxzeNe z%BvfHo14{>+4ay{;-kBNWklVY{Wn7aK;qz=>TJr(BZLmpfsF0IeyKUe5Ee*x1qi-; zc_xj_AlnJb_kr<|-lowTLJ)lfXy6XBS^Hg&{bQJT7D>-Se`Haq8 z6V9Fp-S8vLJ^uB_FrS%!dT;+9`%=|Rhtj6k)s$;q38GJ!3bk@N0pM5vD^bz&{C`K;FKFiX`&n^HI7{nkKNi!!w}|4{2vNDHPmalxeWz zUUZh7?nH`<0!JMODdwSGe5`R^t_(_J{nI<>rhBEACy0h_*bns?SpzGmcZ{!8`7ptGe!dCGtQj1bELCm8ljwNGB=~C?nDNy^cv9d(_1P`Ez8+9n1=c z^%JA8c98-&F+L{11Xo0UOR^qAT2NwDA|WA&?59afIRB5IKArI{B_(C2n%cTk-4>t{ z<^N!X1J0AeL&{T5_YD;pHDvGZm5MB~I724qRNHjI6vbhjd6af=-2y6`(NtLu4c+QP z4c#xnPOpN?m94N-jUk%F>Zc`ydnfM)RZA7MMib6;>|4kLbbCcb7FsQ-b+A&*-C0w! zlBe)>QIWM%5Am(rTtw+$Uj9dxhC^j0YbUzP4OwuCh%%wh+jK5P;RpQ)T?R#H`#}wz z3H!wHB;BB4W;sl5XR4A=lymG>60<@TlxD=VW0C$IzT6ItdW_I1g8p(~Hw>M6GTNYG ztR?`>vimDBYPcgY_O`r#e|Vy|YS{2!70IK==;XM!7lom#c%vAl?oc!_zX2!EpXfeDEjVn;p|sZ4 zrVkD=aKj+wzLuH6g9`-@4+&)Zz>SaA26O-c6@nYDUu)yu)5In`-F7_g)9-la&AaR2 z!GQi$V%CJUE-WQgKfXgs%AhxAF!)SQeTQ`r_57yg6kJRr^1ewuP^hY-csbh&~f zdxf`c(}BWXg~v(SJj`N-kEpf^BO7`1lL_g|<8Vlb2nZCszZD5ZUGw-$-!~u^x-HRX zMryo*!XHEcep9BxYv*;Hbtd6PXWw(Vjt{kUmWBNW_PCqxabO~vaarN0GKJCS1jUzH zMqnz85YmW@MrThaT1|4?FHGm5=1uU-d#FcuY_KV@wK%{6;ydhw&G}~$Jv`7PL+(BQ{gYyNO!zDoqBH$JR-mpw|Ug9yiC#65U;&kaww z`)}59?VaUx(&@B_RW&)TI``2ejFY;Hk`4`K{q14k;jZvX>w!eKIemd%Y;a+5jEF8e zz3c1i$>ZW2SY@t7#FFDTljTxp%Pv$=U1Ke9zLJNZQ$%Tk$(`MeAz!R;#DT24>*y$Z z7-?)nIVYz~Xv#sg-|9f4rit_fLOa2n8Hp6~SFplS=WrkK+qNPv$AlJ-Sa0unW@dNIahG7?E~WCf?@ z5?PGaT4eHNwa@?}Fg_Fbp!eaVSp^kv|A8rAOFb6Q)2!{cGp0|f-l95cZ5Xg0oGb}6 z2zY1YN9KnotbmdNsG!`Bt@;r!AUzcK;j1q%o1Jam7RfH19eEIg+opbCp*t3aNJ7PSO%(#`-mAt{|k|3iQ){ z=q$HhipHC_Fk`_(XQdM`IrN&f;ZFr~s<-Sv2>H3BY7Y*^c-;NFDW}x=x6!GGekiOt zL<5cufMWn3g1JRI0;;Cqq?hiyw(HVEI2{0e@IQ#KZ_KhvOf-;i06j>hU8wm>vvy{G z(-iN5RbUFr%2a19-n2V!VmvFSi5?DWNr)&A`jy~^(t`|p+y_%3m1#OAJX7Q{A)$Qv zcAJmdcjl>nXt>D6)QTAeha3%QtJ5J-*= z%tiDCFrz0%(-~mBOa)J4F8i8h#=zeKvo$jcmm@n(exeA ziniI00@c1SPi?H+$S}yGb}^7otZ?coe=XUIQ>f@iN1GWg0jAI2X-F&h0H9nSP+yHF6p>l>KnO+1Qu-IcS4samHw1a%|aot{I3e6Z-BpJEv(asIn^zHsUH(EI-oI<5|x8dqPA#zx`j3JsZv%s20PX#WBze1wqWyOc(GmG_sb+bUZZs zcf^m;Kuj2>u)&Cm!g8lBV6aTMeUewbN~+d`UA0|Wy$-y{APSXt!%#6T=Q z&Zs?Em?dCUYs7RHp&!Jt9!wAMl9L$9r#@yjW;-T}8l;C}hFJM~-JwLZ&$sAnN_(w3 z&#lO5HAbi=nGl_NItgUtxig;ROEb*cu}m~?tT{mKzkmAwQ9k*-4w1F$9mF6j^ZNAD z!^OtqcL~$MFNBdswY9aygA{3SY`a4%iwv58 zY+8^O@}4+Mc^p9<0nBoEpve^o@^Qxidh=NPBUT9&<>e5@4^Pbc9zkwnI85h@{A|5` zn~h=H$BzmujV9Sa`4`XPT?Lcyup-gGM=ko$dI2O=QN=-}{aj{+_2dMU?0fB6^9KE@ zY)4z#lb0@C0x}G|x@`Mhst|#VhtbN2n844ZS`I5L@nC5<^Tx9AupTfya1MrHD|Mn) zvHw2H0R;eB24dk6p2-`u?bYSnxq6AeAkLysE2 zH$~xy(W5~ZXD>IOqs9Ud|D!9mt^8NoPs^p9G(jf%mzZRIwt5J=Z~K>Fj1R4B%yCY% zSjV_Z^&&Jjyj3Us1HP!XDOh8;4n1|+vUE>i21K{r324;Ry2@W;l~*#++j?GoT>QFw zuGYfGB^tOIayDU5Fa~~hU?y7`7_wB%t(~=K!%oy4?{AB~zj=MvC3-K+yC54ntSxGw ziE-keE5){K(SqRd*BSP2^F&14zXZK&?GFddj|$(SIk55J~*jQT?N>UYpzX*v`Y7+~pEaIASm z{DEdJhTX7y!fi3jU8F1mZI8=`8RwTy$=^f%_~VaanL1yqqH8OuuyPyyx(~vZ2-Sp0vfgfq2H8QwraBi@CTDgtZI?L$bL6HA)E@Pbj-+US zTT>xB{?(_L9b=S}iHt?_kqrjYUZ95cw>$lF9CkXJ4fJJT8+oGe>Tmn{c1^H1G>*k%WM?VU zECo7g#LBc-O>Q}<)feWK;O4{r{RXaz#P8dG#n4$=6Ev4@hh-L~dP8NS5wUSHS}ZU_TwU-hjLeQz-PlnT9+Q6BeEP#~ zvL>Sp*F?twP2oZ&Bn1Q_@gsNt3Yfw%xY#+>vQ8V#ykzMBwVV)lRSjNtv^pDNrX{&( zX<8+=*OyoGRi`YIu--}6i5sg07Yeei1-LFEvMRiSs2vvmUCKM}^F3+QheqUnHB7Ru z%yO_=cxRG_tawI(Ct3pmEg^q*ky;BE2Kjwqf8KFF+MDIMg3G`YFr^b?pUG3O8CDR| z&oSkk)88C9(R8+D*1>q~k~@+*Fm-5w;3iK!Mu-{?e5V_YhVd|Dahg~Gij5lDH^Ztn z)+*+nQ4=+_Sr}JCJO>DKA#rgHj98=wBI*hBJbptU2B=Xl&n<_Bx&|0IG2zM}X9^_l zm*3QO&OKsd*o5qNTgEjLRFNj;^-Py|pZWNs9$k|QU&%@%w|H{(nM+N?) z5xT^Px^2e(F*OVZ^_GQhNMG@C*XaIw~0JHjVw|1&BiPzYv-*Ap~Kb zS3{LR*zemBuc^}=Kfv3;Rhpj3&qzN!Dm;&&_y-gn=Dd~DMO!cf1X>GoVr1kOMw$q$ zX1>3oFu)ni#7}8L$fn~j1=p@Etct*A0-(eU{ry4BNvZi4Ry{eA^q_2-M;-6PKF}U% z+}`^cg0ue=mj6y?_Be7F%KJmzb!5zs!|)hihRaoMM)TdTdzv%+zOLJeicosP59Z`A zU7CJf@DUjxk)KgJ$%6?aX3h>)ttdALIBIZewD$Ij;ksy=n7GNS2Ufj6=x}8B<3Yzl zSR|~3a7E*+nKVAQAE>VwwiN^*8uuFXTmqSA@8v~d*uL}@D38jJxDhK&dz0NK-0*nF zmr26}$t#KHKx!ftOn6WfJz_EX9MkJ7;lhojG0b0HPhuooplYI^9I*y9UKG+05u)(m zzC}NJKTuPs0*VMjoHCMrVRoH zyNlYd&}kS?o*tsupk@+O$4P3jNLVFi7iAm`d_ebEI8p|UF+xp88$!OD8Z&<+a-8*k z^m`mcq<91F_&(}78tqb>Z0bXEm`G>Ec(##v=cAJtEIe3`BP2%NmN8Da3iBecmh0nMfQ4kV@K!UcEEk$1j+C=uDA8y6mXqPDbl-{>NH6n z!(D;k)LGlUY$b?4d%WvP?*uZ}tVV@fQq1TOkSu>*@k3na(L9(H#Fx-WGA4q zsrbsj{gs|!ty_mK!$Y4YCO@{fwzU;E&+C8t7|?;#0eEL~Pd32I}Fcb6w?k?|iIZJg@!$&C6X^obpNTD+@mw_@e#puY%kB7#aU z+z<_DC~%NjP0SQxQb|KimFejcREi)d(0u_q(>alP*3r=n{zsB}kX~gV#-a^h7_FZ) zZ%w3s664gI1xr<;C-1y;EpORiMCr^)36GohFbXD>{!b`e|J)+XQO642p{^yi0_1<7 zj5ctfCP?c*nb{0(u0L76AIZw=;#8xNmsJAoS?Ssdq99qjp8jx0aU9297wE$*jwA`9 zoelk<0f0TQG;KarFM(!wP;jc`WJJ0qpn-c*b$9~R82M?bH3ivD6B9p((?e33J%$8` zk)wA3a2jk&rRlRjfL@3)V3q?$;0po`(*0%flq{gl{qsmqa{#ae#o+KbpdF_L6`-2t zu>#m^UoL_ zJu582g;e-qpVG?Ta`wxi<+5&^ff97F)S=9F1X+Dx@4go#Y zMcv8NPJk^;^wq@oLfOx*Wak5gg_x*~345IWJ&f`bM7JeyD*FOIAwKe^57yiItWgf4 zkBo^yGb}nCo*ozy=kmDAN%PvM(8(yuRL4F;OAJrWm!~r*$pCb(X3|HaP|T;sHEK^M zW(&dl1Z^}guZMhO`9R#P1Yd)x8b_T+P&tu_-}%88Sb~r+`kybgqNPhUGrthzEb1f1 zrO-S5vs4A)YHS3bsKS{;Cy_Xn_v}vcvr^SV@ts8`4cuFDo{)f<(+jxKV=cwZ1a*a= zDeU|UxV#GbR2mp3Um{wA7bEOC%gvHy6`6q<0(E2FohL#EhjAg-^1PKqDF3V>J_X z7P~h&IhiO38s5eVr%o{#pTe-ksDW?x%1)cY8=@CqD%DXF5bhuKF|$NppQklM_bK1G z`O~LJSXoD$uffU#3O)`r1N&D2CJ}AYgY_+%QyJVkoP_5GT6KFi8$-%r#5Z(kXsryM zc%_DP23<17!^5MPSA6yAmfJ@a)RF1M>fF$L%q&NaUe)noe{~f;VnJ9`R2hxcNfqyq zYkc`ZlJ16OEAvxQf@U&CZOL200uN`dkNYR`g|VBpfFhz~@RS}-m1YR`a3D=VNlABR zZTLikGO7Ns%A3`um<4nXwt3h^v=~|hby2Q|D9faLf=q)^CHE+Uf(t_Q-&>g3Slj25 z1#gg_m~mr_aYy-R5rYx%!FUg2=OuhRkUb5QuOdR)PRZb~&uXUZSpN+WB4TxIz=$*m zl5iq@)<``AageM3Xupos@Pkr?G@neyM3I~X|fSkdh*X6w9z77mj5uSb}UK)+bA5Lg* zBhrWH8=}b&jP9avWM-zw!Eg%~9{IZuUQEx7`h!_RCgNQI;* zs)0(8gc6z}OzE4PVAR`z1+&@ZyY+>O7Z=p(;-t!Pq4d;X;SvVTOJE29^0O<&CUD}N zV2ChYO#=~v*eOh|mRu%+Rw!~!WAG37#84Dp)EtFnMGoQ9G+S8%35%Q{ByS^C(&svX z;)(``RyXn|@kpI06n-E@&_@!K;i4je*&Dt%|HMd}?_FZo!^g1Je`$O^3%5};VT!F)iDBRaz)5eRn3W09|4kXk z(GyxK>DtjbFQFI<)WC2u<8c8Au-y7sxw%4x_6`mKq)Tky%4lZq#3#vd&!8Hh;K&$Dr(?Yzw7>nws&7`i@2T7zii$%xS(SMq#&K+15x#a*e!V>D8n- z;Nxd>FawpIvd1D7~cn#ocEF&cx=5SM5SI#G-Q z17TC5kxoP=s6%iGhE`K^qGCF@1dxO%qA&tc8BlPG)ICH*fxj#U5;r7>|GHE`q@PQ8 z(HDKu(^KDhRGj(#e&1)g_dfR#6(JW@h9RfeWyu3`!!Yo1w{#6NX%Pl*(kCB({Hal* z6f(qttDosL*Ne>7q?BlH+($nSX{CNY&4q0G?vD|u4BHio8ovhFqis|yd*(T2s;yn%aqwQ#*Cc&OoKowkvf5VJB9i56A6r%|wX{OC~ z$J~njco5%fdJ}!Zp`6`n(WubmeWM-*@o$byiD`oa9OVa=AVfKy9Y;Sq42^1!v3EXI z*@nJmN0W@jKm3ldb-IOEO`I3sTeWIcWWr+3ZgnL%Qz5k(n+wraKIpQ=h24mAyt^7^ z?2=Ur^@y)fG-xLkriY>UWa2>DY@m9{H3|mQh+}YpHCrwI=JB~qK-0RNa3r8Po=O6l zh0DY_!bdjHpUnS-YTexTLqf>HWcs+ZLPaWoLENb5<>DO7stZ};z|e~Bk)>->8&rVO zzH2`(e^sY958R$EgBlPNRIO*tCwhYm2u+Bpa!1aZ35d}Ftu4K>F(qypujg)*S}Be- zhzLHQ(b{>gRvk_0@)?$oEHPn6BauG5pGg49iCuLCKp67foO5{*rZBT|u6F3lz-MVC%)`9ffSj)K zD!Imc49GOBaq|8{R%>%EONA5Qqa%17uGEG)4UF%9hXBsh|ov;6%0bG=n$pm2O= zRFd3?RMCY_{M6%?cqFN#`^<)0hkkM9GR zvyA4|CT%^Fcq$!zGHst=UGC|7`Ll5tk!T7eLw*53DlLfoVnD+&B=Ed=jd-L{+gQ%HaM!{^ti{vs z2;xGqLj8Whzi{ALq#lUMLv@v7{rLu4+m@uHhOV>h?kuB(_Y!(l3e^|pgp5sGhBEyo7j;H$SxVyZelR8MZdF)5jbXq=am; zV<~?*`VW#He)37zN{6OBU0usz{@d@>xER`16}qXY-|v+{rb2Mplzz>)LGdmbu)~Ns_oV4N{GjqjB~7zp5Hbh-&5b?B zT?`LD$|Mk*KlI)#isXp z&ov)V3Y@Wnwz83S(?2LE383TY?kAo~eT~;3x#rTy?WOh6C&Nb$_Vg?`mEV4BMBm5a z+eax{m@P}P!u-zPhHq^tXk#bjR>(Do`mDmjU~C$h&x`Kn``F3}5+b-QsdLf%9kn(y z9gCYhQ%daVG(a<{oP84f{Qc9hUJZgTJP(i^T0J6eVmPyDVJ8ZEeG{>F%k&+ZHx-(= zXqv%t<}(^g%TtuH8XLs8=XH80ThxdbUI+kL+#APtuf5dT+N#R+?%liVsxEQMEU@7-9h)O9&z(WNz%f_Z(wayVM;J4Jy(7SaX1b?erk$}yvRGsg-X_+-zX zU()+rm#CYh4IXi4P<88r2Mqw?<((smv>$IU|Lh=EvbRM_VS=w*{Yk17SihwuC-)&3 zO$YMWd~rdA4D9yo8TGq$-f_6zZ#w$Np07q8 z>C?e~-Rc|-8Pho}*Q0Y{q;pi^zxm_rqocqNVUu z_+ez*+9W6k#)h7Bx-fBc$y!6@nH9-T7x6$UA_!0?KPb4?-LP-e!<6oZ=-L#<0x*8N zD}fGgAb-&xBaW0O`fkq3%+wcQfwVim4NOZW`$tp)CyKD>-KFc-ASLXy=Wq7j2e`G^r=m?prcLWU8JL53sFdC^{}-)?%Li{aMU zSD;9YnfNvN+&p}#ataXVR9qMf;G#x@V4FT1yZhVu&Kuv)j-fv0zt-B~fhOHNsxzcs z1ISZ_Rjd^Z!7Z=?c1dMcS(&fA7zvX@hYXn+9-d}=7wA$|v}iL)yX^iUA;a+DC}Xse303MyPDK#}4vXPZEpxU|o zAJ&3fnQnWH+yXBfxw_S;`h8|0VR z+ShH)xw`L*0$fS&O5>HSYDtOom^CiA&g`>#!9 z@T)E38^>eea-*@)91>FfP86@+;%-a1bfUH_=b!Gr8u&_Uo;>!CfByMzewdq)o_<}U zoWxHXt*3TzcVGP)T*IQ}@vq~g3IZo<3J1r`gAJfSqv+%E_tVa7Q@eio>?9k#M+c_wJjFZRi?1gka8K`RPQon#xK%zuQGm zi5f*B0Lce6tj8OjR1#sbpR+8;AD4LQa&S?MmdwqnSn6UJQ%nrySyDjBl^I>-x_hZF zFZM>P8YNo`|A2t2%g`yV(1QYEx2J=pnS8gcekHW!PSjs_Gwa@^-85-}|M;TXn;s=^ zcCf4=*8Rs~R%>Bc#NHs8{$@EGeamB!ZB>58W79rz>71%&hlbW%=0oJT#li$eRdI4c zJ1h{w6j^#K^2wyckRe}Qp1)_UEzuE2B9e(TBz5MYL-HKfzV0x;pRM!Wh8#H$SX5pY z%(@I6Jh+tWQ*%5!E6bM)tlQ`6Hs&q((iRRe2KV`ck6Ie-MkEX>+xdDj+5ikDJedOm zGDzO1E$&PNN7;?&Xn|n043PDo;5FbQeoT1cEz-W-@hd~r?`Irh~);#w<+78GMK`g!B`32MkGw?Dur67DmDqP4I>!Ak`fhnNv*Exg>A@3bj zAq<%;$a_j?f zK0MoHpo{WY5U3*(>&g{Kx%-dbV$DgZ_pREo_jBv^{xWTJ_)i-mgXi zpT&C|4Igdtb-icTx$(nx^({n55TRJ9{gRL z07Bm{3gd|k$6g`ysYAGIRs9mUhw6xyY>FLJg->}b)#c$26q8)KVMk)|q~Ymem1nI# zZKyjOZzH^Q_#+Y+Gl#x2Oo@zT41qH== z@$;gWDJYhU;ZOXURrrK9;Z7d@v(ijN{K^{qW4lJv6@OoQ>-=>y3JPjT@@H8^X|xmt z#cm35QQ<3A{{2lh=2s^ImX>suv$DS0!H~jwgI2eLd7tgWcdlzTY=86UGtbri>*Wtm zJAKra*w5oiC9?Gu-IY@>)T&Bn@3W5u7*n!WOI-excrEG#^-9)+Xu6b$Gkc%6zdp_J z!NDQf)ShwUc^S{Uc7oOFhs&CW78Y$M+a9DhCT-fhQur>bc!g?mcVZyU#XX`4&~}tl1y2%#%Wbf`QqNt7TbQnQvV! zbYptBZ6}|_irSfU~L_wc=IOvItC6_PEPms_IB6X#+ftwZr;4v zR%19)=Z$Ce?un8~-e$VkwA+1WFJ7d+e*OA-y)^S45pND<1${lk18bJYB>$v}$(+?| ztzT5S_{bCX(l?(^N}gs6ftXZm|$1NbbDseM^_%5w0ctt~&x zxu@+DHL6143-jsMVpSx~&CPRr;xp~$JiWaO-0jC%$SwT*^=sQnJ^G={IgWy-yM8$= z4J}F5#3=WahpwKPnejh$`#?xnTaJs*QH@o2KIZ+k9=O8M;^Srhw;5N{FupnCvLQ+- zBUatMQ5DYDXAsRk~1YGMajD7ExEMf;*0j9UO}RdFFCxAOi%Y$G)i%L@L**{ zWhFZs+nSo+EA)DAOO`|KyGy!Ovr$J?+v7P;D(-$KdG*Sbm4$_c*{_8i?{@{-aMUK7 zXdE$Z|N0tlNEi=JbYPcdBb}0I;oj_@d2Wt!DtGo;jTY{A#Nuh4Y%r#&>2FHoc761S zsygcWCY8ybZk($I8j@)CN&3>K4}V#gy^b@T_VL!E%;&|$*ETyWE$m)%|MjO&C%Wt6 zb#9H7aIel<7%+B>R!mxDGuglP{_Zu4b6HD#cs#PVnr_!n_SMAFa2eLqUwn0BkL!~s z>l2Mz>{Xgwu%7w4o6;;p`|x^qVb`vn!;a>>AT3QZJKnR2oR8ecaHClnWrpymz?OxJ>w*!CWKUw!NK7PlctkG`=9UF|&4zIbtyB05ncE^q#?HwJId3kx+ zk+GTE`4`43vl~;)%IfF5v*xvEH*emYj$ffG8XFsP?C$Pv4X|t`cX};aVY8^17-h@i zWRjz#9~Y(2;)uH=7TGFqRr@=`BLz(7($Bqn_b&T|oXWQO8ixhBVK>!zW^zgyS=miW zW+f+u!=(c5JIsbVP^s9@blg~&oyZ>VsdN;YZ=(^WU73wz-8X7VrNm3q8V*{T3Cb>W zSR89@wi#L<5*q3lv^XB#b`8ZUr7It-Cwh0U5nOmI zG(Vg}*2D1pSlMdDB%}3-MortRs;XAvLemyx zhFt>IC%W;Eb&bXd)4o|Fxoaz;qp+fFtj`R9;M)>97J~6i~lqM!7*;qt7E^!;ye|nAE z$u9KTU&d25J2mI-Ft1@%hYfc>A|m2OQ|fKuGQFnY{yeZf%?{=c)3^=5=+-F3X+>FN4Rj+ z^5ZrR!;7|E=u;K?weyji+cqJ4egxgNqrJVo$+(c(6tn$I4)Z3W3)3UNSX5}X9cxc- zIy`+xXs#ki1*nBF$({tmnL4$I4v76r{n zRxBb`8iKRkEP-~D*Ab5^yLnt_n4YeA6Yh=}Wz)>aGW`4QkI{=8M(LCV&i6<==zW-P z&2~h-M~+ug+8LX*XdTL+HU+d$zBplNco>;EPNRTJUUkWB{-URs*TbhzwQ=Z+0jCnm z<2bpLQ_X{IrUvK9-G$DL{fM=nh-&ES=okTv`lwa31zUg921fq#Dk>_XCCJHT_#+LJ zpqOaT*5<<0`BKqHZKye8{qo$NRn%KQ196q|Sl-nMvU@4)xay>Q(6eW>BdKxgTGA{A zwwrh?_~etEZPryP5MHKuVLBZfK#7DKi;|NyA8cn#1*cmbhnF%OkfGA2f4b4#M%4a= z*ZTJ3M^C`E6KP9wS+iG3T0D60*W%oekiH={NP#<(yFA%7w`-IO0M-hNiw!1|4d3!w zW(XqjH6 zoMzD>?J&E^IDKT5x3a~ymW4sfWm`|^(q=7A#Lm=}*#qzjAOasG-*gvRcQe8V8f-l&?|@NFFn05 z<-!LOyIovD2>8d%G3!o$tlztUL@ zHJ8{7WfagjOy|TuHAy9JW92ew!5LPB?6*K&O1FPp{bbxS9vjR#TIXFVzC*e7aL;jz z{#v@n+fN<`D)twv4~Mh)VaHkf+Q3eYI$Byz@(ql}GqQ%%A_f_B@LYigTxkbTyNUG&*_X{tzd z@tEt=r$)Cf*l1)NJb2J?$Zp6cyTeq1S^`Nf`Y<#;Ca`qGJ+oP$$IO+n0Ewx zBwT0n6>_lu9w`^w>Un`k&)AsYj*g{FdrlzLP5ULO46#Y+ zPREOEMdOT#Jj-jItjfdn?TH>*MK9y5C-i`TE&|=LoPE3-c@O#Zq(YFNUs3QG7a%yA zj*gC<{8rBZV4Q1=n^Iq-r}Mw$P?orQbu%R;WqW7mN4&aQk|=V3-)|u4Jb7w@C3^Ph zPVrck3^q>A{q$6EYB{T{$Gc7JEmd&pXCe1rPZ;k_nIA2hX`ad~0OY?b9b`kQhCr*I zYfa6}SoEfbTBNP5PvOl{1-Z2(=rdqZ_P1mO0gn%+bTPhv|6V@J-p(owSg7*t1@Bev z1F9%nW79_^9J>_w0RZVYZ@zT-a>d59L*kwsx1#Oepe&8UW_9JXGpLE#1-Q~*8y6~U zhZ-|3E{=|2|At+sp1*pvRpP>hoRi@W!8fj5yB4}o+KzP>rkZ8ncI4!(*3(-!RT^98 zJW;`W%SN`?ZJ~0a*lt^CCLtkV)zmZb#O|Ou-dA#FobT}oJ}(p+_y*gdL+wCbp;1wf zNu^U;E8kh{v+0rzVv*qd@R^MpH!8Fjaqbnbm|2>@q!kisCrT&GZ9Yxy^>`)}|x-oF>L**&%weMuaKxH@$V&{G# zp&%rxjVqU#br!3Ad9b!Ee5n~g@A0MM+w}EyoG_eOb1p<~8udSmaD=+^Q)sy(ei|yH~nKZ$%(6#J3VOW0xd-Hm# zIXlT$2$i`71uiu-JGdTC^wq>$ji`HidcNW@VQ!fzwcNg5%#*dJC|uI0Fss%{WDmX- z@&0{k#L`eOV3xnXe`26BefFQ%%Tsk_(GQUX?mnjttw+c(96Z z{0AfwKE1()pIA6R6GE8Af2{wfj zGMksT_c?4b(GpZqTX}BnMmAGN31?9IVUPMk4vkPTP#=Kh)SbR`iH^^5kpEZX;;@U* zup`s_UBv0c;?fIxX8Wc5Ux$T-<;@o4=928UrH}KS(@MkF#x0rIH1@-e!+qeYewUD7 zmO@u05GI093rWq^2h$BmVT&^Q7FZrIAFYH+JaT8W-F1KvB(`%6Ek@smJkBw$1y)Ht z+jV=iy>K@lAKz~h2C=;r5ZUNc%=`BTDDERVhgn{$pzJ_3ll@kibN$^db7nVdE;wS- zCr!jioRXBy%2Hf7e~FD=9f(>}OUua{f#4~qAwU#iBO@cxKjSj>wqva621vZQxw$e5 z3K9wm+uy!@+h|g-i>?9uTAcmDteJ0v=iUojkKCZxwC`f;!~KENJ8ky&h{(yx=A7Hi z6gQqfR2gU2cxT8VWRQo(V9`cTPmf=LRavv1m3|rC%(+9-pnGEZG8V_{X9xQFc0}^Z zW*x6XK}M(v_QQt_C%HU1RCYBK(ABifyD*D0#9pHjd-(9-=WqsThiVU&b3g+6pBBg6 z-uER*OGr@FoT!eqj|!{$@#Co0H~MQ)^5wb|d|r3PI_WlTT5(!RY6D8hl4hjfgmh4z zCWt zomhBy=D1F&=FDgZ;FJmKgwC=cY0#-4D)eL;!ljw=-I=8A?FCVug<-AmnspuleF^S! zSuzMoE3g1q0xY+0-^M1n7ZSp(UHpn}P*zT^0L3}E)-}s<`9Sg;+)Y|PFT-t-XNwiA z3EuZ&kYE6kL(1)Dkl-kb*x;TwS4wn6WJ07*5qJt(_6uuho;1Ol+IoQ(@sgId|H zUmdmQzyYee$;mue_B?k+SwRPl_x?Dj4i3eFb2D_o&nDXb{{QyqZ( zyo5x^`}f}}bt=Td7Q3b%`UM$ztc!N9Vbqxn>sg%Jt$Y`ed7#D?biUgEA8h}oU;G!@ zJYmc4e(BESCIHwgehadGiryhCu6mw+=b#Tzx9B6Kt8j;;Jw zM|N--xXLdL=g=HCs6C>tp@BmCisMnLb?dhAnzE8IAEA_>{PdCh9bGy)Br*s8I8%-y zUp~Y7Bw=H)km*>?KJ)U*L73bgjiB7|J@^c2E|<|!Q;+~(!RqGpB^fnIsHiYr@aA~? z{{5r-_m|_!>N4$YlEWO97D1>Sv6~$~TQEKS%l-O?8_VXVhPuE{G+HzmK1P*H0~&IE zFr8IA^@bRwFzu(#y95Tc%r#HFI;p>fV!w3Y8}J(ZBe|P^q0d$g0^E*|n_CRj=RMnV zhNAYrg1iiYcRGDs?U7#ELAthq$=5dx1WzBOlHm)+H#$32hD{V90&#$K-@d>xo89jQ zs+Q&jO~xv7Z@cmhE&RLX+w}h2BavWQ)M|T?5!0hkb8BjAe?&2`Y62k_rI+&xy3cq> z2z|jo;Ywx`Kjh9Cc)xh@LpQ6lKK1s&AluQka>}=VbPetSPC>mPebQMGsrGYk-n_Xbsj(nfrq+iyWR%*oZ!mpK3W8Cs=0f_NG1rF=myy~9h{=pm z7O~a;&)6FA?j5hbVWdk4o-NQgO6gUC8DSBhJ^}ZJoroy=`t@~csxOMlb4s#T6csO^ z3{DOMyQiLJ*pTSq<8w#KtO2hA+zctdi~YD`uQwdJ{xMg76<+LJyUEv(yVN_rz0ip= zoJcFv{sZ$8#s;acI7lc66$R2~)$2s*Ai>8>LV*Zx8<+%1we=_F1wpg?R*T)#NW}%t zIFnyz!3frVP+NhZ2lj^LH{{*4UrGj-M!WKgoSdw(@-CI7{2j(ew2QZPmIgHRpN3kP zsiYu4-_g}&Hq~T_DqkMxCI97NMYaP6m?814SiJ!S;AedBWddQ-e4zdX!sgK%pD9u; zhe~}<>VN5rkB?_RbVw5v1QyF^t0T=vivb1a^*gr2kYqA4GD2i8aI%gFodCBveN5Kf zdywX>x`SQLpFh7FJ2*F2ba5tV>Caf+sj%>Jo^yq9p*M$0vnS_5N9t>%LT@}XnQcVg z{HLPle-SkPs*wKg{louay!zy6ay?(X*sKD=n(x^0<8=^b9cL>lE|a`)O!K@|-QQzYYn5*O-@E{SjbF!knFLVO z=S512(49sJpF8^e`San_%0B7X=$6dPKMMMW^h{kmK=Tp3miiT_WtRu3@Go440M$`mN^$b! zNu6jzFPY~c$^k*^>gp)``R{n*(!qmOycT3(aYT&^Ot(wWDN(uyaNE`IZP66xZp!#L6 zToHxTy?VpW8{c0GqnbB?X0u!d#WR?nZq!IXVU5;((fnXv+wAci=(lV+W;v(?@{j|h z_4PE1!$cCR0qyqWq<%G2O3KsV|M6RRULWiJIQ4%D*Y?GnAr(OSBu_lI|;e*lhfalT8aGQ~{CnTDwprGH_6MMtnc0ioTLXKpT#05xcIow%hPi@Eo+W1 z6JmML)I+#h62jsG1^E^6Y2)L;)UFs~(80|kX7!QHkzrMaco(ok_M_d6TU#u5yT+a|%3t`K98j?6kneszU zm0(Zk(;%4l*v}NHj(%Q2^F7H}b#b~tD6!ghuehf|?se%P0f_a_U%vdPo=1h)coBSN z?1l5^TN4|Me&sQ!va+y*=IuX#GRo68J&X;(<42?%Q9K|t?E2f%fNpi#+&n&L0XJnX ztUu{g4f?!ozrqDiR=YZ@h;x744)sUq56geJTUB)hEag^++^D#9qN1asSh=~q!9WrV z3*a#jCf&)t>SzrR2j;z158)_^SIcoW>ovjI8EuD!v4_PaB-HV3;y38B3|!x%4Z?PfTzt36tDrY zOwcyOy_4zoI6F#(IP;#%z$9#JY`bxR5PZ1P3J?ir+GrfUUf|3SMI}L$8=@Z70WxKSeyJQS3lAq4pLj|`>_ z9RtIUa(3lsEGbgb0AhEgJAe!PS=lc$?!Gkb4g~%(XlcIdlkrkdz&so~?mXKocm9#^ z&e|F)fEq{TuL)sgeeZ<}7aoF@`uc27n-b(tn?wqm z1ooB?NcrHgT)Y<+r?U1RK0M&R>f*(VD+M!#XX2mk(BCO;oxV$| z$IUt~5_SWNzrG_EE4b{tF=A048nG2|mYd>nO`!{k1F#;c4NY4E5;TdSEida=45^nI= za4(Wpb`};~gtH0Y^G2mW!UAy^fKK&o(g61aMdCQN1nhIf8v*X=CxZN`t_3foil7Ma zk%@u1dHd+8JOF;KUVRZ>_QvhvLfOzJZXrn}m*lbLC944wZDZfTESd_+h<7b5C4!ep zZX~`f=tB$uVa$u5XO#PW?SgM3Xu&7PKURDk@#@{)E=2LFUW9lUiCV~_aA9ghfE?GV z=&#f{<4oN~o|l3t2coxOSe=Vqp#AJkRC?k#jDYpHt30<}Rm7O!QM}yhs(zU(z*_KD zeJit{jCbw$1Eh?mOJ770q3F9$CBhD`q3rC~kUzb(!PqGFewIxa8zU0rZD zxUiq^BV))`LpmQGEgNh4tr7Sl#mTAKCG71&eO?vjYbOcKpu)TEx;s5l^+DZH5Nh{$ z-~kw)YD$-i@mLhfub(N04}xiN2w)&_S`(kuf5T@hX!uD%nG8b#xDR3d5+u=gKTh6} zEQi?LxP1(nQBPLVIb`=51r0{>;NBO?w41r&gsS)V1<8`^Ni0tB)$@qGJjRQ|^aF8je- zgwOszt$=)Y4%W@SPl<}(6Xke!c_{WZpN659)?O4-AUM7Qyy1;O0D2U0J;`Vn>h?DX zQ`C(7uOPtro4L~nPTz$u!;e((*hH`jegk1y)*^KQapjudgOGojo~u2@W7@t3=F~Tk zBcFSFg9Ih41eUvQ3bzRM(UB0Co%v~L7t>QsyibkRqA7@1O%J<7sd;V z;lF0QX#U1{b&l)bgOYGoTU*;pASSi<00p=oi)y~s?eUt08)LUUV^yY?^5-enWSb5_ zDD4JQ;P%uc<}W&eHQVx6yk`4~yg7BiTq(iiNnF$afd4|=2bWdsbKGU^rhU(ReW^af zFj0WwlIRGkOS3gI@Onvr=O^wSzy3=O=6d;$x4|fTpJ{2jpwWJAARRjIJTc!AD5p5| z#Dn!6r9Z4_nKy6l zr>nw~XQfxeDx0Ev<`VR0hq$qqwmm;P4$sV9xU!Vukamu9V~8RfeZZ-_&yGwg--8y` z4ZgG`xWVJMcKg3q4|4E zXn&yIb!vQmD4D1P3F2%D{}ZZ>Ww%g$m|8T^pkf2wD z1$|Cidmo=gzpjibgjQG$fqd3-?)Yaha>Wg>POwki#U}O-3U~pW_GB_z;07E0^4%!c z)bHR_#1s==Q*ErO+9F?~GY6mar$R8n{`fGN^fIUtWZ70`ROIA_!rX3_4lT&|Cc3+({pGr%WOoh76wn@_7t9jGhhf5 z&8dO(gM!Z=?gTTaR6Tc0@cndzSV79CzPJ5+AIffkwHKil(>n@I+V;u_-38-$K{y@~ zzHZ&$`*iGcXI6MvSU&Fa_Yax;_Cf?}iJ*Mwa$_Lm(LYhWo9*_AS6rW=tZfiVWzKquG@mwBLSBd+{|H7QT>k-P*p)q#YPsv?kFrO z3WXmfL>rwE)H`{eNd=rbYBhX=%b@m_l)yhxi~k^}qW_g@alDN!8Z8ciNn4nkG8=9c zHcA`d%73z>z=g@a*3|d`V*l$;*8fvtKYZlW#D#t8cCRRyi)mmc8xrXOsrxdHqGFRw z&|{-=5cT`l=FjT*K(4&R0Lu{||e))3ajvYl%qrViP)Q~{D1xwZDi{kc_dK!NG zR#xXYPg8ZYB5_RZT+7v8^M3>vWcIFhc@{C{h-QiXVDv^jKba1!-Fn{ccDJ$51F{GpK+pyyi+I|uegm>;wN>G9@pQ0p-@vO zd%J3R)BJ8VzzOe`f>wv=DG>c&`*<@GF3oaH&~#z%L9B|O5;Z`>$yT2%Bj z2d&H-<*%VuH!Iv+D-0N9WJKvk*4{Ti!ez?N+Z;tLG|Kx~=8S zqvqM76Mfy&ZnDlMsO_%MB(DyW@0-A??aa)~8@j)VyP@xe7&eiOP)`^*g z<()#p0Yd-B#KaK!jk4F-*|`Ej+S$ijvtgGwLhT6pry}^w>idq4%ZSIy!@~o&=uEin z+kMq>plBDzf|eRTJt>Np1$ZF-s~Z(>#Npd&Z?@ZJTJuU$>Qw+$&z6+=FE_8nM1N3y zCsXUKAeL*=G!|Y)&JtfHc){HK1gJJORDK4{>D#2GrQsVm>Q@4NvFVxv>cW+)RuN8I zCd2wR_}l_mux~Upf4V6P8}c{Q4Z|ZNYK_Szhd>(xzcrigAl}l02i>4}X>j@BK-1IH z!=QH?{W!%$?`EmIYwhb()D8NQS#^NoF&#mU+4D zXNsFsMT{#?Ci9&V7gtx;uM1~`&x?wRu5@Ci!#}70_=^nNM*L+N{Udxwl;Y2C_0hl3 zwGOzC-%+K1BoR>*EBp0)5nFFCj?&10PuMb~ghfSj14S;a4Xbr8fA^)~?@yKfJirNAbJc^zehRnGMB?@YoI>WF8|iJPqf1Gme>5oT zWG5SGq>I1Ch0B-+9gO<>&o${D#zMbOF3xq$YZ=@l^YY)f;z~g!hQBc1{U7|6^`5Bn zacXh$9~7dUs_iW-EP@jb=gFxBdY{_gVI<3zy25E@HJ(q0h5Vr{?;or(JGOA;5{+2y z*{-)d+nqxAXg6-WTy=(a*=M~3(_;(tg*n*;(YI#L7dk79tqV6~52Tt)ui|C5^b;xH zH9fR@%_O^j;)ILx;s&OqFYCnWoT_5z9h`2f^!h(flML?O@M=P6Hyxda`WfjUydBp+ z?~h)M&G*F3joI1RuS&FWi?vw}OSk(1dCi=`O12ISNuNGV0rN4;m32R^rl%li!O;ttoTlNaa0`^UJ!mK z&C_UgU^YHJKUDJN=$cjZiH|?Ne*1~mrNyBos;U-rE5JMovp~T$Sa1cDl$FuD z>mDryKDmEVzcD#3IwP(8_l>`3v-|!xFM#pXyvKae!IN_{Gj%WK=H^t*Hh1;+_b5fei_q2bUsLrS+maFMRRkrv-Q17kr_pHO;5OD%xBv5Mh6qzM~r5*gqK7Nmm2mgu~$tl z3$s0*`h8CKMc}n=JDx98om1-BW&hz4^nbj++HKdJo({>kU~*~c;AU~Q_ev?p(kzF< z(78iFls26*Ml7PMui+z_NV(Ov9HBd2d9nVFPVcH%)s|~IApR}p7Qo@1(EZ*TlVI|e zYV!e*W{>L(@->e&71of2GUcka74@4;u!vLNwd5|WM6E0DuN2lM83&mRHu7o!yPwT_ zwE0QAPMJ!)Jx=ljOLHrVwX@dNslc`xSS;m<3us?F&d<;P7_BgkUrS0(LTb~pvbNqX z1aVH;@C+8tqgdv(V$Nz?u@H`Z^X4?Rx^rh+TU%Q`^f>CABadPD%4uk5s7*BViynkb z_5@pKD|{c$Xb-?b*CCrIA<48ug5}Vm;^E=pBYM?QH=|TC>W$`_bblY8(@RaM%ZLRr z&|{-$>>>S_-#5m zI*`9clNz!{&yFyt+Iqm5vow$>8nYLX3{5rgu zf}d_yIy}paKHT{KCRoDk^pK z`r$=x&I|sk{071FxX;C^7-c`SK{dqKBIRC*QA*KWwbyjV#eJKAuJ>WjVZ|K>|BHbX zMZkPL=%q(9K6-dKHPJm1dF`-5l2OUbn0GbJF>2DB?f?1nX*_=#`}vV`MAqRo)udy3 z!IR3z%jt#QOqhaTfBeH@xB+i9x4YaaYu=Tf!-;wIaPB7QXD?rBCux3$B3y#00|UKi zu5kPM_5AV`E8bmxO9e|D^x2lg1)S&&^d)5Y?{CC6O%68cCoO>|Ac~cNP#Fw(u)_W9 z>$~7YwYmF8Bm>%hD0`&?PQAclq}{spD>?KmeTrwHW(Z`==N!tukd2{UuYv{VepZ$c za1m*U2YzR=#XJNGxZ_NL;9$d^#>Pfw!R_oEMpNmHAj%Py(aFfP zfB*jXSKfVrmH#u4>1~x$Jh(fV_gC2Q5cO?n&|T92M`9Q-Wa3p`8uXq*$liv&M#Nm4 z=_6dPDs~fBTPsAa;z#yE34ueNbPaWuaVCebSii0tXit z7ZXoG5bA^*mXbrqbzV8iMnz?DrkATUP=NG=4K{u5h>>M8r6sd15Gx?vjbXe3F@pIA z1Q_li{Sc%_8tv^jj`^%vyH>6Jt4E@KJCJi;U7Zg)fZBfk6p@$T3Z*(jEr_V)ILZo+ zXqi)3UYXNB*QK4plO-%I^1vm4KA5$q(X6_zZ3pc+F8%65%a<=-Lq+uyN)tTodq3%x zqyL?4pOpW3$O>3gq&tIx+is!s#ck2Wm@FHW{QP`$OyxrJM|2=&3t|kmxSe~Hou1dV zwe<#oun#=BxWKotX<9VUFbNjFzZ!{|G9T=S=Uk!w?cOH%?bobdpWwIl{pGjc*3T({ zs&f~dWrO!Kr>x8!zDSgpT2q{FBIPJ2Mn^wptP*Sg2=HtQ_N_sy6^@1Fq$Tvgm59@! zXP-dzUj;8+CoCzfM~=`YFZNk-7;j41;Ug?E5%a~+7HD=8_fL|9Bw480;mevqFyVjot4OAfv-s^9?u6 z3&RTp9YHKF7?^r4J3=Qu1SaB(COac0eXf+(;5#I^g)NI>cq zH!|XY(`+8=#GvjY<=x4F2DI7~AXCRFL=!VGT2T5@?G~^Gw>k?W{s6hOL6wG*5PFM7 zM#3L!1-$!)&(K08(Cmx0n|;TQZM<8+X!H2Pha(UQ97dpxkhe@a`C+ZW&K2V|XU{jX zQ2FxqmQjKNntZg7tM#jTOk3!HK8>N&|Vbp@U$>ZhAvobQYPqv>Vl3lSs@0bd;@}oF} z#$_tjJ$q5)oCA6TX8MKJ=uo(XoCFNmHLZWw6Kj0g|3T^SkHG#vDmY#Pr=Aw2a?exG z)=StjapcW|TeolLH8o{Kxa=8C0yd|Y-QwnS;OcwypcIBMK6*}z1=fX?L3Q@$2f+JB z?2$jd!5twkC6!-L5FX*elBWb^ANy54`kE87i9GOjp1-?0DHOgQ{{EyI$&UH2P<)Vm zBfH0=*-Lf;sfU@TSe%$WHs-6x$?QGx%Bv^*kpXo_(aKvV*gFy(%b^PTV7Ltyj?;-_ zbnJyVzrg^|7zkio$Y-(D&em&{F1c9{No1+NS zvI#;xFzCqSWEdh5Y~L&I5(6yk48_{VqZJZ!^5=lGg>?$|M6lYla9%%Xhm#7A~&IX8Sniy|3`* zdEVf%KiJJ#5CR=8A`0x+PM|Z*Qh#3J_g%SiC3!BOnex%De;&plgE=ACF)RjrF788j zw-~lKg>F>@9qvV|?TZu4U(}qOoD}v;3zjJH&thf~R@_b@dw<~j<7l(ORqN=-;y1|* z0ix-^C06^bsj-nve3Fv#-q-6e)4_m=E+@mJgG${vGuNE9vf_!n7Of6=i6uMrj03gR z8E{;=NKqI>Q}<%3zs?KY6}8%?aJJ~8c2M4(8hCJpE2shN*|z{4iKkc6ZrQR5hT&rr zsrV)O9A zRLdCD{>FsN*mRTG(T)#bSrFcGmIq0^IM9?}FZSlv&9L*LJ1+y(+$;~FgnNPY@Zl$< z-UOaP=X5bt_LNgb_uqA-=fY>gWsTO2(V+Km{0=rrLdNo3q;7ZD0a2Km(hW zk0mx7ZWVQc$zz0W7={ zauD3-^$@-D03Z=68MBfNX?1arzGkQ$i<#f=?n=J{Ughc8S=i5=5b>fLJzDJcO~v*F z7&AW=Jub$)_i{f9Q8Lq|3?UdkKRq}&NCjvwS6->}9|Pon!c|n0 z*m-D++ZJPbx|ByE9_QC;#K}i&Rq!buuVUVDrsr4(kB9li>~7Lzc+`I03c82I&!sT#YUCCJBRFGxcP8$_>6zT`S*Vu^#pMUef)MD(- zb3eIDG-pKHGlM}l<*Ho_lW%RJlAPm#0z#8Nl zEg_d)LZ?2;Gd9^UYCy&gbSPTfMx)NFBWlYLd)}fA(aOfA5|)3=+c_^S-GzY|*??3C z%5Yg-zwsCX1`7wr)7P(eV+q`8y|;R+pl!0@92%@n!!QhhnO#}w1r&fy=aQ$+{=FI# zVaN(ZU8Sn3>dR&L4f|+al}%rCQUt&iN+wiFbUFanSq{f>dV2cs)KoYYA&TCH zAG{8W{O|=Z1DeIxUBvl;opL7}&WJwRhNNn;b)VPTyTs#$YewkJL1Qp%h<0S?+fe%0 z2BQ1{my)3gs3U79cOgOoUy=5A8%6$HU#a6j`{^uJQhKvMgN)*~{R3G-1z&^1Q% zJ=9m%y5LF-!%HHW2*%jQ^UWHj`1&Dwz6kJ>BXkCP&;r4&cW~8}p&&rpXL9 zddpPXeasv3G>;wCt721Zz4`BgdF%!6l@9+F&5`0s{y|UH5rhOq#glqft(b+Q1?2{% zME148&dz)o&u^Kgjt}Ll+Rr$VJx%0}?!dEKru7I*&pSe+06(aJ^R;NXB}5Axcig%(bbsQ1GVXS_F*^W(=0r1W3? z{nU5Wwi3G)jJ<)NCrl>$uah@1Rwh)2$t(tQCk^YLW4VoCP}xx=R$@-Z&;v4w59X?Ni?C%4@9?G{1aJCrDDt|I+s^8*DfynEowiULjWgpDCr10BRJ8e)WuUWS) ze%Bm|S{GbPPeoqL0`lSJSK^!4V@`lR-cZoe(lV$cCs(8YGW+e`H83vWBxvM&eVSZd zadEL{$!6C|T#l>%sU%7YAf#fg3Or8=H2&7|dcyv@9G77qS8|qW}1N{;xX=!P1 zOvQsIyc-=|RKU1-^VJ)w$ZRMCek(Dj@13>`y}SQ%BZBAoIJv)Ouel~v@@Uchb~_0( zK<=U6_t@>;D{%K1b_gxao87aZ&e@Ibet0XI%3pCXH9&T=DoF%_qYxGrrhpsoa*C-| z!(>%eh+>&UyNWE^Mi2E^Y*gfur%Xap00~slq>qn`XVlZ{J*gwlp_F_YY4WW^-O>-6 znVys)GUW&`|9bMRwv$6Fsk^*~uAjneF*Nz78)aaZsvq;2#^ysldme3z!VZvBf*O&8 zJ&~hjoh&+>$TWgKT4Z+f^Y65@w1k8?4Qho<5CBhQ+1LV7gZQJs3+aFr1Auq}4&(#2 zIYf|0V0=n^Pig>NJ?B&nM2SMuEgVm3HBW&%k82m-fd9^Xu+azc=mwV`KobokBO{E3 z(=Q7&x%^1+i#+@0?c4H1Lq%IVI|LFntT@rLXDgx=iM>@AY@QE~NyvbGBxWaot}!oY zadQm?*kBJHftu*yOuBe z?($xguX*T@+{R~d2$>3~yb^6&iUHDsGY>HKF9<8+S6f^5@iP81h+euanHkDyB!aTB zxdL~UqxA%n#Ue1v6fOVB(Z8H8xYxTlJ0Alz@RCzi3biKqmrn+Do{0Mmy@!>O5yeqt znIRElieVgvZz+7zH7ROwab-Ea%{uDz8~JagrTM7fFv|=~n?Z;5ln=f0U-S5W&!=h$ z3T_6}C@iT&nN3?R;ok2dA#Iir6^#kW+wniD!(3**erU_#W;SNi&3~3aw3pc*JpS|; zdHhD*X;1h4EezS+ct}^?U3Nlt#h4&Y3QYgu#Wcfj0QZlq%$ZF&NvU(>TI7qzpQu9^ z#4dk>6MFNlkw&r1{;BoXA}2D}h}9f}2_2HAoAp*^Q}^Gc(OdotZ9jW1SLGyYT#MX{ zot##${o=sYK|LGhpIgmC6I^*712Fz-Z_h;~0H!X_fD7QfjN$`wkuSILQ&3;Pq$Di1 zw`-;?fMzR0Jq&!AYrsWbB##e&Mq#iA@cbYyk zs120i=dnNy2UP0HUT9$m5EYrc0%P7UAbsLf!dpi($~zmgR%TP_?ol6C9(u3BT+$fm z?!LMeL>J+!z#jwxt7rdCjdoDV`bd4#<20Yufq@0Zbb zbSAbabbj87NpI;gWt#ypk6n80UMf)1^uhCQ|1Atw`AtIpAXCvuJ3Wl@u;>P&gfv^6 zzk}^%zuyJKJso**x|n*k20SA&OeHA#zH0sYPwdD6$fnOhchrGtS5;{}+qZ*VG4T+3 z<#XC8JTs!*(P2~AB_Bx8_e+et=5cNa$D zp2H0iQy-`i?txIKK>Xw1&zvx*r9~=-{Inmys#pjOU^cfNFq_5_s4c@#rfg2aQzRiM zMb4Zd!44+X{b&&a^{0?%AP!c@a00F5u5|_goV*}1@Psk3j`y&_r(`uD#NR0Sxcf6> zU1rcEa0F5`$&$ei1O8 zunwamno-yEJ)UI$_;K~#dC@^WypO%l($dmI9=d+L`pGJksF(N*|NJ-f%G)4ASl#k| zN1m7v6hU90MILav!hDz|`fI)n*M~y|UhXYFZqU=w-ShI=1pO{yKfORRT5v8Q23RtZnQ3Hm7ciu7~BPs#88oZ2Ng6B(*{KwVCtt#Js1x^{KSOqd{ za#l}J7}8@)9i@yxncoieB@~KFQo>S#3YX&Z!K4vadP{O^&u!Rg_>~6j*ispgZ6$qA zu=lFDx;mx^b-Id)8s%VCp$M{;iKS&Y00yDgEigTDQW2#=KL?*--A5Hdv;w`;nR=W0K{yF_YUDo-MrKb=OWQQ@-s6eJ3}6o<{w!JfV>yh5pd(4ets7~F3AjEuEj)) zJDBa*gRz8AoxT83Vw+%ipGV*17K8Cczy?^^t1c^97HG)ItfyPd@W>p6mcuOjnp#(K~a$j*QB|Kr*&+3i--4i9 zvr87WI(UV6%GcoIY3b>6!-MRJ^@Cz7Lez_ca-Dw0nb~!9+wKCI$fQ{qlFA4vC&ng< zd6eNdwc!CrB&iq?4+p;aowzB2(mys$6?>mh&1426Kx({lZ|X}tilcW%H3jGTkEfkp ziOC74f!Khr6NrTihytDx_N(vdgM)*CZBPWS`Hav2zHBw%-Y3eCKD>q0!?erDi$fU( zN_wS|weOLmN6RMzpt#iuqG`?ARI;55(aAOE>({TOSrIkx=30yBk421Dt;-S(>h7y; z#atRUloFM&+bqAAeT15fCq)iBgAq;lEYSMBcEg6uwyiDJeTQLOMiU)1jG3fK4MNbm zoFgCP4^j9F+WCTI^h+f0TIO|eNl8g{HE5x)MMWB57eQ6m3Iff{&$TRpg*?tZa7L7) zyyH0P+k5x!;kz)`Dcn#53>zuh2&Fu}pWVWEW)bctyr4akq^mKmj#m#TRapJX0rfaem z#lT_mqFo29aT_$T^yncn$O8n~COih*hcu)H@B{OxK-#;y#t^t#uGui}#il;LRx8D- zdC%OFM~~Do5`x?eHkL2=F9>AD!}wEMlCLz=hv|~8s65DM5tMt-#ahwN{0m+FiD~6C z-|XJY#YzFwz#B#sAcT|FfdNU_GG)UqszK#OkM0YEfH4q?^&HufD9?ejRU8&>gUbY@ zvqd}lm1u<@F$<{7UXkSmz={A5#grie;P;+iT5Q7mi60j>5OM>fS_t7w42cMm1W#hW zLKPd{kZI`QnWx#k{L}fk=K%pvVq-ZWi=u$8{pQw}R`y0$dB}E?2`egab~V6u-ay-( zkv|kPHK2c7-M!Ic?kRa3?Sq3cGR%PF0(Se5hX31+BHaLF%R==y-I23&9+A3XAkO0C z4_$dDPY3Ti^BnoYEpId_KMUjkP)^-Lr3(a2kiDSYY;7VZqy;c)DEf@j4DkiD%;}{3 ztMr5=J9S$RO@pgx&Viw0>&yvpfrmfzG6w2GV0Um_wqg~= zxDt!pU7Q0{v&B$->a5l~X67UbLgyXx^FPA3Ia||Yv881K02m7bj&4q%di$w-R_6PJ z26wc=h)vhWk5|41K;+@$8^dh+&B?YF0&ZIlhrsb+Wo3n(u#Ff2K*6l5I!^6KBuVh3 z`~m`q5?w&jiIE^PK4XPQfl)xcAA%H5dOzoF=W?fz1c)$(y#9X_cK$I@*KruXV8f_% z{WuRuU5#3k?5bTU4$>8qRCIQ^^$9K@}`T^?#W(jpOY#5k8OVZ;SIuPP?Gk3&@FW;AMi3=ecSVl`z8ZSzm14f-GonC< z5BwrLglVuJNWk*efbb3iL#VX0;wtBc!otpxQn2?^ zI$bqOdTBtg0xk-<*iNNjGA;)t*ab2HhAv;GK5s&Kbx$s+=yL*JE7V55nt6rxf~orC zj>qFa+zaPG6qHFJ9x2-#UgSuregBuQW58scwRU1m*ob;~6)afv*Nve&6Y?!cRVqIF z1KY-Gvt{uoiL*wzJ$C%`Eu0>6qw8VJDKzeVMsfig6U|ka2!;b!sGHq)msu--A?i=4 zy8oyjWj32F;Pd-a(DtDEP0D$YK+`^Y>80nhW6ICqg*n4a1#c01W&7>f%C7UdALr~^Q${0!t6IEx$TSr!_NfmhqHV;UO=TCeBBPuFQg!^T zN<0b&kYgekR9&T|ZYT5!bf+l;hTfSD&u9oI?aMQ0y#p%kqa<2Ma5ddUaTJSE|1 zMHIn{{t}gFY}|lR4%93B6z;-~S`?^I>pAMhN;Kv914D1vM%Mnn7UT7o6cs&8zadKq z>_Nm#B*+~KIwI|x{%+J$BGKk>LePrNvf5QYzhzv`L|`RcLN=rRi!jVYJyX{|r^b7h zta+K^nPB>@j}{=g-rx~#37k845;i#hkNUq8D)V2I>aOp<2hC<4q=bOezN;uG0M71 zz%~^0emG@9EQ@o0ZFgoqT@cl<*LyF!zGA5bzcxHv|G>bBlG)#1Gas|}_#=EJUt-Og zzbrrLg+1EW+s&t}OCMzxsd*7KiJyke(9WiPzcaS8qst$Q`(v6Wx_^DxjaLqhVpMK9 zT(EVk1H#f`v8WY&i)CTPsh&WxE4FIYqEwqYOJj4aipr^}PPP?PHcLeYlf7C&C$C%4o|L)!Z*+6|umAu6 diff --git a/docs/_static/djangocache-get.png b/docs/_static/djangocache-get.png index 5350e8c9cd747a1f077ed35dd21c84ae0781046a..42a226a55d72c1d992cdb433995888dd13237dec 100644 GIT binary patch literal 32506 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 literal 27696 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 diff --git a/docs/_static/djangocache-set.png b/docs/_static/djangocache-set.png index dcd41d1fbc1969206a8a4680a424b54324b950fa..7aae1b1c4ddd945a6f4f1511001af5701e7bb25e 100644 GIT binary patch literal 33306 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 Uu3Q<1 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 YD?6JnwPt!I`7Vue)Mh_C-qs!Y3BlS&>;M1& diff --git a/docs/cache-benchmarks.rst b/docs/cache-benchmarks.rst index 00327c4..f4c9cfb 100644 --- a/docs/cache-benchmarks.rst +++ b/docs/cache-benchmarks.rst @@ -91,14 +91,14 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) The high maximum store latency is likely an artifact of disk/OS interactions. ========= ========= ========= ========= ========= ========= ========= ========= -Timings for diskcache.FanoutCache(shards=8, timeout=0.025) +Timings for diskcache.FanoutCache(shards=8, timeout=0.010) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 15.974us 27.895us 41.008us 562.906us 1.570s - set 9021 0 281.811us 316.858us 398.159us 1.189ms 2.526s - delete 1012 104 240.803us 283.003us 321.150us 499.964us 229.842ms - Total 98999 4.326s + get 88966 9705 16.928us 29.087us 40.054us 141.859us 1.692s + set 9021 0 275.850us 312.090us 386.953us 611.067us 2.486s + delete 1012 104 236.034us 282.049us 354.052us 546.932us 228.918ms + Total 98999 4.407s ========= ========= ========= ========= ========= ========= ========= ========= Notice the low overhead of the :class:`FanoutCache @@ -203,20 +203,21 @@ the miss rate is variable due to the interleaved operations of concurrent workers. ========= ========= ========= ========= ========= ========= ========= ========= -Timings for diskcache.FanoutCache(shards=8, timeout=0.025) +Timings for diskcache.FanoutCache(shards=8, timeout=0.010) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 70780 23.127us 45.061us 86.069us 7.667ms 19.697s - set 71530 31 257.015us 1.410ms 8.780ms 27.772ms 51.284s - delete 7916 767 219.822us 1.366ms 8.804ms 26.998ms 5.474s - Total 791992 76.455s + get 712546 71626 24.080us 46.015us 72.956us 6.997ms 20.241s + set 71530 106 253.916us 1.400ms 8.787ms 15.915ms 47.683s + delete 7916 779 216.961us 1.345ms 8.602ms 11.446ms 4.516s + Total 791992 72.440s ========= ========= ========= ========= ========= ========= ========= ========= With one shard allocated per worker and a low timeout, the maximum latency is -more reasonable and corresponds to the specified 25 millisecond timeout. Some +more reasonable and corresponds to the specified 10 millisecond timeout. Some set and delete operations were therefore canceled and recorded as cache -misses. The miss rate due to timeout is less than 0.05%. +misses. The miss rate due to timeout is about 0.01% so our success rate is +four-nines or 99.99%. ========= ========= ========= ========= ========= ========= ========= ========= Timings for pylibmc.Client diff --git a/docs/djangocache-benchmarks.rst b/docs/djangocache-benchmarks.rst index 5a1a36b..7bfc0ce 100644 --- a/docs/djangocache-benchmarks.rst +++ b/docs/djangocache-benchmarks.rst @@ -179,16 +179,18 @@ Timings for diskcache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 68585 35.048us 61.989us 107.050us 11.898ms 28.819s - set 71530 0 324.011us 1.491ms 8.872ms 36.179ms 56.072s - delete 7916 0 254.154us 1.410ms 8.748ms 27.164ms 5.651s - Total 791992 90.542s + get 712546 69423 36.001us 59.128us 92.983us 7.305ms 28.354s + set 71530 0 300.169us 1.451ms 8.877ms 39.359ms 51.403s + delete 7916 0 239.134us 1.378ms 8.740ms 14.397ms 4.926s + Total 791992 84.683s ========= ========= ========= ========= ========= ========= ========= ========= :class:`DjangoCache ` defaults to using eight shards -with a 25 millisecond timeout. Notice that cache get operations are in -aggregate twice as fast as Memcached. And total cache time for all operations -is only 30% slower. +with a 10 millisecond timeout. Notice that cache get operations are in +aggregate more than twice as fast as Memcached. And total cache time for all +operations is only 20% slower. The higher set and delete latencies are due to +the retry behavior of :class:`DjangoCache ` objects. If +lower latency is required then the retry behavior can be disabled. ========= ========= ========= ========= ========= ========= ========= ========= Timings for filebased diff --git a/docs/tutorial.rst b/docs/tutorial.rst index d091d74..f53b684 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -288,8 +288,8 @@ methods may silently fail. Most methods that handle :exc:`Timeout ` exceptions also include a `retry` keyword parameter (default ``False``) to automatically repeat attempts that timeout. :class:`FanoutCache ` will never raise a -:exc:`Timeout ` exception. The default `timeout` is 0.025 -(25 milliseconds). +:exc:`Timeout ` exception. The default `timeout` is 0.010 +(10 milliseconds). >>> from diskcache import FanoutCache >>> cache = FanoutCache('/tmp/mycachedir', shards=4, timeout=1) diff --git a/tests/benchmark_core.py b/tests/benchmark_core.py index 6dd9dcc..9ad42db 100644 --- a/tests/benchmark_core.py +++ b/tests/benchmark_core.py @@ -41,10 +41,10 @@ {'shards': 4, 'timeout': 1.0} )) caches.append(( - 'diskcache.FanoutCache(shards=8, timeout=0.025)', + 'diskcache.FanoutCache(shards=8, timeout=0.010)', diskcache.FanoutCache, ('tmp',), - {'shards': 8, 'timeout': 0.025} + {'shards': 8, 'timeout': 0.010} )) diff --git a/tests/timings_core_p1.txt b/tests/timings_core_p1.txt index 0c8220a..f09110c 100644 --- a/tests/timings_core_p1.txt +++ b/tests/timings_core_p1.txt @@ -24,14 +24,14 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ========= ========= ========= ========= ========= ========= ========= ========= -Timings for diskcache.FanoutCache(shards=8, timeout=0.025) +Timings for diskcache.FanoutCache(shards=8, timeout=0.010) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 15.974us 27.895us 41.008us 562.906us 1.570s - set 9021 0 281.811us 316.858us 398.159us 1.189ms 2.526s - delete 1012 104 240.803us 283.003us 321.150us 499.964us 229.842ms - Total 98999 4.326s + get 88966 9705 16.928us 29.087us 40.054us 141.859us 1.692s + set 9021 0 275.850us 312.090us 386.953us 611.067us 2.486s + delete 1012 104 236.034us 282.049us 354.052us 546.932us 228.918ms + Total 98999 4.407s ========= ========= ========= ========= ========= ========= ========= ========= diff --git a/tests/timings_core_p8.txt b/tests/timings_core_p8.txt index 5d103fc..f341249 100644 --- a/tests/timings_core_p8.txt +++ b/tests/timings_core_p8.txt @@ -24,14 +24,14 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ========= ========= ========= ========= ========= ========= ========= ========= -Timings for diskcache.FanoutCache(shards=8, timeout=0.025) +Timings for diskcache.FanoutCache(shards=8, timeout=0.010) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 70780 23.127us 45.061us 86.069us 7.667ms 19.697s - set 71530 31 257.015us 1.410ms 8.780ms 27.772ms 51.284s - delete 7916 767 219.822us 1.366ms 8.804ms 26.998ms 5.474s - Total 791992 76.455s + get 712546 71626 24.080us 46.015us 72.956us 6.997ms 20.241s + set 71530 106 253.916us 1.400ms 8.787ms 15.915ms 47.683s + delete 7916 779 216.961us 1.345ms 8.602ms 11.446ms 4.516s + Total 791992 72.440s ========= ========= ========= ========= ========= ========= ========= ========= diff --git a/tests/timings_djangocache.txt b/tests/timings_djangocache.txt index ef19271..21439d5 100644 --- a/tests/timings_djangocache.txt +++ b/tests/timings_djangocache.txt @@ -40,10 +40,10 @@ Timings for diskcache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 68585 35.048us 61.989us 107.050us 11.898ms 28.819s - set 71530 0 324.011us 1.491ms 8.872ms 36.179ms 56.072s - delete 7916 0 254.154us 1.410ms 8.748ms 27.164ms 5.651s - Total 791992 90.542s + get 712546 69423 36.001us 59.128us 92.983us 7.305ms 28.354s + set 71530 0 300.169us 1.451ms 8.877ms 39.359ms 51.403s + delete 7916 0 239.134us 1.378ms 8.740ms 14.397ms 4.926s + Total 791992 84.683s ========= ========= ========= ========= ========= ========= ========= ========= From e8b9c4a8c1d9d8fdb55e937f31eaf2053f518ab2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 10:23:48 -0700 Subject: [PATCH 172/550] Change dictionary operators to use retry=True semantics --- diskcache/fanout.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 8733116..4a1e392 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -81,6 +81,9 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): return False + __set = set + + def __setitem__(self, key, value): """Set `key` and `value` item in cache. @@ -88,14 +91,7 @@ def __setitem__(self, key, value): :param value: value for item """ - index = self._hash(key) % self._count - set_func = self._shards[index].set - - while True: - try: - return set_func(key, value) - except Timeout: - continue + self.__set(key, value, retry=True) def add(self, key, value, expire=None, read=False, tag=None, retry=False): @@ -224,6 +220,9 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, return default + __get = get + + def __getitem__(self, key): """Return corresponding value for `key` from cache. @@ -232,9 +231,11 @@ def __getitem__(self, key): :raises KeyError: if key is not found """ - value = self.get(key, default=ENOVAL) + value = self.__get(key, default=ENOVAL, retry=True) + if value is ENOVAL: raise KeyError(key) + return value @@ -320,6 +321,9 @@ def delete(self, key, retry=False): return False + __delete = delete + + def __delitem__(self, key): """Delete corresponding item for `key` from cache. @@ -327,14 +331,10 @@ def __delitem__(self, key): :raises KeyError: if key is not found """ - index = self._hash(key) % self._count - del_func = self._shards[index].__delitem__ + deleted = self.__delete(key, retry=True) - while True: - try: - return del_func(key) - except Timeout: - continue + if not deleted: + raise KeyError(key) memoize = memoize From c41a0c17d0829dfca1653ef40ee228bbd26a3e62 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 10:32:00 -0700 Subject: [PATCH 173/550] Remove __ versions of get, set, delete (premature design optimization) --- diskcache/fanout.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 4a1e392..444f0dd 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -81,9 +81,6 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): return False - __set = set - - def __setitem__(self, key, value): """Set `key` and `value` item in cache. @@ -91,7 +88,7 @@ def __setitem__(self, key, value): :param value: value for item """ - self.__set(key, value, retry=True) + self.set(key, value, retry=True) def add(self, key, value, expire=None, read=False, tag=None, retry=False): @@ -220,9 +217,6 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, return default - __get = get - - def __getitem__(self, key): """Return corresponding value for `key` from cache. @@ -231,7 +225,7 @@ def __getitem__(self, key): :raises KeyError: if key is not found """ - value = self.__get(key, default=ENOVAL, retry=True) + value = self.get(key, default=ENOVAL, retry=True) if value is ENOVAL: raise KeyError(key) @@ -321,9 +315,6 @@ def delete(self, key, retry=False): return False - __delete = delete - - def __delitem__(self, key): """Delete corresponding item for `key` from cache. @@ -331,7 +322,7 @@ def __delitem__(self, key): :raises KeyError: if key is not found """ - deleted = self.__delete(key, retry=True) + deleted = self.delete(key, retry=True) if not deleted: raise KeyError(key) From 2484f506e5820e1cc272019d880d0727241f6311 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 10:34:09 -0700 Subject: [PATCH 174/550] Add todo notes --- docs/case-study-fuzz-delays.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/case-study-fuzz-delays.rst b/docs/case-study-fuzz-delays.rst index e2a6035..6b0ebdd 100644 --- a/docs/case-study-fuzz-delays.rst +++ b/docs/case-study-fuzz-delays.rst @@ -1,3 +1,7 @@ +TODO +* Rename to delay-fuzzer so: case-study-delay-fuzzer +* Check the line numbers! I don't think it's working :( + Raymond keynote: https://dl.dropboxusercontent.com/u/3967849/pybay2017_keynote/_build/html/index.html From ac478babd07ad38223598728d8a3dad16aca9a86 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 10:34:55 -0700 Subject: [PATCH 175/550] Rename Fuzz Delays case study to Delay Fuzzer --- docs/{case-study-fuzz-delays.rst => case-study-delay-fuzzer.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{case-study-fuzz-delays.rst => case-study-delay-fuzzer.rst} (100%) diff --git a/docs/case-study-fuzz-delays.rst b/docs/case-study-delay-fuzzer.rst similarity index 100% rename from docs/case-study-fuzz-delays.rst rename to docs/case-study-delay-fuzzer.rst From afd241bd74557f35f1f4cf8a606ff16067eb8675 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 12:19:08 -0700 Subject: [PATCH 176/550] Test and update delay fuzzer code --- docs/case-study-delay-fuzzer.rst | 89 ++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/docs/case-study-delay-fuzzer.rst b/docs/case-study-delay-fuzzer.rst index 6b0ebdd..e71bdae 100644 --- a/docs/case-study-delay-fuzzer.rst +++ b/docs/case-study-delay-fuzzer.rst @@ -1,6 +1,5 @@ -TODO -* Rename to delay-fuzzer so: case-study-delay-fuzzer -* Check the line numbers! I don't think it's working :( +Case Study: Delay Fuzzer +======================== Raymond keynote: https://dl.dropboxusercontent.com/u/3967849/pybay2017_keynote/_build/html/index.html @@ -13,24 +12,19 @@ testing. // discuss sys.settrace - >>> def fuzzdelays(function): - ... """Insert random delays into function using `sys.settrace`. + >>> def delayfuzzer(function): + ... """Insert random delays into function. ... - ... WARNING: The use of `sys.settrace` will affect other Python tools like - ... `pdb` and `coverage`. Use only in testing scenarios! + ... WARNING: Not to be used in production scenarios. + ... The use of `sys.settrace` may affect other Python + ... tools like `pdb` and `coverage`. ... - ... Decorator to insert random delays into a function to encourage race - ... conditions in multi-threaded code. + ... Decorator to insert random delays into a function to + ... encourage race conditions in multi-threaded code. ... ... """ ... from functools import wraps ... from sys import settrace - ... from time import sleep - ... from random import random - ... - ... def sleeper(frame, event, arg): - ... "Sleep for random period between 0 and 100 milliseconds." - ... sleep(random() / 10) ... ... try: ... code = function.__code__ @@ -38,17 +32,16 @@ testing. ... code = function.co_code ... ... def tracer(frame, event, arg): - ... "Tracer looking for call events with matching function code." + ... "Activate sleeper in calls to function." ... if event == 'call' and frame.f_code is code: - ... sleep(random() / 10) ... return sleeper ... ... @wraps(function) ... def wrapper(*args, **kwargs): - ... """Wrap function to set tracer before calling function. + ... """Set tracer before calling function. ... - ... Tracing is thread-local so set the tracer before every function - ... call. + ... Tracing is thread-local so set the tracer before + ... every function call. ... ... """ ... settrace(tracer) @@ -56,11 +49,44 @@ testing. ... ... return wrapper -Create a test: +Sleeper function that prints location: + + >>> from time import sleep + >>> from random import expovariate + >>> def sleeper(frame, event, arg): + ... "Sleep for random period." + ... lineno = frame.f_lineno + ... print('Tracing line %s in diskcache/core.py' % lineno) + ... sleep(expovariate(100)) + +Check that it's working: >>> import diskcache - >>> import multiprocessing - >>> import threading + >>> diskcache.Cache.incr = delayfuzzer(diskcache.Cache.incr) + >>> cache = diskcache.FanoutCache('tmp') + >>> cache.incr(0) + Tracing line 797 in diskcache/core.py + Tracing line 798 in diskcache/core.py + Tracing line 800 in diskcache/core.py + Tracing line 804 in diskcache/core.py + Tracing line 805 in diskcache/core.py + Tracing line 807 in diskcache/core.py + Tracing line 808 in diskcache/core.py + Tracing line 811 in diskcache/core.py + Tracing line 812 in diskcache/core.py + Tracing line 813 in diskcache/core.py + Tracing line 814 in diskcache/core.py + Tracing line 815 in diskcache/core.py + Tracing line 815 in diskcache/core.py + 1 + >>> cache.clear() + 1 + +Simple sleeper function: + + >>> def sleeper(frame, event, arg): + ... "Sleep for random period." + ... sleep(expovariate(100)) Increment all numbers in a range: @@ -70,10 +96,8 @@ Increment all numbers in a range: Process worker to start many tasks in separate threads. + >>> import threading >>> def worker(): - ... # WARNING: Monkey-patch incr method to use fuzzdelays. - ... diskcache.FanoutCache.incr = fuzzdelays(diskcache.FanoutCache.incr) - ... ... cache = diskcache.FanoutCache('tmp') ... threads = [] ... @@ -89,13 +113,11 @@ Process worker to start many tasks in separate threads. Start many worker processes: + >>> import multiprocessing >>> def main(): - ... cache = diskcache.FanoutCache('tmp') - ... cache.clear() - ... ... processes = [] ... - ... for num in range(8): + ... for _ in range(8): ... process = multiprocessing.Process(target=worker) ... processes.append(process) ... @@ -104,12 +126,13 @@ Start many worker processes: ... ... for process in processes: ... process.join() - ... - ... assert sorted(cache) == list(range(100)) - ... assert all(cache[key] == 64 for key in cache) Ok, here goes: >>> main() + >>> sorted(cache) == list(range(100)) + True + >>> all(cache[key] == 64 for key in cache) + True Yaay! It worked. From 5d65c3823784d2d403113cb494bd001f19525d93 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 23 Aug 2017 12:24:19 -0700 Subject: [PATCH 177/550] Bump version to 2.9.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 9c44f28..622cd6f 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -27,8 +27,8 @@ __title__ = 'diskcache' -__version__ = '2.8.3' -__build__ = 0x020803 +__version__ = '2.9.0' +__build__ = 0x020900 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From eb177a0cc9e981c345c7fbefa101c58add64bea4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Sep 2017 16:29:19 -0700 Subject: [PATCH 178/550] Add SF Python 2017 meetup talk notes --- docs/sf-python-2017-meetup-talk.rst | 147 ++++++++++++++++++++++++++++ tests/talk/benchmark.py | 23 +++++ tests/talk/crawler.py | 50 ++++++++++ tests/talk/manage.py | 22 +++++ tests/talk/talk/__init__.py | 0 tests/talk/talk/settings.py | 141 ++++++++++++++++++++++++++ tests/talk/talk/urls.py | 23 +++++ tests/talk/talk/views.py | 11 +++ tests/talk/talk/wsgi.py | 16 +++ 9 files changed, 433 insertions(+) create mode 100644 docs/sf-python-2017-meetup-talk.rst create mode 100644 tests/talk/benchmark.py create mode 100644 tests/talk/crawler.py create mode 100755 tests/talk/manage.py create mode 100644 tests/talk/talk/__init__.py create mode 100644 tests/talk/talk/settings.py create mode 100644 tests/talk/talk/urls.py create mode 100644 tests/talk/talk/views.py create mode 100644 tests/talk/talk/wsgi.py diff --git a/docs/sf-python-2017-meetup-talk.rst b/docs/sf-python-2017-meetup-talk.rst new file mode 100644 index 0000000..c484316 --- /dev/null +++ b/docs/sf-python-2017-meetup-talk.rst @@ -0,0 +1,147 @@ +SF Python 2017 Meetup Talk +========================== + +* Can we have some fun together in this talk? +* Can I show you some code that I would not run in production? +* Story: Fun of Reinvention by David Beazley at PyCon Israel this year. + * Encourages us to scratch our itch under the code phrase: + "It's just a prototype." Not a bad place to start. Often how it ends :) + + +Landscape +--------- + + +Backends +-------- + + +Frameworks +---------- + + +I can haz mor memory? +--------------------- + +* Redis is great technology: free, open source, fast. + * But another process to manage and more memory required. + +$ emacs talk/settings.py +$ emacs talk/urls.py +$ emacs talk/views.py + +$ gunicorn --reload talk.wsgi + +$ emacs benchmark.py + +$ python benchmark.py + +* I dislike benchmarks in general so don't copy this code. I kind of stole it + from Beazley in another great talk he did on concurrency in Python. He said + it was kind of lousy code but it's just so simple. + +$ python manage.py shell + +>>> import time +>>> from django.conf import settings +>>> from django.core.cache import caches +>>> for key in settings.CACHES.keys(): +... caches[key].clear() +>>> while True: +... !ls /tmp/filebased | wc -l +... time.sleep(1) + + +Fool me once, strike one. Feel me twice? Strike three. +------------------------------------------------------ + +* Filebased cache has two severe drawbacks. + + 1. Culling is random. + 2. set() uses glob.glob1() which slows linearly with directory size. + + +DiskCache +--------- + + +Features +-------- + + +Use Case: Static file serving with read() +----------------------------------------- + + +Use Case: Analytics with incr()/pop() +------------------------------------- + + +Case Study: Baby Web Crawler +---------------------------- + +* Convert from ephemeral, single-process to persistent, multi-process. + + +"get" Time vs Percentile +------------------------ + +* Tradeoff cache latency and miss-rate using timeout. + + +"set" Time vs Percentile +------------------------ + +* Django-filebased cache so slow, can't plot. + + +Design +------ + +* Cache is a single shard. FanoutCache uses multiple shards. Trick is cross-platform hash. +* Pickle can actually be fast if you use a higher protocol. Default 0. Up to 4 now. + * Don't choose higher than 2 if you want to be portable between Python 2 and 3. +* Size limit really indicates when to start culling. Limit number of items deleted. + + +SQLite +------ + +* Tradeoff cache latency and miss-rate using timeout. +* SQLite supports 64-bit integers and floats, UTF-8 text and binary blobs. +* Use a context manager for isolation level management. + * Transactions are amazing though. +* Pragmas tune the behavior and performance of SQLite. + + * Default is very robust and slow. + * Use write-ahead-log so writers don't block readers. + * Memory-map pages for fast lookups. + + +Best way to make money in photography? Sell all your gear. +---------------------------------------------------------- + +- Story: Who saw eclipse? Awesome, right? + - Hard to really photograph the experience. + - This is me, staring up at the sun, blinding myself as I hold my glasses + and my phone to take a photo. Clearly lousy. +- Software talks are hard to get right and I can't cover everything related + to caching in 20 minutes. I hope you've learned something tonight or at + least seen something interesting. + + +Conclusion +---------- + +- Windows support mostly "just worked" + - SQLite is truly cross-platform + - Filesystems are a little different + - AppVeyor was about half as fast as Travis + - check() to fix inconsistencies +- Caveats + - Not well suited to queues (want read:write at 10:1 or higher) + - NFS and SQLite do not play nice +- Alternative databases: BerkeleyDB, LMDB, RocksDB, LevelDB, etc. +- Engage with me on Github, find bugs, complain about performance. +- If you like the project, star-it on Github and share it with friends. +- Thanks for letting me share tonight. Questions? diff --git a/tests/talk/benchmark.py b/tests/talk/benchmark.py new file mode 100644 index 0000000..d9b207d --- /dev/null +++ b/tests/talk/benchmark.py @@ -0,0 +1,23 @@ +import random, requests, signal, time, threading + +signal.signal(signal.SIGINT, lambda signum, frame: exit()) + + +count = 0 + +def monitor(): + global count + while True: + time.sleep(1) + print(f"{'*' * (count // 8)}") + count = 0 + +thread = threading.Thread(target=monitor) +thread.daemon = True +thread.start() + + +while True: + value = int(random.expovariate(1) * 100) + response = requests.get(f'http://127.0.0.1:8000/echo/{value}') + count += 1 diff --git a/tests/talk/crawler.py b/tests/talk/crawler.py new file mode 100644 index 0000000..6097ae0 --- /dev/null +++ b/tests/talk/crawler.py @@ -0,0 +1,50 @@ +import bs4, requests, signal, urllib.parse + +signal.signal(signal.SIGINT, lambda signum, frame: exit()) + + +def get(url): + "Get url and return response text." + print(url) + response = requests.get(url) + return response.text + + +def parse(url, text): + "Parse url with given text and yield links." + soup = bs4.BeautifulSoup(text, 'lxml') + + for anchor in soup.find_all('a', href=True): + full_url = urllib.parse.urljoin(url, anchor['href']) + href, _ = urllib.parse.urldefrag(full_url) + + if href.startswith(root): + yield href + + +from collections import deque + +def crawl(root='http://www.grantjenks.com'): + "Crawl root url." + urls = deque([root]) + results = dict() + + while True: + try: + url = urls.popleft() + except IndexError: + break + + if url in results: + continue + + text = get(url) + + for link in parse(url, text): + urls.append(link) + + results[url] = text + + +if __name__ == '__main__': + crawl() diff --git a/tests/talk/manage.py b/tests/talk/manage.py new file mode 100755 index 0000000..0c84391 --- /dev/null +++ b/tests/talk/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/tests/talk/talk/__init__.py b/tests/talk/talk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/talk/talk/settings.py b/tests/talk/talk/settings.py new file mode 100644 index 0000000..aa4165f --- /dev/null +++ b/tests/talk/talk/settings.py @@ -0,0 +1,141 @@ +""" +Django settings for talk project. + +Generated by 'django-admin startproject' using Django 1.10.6. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.10/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '_lzt+2b46g)@x%set-4u7j-vjw-_%sq4xdco990z(l4o2$^_)*' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +ALLOWED_HOSTS = ['127.0.0.1'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'talk.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'talk.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.10/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.10/howto/static-files/ + +STATIC_URL = '/static/' + + +CACHES = { + 'filebased': { + 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + 'LOCATION': '/tmp/filebased', + 'OPTIONS': { + 'MAX_ENTRIES': 1000, + } + }, + 'memcached': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': [ + '127.0.0.1:11211', + ], + }, + 'diskcache': { + 'BACKEND': 'diskcache.DjangoCache', + 'LOCATION': '/tmp/diskcache', + } +} diff --git a/tests/talk/talk/urls.py b/tests/talk/talk/urls.py new file mode 100644 index 0000000..6456693 --- /dev/null +++ b/tests/talk/talk/urls.py @@ -0,0 +1,23 @@ +"""talk URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin + +from . import views + +urlpatterns = [ + url(r'^echo/(?P.*)$', views.echo), +] diff --git a/tests/talk/talk/views.py b/tests/talk/talk/views.py new file mode 100644 index 0000000..dddc812 --- /dev/null +++ b/tests/talk/talk/views.py @@ -0,0 +1,11 @@ +import time + +from django.http import HttpResponse +from django.views.decorators.cache import cache_page + +# @cache_page(3600, cache='filebased') +# @cache_page(3600, cache='memcached') +# @cache_page(3600, cache='diskcache') +def echo(request, value): + time.sleep(0.1) + return HttpResponse(value, content_type='text/plain') diff --git a/tests/talk/talk/wsgi.py b/tests/talk/talk/wsgi.py new file mode 100644 index 0000000..a636d03 --- /dev/null +++ b/tests/talk/talk/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for talk project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk.settings") + +application = get_wsgi_application() From 9061712bc5042a6e066c8db6241ab64dce501193 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Sep 2017 17:13:36 -0700 Subject: [PATCH 179/550] Improve notes for Disk Cache talk --- docs/sf-python-2017-meetup-talk.rst | 123 ++++++++++++++++++++-------- tests/talk/benchmark.py | 12 +++ 2 files changed, 99 insertions(+), 36 deletions(-) diff --git a/docs/sf-python-2017-meetup-talk.rst b/docs/sf-python-2017-meetup-talk.rst index c484316..ec0931c 100644 --- a/docs/sf-python-2017-meetup-talk.rst +++ b/docs/sf-python-2017-meetup-talk.rst @@ -3,53 +3,80 @@ SF Python 2017 Meetup Talk * Can we have some fun together in this talk? * Can I show you some code that I would not run in production? -* Story: Fun of Reinvention by David Beazley at PyCon Israel this year. - * Encourages us to scratch our itch under the code phrase: - "It's just a prototype." Not a bad place to start. Often how it ends :) +* Great talk by David Beazley at PyCon Israel this year. + + * Encourages us to scratch our itch under the code phrase: "It's just a + prototype." Not a bad place to start. Often how it ends :) Landscape --------- +* At face value, caches seem simple: get/set/delete. +* But zoom in a little and you find just more and more detail. + Backends -------- +* Choices have very different designs and tradeoffs. + Frameworks ---------- +* Caches have broad applications. +* Web and scientific communities reach for them first. + I can haz mor memory? --------------------- * Redis is great technology: free, open source, fast. - * But another process to manage and more memory required. +* But another process to manage and more memory required. + +:: + + $ emacs talk/settings.py + $ emacs talk/urls.py + $ emacs talk/views.py + +:: -$ emacs talk/settings.py -$ emacs talk/urls.py -$ emacs talk/views.py + $ gunicorn --reload talk.wsgi -$ gunicorn --reload talk.wsgi +:: -$ emacs benchmark.py + $ emacs benchmark.py -$ python benchmark.py +:: + + $ python benchmark.py * I dislike benchmarks in general so don't copy this code. I kind of stole it from Beazley in another great talk he did on concurrency in Python. He said it was kind of lousy code but it's just so simple. -$ python manage.py shell +:: + + $ python manage.py shell + +:: + + >>> import time + >>> from django.conf import settings + >>> from django.core.cache import caches + +:: + + >>> for key in settings.CACHES.keys(): + ... caches[key].clear() + +:: ->>> import time ->>> from django.conf import settings ->>> from django.core.cache import caches ->>> for key in settings.CACHES.keys(): -... caches[key].clear() ->>> while True: -... !ls /tmp/filebased | wc -l -... time.sleep(1) + >>> while True: + ... !ls /tmp/filebased | wc -l + ... time.sleep(1) Fool me once, strike one. Feel me twice? Strike three. @@ -64,18 +91,37 @@ Fool me once, strike one. Feel me twice? Strike three. DiskCache --------- +* Wanted to solve Django-filebased cache problems. +* Felt like something was missing in the landscape. +* Found an unlikely hero in SQLite. + + +I'd rather drive a slow car fast than a fast car slow +----------------------------------------------------- + +* Story: driving down the Grapevine in SoCal in friend's 1960s VW Bug. + Features -------- +* Lot's of features. Maybe a few too many. Ex. never used the tag metadata and + eviction feature. + Use Case: Static file serving with read() ----------------------------------------- +* Some fun features. Data is stored in files and web servers are good at + serving files. + Use Case: Analytics with incr()/pop() ------------------------------------- +* Tried to create really functional APIs. +* All write operations are atomic. + Case Study: Baby Web Crawler ---------------------------- @@ -110,7 +156,6 @@ SQLite * Tradeoff cache latency and miss-rate using timeout. * SQLite supports 64-bit integers and floats, UTF-8 text and binary blobs. * Use a context manager for isolation level management. - * Transactions are amazing though. * Pragmas tune the behavior and performance of SQLite. * Default is very robust and slow. @@ -121,11 +166,13 @@ SQLite Best way to make money in photography? Sell all your gear. ---------------------------------------------------------- -- Story: Who saw eclipse? Awesome, right? - - Hard to really photograph the experience. - - This is me, staring up at the sun, blinding myself as I hold my glasses +* Who saw eclipse? Awesome, right? + + * Hard to really photograph the experience. + * This is me, staring up at the sun, blinding myself as I hold my glasses and my phone to take a photo. Clearly lousy. -- Software talks are hard to get right and I can't cover everything related + +* Software talks are hard to get right and I can't cover everything related to caching in 20 minutes. I hope you've learned something tonight or at least seen something interesting. @@ -133,15 +180,19 @@ Best way to make money in photography? Sell all your gear. Conclusion ---------- -- Windows support mostly "just worked" - - SQLite is truly cross-platform - - Filesystems are a little different - - AppVeyor was about half as fast as Travis - - check() to fix inconsistencies -- Caveats - - Not well suited to queues (want read:write at 10:1 or higher) - - NFS and SQLite do not play nice -- Alternative databases: BerkeleyDB, LMDB, RocksDB, LevelDB, etc. -- Engage with me on Github, find bugs, complain about performance. -- If you like the project, star-it on Github and share it with friends. -- Thanks for letting me share tonight. Questions? +* Windows support mostly "just worked" + + * SQLite is truly cross-platform + * Filesystems are a little different + * AppVeyor was about half as fast as Travis + * check() to fix inconsistencies + +* Caveats + + * Not well suited to queues (want read:write at 10:1 or higher) + * NFS and SQLite do not play nice + +* Alternative databases: BerkeleyDB, LMDB, RocksDB, LevelDB, etc. +* Engage with me on Github, find bugs, complain about performance. +* If you like the project, star-it on Github and share it with friends. +* Thanks for letting me share tonight. Questions? diff --git a/tests/talk/benchmark.py b/tests/talk/benchmark.py index d9b207d..e115162 100644 --- a/tests/talk/benchmark.py +++ b/tests/talk/benchmark.py @@ -17,6 +17,18 @@ def monitor(): thread.start() +# Histogram of expovariate values: +# value | count +# ----- | ----- +# 64 | ************************************************************* +# 127 | ******************************** +# 191 | *************** +# 254 | ****** +# 318 | *** +# 382 | ** +# 445 | * +# 509 | + while True: value = int(random.expovariate(1) * 100) response = requests.get(f'http://127.0.0.1:8000/echo/{value}') From e260fed34b31bd61bd0497a04255c2bb1d82ec34 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Sep 2017 17:39:23 -0700 Subject: [PATCH 180/550] Change crawler to work locally --- tests/talk/crawler.py | 4 +++- tests/talk/talk/urls.py | 2 ++ tests/talk/talk/views.py | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/talk/crawler.py b/tests/talk/crawler.py index 6097ae0..fc36154 100644 --- a/tests/talk/crawler.py +++ b/tests/talk/crawler.py @@ -2,6 +2,8 @@ signal.signal(signal.SIGINT, lambda signum, frame: exit()) +root='http://127.0.0.1:8000/' + def get(url): "Get url and return response text." @@ -24,7 +26,7 @@ def parse(url, text): from collections import deque -def crawl(root='http://www.grantjenks.com'): +def crawl(): "Crawl root url." urls = deque([root]) results = dict() diff --git a/tests/talk/talk/urls.py b/tests/talk/talk/urls.py index 6456693..ac79390 100644 --- a/tests/talk/talk/urls.py +++ b/tests/talk/talk/urls.py @@ -20,4 +20,6 @@ urlpatterns = [ url(r'^echo/(?P.*)$', views.echo), + url(r'^$', views.index), + url(r'^crawl/(?P.*)$', views.crawl), ] diff --git a/tests/talk/talk/views.py b/tests/talk/talk/views.py index dddc812..675b31e 100644 --- a/tests/talk/talk/views.py +++ b/tests/talk/talk/views.py @@ -1,4 +1,4 @@ -import time +import random, time from django.http import HttpResponse from django.views.decorators.cache import cache_page @@ -9,3 +9,17 @@ def echo(request, value): time.sleep(0.1) return HttpResponse(value, content_type='text/plain') + + +def index(request): + return HttpResponse('0') + + +def crawl(request, value): + time.sleep(random.random()) + value = int(value) + random.seed(value) + nums = random.sample(range(100), 5) + link = '{0}
' + links = ''.join(link.format(num) for num in nums) + return HttpResponse('{}'.format(links)) From 76c6c7234cf335a7c97fb6796c2107021e26b222 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Sep 2017 17:44:18 -0700 Subject: [PATCH 181/550] Increase font size for presenting --- docs/sf-python-2017-meetup-talk.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sf-python-2017-meetup-talk.rst b/docs/sf-python-2017-meetup-talk.rst index ec0931c..4133cf8 100644 --- a/docs/sf-python-2017-meetup-talk.rst +++ b/docs/sf-python-2017-meetup-talk.rst @@ -1,6 +1,10 @@ SF Python 2017 Meetup Talk ========================== +.. raw:: html + + + * Can we have some fun together in this talk? * Can I show you some code that I would not run in production? * Great talk by David Beazley at PyCon Israel this year. From 2bd0950c9be83dec897369f292609b97c5cce7b3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Sep 2017 20:38:32 -0700 Subject: [PATCH 182/550] Update talk notes and publish after talk --- README.rst | 2 + docs/index.rst | 1 + docs/sf-python-2017-meetup-talk.rst | 61 ++++++++++++++++------------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/README.rst b/README.rst index 085f83f..158e752 100644 --- a/README.rst +++ b/README.rst @@ -99,12 +99,14 @@ introduction, benchmarks, development, and API. * `DiskCache Cache Benchmarks`_ * `DiskCache DjangoCache Benchmarks`_ * `Case Study: Web Crawler`_ +* `Talk: All Things Cached - SF Python 2017 Meetup`_ * `DiskCache API Reference`_ * `DiskCache Development`_ .. _`DiskCache Tutorial`: http://www.grantjenks.com/docs/diskcache/tutorial.html .. _`DiskCache Cache Benchmarks`: http://www.grantjenks.com/docs/diskcache/cache-benchmarks.html .. _`DiskCache DjangoCache Benchmarks`: http://www.grantjenks.com/docs/diskcache/djangocache-benchmarks.html +.. _`Talk: All Things Cached - SF Python 2017 Meetup`: http://www.grantjenks.com/docs/diskcache/sf-python-2017-meetup-talk.html .. _`Case Study: Web Crawler`: http://www.grantjenks.com/docs/diskcache/case-study-web-crawler.html .. _`DiskCache API Reference`: http://www.grantjenks.com/docs/diskcache/api.html .. _`DiskCache Development`: http://www.grantjenks.com/docs/diskcache/development.html diff --git a/docs/index.rst b/docs/index.rst index deb1d27..23c7c8c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -102,6 +102,7 @@ introduction, benchmarks, development, and API. cache-benchmarks djangocache-benchmarks case-study-web-crawler + sf-python-2017-meetup-talk api development diff --git a/docs/sf-python-2017-meetup-talk.rst b/docs/sf-python-2017-meetup-talk.rst index 4133cf8..214b66b 100644 --- a/docs/sf-python-2017-meetup-talk.rst +++ b/docs/sf-python-2017-meetup-talk.rst @@ -1,10 +1,7 @@ -SF Python 2017 Meetup Talk -========================== - -.. raw:: html - - +Talk: All Things Cached - SF Python 2017 Meetup +=============================================== +* `Python All Things Cached Slides`_ * Can we have some fun together in this talk? * Can I show you some code that I would not run in production? * Great talk by David Beazley at PyCon Israel this year. @@ -23,7 +20,7 @@ Landscape Backends -------- -* Choices have very different designs and tradeoffs. +* Backends have very different designs and tradeoffs. Frameworks @@ -59,19 +56,19 @@ I can haz mor memory? * I dislike benchmarks in general so don't copy this code. I kind of stole it from Beazley in another great talk he did on concurrency in Python. He said - it was kind of lousy code but it's just so simple. + not to copy it so I'm telling you not to copy it. :: $ python manage.py shell -:: +.. code-block:: pycon >>> import time >>> from django.conf import settings >>> from django.core.cache import caches -:: +.. code-block:: pycon >>> for key in settings.CACHES.keys(): ... caches[key].clear() @@ -109,7 +106,7 @@ I'd rather drive a slow car fast than a fast car slow Features -------- -* Lot's of features. Maybe a few too many. Ex. never used the tag metadata and +* Lot's of features. Maybe a few too many. Ex: never used the tag metadata and eviction feature. @@ -148,10 +145,16 @@ Case Study: Baby Web Crawler Design ------ -* Cache is a single shard. FanoutCache uses multiple shards. Trick is cross-platform hash. -* Pickle can actually be fast if you use a higher protocol. Default 0. Up to 4 now. - * Don't choose higher than 2 if you want to be portable between Python 2 and 3. -* Size limit really indicates when to start culling. Limit number of items deleted. +* Cache is a single shard. FanoutCache uses multiple shards. Trick is + cross-platform hash. +* Pickle can actually be fast if you use a higher protocol. Default 0. Up to 4 + now. + + * Don't choose higher than 2 if you want to be portable between Python 2 + and 3. + +* Size limit really indicates when to start culling. Limit number of items + deleted. SQLite @@ -173,30 +176,32 @@ Best way to make money in photography? Sell all your gear. * Who saw eclipse? Awesome, right? * Hard to really photograph the experience. - * This is me, staring up at the sun, blinding myself as I hold my glasses - and my phone to take a photo. Clearly lousy. + * This is me, staring up at the sun, blinding myself as I hold my glasses and + my phone to take a photo. Clearly lousy. -* Software talks are hard to get right and I can't cover everything related - to caching in 20 minutes. I hope you've learned something tonight or at - least seen something interesting. +* Software talks are hard to get right and I can't cover everything related to + caching in 20 minutes. I hope you've learned something tonight or at least + seen something interesting. Conclusion ---------- -* Windows support mostly "just worked" +* Windows support mostly "just worked". - * SQLite is truly cross-platform - * Filesystems are a little different - * AppVeyor was about half as fast as Travis - * check() to fix inconsistencies + * SQLite is truly cross-platform. + * Filesystems are a little different. + * AppVeyor was about half as fast as Travis. + * check() to fix inconsistencies. -* Caveats +* Caveats: - * Not well suited to queues (want read:write at 10:1 or higher) - * NFS and SQLite do not play nice + * NFS and SQLite do not play nice. + * Not well suited to queues (want read:write at 10:1 or higher). * Alternative databases: BerkeleyDB, LMDB, RocksDB, LevelDB, etc. * Engage with me on Github, find bugs, complain about performance. * If you like the project, star-it on Github and share it with friends. * Thanks for letting me share tonight. Questions? + +.. _`Python All Things Cached Slides`: http://bit.ly/dc-2017-slides From 591d4cba5658e1040b333fdbb296aa96c5cb8665 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Nov 2017 11:49:37 -0800 Subject: [PATCH 183/550] Add test case for Issue #54 --- tests/test_core.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 63609a2..6928d30 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -14,6 +14,7 @@ import sys import threading import time +import unittest import warnings import zlib @@ -1185,6 +1186,26 @@ def test_pickle(cache): assert other[key] == cache[key] +@unittest.skip('Issue #54') +@setup_cache +def test_key_roundtrip(cache): + key_part_0 = u"part0" + key_part_1 = u"part1" + to_test = [ + (key_part_0, key_part_1), + [key_part_0, key_part_1], + ] + + for key in to_test: + cache.clear() + cache[key] = {'example0': ['value0']} + keys = list(cache) + assert len(keys) == 1 + cache_key = keys[0] + assert cache[key] == {'example0': ['value0']} + assert cache[cache_key] == {'example0': ['value0']} + + if __name__ == '__main__': import nose nose.runmodule() From d790dc5081220f0b922493e6c7a179892d88bbaf Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Nov 2017 16:23:22 -0800 Subject: [PATCH 184/550] Add cull() method --- diskcache/core.py | 89 +++++++++++++++++++++++++++++++++++++--------- tests/test_core.py | 36 +++++++++++++++++++ 2 files changed, 108 insertions(+), 17 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index e17435b..6874ea1 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -64,6 +64,7 @@ def __repr__(self): u'eviction_policy': u'least-recently-stored', u'size_limit': 2 ** 30, # 1gb u'cull_limit': 10, + u'sqlite_auto_vacuum': 1, # FULL u'sqlite_cache_size': 2 ** 13, # 8,192 pages u'sqlite_journal_mode': u'WAL', u'sqlite_mmap_size': 2 ** 26, # 64mb @@ -377,14 +378,12 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): # Setup Settings table. - sql('CREATE TABLE IF NOT EXISTS Settings (' - ' key TEXT NOT NULL UNIQUE,' - ' value)' - ) - - current_settings = dict(sql( - 'SELECT key, value FROM Settings' - ).fetchall()) + try: + current_settings = dict(sql( + 'SELECT key, value FROM Settings' + ).fetchall()) + except sqlite3.OperationalError: + current_settings = {} sets = DEFAULT_SETTINGS.copy() sets.update(current_settings) @@ -393,6 +392,19 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): for key in METADATA: sets.pop(key, None) + # Chance to set pragmas before any tables are created. + + for key, value in sorted(sets.items()): + if not key.startswith('sqlite_'): + continue + + self.reset(key, value, update=False) + + sql('CREATE TABLE IF NOT EXISTS Settings (' + ' key TEXT NOT NULL UNIQUE,' + ' value)' + ) + # Setup Disk object (must happen after settings initialized). kwargs = { @@ -668,8 +680,8 @@ def _row_insert(self, key, raw, now, columns): ) - def _cull(self, now, sql, cleanup): - cull_limit = self.cull_limit + def _cull(self, now, sql, cleanup, limit=None): + cull_limit = self.cull_limit if limit is None else limit if cull_limit == 0: return @@ -1509,6 +1521,42 @@ def expire(self, now=None): return self._select_delete(select, args, row_index=1) + def cull(self): + """Cull items from cache. + + """ + now = time.time() + + # Remove expired items. + + self.expire(now) + + # Remove items by policy. + + select_policy_template = EVICTION_POLICY[self.eviction_policy]['cull'] + + if select_policy_template is None: + return + + select_policy = select_policy_template % 'filename' + + while self.volume() > self.size_limit: + with self._transact() as (sql, cleanup): + rows = sql(select_policy, (100,)).fetchall() + + if not rows: + break + + delete_policy = ( + 'DELETE FROM Cache WHERE rowid IN (%s)' + % (select_policy_template % 'rowid') + ) + sql(delete_policy, (100,)) + + for filename, in rows: + cleanup(filename) + + def clear(self): """Remove all items from cache. @@ -1734,15 +1782,16 @@ def __setstate__(self, state): self.__init__(*state) - def reset(self, key, value=ENOVAL): + def reset(self, key, value=ENOVAL, update=True): """Reset `key` and `value` item from Settings table. + Use `reset` to update the value of Cache settings correctly. Cache + settings are stored in the Settings table of the SQLite database. If + `update` is ``False`` then no attempt is made to update the database. + If `value` is not given, it is reloaded from the Settings table. Otherwise, the Settings table is updated. - Settings attributes on cache objects are lazy-loaded and - read-only. Use `reset` to update the value. - Settings with the ``disk_`` prefix correspond to Disk attributes. Updating the value will change the unprefixed attribute on the associated Disk instance. @@ -1751,6 +1800,9 @@ def reset(self, key, value=ENOVAL): pragmas. Updating the value will execute the corresponding PRAGMA statement. + SQLite PRAGMA statements may be executed before the Settings table + exists in the database by setting `update` to ``False``. + :param str key: Settings key for item :param value: value for item (optional) :return: updated value for item @@ -1763,9 +1815,12 @@ def reset(self, key, value=ENOVAL): setattr(self, key, value) return value else: - with self._transact() as (sql, _): - update = 'UPDATE Settings SET value = ? WHERE key = ?' - sql(update, (value, key)) + if update: + with self._transact() as (sql, _): + statement = 'UPDATE Settings SET value = ? WHERE key = ?' + sql(statement, (value, key)) + else: + sql = self._sql if key.startswith('sqlite_'): diff --git a/tests/test_core.py b/tests/test_core.py index 6928d30..0964e7f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1186,6 +1186,42 @@ def test_pickle(cache): assert other[key] == cache[key] +@setup_cache +def test_auto_vacuum(cache): + assert cache._sql('PRAGMA auto_vacuum').fetchall() == [(1,)] + + +@setup_cache +def test_size_limit_with_files(cache): + cache.reset('cull_limit', 0) + size_limit = 30 * cache.disk_min_file_size + cache.reset('size_limit', size_limit) + value = b'foo' * cache.disk_min_file_size + + for key in range(40): + cache.set(key, value) + + assert cache.volume() > size_limit + cache.cull() + assert cache.volume() < size_limit + + +@setup_cache +def test_size_limit_with_database(cache): + cache.reset('cull_limit', 0) + size_limit = 2 * cache.disk_min_file_size + cache.reset('size_limit', size_limit) + value = b'0123456789' * 10 + count = size_limit // (8 + len(value)) + + for key in range(count): + cache.set(key, value) + + assert cache.volume() > size_limit + cache.cull() + assert cache.volume() <= size_limit + + @unittest.skip('Issue #54') @setup_cache def test_key_roundtrip(cache): From 666f39448a8207ed8b4f4191a7bca8ff03d9562f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 12 Dec 2017 10:21:15 -0800 Subject: [PATCH 185/550] Set SQLite pragmas on every connection --- diskcache/core.py | 20 +++++++++++++++++--- docs/api.rst | 5 +++-- docs/tutorial.rst | 4 ++-- tests/test_core.py | 36 ++++++++++++++++++++++++++++++++++-- 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 6874ea1..e69ed9c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -66,9 +66,9 @@ def __repr__(self): u'cull_limit': 10, u'sqlite_auto_vacuum': 1, # FULL u'sqlite_cache_size': 2 ** 13, # 8,192 pages - u'sqlite_journal_mode': u'WAL', + u'sqlite_journal_mode': u'wal', u'sqlite_mmap_size': 2 ** 26, # 64mb - u'sqlite_synchronous': u'NORMAL', + u'sqlite_synchronous': 1, # NORMAL u'disk_min_file_size': 2 ** 15, # 32kb u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, } @@ -502,7 +502,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): self.close() self._timeout = timeout - assert self._sql + self._sql # pylint: disable=pointless-statement @property @@ -534,6 +534,20 @@ def _sql(self): 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) + return con.execute diff --git a/docs/api.rst b/docs/api.rst index 0d826c4..a992e78 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -50,8 +50,9 @@ Read the :ref:`Settings tutorial ` for details. of cache. * `cull_limit` (int) default ten - maximum number of items culled during `set` or `add` operations. - * `sqlite_synchronous` (str) default "NORMAL" - SQLite synchronous pragma. - * `sqlite_journal_mode` (str) default "WAL" - SQLite journal mode pragma. + * `sqlite_synchronous` (int) default 1, "NORMAL" - SQLite synchronous + pragma. + * `sqlite_journal_mode` (str) default "wal" - SQLite journal mode pragma. * `sqlite_cache_size` (int, in pages) default 8,192 - SQLite cache size pragma. * `sqlite_mmap_size` (int, in bytes) default 64 megabytes - SQLite mmap size diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f53b684..e851a01 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -519,8 +519,8 @@ An additional set of attributes correspond to SQLite pragmas. Changing these values will also execute the appropriate ``PRAGMA`` statement. See the `SQLite pragma documentation`_ for more details. -* `sqlite_synchronous`, default NORMAL. -* `sqlite_journal_mode`, default WAL. +* `sqlite_synchronous`, default 1, "NORMAL". +* `sqlite_journal_mode`, default "wal". * `sqlite_cache_size`, default 8,192 pages. * `sqlite_mmap_size`, default 64 megabytes. diff --git a/tests/test_core.py b/tests/test_core.py index 0964e7f..edc1560 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1187,8 +1187,40 @@ def test_pickle(cache): @setup_cache -def test_auto_vacuum(cache): - assert cache._sql('PRAGMA auto_vacuum').fetchall() == [(1,)] +def test_pragmas(cache): + results = [] + + def compare_pragmas(): + valid = True + + for key, value in dc.DEFAULT_SETTINGS.items(): + if not key.startswith('sqlite_'): + continue + + pragma = key[7:] + + result = cache._sql('PRAGMA %s' % pragma).fetchall() + + if result == [(value,)]: + continue + + args = pragma, result, [(value,)] + print('pragma %s mismatch: %r != %r' % args) + valid = False + + results.append(valid) + + threads = [] + + for count in range(8): + thread = threading.Thread(target=compare_pragmas) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + + assert all(results) @setup_cache From 434c12849431463065d7ba38cdf2ff0c6321ee99 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 12 Dec 2017 11:52:40 -0800 Subject: [PATCH 186/550] Add cull() to FanoutCache and DjangoCache with docs and 100% testing/coverage --- diskcache/core.py | 45 +++++++++++++++++++++++----------- diskcache/djangocache.py | 9 +++++++ diskcache/fanout.py | 9 +++++++ docs/tutorial.rst | 42 +++++++++++++++++++++++++------- tests/test_core.py | 51 ++++++++++++++++++++++++++++++++++++++- tests/test_djangocache.py | 2 +- tests/test_fanout.py | 33 +++++++++++++++++++++++++ 7 files changed, 166 insertions(+), 25 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index e69ed9c..00efa1b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1536,14 +1536,24 @@ def expire(self, now=None): def cull(self): - """Cull items from cache. + """Cull items from cache until volume is less than size limit. + + Removing items is an iterative process. In each iteration, a subset of + items is removed. Concurrent writes may occur between iterations. + + If a :exc:`Timeout` occurs, the first element of the exception's + `args` attribute will be the number of items removed before the + exception occurred. + + :return: count of items removed + :raises Timeout: if database timeout expires """ now = time.time() # Remove expired items. - self.expire(now) + count = self.expire(now) # Remove items by policy. @@ -1554,21 +1564,27 @@ def cull(self): select_policy = select_policy_template % 'filename' - while self.volume() > self.size_limit: - with self._transact() as (sql, cleanup): - rows = sql(select_policy, (100,)).fetchall() + try: + while self.volume() > self.size_limit: + with self._transact() as (sql, cleanup): + rows = sql(select_policy, (10,)).fetchall() - if not rows: - break + if not rows: + break - delete_policy = ( - 'DELETE FROM Cache WHERE rowid IN (%s)' - % (select_policy_template % 'rowid') - ) - sql(delete_policy, (100,)) + count += len(rows) + delete = ( + 'DELETE FROM Cache WHERE rowid IN (%s)' + % (select_policy_template % 'rowid') + ) + sql(delete, (10,)) - for filename, in rows: - cleanup(filename) + for filename, in rows: + cleanup(filename) + except Timeout: + raise Timeout(count) + + return count def clear(self): @@ -1819,6 +1835,7 @@ def reset(self, key, value=ENOVAL, update=True): :param str key: Settings key for item :param value: value for item (optional) + :param bool update: update database Settings table (default True) :return: updated value for item :raises Timeout: if database timeout expires diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 99cd739..fedd93c 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -294,6 +294,15 @@ def evict(self, tag): return self._cache.evict(tag) + def cull(self): + """Cull items from cache until volume is less than size limit. + + :return: count of items removed + + """ + return self._cache.cull() + + def clear(self, **kwargs): "Remove *all* values from the cache at once." # pylint: disable=unused-argument diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 444f0dd..565537a 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -391,6 +391,15 @@ def evict(self, tag): return self._remove('evict', args=(tag,)) + def cull(self): + """Cull items from cache until volume is less than size limit. + + :return: count of items removed + + """ + return self._remove('cull') + + def clear(self): """Remove all items from cache. diff --git a/docs/tutorial.rst b/docs/tutorial.rst index e851a01..054f662 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -174,7 +174,7 @@ The :meth:`pop ` operation is atomic and using :meth:`incr statistics in long-running systems. Unlike :meth:`get ` the `read` argument is not supported. -Another three methods remove items from the cache. +Another four methods remove items from the cache. >>> cache.reset('cull_limit', 0) # Disable automatic evictions. >>> for num in range(10): @@ -187,13 +187,13 @@ Another three methods remove items from the cache. 10 :meth:`Expire ` removes all expired keys from the -cache. Resetting the `cull_limit` to zero will disable culling during -:meth:`set ` and :meth:`add ` -operations. Because culling is performed lazily, the reported length of the -cache includes expired items. Iteration likewise includes expired items because -it is a read-only operation. To exclude expired items you must explicitly call -:meth:`expire ` which works regardless of the -`cull_limit`. +cache. Resetting the :ref:`cull_limit ` to zero will disable +culling during :meth:`set ` and :meth:`add +` operations. Because culling is performed lazily, the +reported length of the cache includes expired items. Iteration likewise +includes expired items because it is a read-only operation. To exclude expired +items you must explicitly call :meth:`expire ` which +works regardless of the :ref:`cull_limit `. >>> for num in range(100): ... cache.set(num, num, tag=u'odd' if num % 2 else u'even') @@ -223,6 +223,30 @@ Likewise, the tag index may be created or dropped using methods:: But prefer initializing the cache with a tag index rather than explicitly creating or dropping the tag index. +To manually enforce the cache's size limit, use the :meth:`cull +` method. :meth:`Cull ` begins by +removing expired items from the cache and then uses the eviction policy to +remove items until the cache volume is less than the size limit. + + >>> cache.clear() + >>> cache.reset('size_limit', int(1e6)) + >>> cache.reset('cull_limit', 0) + >>> for count in range(1000): + >>> cache[count] = b'A' * 1000 + >>> cache.volume() + 1437696 + >>> cache.cull() + 320 + >>> cache.volume() + 999424 + +Some users may defer all culling to a cron-like process by setting the +:ref:`cull_limit ` to zero and calling :meth:`cull +` to manually remove items. Like :meth:`evict +` and :meth:`expire `, calls to +:meth:`cull ` will work regardless of the :ref:`cull_limit +`. + :meth:`Clear ` simply removes all items from the cache. >>> cache.clear() @@ -473,7 +497,7 @@ during initialization when passed as keyword arguments. * `cull_limit`, default ten. The maximum number of keys to cull when adding a new item. Set to zero to disable automatic culling. Some systems may disable automatic culling in exchange for a cron-like job that regularly calls - :meth:`expire ` in a separate process. + :meth:`cull ` in a separate process. * `statistics`, default False, disabled. The setting to collect :ref:`cache statistics `. * `tag_index`, default False, disabled. The setting to create a database diff --git a/tests/test_core.py b/tests/test_core.py index edc1560..d1cf5e3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1235,7 +1235,7 @@ def test_size_limit_with_files(cache): assert cache.volume() > size_limit cache.cull() - assert cache.volume() < size_limit + assert cache.volume() <= size_limit @setup_cache @@ -1254,6 +1254,50 @@ def test_size_limit_with_database(cache): assert cache.volume() <= size_limit +@setup_cache +def test_cull_eviction_policy_none(cache): + cache.reset('eviction_policy', 'none') + size_limit = 2 * cache.disk_min_file_size + cache.reset('size_limit', size_limit) + value = b'0123456789' * 10 + count = size_limit // (8 + len(value)) + + for key in range(count): + cache.set(key, value) + + assert cache.volume() > size_limit + cache.cull() + assert cache.volume() > size_limit + + +@setup_cache +def test_cull_size_limit_0(cache): + cache.reset('cull_limit', 0) + size_limit = 2 * cache.disk_min_file_size + cache.reset('size_limit', 0) + value = b'0123456789' * 10 + count = size_limit // (8 + len(value)) + + for key in range(count): + cache.set(key, value) + + assert cache.volume() > size_limit + cache.cull() + assert cache.volume() <= size_limit + + +@setup_cache +@nt.raises(dc.Timeout) +def test_cull_timeout(cache): + transact = mock.Mock() + transact.side_effect = [dc.Timeout] + + with mock.patch.object(cache, 'expire', lambda now: 0): + with mock.patch.object(cache, 'volume', lambda: int(1e12)): + with mock.patch.object(cache, '_transact', transact): + cache.cull() + + @unittest.skip('Issue #54') @setup_cache def test_key_roundtrip(cache): @@ -1274,6 +1318,11 @@ def test_key_roundtrip(cache): assert cache[cache_key] == {'example0': ['value0']} +def test_constant(): + import diskcache.core + assert repr(diskcache.core.ENOVAL) == 'ENOVAL' + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index bc11ced..530a9d5 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -859,7 +859,7 @@ def test_custom_key_func(self): pass def test_cull(self): - pass # DiskCache has its own cull strategy. + cache.cull() def test_zero_cull(self): pass # DiskCache has its own cull strategy. diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 9db7e32..20dd552 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -404,6 +404,39 @@ def test_evict(cache): assert len(cache.check()) == 0 +@setup_cache +def test_size_limit_with_files(cache): + shards = 8 + cache.reset('cull_limit', 0) + size_limit = 30 * cache.disk_min_file_size + cache.reset('size_limit', size_limit) + value = b'foo' * cache.disk_min_file_size + + for key in range(40 * shards): + cache.set(key, value) + + assert (cache.volume() // shards) > size_limit + cache.cull() + assert (cache.volume() // shards) <= size_limit + + +@setup_cache +def test_size_limit_with_database(cache): + shards = 8 + cache.reset('cull_limit', 0) + size_limit = 2 * cache.disk_min_file_size + cache.reset('size_limit', size_limit) + value = b'0123456789' * 10 + count = size_limit // (8 + len(value)) * shards + + for key in range(count): + cache.set(key, value) + + assert (cache.volume() // shards) > size_limit + cache.cull() + assert (cache.volume() // shards) <= size_limit + + @setup_cache def test_clear(cache): for value in range(100): From b01c97be474228da19c718182a507c6de9da2ee2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 12 Dec 2017 12:19:38 -0800 Subject: [PATCH 187/550] Make Index.setdefault atomic using Cache.add --- diskcache/persistent.py | 34 ++++++++++++++++++++++++++++++++++ diskcache/stampede.py | 7 +++++-- tests/test_index.py | 19 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index ae7889e..9f0c5d5 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -728,6 +728,7 @@ def __getitem__(self, key): KeyError: 'c' :param key: key for item + :return: value for item in index with given key :raises KeyError: if key is not found """ @@ -799,6 +800,39 @@ def __delitem__(self, key): return + def setdefault(self, key, default=None): + """Set and get value for `key` in index using `default`. + + If `key` is not in index then set corresponding value to `default`. If + `key` is in index then ignore `default` and return existing value. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> index.setdefault('a', 0) + 0 + >>> index.setdefault('a', 1) + 0 + + :param key: key for item + :param default: value if key is missing (default None) + :return: value for item in index with given key + + """ + _cache = self._cache + + while True: + try: + return self[key] + except KeyError: + while True: + try: + _cache.add(key, default) + except Timeout: + continue + else: + break + + def pop(self, key, default=ENOVAL): """Remove corresponding item for `key` from index and return value. diff --git a/diskcache/stampede.py b/diskcache/stampede.py index 0c501f7..b103338 100644 --- a/diskcache/stampede.py +++ b/diskcache/stampede.py @@ -21,10 +21,13 @@ class StampedeBarrier(object): Example: - >>> stampede_barrier = StampedeBarrier('/tmp/user_data', expire=3) - >>> @stampede_barrier + ```python + stampede_barrier = StampedeBarrier('/tmp/user_data', expire=3) + + @stampede_barrier def load_user_info(user_id): return database.lookup_user_info_by_id(user_id) + ``` """ # pylint: disable=too-few-public-methods diff --git a/tests/test_index.py b/tests/test_index.py index af7453c..423c3c9 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -170,6 +170,25 @@ def test_popitem_timeout(index): assert value == (0, 1) +@setup_index +def test_setdefault(index): + assert index.setdefault('a', 0) == 0 + assert index.setdefault('a', 1) == 0 + + +@setup_index +def test_setdefault_timeout(index): + cache = mock.MagicMock() + cache.__getitem__ = mock.Mock() + cache.__getitem__.side_effect = [KeyError, 0] + cache.add = mock.Mock() + cache.add.side_effect = [dc.Timeout, 0] + + with mock.patch.object(index, '_cache', cache): + value = index.setdefault('a', 0) + assert value == 0 + + @setup_index def test_iter(index): letters = 'abcde' From 0bd9d611d05a47f56ab13d95fc9cac1d2775fcc9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 12 Dec 2017 12:27:01 -0800 Subject: [PATCH 188/550] Optimize pickled keys and values using pickletools --- diskcache/core.py | 7 +++++-- tests/test_core.py | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 00efa1b..f08897d 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -9,6 +9,7 @@ import io import os import os.path as op +import pickletools import sqlite3 import struct import sys @@ -168,7 +169,8 @@ def put(self, key): or (type_key is float)): return key, True else: - result = pickle.dumps(key, protocol=self.pickle_protocol) + data = pickle.dumps(key, protocol=self.pickle_protocol) + result = pickletools.optimize(data) return sqlite3.Binary(result), False @@ -235,7 +237,8 @@ def store(self, value, read): return size, MODE_BINARY, filename, None else: - result = pickle.dumps(value, protocol=self.pickle_protocol) + data = pickle.dumps(value, protocol=self.pickle_protocol) + result = pickletools.optimize(data) if len(result) < min_file_size: return 0, MODE_PICKLE, None, sqlite3.Binary(result) diff --git a/tests/test_core.py b/tests/test_core.py index d1cf5e3..9375564 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1298,7 +1298,6 @@ def test_cull_timeout(cache): cache.cull() -@unittest.skip('Issue #54') @setup_cache def test_key_roundtrip(cache): key_part_0 = u"part0" From 0edf10228e8e3d05fff608a594e5393c6e7322ae Mon Sep 17 00:00:00 2001 From: Eli Stevens Date: Tue, 12 Dec 2017 19:38:56 -0800 Subject: [PATCH 189/550] Disk.filename now takes unused key, value params This allows Disk subclasses to allow for discoverable names based on key, value, md5, etc. when writing files to disk. Disk.store has an incompatible signature change, however, in that it now needs to accept key arguments. --- diskcache/core.py | 23 +++++++++++++---------- tests/test_core.py | 24 ++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index e17435b..41c7189 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -186,12 +186,13 @@ def get(self, key, raw): return pickle.load(BytesIO(key)) - def store(self, value, read): + def store(self, value, read, key=None): """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. Can be None. :return: (size, mode, filename, value) tuple for Cache table """ @@ -208,14 +209,14 @@ def store(self, value, read): if len(value) < min_file_size: return 0, MODE_RAW, None, sqlite3.Binary(value) else: - filename, full_path = self.filename() + filename, full_path = self.filename(key, value) with open(full_path, 'wb') as writer: writer.write(value) return len(value), MODE_BINARY, filename, None elif type_value is TextType: - filename, full_path = self.filename() + filename, full_path = self.filename(key, value) with io_open(full_path, 'w', encoding='UTF-8') as writer: writer.write(value) @@ -225,7 +226,7 @@ def store(self, value, read): elif read: size = 0 reader = ft.partial(value.read, 2 ** 22) - filename, full_path = self.filename() + filename, full_path = self.filename(key, value) with open(full_path, 'wb') as writer: for chunk in iter(reader, b''): @@ -239,7 +240,7 @@ def store(self, value, read): if len(result) < min_file_size: return 0, MODE_PICKLE, None, sqlite3.Binary(result) else: - filename, full_path = self.filename() + filename, full_path = self.filename(key, value) with open(full_path, 'wb') as writer: writer.write(result) @@ -279,7 +280,7 @@ def fetch(self, mode, filename, value, read): return pickle.load(BytesIO(value)) - def filename(self): + def filename(self, key=None, value=None): """Return filename and full-path tuple for file storage. Filename will be a randomly generated 28 character hexadecimal string @@ -287,6 +288,8 @@ def filename(self): reduce the size of directories. On older filesystems, lookups in directories with many files are slow. + :param key: key for item. Ignored by default implementation. Can be None. + :param value: value for item. Ignored by default implementation. """ hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8') sub_dir = op.join(hex_name[:2], hex_name[2:4]) @@ -569,7 +572,7 @@ def set(self, key, value, expire=None, read=False, tag=None): now = time.time() db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire - size, mode, filename, db_value = self._disk.store(value, read) + size, mode, filename, db_value = self._disk.store(value, read, key=key) columns = (expire_time, tag, size, mode, filename, db_value) # The order of SELECT, UPDATE, and INSERT is important below. @@ -746,7 +749,7 @@ def add(self, key, value, expire=None, read=False, tag=None): now = time.time() db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire - size, mode, filename, db_value = self._disk.store(value, read) + size, mode, filename, db_value = self._disk.store(value, read, key=key) columns = (expire_time, tag, size, mode, filename, db_value) with self._transact(filename) as (sql, cleanup): @@ -809,7 +812,7 @@ def incr(self, key, delta=1, default=0): raise KeyError(key) value = default + delta - columns = (None, None) + self._disk.store(value, False) + columns = (None, None) + self._disk.store(value, False, key=key) self._row_insert(db_key, raw, now, columns) self._cull(now, sql, cleanup) return value @@ -821,7 +824,7 @@ def incr(self, key, delta=1, default=0): raise KeyError(key) value = default + delta - columns = (None, None) + self._disk.store(value, False) + columns = (None, None) + self._disk.store(value, False, key=key) self._row_update(rowid, now, columns) self._cull(now, sql, cleanup) cleanup(filename) diff --git a/tests/test_core.py b/tests/test_core.py index 63609a2..f1ca44f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -107,11 +107,11 @@ def get(self, key, raw): data = super(JSONDisk, self).get(key, raw) return json.loads(zlib.decompress(data).decode('utf-8')) - def store(self, value, read): + def store(self, value, read, key=None): if not read: json_bytes = json.dumps(value).encode('utf-8') value = zlib.compress(json_bytes, self.compress_level) - return super(JSONDisk, self).store(value, read) + return super(JSONDisk, self).store(value, read, key=key) def fetch(self, mode, filename, value, read): data = super(JSONDisk, self).fetch(mode, filename, value, read) @@ -133,6 +133,26 @@ def test_custom_disk(): shutil.rmtree('tmp', ignore_errors=True) +class FilenameDisk(diskcache.Disk): + def filename(self, key=None, value=None): + self.last_key = key + self.last_value = value + return super(FilenameDisk, self).filename(key, value) + + +def test_custom_filename_disk(): + with dc.Cache('tmp', disk=FilenameDisk) as cache: + values = [False, True, 0, 1.23] + + for value in values: + # The value has to be large enough to trigger getting sent to disk. + cache[value] = [value] * int(1e6) + assert getattr(cache.disk, 'last_key', None) == value + assert getattr(cache.disk, 'last_value', None) == [value] * int(1e6) + + shutil.rmtree('tmp', ignore_errors=True) + + @nt.raises(EnvironmentError) def test_init_makedirs(): shutil.rmtree('tmp', ignore_errors=True) From d0936a58432853bc2fbf53346293c3ef61bf5889 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Dec 2017 09:48:08 -0800 Subject: [PATCH 190/550] Add copy and rsync tests for core and fanout --- tests/test_core.py | 101 +++++++++++++++++++++++++++++++++++++++++++ tests/test_fanout.py | 101 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 9375564..1a92051 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,7 @@ "Test diskcache.core.Cache." +from __future__ import print_function + import collections as co import errno import functools as ft @@ -8,9 +10,11 @@ import mock import nose.tools as nt import os +import os.path as op import random import shutil import sqlite3 +import subprocess as sp import sys import threading import time @@ -1322,6 +1326,103 @@ def test_constant(): assert repr(diskcache.core.ENOVAL) == 'ENOVAL' +def test_copy(): + cache_dir1 = op.join('tmp', 'foo') + + with dc.Cache(cache_dir1) as cache1: + for count in range(10): + cache1[count] = str(count) + + for count in range(10, 20): + cache1[count] = str(count) * int(1e5) + + cache_dir2 = op.join('tmp', 'bar') + shutil.copytree(cache_dir1, cache_dir2) + + with dc.Cache(cache_dir2) as cache2: + for count in range(10): + assert cache2[count] == str(count) + + for count in range(10, 20): + assert cache2[count] == str(count) * int(1e5) + + shutil.rmtree('tmp', ignore_errors=True) + + +def run(command): + print('run$ %r' % command) + try: + result = sp.check_output(command, stderr=sp.STDOUT) + print(result) + except sp.CalledProcessError as exc: + print(exc.output) + raise + + +def test_rsync(): + try: + run(['rsync', '--version']) + except OSError: + return # No rsync installed. Skip test. + + cache_dir1 = op.join('tmp', 'foo') + os.sep + cache_dir2 = op.join('tmp', 'bar') + os.sep + + # Store some items in cache_dir1. + + with dc.Cache(cache_dir1) as cache1: + for count in range(100): + cache1[count] = str(count) + + for count in range(100, 200): + cache1[count] = str(count) * int(1e5) + + # Rsync cache_dir1 to cache_dir2. + + args = ['rsync', '-a', '--stats', cache_dir1, cache_dir2] + run(args) + + # Validate items in cache_dir2. + + with dc.Cache(cache_dir2) as cache2: + for count in range(100): + assert cache2[count] == str(count) + + for count in range(100, 200): + assert cache2[count] == str(count) * int(1e5) + + # Store more items in cache_dir2. + + with dc.Cache(cache_dir2) as cache2: + for count in range(200, 300): + cache2[count] = str(count) + + for count in range(300, 400): + cache2[count] = str(count) * int(1e5) + + # Rsync cache_dir2 to cache_dir1. + + args = ['rsync', '-a', '--stats', cache_dir2, cache_dir1] + run(args) + + # Validate items in cache_dir1. + + with dc.Cache(cache_dir1) as cache1: + for count in range(100): + assert cache1[count] == str(count) + + for count in range(100, 200): + assert cache1[count] == str(count) * int(1e5) + + for count in range(200, 300): + assert cache1[count] == str(count) + + for count in range(300, 400): + assert cache1[count] == str(count) * int(1e5) + + shutil.rmtree('tmp', ignore_errors=True) + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 20dd552..12e0386 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -1,14 +1,18 @@ "Test diskcache.fanout.FanoutCache." +from __future__ import print_function + import errno import functools as ft import io import mock import nose.tools as nt import os +import os.path as op import random import shutil import sqlite3 +import subprocess as sp import sys import threading import time @@ -583,6 +587,103 @@ def fibrec(num): assert misses2 == misses1 +def test_copy(): + cache_dir1 = op.join('tmp', 'foo') + + with dc.FanoutCache(cache_dir1) as cache1: + for count in range(10): + cache1[count] = str(count) + + for count in range(10, 20): + cache1[count] = str(count) * int(1e5) + + cache_dir2 = op.join('tmp', 'bar') + shutil.copytree(cache_dir1, cache_dir2) + + with dc.FanoutCache(cache_dir2) as cache2: + for count in range(10): + assert cache2[count] == str(count) + + for count in range(10, 20): + assert cache2[count] == str(count) * int(1e5) + + shutil.rmtree('tmp', ignore_errors=True) + + +def run(command): + print('run$ %r' % command) + try: + result = sp.check_output(command, stderr=sp.STDOUT) + print(result) + except sp.CalledProcessError as exc: + print(exc.output) + raise + + +def test_rsync(): + try: + run(['rsync', '--version']) + except OSError: + return # No rsync installed. Skip test. + + cache_dir1 = op.join('tmp', 'foo') + os.sep + cache_dir2 = op.join('tmp', 'bar') + os.sep + + # Store some items in cache_dir1. + + with dc.FanoutCache(cache_dir1) as cache1: + for count in range(100): + cache1[count] = str(count) + + for count in range(100, 200): + cache1[count] = str(count) * int(1e5) + + # Rsync cache_dir1 to cache_dir2. + + args = ['rsync', '-a', '--stats', cache_dir1, cache_dir2] + run(args) + + # Validate items in cache_dir2. + + with dc.FanoutCache(cache_dir2) as cache2: + for count in range(100): + assert cache2[count] == str(count) + + for count in range(100, 200): + assert cache2[count] == str(count) * int(1e5) + + # Store more items in cache_dir2. + + with dc.FanoutCache(cache_dir2) as cache2: + for count in range(200, 300): + cache2[count] = str(count) + + for count in range(300, 400): + cache2[count] = str(count) * int(1e5) + + # Rsync cache_dir2 to cache_dir1. + + args = ['rsync', '-a', '--stats', cache_dir2, cache_dir1] + run(args) + + # Validate items in cache_dir1. + + with dc.FanoutCache(cache_dir1) as cache1: + for count in range(100): + assert cache1[count] == str(count) + + for count in range(100, 200): + assert cache1[count] == str(count) * int(1e5) + + for count in range(200, 300): + assert cache1[count] == str(count) + + for count in range(300, 400): + assert cache1[count] == str(count) * int(1e5) + + shutil.rmtree('tmp', ignore_errors=True) + + if __name__ == '__main__': import nose nose.runmodule() From 2d7f403918c3583854035b8cd392c4fec8c53572 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Dec 2017 10:00:06 -0800 Subject: [PATCH 191/550] Update docs with sqlite_auto_vacuum setting --- docs/api.rst | 7 ++++--- docs/tutorial.rst | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index a992e78..90f15f9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -50,13 +50,14 @@ Read the :ref:`Settings tutorial ` for details. of cache. * `cull_limit` (int) default ten - maximum number of items culled during `set` or `add` operations. - * `sqlite_synchronous` (int) default 1, "NORMAL" - SQLite synchronous - pragma. - * `sqlite_journal_mode` (str) default "wal" - SQLite journal mode pragma. + * `sqlite_auto_vacuum` (int) default 1, "FULL" - SQLite auto vacuum pragma. * `sqlite_cache_size` (int, in pages) default 8,192 - SQLite cache size pragma. + * `sqlite_journal_mode` (str) default "wal" - SQLite journal mode pragma. * `sqlite_mmap_size` (int, in bytes) default 64 megabytes - SQLite mmap size pragma. + * `sqlite_synchronous` (int) default 1, "NORMAL" - SQLite synchronous + pragma. * `disk_min_file_size` (int, in bytes) default one kilobyte - values with greater size are stored in files. * `disk_pickle_protocol` (int) default highest Pickle protocol - the Pickle diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 054f662..4dc32c3 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -543,10 +543,11 @@ An additional set of attributes correspond to SQLite pragmas. Changing these values will also execute the appropriate ``PRAGMA`` statement. See the `SQLite pragma documentation`_ for more details. -* `sqlite_synchronous`, default 1, "NORMAL". -* `sqlite_journal_mode`, default "wal". +* `sqlite_auto_vacuum`, default 1, "FULL". * `sqlite_cache_size`, default 8,192 pages. +* `sqlite_journal_mode`, default "wal". * `sqlite_mmap_size`, default 64 megabytes. +* `sqlite_synchronous`, default 1, "NORMAL". Each of these settings can passed to :class:`DjangoCache ` via the ``OPTIONS`` key mapping. Always measure before From c8aecac512d7fba0de9b1c32312434c00e5db724 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Dec 2017 10:43:35 -0800 Subject: [PATCH 192/550] Use diskcache.UNKNOWN rather than None for Disk.filename (and update tests) --- diskcache/__init__.py | 4 +++- diskcache/core.py | 22 +++++++++++++++------- tests/test_core.py | 32 +++++++++++++++++++------------- tests/test_fanout.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 622cd6f..15b858f 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -1,7 +1,7 @@ "DiskCache: disk and file backed cache." from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning, Timeout -from .core import DEFAULT_SETTINGS, EVICTION_POLICY +from .core import DEFAULT_SETTINGS, ENOVAL, EVICTION_POLICY, UNKNOWN from .fanout import FanoutCache from .persistent import Deque, Index @@ -12,7 +12,9 @@ 'EmptyDirWarning', 'Timeout', 'DEFAULT_SETTINGS', + 'ENOVAL', 'EVICTION_POLICY', + 'UNKNOWN', 'FanoutCache', 'Deque', 'Index', diff --git a/diskcache/core.py b/diskcache/core.py index 01c1d75..0da9a2e 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -48,10 +48,11 @@ def __new__(cls, name): return tuple.__new__(cls, (name,)) def __repr__(self): - return self[0] + return '%s' % self[0] DBNAME = 'cache.db' ENOVAL = Constant('ENOVAL') +UNKNOWN = Constant('UNKNOWN') MODE_NONE = 0 MODE_RAW = 1 @@ -189,13 +190,13 @@ def get(self, key, raw): return pickle.load(BytesIO(key)) - def store(self, value, read, key=None): + 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. Can be None. + :param key: key for item (default UNKNOWN) :return: (size, mode, filename, value) tuple for Cache table """ @@ -284,16 +285,23 @@ def fetch(self, mode, filename, value, read): return pickle.load(BytesIO(value)) - def filename(self, key=None, value=None): + 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 are slow. + directories with many files may be slow. + + The default implementation ignores the `key` and `value` parameters. + + In some scenarios, for example :meth:`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) - :param key: key for item. Ignored by default implementation. Can be None. - :param value: value for item. Ignored by default implementation. """ hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8') sub_dir = op.join(hex_name[:2], hex_name[2:4]) diff --git a/tests/test_core.py b/tests/test_core.py index 0ce7437..6828e17 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5,6 +5,7 @@ import collections as co import errno import functools as ft +import hashlib import io import json import mock @@ -112,7 +113,7 @@ def get(self, key, raw): data = super(JSONDisk, self).get(key, raw) return json.loads(zlib.decompress(data).decode('utf-8')) - def store(self, value, read, key=None): + def store(self, value, read, key=dc.UNKNOWN): if not read: json_bytes = json.dumps(value).encode('utf-8') value = zlib.compress(json_bytes, self.compress_level) @@ -138,22 +139,27 @@ def test_custom_disk(): shutil.rmtree('tmp', ignore_errors=True) -class FilenameDisk(diskcache.Disk): - def filename(self, key=None, value=None): - self.last_key = key - self.last_value = value - return super(FilenameDisk, self).filename(key, value) +class SHA256FilenameDisk(diskcache.Disk): + def filename(self, key=dc.UNKNOWN, value=dc.UNKNOWN): + filename = hashlib.sha256(key).hexdigest()[:32] + full_path = op.join(self._directory, filename) + return filename, full_path def test_custom_filename_disk(): - with dc.Cache('tmp', disk=FilenameDisk) as cache: - values = [False, True, 0, 1.23] + with dc.Cache('tmp', disk=SHA256FilenameDisk) as cache: + for count in range(100, 200): + key = str(count).encode('ascii') + cache[str(count)] = str(count) * int(1e5) - for value in values: - # The value has to be large enough to trigger getting sent to disk. - cache[value] = [value] * int(1e6) - assert getattr(cache.disk, 'last_key', None) == value - assert getattr(cache.disk, 'last_value', None) == [value] * int(1e6) + for count in range(100, 200): + key = str(count).encode('ascii') + filename = hashlib.sha256(key).hexdigest()[:32] + full_path = op.join('tmp', filename) + + with open(full_path) as reader: + content = reader.read() + assert content == str(count) * int(1e5) shutil.rmtree('tmp', ignore_errors=True) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 12e0386..2da99e5 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -4,6 +4,7 @@ import errno import functools as ft +import hashlib import io import mock import nose.tools as nt @@ -684,6 +685,34 @@ def test_rsync(): shutil.rmtree('tmp', ignore_errors=True) +class SHA256FilenameDisk(dc.Disk): + def filename(self, key=dc.UNKNOWN, value=dc.UNKNOWN): + filename = hashlib.sha256(key).hexdigest()[:32] + full_path = op.join(self._directory, filename) + return filename, full_path + + +def test_custom_filename_disk(): + with dc.FanoutCache('tmp', disk=SHA256FilenameDisk) as cache: + for count in range(100, 200): + key = str(count).encode('ascii') + cache[str(count)] = str(count) * int(1e5) + + disk = SHA256FilenameDisk('tmp') + + for count in range(100, 200): + key = str(count).encode('ascii') + subdir = '%03d' % (disk.hash(key) % 8) + filename = hashlib.sha256(key).hexdigest()[:32] + full_path = op.join('tmp', subdir, filename) + + with open(full_path) as reader: + content = reader.read() + assert content == str(count) * int(1e5) + + shutil.rmtree('tmp', ignore_errors=True) + + if __name__ == '__main__': import nose nose.runmodule() From b6c12bb253deabf7b6f36e0e0f430912ecacbe38 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Dec 2017 17:35:11 -0800 Subject: [PATCH 193/550] Fix tests for Python 3 --- tests/test_core.py | 2 +- tests/test_fanout.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 6828e17..1b1c476 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -150,7 +150,7 @@ def test_custom_filename_disk(): with dc.Cache('tmp', disk=SHA256FilenameDisk) as cache: for count in range(100, 200): key = str(count).encode('ascii') - cache[str(count)] = str(count) * int(1e5) + cache[key] = str(count) * int(1e5) for count in range(100, 200): key = str(count).encode('ascii') diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 2da99e5..5c85bd4 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -696,7 +696,7 @@ def test_custom_filename_disk(): with dc.FanoutCache('tmp', disk=SHA256FilenameDisk) as cache: for count in range(100, 200): key = str(count).encode('ascii') - cache[str(count)] = str(count) * int(1e5) + cache[key] = str(count) * int(1e5) disk = SHA256FilenameDisk('tmp') From 4db0721f7d5e2a1bb06a7371b14afa3e2bf20f16 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 09:56:16 -0800 Subject: [PATCH 194/550] Update rsync tests to use --checksum rather than size/time heuristic --- tests/test_core.py | 4 ++-- tests/test_fanout.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 1b1c476..c3aec3d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1405,7 +1405,7 @@ def test_rsync(): # Rsync cache_dir1 to cache_dir2. - args = ['rsync', '-a', '--stats', cache_dir1, cache_dir2] + args = ['rsync', '-a', '--stats', '--checksum', cache_dir1, cache_dir2] run(args) # Validate items in cache_dir2. @@ -1428,7 +1428,7 @@ def test_rsync(): # Rsync cache_dir2 to cache_dir1. - args = ['rsync', '-a', '--stats', cache_dir2, cache_dir1] + args = ['rsync', '-a', '--stats', '--checksum', cache_dir2, cache_dir1] run(args) # Validate items in cache_dir1. diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 5c85bd4..d18c540 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -641,7 +641,7 @@ def test_rsync(): # Rsync cache_dir1 to cache_dir2. - args = ['rsync', '-a', '--stats', cache_dir1, cache_dir2] + args = ['rsync', '-a', '--stats', '--checksum', cache_dir1, cache_dir2] run(args) # Validate items in cache_dir2. @@ -664,7 +664,7 @@ def test_rsync(): # Rsync cache_dir2 to cache_dir1. - args = ['rsync', '-a', '--stats', cache_dir2, cache_dir1] + args = ['rsync', '-a', '--stats', '--checksum', cache_dir2, cache_dir1] run(args) # Validate items in cache_dir1. From c6c5b27d0cb751058d06b64a13bc50b2602ee037 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 09:56:40 -0800 Subject: [PATCH 195/550] Drop version column from Cache table --- diskcache/core.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 0da9a2e..baf7f2c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -447,7 +447,6 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): ' rowid INTEGER PRIMARY KEY,' ' key BLOB,' ' raw INTEGER,' - ' version INTEGER DEFAULT 0,' ' store_time REAL,' ' expire_time REAL,' ' access_time REAL,' @@ -659,7 +658,6 @@ def _row_update(self, rowid, now, columns): sql = self._sql expire_time, tag, size, mode, filename, value = columns sql('UPDATE Cache SET' - ' version = ?,' ' store_time = ?,' ' expire_time = ?,' ' access_time = ?,' @@ -670,7 +668,6 @@ def _row_update(self, rowid, now, columns): ' filename = ?,' ' value = ?' ' WHERE rowid = ?', ( - 0, # version now, # store_time expire_time, now, # access_time @@ -689,12 +686,11 @@ def _row_insert(self, key, raw, now, columns): sql = self._sql expire_time, tag, size, mode, filename, value = columns sql('INSERT INTO Cache(' - ' key, raw, version, store_time, expire_time, access_time,' + ' key, raw, store_time, expire_time, access_time,' ' access_count, tag, size, mode, filename, value' - ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( + ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( key, raw, - 0, # version now, # store_time expire_time, now, # access_time From 5c81820e4f0aab2265b904ff0a51812de8ec20b0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 10:11:39 -0800 Subject: [PATCH 196/550] Add --delete switch to rsync tests --- tests/test_core.py | 7 +++---- tests/test_fanout.py | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index c3aec3d..767e38d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1391,6 +1391,7 @@ def test_rsync(): except OSError: return # No rsync installed. Skip test. + rsync_args = ['rsync', '-a', '--checksum', '--delete', '--stats'] cache_dir1 = op.join('tmp', 'foo') + os.sep cache_dir2 = op.join('tmp', 'bar') + os.sep @@ -1405,8 +1406,7 @@ def test_rsync(): # Rsync cache_dir1 to cache_dir2. - args = ['rsync', '-a', '--stats', '--checksum', cache_dir1, cache_dir2] - run(args) + run(rsync_args + [cache_dir1, cache_dir2]) # Validate items in cache_dir2. @@ -1428,8 +1428,7 @@ def test_rsync(): # Rsync cache_dir2 to cache_dir1. - args = ['rsync', '-a', '--stats', '--checksum', cache_dir2, cache_dir1] - run(args) + run(rsync_args + [cache_dir2, cache_dir1]) # Validate items in cache_dir1. diff --git a/tests/test_fanout.py b/tests/test_fanout.py index d18c540..6d43f95 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -627,6 +627,7 @@ def test_rsync(): except OSError: return # No rsync installed. Skip test. + rsync_args = ['rsync', '-a', '--checksum', '--delete', '--stats'] cache_dir1 = op.join('tmp', 'foo') + os.sep cache_dir2 = op.join('tmp', 'bar') + os.sep @@ -641,8 +642,7 @@ def test_rsync(): # Rsync cache_dir1 to cache_dir2. - args = ['rsync', '-a', '--stats', '--checksum', cache_dir1, cache_dir2] - run(args) + run(rsync_args + [cache_dir1, cache_dir2]) # Validate items in cache_dir2. @@ -664,8 +664,7 @@ def test_rsync(): # Rsync cache_dir2 to cache_dir1. - args = ['rsync', '-a', '--stats', '--checksum', cache_dir2, cache_dir1] - run(args) + run(rsync_args + [cache_dir2, cache_dir1]) # Validate items in cache_dir1. From 2920247674a7c86bfd6a4153a08b1187d941b2e5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 10:21:54 -0800 Subject: [PATCH 197/550] Set SQLite page size pragma to 4 kilobytes --- diskcache/core.py | 1 + docs/api.rst | 2 ++ docs/tutorial.rst | 1 + 3 files changed, 4 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index baf7f2c..0d40efd 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -70,6 +70,7 @@ def __repr__(self): u'sqlite_cache_size': 2 ** 13, # 8,192 pages u'sqlite_journal_mode': u'wal', u'sqlite_mmap_size': 2 ** 26, # 64mb + u'sqlite_page_size': 4096, u'sqlite_synchronous': 1, # NORMAL u'disk_min_file_size': 2 ** 15, # 32kb u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, diff --git a/docs/api.rst b/docs/api.rst index 90f15f9..5cc2267 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -56,6 +56,8 @@ Read the :ref:`Settings tutorial ` for details. * `sqlite_journal_mode` (str) default "wal" - SQLite journal mode pragma. * `sqlite_mmap_size` (int, in bytes) default 64 megabytes - SQLite mmap size pragma. + * `sqlite_page_size` (int, in bytes) default 4 kilobytes - SQLite page size + pragma. * `sqlite_synchronous` (int) default 1, "NORMAL" - SQLite synchronous pragma. * `disk_min_file_size` (int, in bytes) default one kilobyte - values with diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 4dc32c3..3a06d5f 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -547,6 +547,7 @@ pragma documentation`_ for more details. * `sqlite_cache_size`, default 8,192 pages. * `sqlite_journal_mode`, default "wal". * `sqlite_mmap_size`, default 64 megabytes. +* `sqlite_page_size`, default 4 kilobytes. * `sqlite_synchronous`, default 1, "NORMAL". Each of these settings can passed to :class:`DjangoCache From d8f79d5a660a9ed25355f4dabbd1b04b32e163e1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 16:05:39 -0800 Subject: [PATCH 198/550] Fix small bug in persistent deque and remove stress tests --- diskcache/persistent.py | 3 ++- tests/stress_test_deque_mp.py | 17 ++++------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 9f0c5d5..9e4d7f0 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -562,7 +562,8 @@ def reverse(self): self.clear() self.extend(temp) finally: - temp._cache.close() + if temp is not None: + temp._cache.close() del temp rmtree(directory) diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py index cdd964d..4624d71 100644 --- a/tests/stress_test_deque_mp.py +++ b/tests/stress_test_deque_mp.py @@ -80,12 +80,6 @@ def stress_pop(deque): pass -register(stress_pop) -register(stress_pop) -register(stress_pop) -register(stress_pop) - - @register def stress_popleft(deque): try: @@ -94,12 +88,6 @@ def stress_popleft(deque): pass -register(stress_popleft) -register(stress_popleft) -register(stress_popleft) -register(stress_popleft) - - @register def stress_reverse(deque): deque.reverse() @@ -114,7 +102,10 @@ def stress_rotate(deque): def stress(seed, deque): random.seed(seed) for count in range(OPERATIONS): - function = random.choice(functions) + if len(deque) > 100: + function = random.choice([stress_pop, stress_popleft]) + else: + function = random.choice(functions) function(deque) From 24fadab14ba311b97a32b9e02bd0dc1892bd88ad Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 16:06:08 -0800 Subject: [PATCH 199/550] Change eviction policy to use new-style string formatting and add test for custom eviction policy --- diskcache/core.py | 45 ++++++++++++++++++++++++--------------------- tests/test_core.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 0d40efd..977f31b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -95,15 +95,15 @@ def __repr__(self): ' Cache (store_time)' ), 'get': None, - 'cull': 'SELECT %s FROM Cache ORDER BY store_time LIMIT ?', + 'cull': 'SELECT {fields} FROM Cache ORDER BY store_time LIMIT ?', }, 'least-recently-used': { 'init': ( 'CREATE INDEX IF NOT EXISTS Cache_access_time ON' ' Cache (access_time)' ), - 'get': 'access_time = ((julianday("now") - 2440587.5) * 86400.0)', - 'cull': 'SELECT %s FROM Cache ORDER BY access_time LIMIT ?', + 'get': 'access_time = {now}', + 'cull': 'SELECT {fields} FROM Cache ORDER BY access_time LIMIT ?', }, 'least-frequently-used': { 'init': ( @@ -111,7 +111,7 @@ def __repr__(self): ' Cache (access_count)' ), 'get': 'access_count = access_count + 1', - 'cull': 'SELECT %s FROM Cache ORDER BY access_count LIMIT ?', + 'cull': 'SELECT {fields} FROM Cache ORDER BY access_count LIMIT ?', }, } @@ -739,21 +739,20 @@ def _cull(self, now, sql, cleanup, limit=None): # Evict keys by policy. - select_policy_template = EVICTION_POLICY[self.eviction_policy]['cull'] + select_policy = EVICTION_POLICY[self.eviction_policy]['cull'] - if select_policy_template is None or self.volume() < self.size_limit: + if select_policy is None or self.volume() < self.size_limit: return - select_policy = select_policy_template % 'filename' - - rows = sql(select_policy, (cull_limit,)).fetchall() + select_filename = select_policy.format(fields='filename', now=now) + rows = sql(select_filename, (cull_limit,)).fetchall() if rows: - delete_policy = ( + delete = ( 'DELETE FROM Cache WHERE rowid IN (%s)' - % (select_policy_template % 'rowid') + % (select_policy.format(fields='rowid', now=now)) ) - sql(delete_policy, (cull_limit,)) + sql(delete, (cull_limit,)) for filename, in rows: cleanup(filename) @@ -868,7 +867,10 @@ def incr(self, key, delta=1, default=0): columns = 'store_time = ?, value = ?' update_column = EVICTION_POLICY[self.eviction_policy]['get'] - columns += '' if update_column is None else ', ' + update_column + + if update_column is not None: + columns += ', ' + update_column.format(now=now) + update = 'UPDATE Cache SET %s WHERE rowid = ?' % columns sql(update, (now, value, rowid)) @@ -918,7 +920,6 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): """ db_key, raw = self._disk.put(key) update_column = EVICTION_POLICY[self.eviction_policy]['get'] - update = 'UPDATE Cache SET %s WHERE rowid = ?' select = ( 'SELECT rowid, expire_time, tag, mode, filename, value' ' FROM Cache WHERE key = ? AND raw = ?' @@ -950,7 +951,6 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): raise else: # Slow path, transaction required. - cache_hit = ( 'UPDATE Settings SET value = value + 1 WHERE key = "hits"' ) @@ -983,8 +983,11 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): if self.statistics: sql(cache_hit) + now = time.time() + update = 'UPDATE Cache SET %s WHERE rowid = ?' + if update_column is not None: - sql(update % update_column, (rowid,)) + sql(update % update_column.format(now=now), (rowid,)) if expire_time and tag: return (value, db_expire_time, db_tag) @@ -1568,17 +1571,17 @@ def cull(self): # Remove items by policy. - select_policy_template = EVICTION_POLICY[self.eviction_policy]['cull'] + select_policy = EVICTION_POLICY[self.eviction_policy]['cull'] - if select_policy_template is None: + if select_policy is None: return - select_policy = select_policy_template % 'filename' + select_filename = select_policy.format(fields='filename', now=now) try: while self.volume() > self.size_limit: with self._transact() as (sql, cleanup): - rows = sql(select_policy, (10,)).fetchall() + rows = sql(select_filename, (10,)).fetchall() if not rows: break @@ -1586,7 +1589,7 @@ def cull(self): count += len(rows) delete = ( 'DELETE FROM Cache WHERE rowid IN (%s)' - % (select_policy_template % 'rowid') + % select_policy.format(fields='rowid', now=now) ) sql(delete, (10,)) diff --git a/tests/test_core.py b/tests/test_core.py index 767e38d..86e2f2f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1448,6 +1448,43 @@ def test_rsync(): shutil.rmtree('tmp', ignore_errors=True) +@setup_cache +def test_custom_eviction_policy(cache): + dc.EVICTION_POLICY['lru-gt-1s'] = { + 'init': ( + 'CREATE INDEX IF NOT EXISTS Cache_access_time ON' + ' Cache (access_time)' + ), + 'get': 'access_time = {now}', + 'cull': ( + 'SELECT {fields} FROM Cache' + ' WHERE access_time < ({now} - 1)' + ' ORDER BY access_time LIMIT ?' + ), + } + + size_limit = int(1e5) + + cache.reset('eviction_policy', 'lru-gt-1s') + cache.reset('size_limit', size_limit) + + for count in range(100, 150): + cache[count] = str(count) * 500 + + size = cache.volume() + assert size > size_limit + assert cache.cull() == 0 + assert size == cache.volume() + + for count in range(100, 150): + assert cache[count] == str(count) * 500 + + time.sleep(1.1) + + assert cache.cull() == 20 + assert cache.volume() < size_limit + + if __name__ == '__main__': import nose nose.runmodule() From 693c655f9a2eb6a0d883dc24452b004379ab5007 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 16:10:46 -0800 Subject: [PATCH 200/550] pylint fix for Disk.filename unused arguments --- diskcache/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/core.py b/diskcache/core.py index 977f31b..ed83522 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -304,6 +304,7 @@ def filename(self, key=UNKNOWN, value=UNKNOWN): :param value: value for item (default UNKNOWN) """ + # pylint: disable=unused-argument hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8') sub_dir = op.join(hex_name[:2], hex_name[2:4]) name = hex_name[4:] + '.val' From 97b960d921da89e44ef9e95cb410702661fa4c12 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 16:25:44 -0800 Subject: [PATCH 201/550] Update benchmark docstrings with notes about generating data --- tests/benchmark_core.py | 8 +++++++- tests/benchmark_djangocache.py | 8 +++++++- tests/plot.py | 7 ++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/benchmark_core.py b/tests/benchmark_core.py index 9ad42db..1811de0 100644 --- a/tests/benchmark_core.py +++ b/tests/benchmark_core.py @@ -1,4 +1,10 @@ -"Benchmark diskcache.Core." +"""Benchmark diskcache.Cache + +$ export PYTHONPATH=/Users/grantj/repos/python-diskcache +$ python tests/benchmark_core.py -p 1 > tests/timings_core_p1.txt +$ python tests/benchmark_core.py -p 8 > tests/timings_core_p8.txt + +""" from __future__ import print_function diff --git a/tests/benchmark_djangocache.py b/tests/benchmark_djangocache.py index 13a50b3..4a0c81b 100644 --- a/tests/benchmark_djangocache.py +++ b/tests/benchmark_djangocache.py @@ -1,4 +1,10 @@ -"Benchmark diskcache.DjangoCache" +"""Benchmark diskcache.DjangoCache + +$ export PYTHONPATH=/Users/grantj/repos/python-diskcache +$ python tests/benchmark_djangocache.py > tests/timings_djangocache.txt + + +""" from __future__ import print_function diff --git a/tests/plot.py b/tests/plot.py index ea1c0c1..d8d1be0 100644 --- a/tests/plot.py +++ b/tests/plot.py @@ -1,4 +1,9 @@ -"Plot benchmark docs." +"""Plot Benchmarks for docs + +$ export PYTHONPATH=/Users/grantj/repos/python-diskcache +$ python tests/plot.py --show tests/timings_core_p1.txt + +""" import argparse import collections as co From 9f7000b166ffcf7a4c7011baa0345e4deee5ef86 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 16:51:33 -0800 Subject: [PATCH 202/550] Update benchmark timings --- tests/timings_core_p1.txt | 40 +++++++++++++++++------------------ tests/timings_core_p8.txt | 40 +++++++++++++++++------------------ tests/timings_djangocache.txt | 40 +++++++++++++++++------------------ 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/tests/timings_core_p1.txt b/tests/timings_core_p1.txt index f09110c..243045a 100644 --- a/tests/timings_core_p1.txt +++ b/tests/timings_core_p1.txt @@ -4,10 +4,10 @@ Timings for diskcache.Cache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 17.881us 28.849us 41.962us 472.069us 1.701s - set 9021 0 300.884us 338.078us 394.106us 658.989us 2.736s - delete 1012 104 261.068us 299.931us 338.078us 598.907us 248.081ms - Total 98999 4.686s + get 88966 9705 12.159us 17.166us 28.849us 174.999us 1.206s + set 9021 0 68.903us 93.937us 188.112us 10.297ms 875.907ms + delete 1012 104 47.207us 66.042us 128.031us 7.160ms 89.599ms + Total 98999 2.171s ========= ========= ========= ========= ========= ========= ========= ========= @@ -16,10 +16,10 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 15.974us 28.133us 41.962us 522.852us 1.605s - set 9021 0 281.096us 318.050us 388.145us 5.438ms 2.537s - delete 1012 104 237.942us 283.003us 345.945us 2.058ms 231.609ms - Total 98999 4.374s + get 88966 9705 15.020us 19.073us 32.902us 349.998us 1.406s + set 9021 0 72.002us 96.083us 200.033us 8.962ms 879.545ms + delete 1012 104 49.114us 69.141us 236.034us 4.808ms 86.015ms + Total 98999 2.372s ========= ========= ========= ========= ========= ========= ========= ========= @@ -28,10 +28,10 @@ Timings for diskcache.FanoutCache(shards=8, timeout=0.010) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 16.928us 29.087us 40.054us 141.859us 1.692s - set 9021 0 275.850us 312.090us 386.953us 611.067us 2.486s - delete 1012 104 236.034us 282.049us 354.052us 546.932us 228.918ms - Total 98999 4.407s + get 88966 9705 15.020us 20.027us 34.094us 627.995us 1.420s + set 9021 0 72.956us 100.851us 203.133us 9.623ms 927.824ms + delete 1012 104 50.783us 72.002us 132.084us 8.396ms 78.898ms + Total 98999 2.426s ========= ========= ========= ========= ========= ========= ========= ========= @@ -40,10 +40,10 @@ Timings for pylibmc.Client ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 25.988us 30.041us 41.962us 269.890us 2.407s - set 9021 0 28.133us 31.948us 45.061us 88.930us 262.482ms - delete 1012 104 25.988us 29.087us 39.101us 65.804us 27.031ms - Total 98999 2.697s + get 88966 9705 25.988us 29.802us 41.008us 139.952us 2.388s + set 9021 0 27.895us 30.994us 40.054us 97.990us 254.248ms + delete 1012 104 25.988us 29.087us 38.147us 89.169us 27.159ms + Total 98999 2.669s ========= ========= ========= ========= ========= ========= ========= ========= @@ -52,8 +52,8 @@ Timings for redis.StrictRedis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 45.061us 49.114us 77.009us 197.887us 4.171s - set 9021 0 46.015us 50.068us 77.963us 179.052us 429.199ms - delete 1012 104 44.823us 56.982us 77.009us 104.189us 47.746ms - Total 98999 4.648s + get 88966 9705 44.107us 54.121us 73.910us 204.086us 4.125s + set 9021 0 45.061us 56.028us 75.102us 237.942us 427.197ms + delete 1012 104 44.107us 54.836us 72.002us 126.839us 46.771ms + Total 98999 4.599s ========= ========= ========= ========= ========= ========= ========= ========= diff --git a/tests/timings_core_p8.txt b/tests/timings_core_p8.txt index f341249..c7c5713 100644 --- a/tests/timings_core_p8.txt +++ b/tests/timings_core_p8.txt @@ -4,10 +4,10 @@ Timings for diskcache.Cache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72929 16.928us 29.802us 45.061us 517.130us 13.617s - set 71530 0 303.030us 360.966us 36.302ms 6.251s 269.090s - delete 7916 773 265.837us 330.925us 35.141ms 1.339s 17.652s - Total 791992 300.358s + get 712546 71214 15.974us 23.127us 40.054us 4.953ms 12.349s + set 71530 0 94.891us 1.328ms 21.307ms 1.846s 131.728s + delete 7916 807 65.088us 1.278ms 19.610ms 1.244s 13.811s + Total 791992 157.888s ========= ========= ========= ========= ========= ========= ========= ========= @@ -16,10 +16,10 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72975 17.166us 34.094us 73.195us 8.381ms 15.575s - set 71530 0 228.882us 1.421ms 19.039ms 333.486ms 79.159s - delete 7916 784 198.126us 1.385ms 19.165ms 107.130ms 8.838s - Total 791992 103.572s + get 712546 71623 19.073us 35.048us 59.843us 12.980ms 16.849s + set 71530 0 108.004us 1.313ms 9.176ms 333.361ms 50.821s + delete 7916 767 73.195us 1.264ms 9.033ms 108.232ms 4.964s + Total 791992 72.634s ========= ========= ========= ========= ========= ========= ========= ========= @@ -28,10 +28,10 @@ Timings for diskcache.FanoutCache(shards=8, timeout=0.010) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 71626 24.080us 46.015us 72.956us 6.997ms 20.241s - set 71530 106 253.916us 1.400ms 8.787ms 15.915ms 47.683s - delete 7916 779 216.961us 1.345ms 8.602ms 11.446ms 4.516s - Total 791992 72.440s + get 712546 71106 25.034us 47.922us 101.089us 9.015ms 22.336s + set 71530 39 134.945us 1.324ms 5.763ms 16.027ms 33.347s + delete 7916 775 88.930us 1.267ms 5.017ms 13.732ms 3.308s + Total 791992 58.991s ========= ========= ========= ========= ========= ========= ========= ========= @@ -40,10 +40,10 @@ Timings for pylibmc.Client ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72146 83.208us 105.143us 120.878us 520.945us 61.320s - set 71530 0 85.115us 107.050us 123.024us 458.002us 6.285s - delete 7916 792 82.016us 103.951us 119.925us 298.977us 673.505ms - Total 791992 68.279s + get 712546 72043 83.923us 107.050us 123.978us 617.027us 61.824s + set 71530 0 84.877us 108.004us 124.931us 312.090us 6.283s + delete 7916 796 82.970us 105.858us 123.024us 288.963us 680.970ms + Total 791992 68.788s ========= ========= ========= ========= ========= ========= ========= ========= @@ -52,8 +52,8 @@ Timings for redis.StrictRedis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72652 141.144us 174.999us 210.047us 931.978us 103.515s - set 71530 0 142.097us 174.999us 211.000us 623.941us 10.457s - delete 7916 811 139.952us 172.138us 205.994us 288.963us 1.138s - Total 791992 115.110s + get 712546 72093 138.044us 169.039us 212.908us 151.121ms 101.197s + set 71530 0 138.998us 169.992us 216.007us 1.200ms 10.173s + delete 7916 752 136.137us 167.847us 211.954us 1.059ms 1.106s + Total 791992 112.476s ========= ========= ========= ========= ========= ========= ========= ========= diff --git a/tests/timings_djangocache.txt b/tests/timings_djangocache.txt index 21439d5..c80e180 100644 --- a/tests/timings_djangocache.txt +++ b/tests/timings_djangocache.txt @@ -4,10 +4,10 @@ Timings for locmem ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 140750 35.048us 56.982us 59.128us 8.609ms 28.325s - set 71530 0 36.955us 38.147us 46.015us 6.582ms 2.670s - delete 7916 0 31.948us 34.809us 36.955us 2.065ms 255.893ms - Total 791992 31.252s + get 712546 140750 36.001us 57.936us 60.081us 10.202ms 28.962s + set 71530 0 36.955us 39.101us 45.061us 2.784ms 2.709s + delete 7916 0 32.902us 35.048us 37.193us 1.524ms 265.399ms + Total 791992 31.936s ========= ========= ========= ========= ========= ========= ========= ========= @@ -16,10 +16,10 @@ Timings for memcached ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 69192 88.930us 102.043us 123.978us 917.912us 63.269s - set 71530 0 92.030us 106.096us 127.077us 804.901us 6.604s - delete 7916 0 87.023us 100.136us 122.070us 201.941us 687.053ms - Total 791992 70.560s + get 712546 69185 87.023us 99.182us 110.865us 576.973us 61.758s + set 71530 0 89.169us 102.043us 114.202us 259.876us 6.395s + delete 7916 0 85.115us 97.990us 108.957us 201.941us 672.212ms + Total 791992 68.825s ========= ========= ========= ========= ========= ========= ========= ========= @@ -28,10 +28,10 @@ Timings for redis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 68891 174.046us 213.146us 251.055us 1.084ms 126.502s - set 71530 0 179.052us 216.007us 252.962us 478.983us 13.056s - delete 7916 770 156.879us 193.119us 227.213us 293.970us 1.268s - Total 791992 140.826s + get 712546 69526 160.933us 195.980us 239.134us 1.365ms 116.816s + set 71530 0 166.178us 200.987us 242.949us 587.940us 12.143s + delete 7916 791 143.051us 177.860us 217.915us 330.925us 1.165s + Total 791992 130.124s ========= ========= ========= ========= ========= ========= ========= ========= @@ -40,10 +40,10 @@ Timings for diskcache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 69423 36.001us 59.128us 92.983us 7.305ms 28.354s - set 71530 0 300.169us 1.451ms 8.877ms 39.359ms 51.403s - delete 7916 0 239.134us 1.378ms 8.740ms 14.397ms 4.926s - Total 791992 84.683s + get 712546 69509 33.855us 56.982us 79.155us 11.908ms 30.078s + set 71530 0 178.814us 1.355ms 5.032ms 26.620ms 34.461s + delete 7916 0 107.050us 1.280ms 4.738ms 17.217ms 3.303s + Total 791992 67.842s ========= ========= ========= ========= ========= ========= ========= ========= @@ -52,8 +52,8 @@ Timings for filebased ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712598 99964 101.805us 171.900us 365.973us 5.407ms 83.088s - set 71557 0 7.903ms 10.250ms 12.787ms 34.464ms 578.779s - delete 7837 0 200.987us 346.899us 596.046us 1.250ms 1.736s - Total 791992 663.603s + get 712749 103843 112.772us 193.119us 423.908us 18.428ms 92.428s + set 71431 0 8.893ms 11.742ms 14.790ms 44.201ms 646.879s + delete 7812 0 223.875us 389.099us 679.016us 15.058ms 1.940s + Total 791992 741.247s ========= ========= ========= ========= ========= ========= ========= ========= From 0af6216536521e25afa54f880462d41f865377dd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 17:03:19 -0800 Subject: [PATCH 203/550] Add small test for incr coverage --- tests/test_core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 86e2f2f..80e4db0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1485,6 +1485,14 @@ def test_custom_eviction_policy(cache): assert cache.volume() < size_limit +@setup_cache +def test_lru_incr(cache): + cache.reset('eviction_policy', 'least-recently-used') + cache.incr(0) + cache.decr(0) + assert cache[0] == 0 + + if __name__ == '__main__': import nose nose.runmodule() From 34304475a157d1fd51561f01239c37ecd25d5086 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 17:16:19 -0800 Subject: [PATCH 204/550] Rerun one benchmark --- tests/timings_core_p1.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/timings_core_p1.txt b/tests/timings_core_p1.txt index 243045a..7d6394d 100644 --- a/tests/timings_core_p1.txt +++ b/tests/timings_core_p1.txt @@ -16,10 +16,10 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 15.020us 19.073us 32.902us 349.998us 1.406s - set 9021 0 72.002us 96.083us 200.033us 8.962ms 879.545ms - delete 1012 104 49.114us 69.141us 236.034us 4.808ms 86.015ms - Total 98999 2.372s + get 88966 9705 15.020us 20.027us 33.855us 437.021us 1.425s + set 9021 0 71.049us 100.136us 203.133us 9.186ms 892.262ms + delete 1012 104 48.161us 69.141us 129.952us 5.216ms 87.294ms + Total 98999 2.405s ========= ========= ========= ========= ========= ========= ========= ========= From 672451fdc8d1565b225461713ab587db40b73e36 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 17:33:12 -0800 Subject: [PATCH 205/550] Update benchmark graphs, data, and comments --- docs/_static/core-p1-delete.png | Bin 47490 -> 45852 bytes docs/_static/core-p1-get.png | Bin 46498 -> 45803 bytes docs/_static/core-p1-set.png | Bin 48068 -> 46824 bytes docs/_static/core-p8-delete.png | Bin 48371 -> 47424 bytes docs/_static/core-p8-get.png | Bin 46900 -> 46972 bytes docs/_static/core-p8-set.png | Bin 48147 -> 47227 bytes docs/_static/djangocache-delete.png | Bin 33677 -> 30412 bytes docs/_static/djangocache-get.png | Bin 32506 -> 33536 bytes docs/_static/djangocache-set.png | Bin 33306 -> 33357 bytes docs/cache-benchmarks.rst | 92 ++++++++++++++-------------- docs/djangocache-benchmarks.rst | 50 +++++++-------- 11 files changed, 71 insertions(+), 71 deletions(-) diff --git a/docs/_static/core-p1-delete.png b/docs/_static/core-p1-delete.png index cd3726f47ddda48fc9e671dc3dcee5b52124ab9f..6cf196f17d253df0244b374281281f2a1fb667fb 100644 GIT binary patch literal 45852 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 diff --git a/docs/_static/core-p1-get.png b/docs/_static/core-p1-get.png index 7c21c07e99b0319913ed60b425467c99fa7f99dd..434d3e5e25c10e01134d80931a20ea0d5b3bb8ce 100644 GIT binary patch literal 45803 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%0As}NJvQd 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 J($!3S{|n=Q5_bRq literal 48068 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 literal 48371 zcmeFa2UL~mwk?QSmSwIgF_Tmbpn#$x88d7VL6U+56Nu!DfCO^}+$0qhm7G;_Mo~c! z$sk!pB?li-x{NaB76V)58t=eTyxH~_6?;&vh(Mxn8U`# zHlHE4SDB4%2L1o$w_ouufy?J;;E!Ldb~99e!++<0JAEBL&o-CSuwrBT)0+ODRbZv!_^PVXw2dsuK;vY&Mzf zQ_@y4xzGM6?C4cPwfHjCaxZ10^QShd9J}~?Jg?!&`C^$eqq!4SJ9Zs9WPYu(zc+VJ zcYDgmWa-LIQ*QfV>1J_}?9KD0;Z)fAJ~TNmrN6LUnZJv!g6-gHIr@z&;}<^qaq|tH zne^wnKLY8GEZ&Rk^yj_39@FVh&);ylY;4E>{LgPrl3%gIbdz6!{)l8#zVDHdzNV<9 zs=2+=Po5066z>u7Q+WUW{ZPWUZ>Ot_Mer|;8RavdnDfX@+bQ_c<^Bp8=QLhEX=xq2 zuDv2EHO5BcL~K=Al11Zm3EjqbcQ`L9=s1sjaMmptXa7DtuA`Y~s_i!XF*|#YUY^(C zkVB8+?EAluX@8a4;XHm$dJkLIio`K(Hnz`SzBD(7+`D&=qw~d!7fdfBUE8C_Qf)i6 zqdIpTj@A-$e3E{y_gR8*)w3XZU&E@GdYUQLVbzIdE-rJeSDU2|`s{b-iYf_`Z|GKL zW8aNClqJ>J+%pJbTf$^FPclmMQGs zy_@~dKP@x!*_7lg&T_LI9cT^l=NA@MuCK43GiOf8EuoB1&-IG+L-)!fjc@{i zk(82Bd%An(9S$+&=0g7++lRlty;UB_STr&+@;*Ob-h1Pbia>^^^T>paoBv@7aZV)&^Xw>b+I_*}boZTFr%<(V#164A{%xt&~L9r15-a*PTdY&ZP$ z{OS4g=T)EWojur5RXXXsZr!?Y1wWC#;bC8QclYW9lXi364+SM9CEN5rEK`ft(!lBV z4GyZs>K6>u*)=qUeO$EuP;Pm-8qPYi>7II&=6n6vv)%RSLkTgNlbZMl6*;-xQRMa()YgoSj=t&c zp6H%z-O8-DZ{{qI)KGC5E%pD_*cg!Kz0s)W^Q+OpN?p#6cC96A(+4Y#-MoHXsrcEx zCj8!EuURTw@p)1GH(N9-NP*!-yJw~ zNIm=N{KcG{QMZ-~88+p4-*j`U=%|V|w^ z*btBR=Iz_6-u!C346E-S4o^)^Eau>d%p7kLnlo=+S!-!XyyLKG!6S(yGiEJ_8<<>n z^~6-O-=)1eGJAH&%gd|OrP$E(TEHou`}Xa=wemh*yg?x$5e(0D)$vBY{A%HWI_VDY zv9GT!uVs{YtlATIe#A0LH&X{U zB{A(V!?}HC58GBYwh^Y6*Xg9x)RmKyllzsGnduYn?yTH@Z2{*z#yjq*tz-UE<2fFu z@!^GXii&nEeiC-ktKKgYK7DqP|rB`wchDFQRB`$ss^93&9+E8fdhHU-V<#>R$28xP`J073! zV`S&&=Zo63m2pN?9I2ioqv4samg`D>%F?_{kd;?dQ9HCwi9$r-o(-qWc+Va;O|$4P-(6fhnap{N{li^#R-As74$~xmAjfzpVvh>*E=TCk zFSh&9b0Yig+qJTy29?o|R=xj}YG)SReEa(K%?G?Td@1rd=6W4}udS`Myu1Y$(q0*} z^6jmqI}FRin~I!MdA6U<{T+7<&qr&lCf?{wQcB8-rGi?y@eTYDyTY+4#oiVA?I`p( zy$zdRwgERD5AH{AW2cFPUDr0=<(v|>A8aO((^fKXXX2Xev1ajSRR<4ZAP{LqkJ3oZKb*t|JZD z4741+HdNrZBfqXr+uvm}S$b-`pCborPT|bQhsDWO&8lhVdbOI<5qE7ntAnz#cBW5F zjwQSG4h)n{PK@=Z`E*3RsEpB#>8wd?Zjo^sjWexHdW!wML0DM0usQt*1)YE>)i7mb z*z~H@xS+tmPc<(aE;Trh2~;Wwz3djq%O7j&?2O8s9E=(L(dgbeoXse~_8jOhl!?HW zLFkBc?=A_tb#)LRRKkXj2ySs5o8D{RtEhtb}d%#Nx z+mh03d5o?uGrmv>Xk#%uyH@~?)tQ%%)0k~tlEs?Ju>oGbty@); zySi;mT`KXc_%&VzmXwy7Ntiinge}dwbPK>|WT3_@lfj&L>@I^e`tgrIgpClary7^y zbnn}@Zy)j^f_N9ILp> zsAET3TU$p*n*IBwVt`IwXJ-#$$!ru55Gc$^PD-lnoBBjAV#b}wzIwh=?Ni;I;U|v6 zT`87LH+?o~JTuD}*^iB+*8K5NQ&*RB{;Bct@qvL-#r8&b8Ff4nFCU*^ZkO>FS%%EM zspTn;kc0A(mSiS>1PJP+-3xhPH`f2Xt&N}IlNpwBWN`WhEmmyOPO-k5nwqNr;og@X zjt7yEn+UFHXlRJJEf=>AVLh(ETNiMMdIU`2EEbLn-No`zMT(9$t`a76knC0!d&d2czx1PK zE8F%81w@bsGA5jr`}bd+u>ADo36D){$-ddMXFm|NI2paGx#vqZz?pC|zigFn4i`dRiG7z^ zI@_G3U3aIup6A!iNUL`qGnvmVtp(gyX;mn-9sw{)CtVXT^+>#7c^uOIwNr>z!HEsd z$4Id_@KBmiDp9F+}!0@Zpa0` zCze$|KXEC+tRXr#R}T9*?TPP$n8Kn*A0CRSUSb}iR0-hb8&GI?gf|}M3}Gf z*A}t*evni0oD#BI>BX6I%bx6-;k`*idA;Ieins9ySpqs~M{(aqbWR};e*60M*_Ko9 z3~=Iv(s|P-N85u7ZG_rpD&?}*{_4usW3CxzaQAS!gH3j6Y3XnC=50%{Zaw)>)Z#UO zM@o}V{Ynwz&Ehs~r!QZ=ybMciKMIZBZ=Q-P_FP-A9hcCxStQXUGdAkzi`NEqDe;F< z8)Vnj)onn8JRG4e&=_B-vvsJup?mXCrLN0%opWDiuGz35H)Ffw!GpK6?*T@w#Q!_; zd<2ZnojbRT;1lv;ag46asm|*75oh*?zgMlEF~IWbWB4rsJHDSnt+;OO(Lj0{u?-^ITNxJwITf1J5| z<;rptCRZ~vGynMGj}vMAA0_PTEhlXY@1}5W^?#c7P-e}V?0ehJG7rZas%6*I)b#ZB zZs9)?ZJajR79p0Pkm)cOj(a*U0DE%(i<58XEnK*8&9-f22Gwz4TbxF%8}Z>{6}~kM zAt!1_8$4&uUGm9)+E7m6{rk;w_oI+53iX$*>)KCcKbyJx&l-8(|6z@c7yqrmPOj^o z&b0))r*d?#eesGF{ECW&W7-Swk{+d0oShrDOf&!v>SeT`RdgJqZ2}H#j@P#h2`{ug^75$VMy{r4W$LE@@G9*7!BSYgis0v7-NJC9#M@Q)uq zYPdYHy$K>9OR9t%Dk%2%_71XZa5{qR(%09gOrhvp?^mtDc$X>XeX_FoIXSY^rcHC` zeY@1=`-l4%kFLO{xbFTCcBu^Ubo+_dzjQWa>MBZ)oPO+ff6J+>Kqa>Mla)G-3cwJ` zx0VQO!z%F>IHvwc!ma{wsBN?0+O_*og_Jzqb8VY_-|0Kcw(M>=-+u>N@F^mOh9>bV zuCEI^otK(RtXj3|fP#XWx%qR{CaMwYQ8jLapVl3`_vCQE{y;}$x-F!JTbbs z$B;-I_9cfV?s$cSgj~FIsbA80prtrmHH?`8v}iPSMAs?k!-s?OIYf)<^<%3BO5}z3 za3Ov>%n}k^aB~bxgYQV~Whf&Cs=YY5&uOUY%*fICbSbm0sJN`rdzWBRvkkDtq&DfW zh@U{^mK!&2@N33}Vo!Ax>7?44IJ#W=b-wl2H?to}J82?!eXwxDMl~uAS4)g)LTN45 z6(nxm;&HzT$7C}F^H7#J6o`Y$$5M`Q)~zKqZmB4aqQB&z5UO|S z_qSW&7{RmM{PRu}Mem<>0)7})M%Qtw)4G4MdBec2w%1qYuGp%7o9=f}QBjTC;LuP7 zlAKkXZnEWDI)wdlKR+bQnKNfLnXqWIIh-N&#;b*>_{Sq}O>TayVrlu3>FMdsWUgi$ zP*gmEADvSBJUo5}@H%kygy_RHW6BxIK?kaG>S`x*USLHgj*Achmvt$oXcpxwJ}D_} zB(XVj=c*h%dJi@HhSp($581C@$C))qGnDu4y)3Am5;!q7Eb;IZD6%1E2ETSn9OZXJ zb&h_MnuG#riXK_nVL0HcOS9U+xLE(VFXfoP8z$?{HQJXiw0Oyqbqt5^_s@Lm>WW6* z9qYY%!T@y{9SQ7%nVAb7JCO^VGS0R23AUY@oTBDDITMOFE*qENAs6>DtWcZDar-eh zhmGoy&vjFF7!>g!AMZgvHWyvzSeen&V}5NuSDqJNz^Xl04e)2)hYzYzn(wi6 ze<{NM_nZGuu3te8x%ha zR}Q+lPT@h|#v{?+9O_*r`~AbUl3%8!r44*K<$m_v?d8dC5*A-CPh#Wd6&5N2VwY|$ zlnhuS>&8AH%*s2$_^^+Ur6Oi8T4w-8&;jL&6{i(wT2Ra?L^8Nen=t^gD+2t&%^NoW zDx-k@vVR~0M=1JBW7AdMUcOZ|Mn`Hsm*mmzw0^hjq}#XW5eP^1UHR$7sl_W-M!b2m z7kqLnN=-YH0$SS-{c9!!Kih(6_j>Rk{PXAIYk7G?K}d_e0}gFLvQ`E=-f03-K#d3j z+zO=4a3}@Lz(gHIH_9BK@T#FrO{qM?2$d!Y=K#VbV3 zpJadX)pgcbDx_1kqv8B1vtv*hh@JYqp37z20=e>_ztfOLM#T&q_4BLGWxMij0@^T` z(`U@+G(nza2BNMKzkTQSZLeFmZl%j?+$aym?c}w0#U8cqw>i9YFepgSc zHu z3rr=DZ4m~&+G|4;{VRy3uLe(h`m7XP?ZccI%LCUbK920poYJwdut4b)o2Z*{UIW!l zGFM|$QxI-xO%5t}&3iRTemURP;SNG{8Hwhmv$Ashe6ftVR(=5Nb%b4ay+dmdNYb?Q zbQ@q=E%#Iu?D(Oxk+&6PQnFjB{Xx@T7jT3E|A@tLid)O!RrzDP=|n`MX7S5KX~gaa zl}wKhw=a@o0zfOpqVX!q<4$w?20UhKP%~hu4=~w*9YKx;1qXi@m9;%K^r`k2z}iL1Tp#N{<~hH5J=>68V;wZ`G<-Po-9^+J)MF$QksUT9oGY zoTLtWF8gnPOtdPxq|A+=Q&L(c`TH(IEPO*$PbgnvAk)BEl?_-qRMl6|cqpq!SU`Tj%PfrM+2F{^cjjwKw# zp*(r8k<3B7IB`w7PRvZoKj&_b7Oy`AiC?_9?m^{ds95sSyVK_R_>M&S-gwt)T z)PFJ%*Lt?4=;;I0VJ~04gq(Lu!CzV%{N08g8^T{3Bp*u4tzEyq!Q%FbWx*e-63ui6 zJ8OpB8VNoiE7z7K(@l~5!FAn{rAL0&`B%x6IPAYB0snUz`@c1f{l5li{nyW#sWK}J z8H+?cadAz+`~}?HiM6Nu=&JAkSr1F{m&~SWaZmvfxB9<-H;bd zoWf&CjpfUiKM=PGC%q2U07SHG=^^B}QgGT)8BSy1u{1$`#FQmloq+}~1r=aKd_6wG z_1Z0*J!%!iNPbb#qaX!I(~NGUa8K%BjvFVbkGx?>--gA{t`K|xyTd2}qr1(ddRzK< zU;YCzt6<(RB=eg$ZE;xggXB~`i$vc<>u5rIs$a_r7`P+4LbcVLzZ9_6qmy+cDE`;*chZ`}bY8GeAgeqtur*aZ)zSMAj~t?TUTBvxObtfB()hQmK7^|HR`1f6+ozaK#zf<$a0ln#wp{V-J3;ZOlue=f)$q7zce z|0F|IRvtkqTMouK8i=x`{D`g^_~zh)_n%=!g@cM8vhwCr*}v!Nyke*(UZ8TY*v|r- z5t#~c$fb(=-A2B`X1Z%VJr<)p+iluZIi(!RCLK`sK~}ua8-|LT{0IgHp%u`iK>VKt z(a{H3Ltwz9I*wylIDKNM22^P!HqBx%BspA;2j?yoRD}${`R=~hUu(W}R@k?1-z4wM z1^Pq|?H2ur{7S_2y)CEabUdVo!6mrxjTEblG8g)<_y0dI#Nhq^onii|gGl+p%rNYO z7To!nHsW-%%{5I&q|AZpC`du1kvZFt;goWIV`js9)Y@b=ujdzZpA93Xx%(t#D~iXxefKU8%%!ulGa*wHa+?KM@mU zcv7$_!tjq0!!K|MoDI{jrhBC0#`?!f$TabDSl9FZ=p1Emt)nRC z)-sSPh|~i`+c}XrIT`_HQKTB{L=U|y339@9S4PBSgrfo@vXQ)6IO~oHnTY{UXn6ul zL9F1K-Y9#l*k<5?iZjlzoPXc`{kZdo`-X;mc!Pq1TEYIT*nE8F1$LHS?c-}@4`v{x zGpKB0)ib6~_ww*KJ~%)J`fS~mH^ZLJ^7AhJwv}PP-wz&V!JUNaA=sVZE!!DAemQtY zU|B+4A>*tCt?_P5Obh{fhB8baAN^%w<}Bvlux(r1=Wv!2ZvAnT8^;nqei?)vgsNtz z@!p)a`t;O^k;4$hD^cQs``Q3|)l$_c`iRf$n@mKuoP6^e6~B;`Y$e0|{6>d5Bc>*Y zGr{ZKOE9f74EyUT`H1*^f{kdxGNTrf##vZ))Pw0DY$2!kd_0^>2+<{`W5VF=_WlpU$6&`4tK{$IS#c<3^yEse%I99t*c~1pXXZol0DFQ zv~~R*P6>6e#{8iUbik}u4F?`G$XVx|oaTnJ_SOdWO*6x$&G^;e%j+vB3HR?})irE~ z3URYwr7F9#a>oF)uka%=I&V`J!RN+K^2kL4i(-<$oIbG+Gpp5Z8dHOpZ66BoG zp&GNfflBmp^CdnRcOl(UUJVAr844a}XbAVT&HISXVcZ}PeV z6LP`F;zXNvb*CR+t{K*+C3~}6qgky83gj`u7A7?l?75cC&;uLwoRhXtboCOB`z)W-98T%ef) zRADN#cXW&ZF`x-YqLjv5I>%;xKDjH{K5$g5kUaMs5kLO)`#%LVUhai`fa*~Y0)pN` zx*4tq(&HO*+=+*RG=EFSzG*ReU9cD)!=E$odalr?oX*;0TIi)P`LLc5Fzth_XWXEl zdNn@8RTWX90TeM7D~DVvIF%;H0526GBgW|$tUzsF&h;#?VFnWp3+b_rt&TN#5>0Jw;ZT;U zYUq)|)d38yk>k#tnZJ>Rp+8{tLt}`*OA4mD1Df_)2BKaCtTQ$s4Co;c z0rKlYu1J7$Lqxa~Sf;?Ww3q}%tm`j4;^ zlHWsOoCD?vHg(oMT#ls%13{FyZO6I%Ng`697^ny5dSbN`g9ke3Fbn}|3C0>!;!t9l zU}l$w_ie_mX7EUUB%XXvXv_g@IM^T zH?czqq}ZW{q9^ojN^XP^0wq`A8Gk2rn9M4{=LG;6Y(&`_ZSrF-hHh_m>-zPg3o~X}cE}|msyNEvqg=1~ z2Jw)2nAlthIhv`qQAAI}Bibr6HR6ubeMU~SA3iOnp+{dl!>QYR&YE^@2+xDuJ6H~$ zNa^rRMib@(HR|v50X7S-yLZ2sE2^NFwSj#RcO$3X7Cvh7szMq(Y+%r$txNS0TeIHC zg*2=a9O3c2>>`xwybN$w9tHZbT3R~f^`T5&{`|zA^uD}}{5ok#>d{(ZM8T5lbV4}| zi$Vpv?g&I&X!{|kV6j6Lh~)zlU?QW7n*~s_{?78P`-y2q$|h180;t9m_Ih_!3Y~@M zdie&qx)1uX?@LQnaA}z#s}3+}>ET#>K$_rSSHOI_fLl6}K9Ba8lNOfA0CH!3P0can zi}HtJRbOkUkpG5+ z5j7cQCMPbL)Mz`75>+Au3ttSRlT`v33)sp7QTal(Emb z9v$5eRawEX=*fizT#^K>`8RINuV1=sStvHJ0x)}m7BA~k7s>A;Buy9}7PJs-sOA>4 zlE52IArkcab4u9r@ZTIk@Mvmnz2EM17{R#$j*!10A!QFsJ3G6ihu!YjsH;G>{9W<_ zMDc5PvtwHKe%(h;8|J#d16e@Y{GfON@haf%G-H(DJO|ue#`hrYh+BQXU4O3e&UC(i z!^oY5?PXyBl&4{2RZcXm<7I$<_vIxA6I2bdA4DEuq1r=l`m9)a6M1|eoTOz4m);2a zgf4}J`yR{OV>M!Q&CK7$e++Ch0^yuCYr5ReahvMa#9K8Vf(z)=QxgO;;NiQdJpzz| zvy?4ZuepuzkvY+6rup*pjR_kdSD>yK7^fH|&rf7;JM)plXfZ4Edt%>WzXl<@WKA_KKd*~LVJ@(lC`|^T&TlO4@)+)va;@O4a`PPit z;e=$l?BD#|C)4hRwygR5_yw5s$kzb1-~dxlE9nWIbsOpyL66-mq=ulT(1RywZ6QsNgEdHz10SYBa3%JJWGWdH)Zv;Z9 z!1%XIz+-?>10bj2WhPJ%86C+lz+f zXibBLB!8U4P)8Wf_yE&+q-iNkz=|M2N*fTs{(VjJl*gS_L1s7rMXwBHWM|RRKmK?M z%Tp*Giq9P4V%W}{LQKTlkDNUD4A@K0#gqzZb`B2XWKsIP`Tmg%**4$?bO)5d`^L+7 z_3Bl)N{Abl?VjtjB6@b_+j;OS2VQ3~nWtl`=Dg%$z0bkNw(}NR55NQbJ%FcviCGIc zil|8jIi~H+jYS9K<@0-4V0+=8@_q`JuU(U8Qnc82Ye}7DEnXCPkoDU`0P1K(;uvN% zHl1t1aslKMdLm4p_oU~!^-m37l6DaMY})XTC3eKxLDl26VY6Sv0Ws^UN zqFfV|*Nea1;9YG{Bq6=L78nDNw}v(0@1hM>mFY?^T;#W=CcS?L*Qx+U+Z~6DABkG5 zmTiE&bvyPb1kS&$+)XAf(iUI$68M1)q^&MBabg;9R989Z-qqQgw^@9B^LLMonibfx^R15Bz9kc-+ug^62!yUyEsSC;EA3Z|y7^qQ>Y z8Vi)Gpfk??Y4Mm7e6ehNq<<{~eG|`&Mf@fPA6d6o6r9(i7k>^sI5Uh($^lApD=fLb zKyMBMEfzVaj73(m`oXehyjAsp)se|-4>dH6N*~v2H^O+(MU-5V@$<(EL9_a3Ib>z$ z_C-6vuv~`gdxn*_0Z14|nSmN(5tQrsZQEEMNmbTxy`v&tK;;QFh`74CUJPf!{)aw6 zJlpm1*rf)`RH)O1RRpf1_n8~|bloLfga3E`Tz*Pa#%w$L4CN)essO!6X#y4XDZo0!knG?nnL^ zAXWhpjByD46|Ub`9e3lZUXl24zo zEL^U;UnzS)A}V3Aw^M4L}-JGss{d8)mKovt6~M6&*#U&ivzLT2-=~t+OTEI zVW`QK&<5i5@|WN`4>8fBU?Da6qY>-ei_!{;!NzEIRw+I#pJCS-felPy+yjaV8{5vO zS3)N;Hxxw>Yy~FZEWPf5X7{q#c zda9T{Wbp=lS2*hKzC5~#E*kA+q2%NyTcVcMLtY}((0*71jAt3d;skPqEZGB5#%@EH zp>zb^#ry5a7pDC~L+U^(up_5%{AAol7;YEK(3=#4YEj_- zD9SKIEg#emzQ^L}Gk0cq1dx6bMSgxT^oIdb0gP?CYQwNhjK03PfgS7%W4Kj>IvqWC zspa^UG*eSk2lOlq!g0{NpXFij=x%CpV*v&ByF2)r_=X>L!omFy;tb1)2&OhS3$<|20GF9k5n7yN)XB%S4KrpqM|4j zguIXsxQ{}+5@koE6X9kOWXN+*&b#j)mQ$A>YTtVzX7$EaX=o&j(oR)JcvJ;IAuCI~ zS%VH?WjQ&y$F~V|?40xp;90zQaS#e+VnQi+n3kVD3X3A{J2lwR5w6V9a|r;AVS#ma z50GvZ2q1>?l{t$k3ef{ZAc*HZi$WD9Vt29?f#{NF;s|KG3H-~RZohRiUGRvqTksGF zh+WWmEDWwQ0|_q!j1)ohLG#-x65H4aFMlmYGk}xf; zM{gm__B%midZNE2@L2wXDK&V62A65%q^e}r6xyu^DXRkIP2t%2^JzFE2krp6WgZPp z8{{aEp#Chujp11GjF(hHkQx5O*!kHaU@)*be94W&sNi!>B)+G$Bq)lSm%!kAm}Exw z*WE$gK3IzbvT@0XX&SHQ<6}@K*q%LJywHU2iB{^Q&jGRQ2 zBIJ!I6GSp7m*mD(Xh2g_MGDC~J70MR@Sr1OD~Z5~hW5vin3#d6b86y*&=m6V<42-0 zL9LX()RQO24eFH_LnG*d0**Vmb#V-KJ+-jFC>&)1Mv-bREKMF4m#J%#tLbz{G_c~+ z0aRefdW629ry!t-R!>&*U8N{B=taD?%S5|wihX}DsCL%ELFomp{Nm*k3rhGG6yV-zp(uXzkjyO`*ACTU;j3BVHdOSpoM=4LoBydM8$hSq8={A~?iD zJtm4BLFwg9cD|F^f)g=5Lb}liOzq%1i|`0xV;6wT$^Z}H)N+dT_%reNrG{&*gzq0r zR+D#Q7t}h6Ch;vS5RQ&R9m&nT2-(O^2pyis)>2qMcM#`F+=Oo)-9uMdS+^yA ztiN5%E#eGAmyJ9V;{6)W_otnlgaC@)ZKO+dJ)fuKf!=SA_otPK5I}bf`ad1JP6^-t znQOT=UcFd`hrf9^1Eub(~DTD$G?1vhnZWE|j5JCoUOxPcm#*%S8-}PdyGC6fY zOg4S_aw4%43Gu1Ccu@g*%aRRZ$WFJNmA zP%QWuhB6w-E?&GyqbNG-Qg@imq6N;QYaiJqM?0CQVMI#3L;-N8$cjW!My)!Kve*NH zd_251Mm0c&P2aweeNv+a0qECx%bws|TTwGFwBy0S*!tpgzPqWyr9zGF(4Hw9+tw`P z6c40i56mpm30?Fw>k znnRokkBfsbtC}XSErPc|O(%JIc`=hB)da#mVOE6(i9ydmFroGFK12bFi;rd)QDo^c zu-|mC4BgscehOuzUm01DJcNZ#y zBrip_gSU2*AD`XXvu9c6S}?0XS?zIYk^d-x%P>ToC>yX#x{rrD!5IMNstk)y741ar zh3E>6AfYAl=waw8>HyRaA3Y-8-ftpCH#7Rutc9?ieX3r9VG?*5JfSrMF!7j0)vyk! z<$m6SW~LxzbvlEt0HYvABOIW3djLt32Li=;3%Z&sCS-nmok$R_3lM-B! z05zJlC_wY1_M5EQkjuq=yetFspML7~Jr#D0x(pNePKj%!6pa-_grvh*XJR=Db|l7% z>VKYnmk8;M_x!aw9lv$UEkBHwC_HopszQLCJs0-h zUiK6$hyfAh=sldlPfy>&U$W0GuJs6Qtcd#cdZz*Om1j0SebGoHPU*k$YFfF;iH>GsH3;DDpvOC)2GBNI8RO3kr0FeuYv?zsPzmQOH>9P9K1Wr=EYzX zgA)saibgdr0ZFcYGSk9h6$V<2X3UUh5p?`o&-Z~S@5Bv5>7x2B2A&F2(XB>({8k?X4g zUCK8*_qUv39Y;4UnZV=<%;@m4ZWk=2c_Em-m6`!oxe|nwsT1eQl^apFs>SHkMe9}! zp_r3+JO*2oZJ{;_U74Pvtb+-;n|Np@TLz&KmbzIP%1{z`-xn4ZM#o@A62>irB0|G4 z%n$M}=zur#{ppwO>lu)0(%X}p3;aS_T3TwCu{h-OEidLN(=Z*H7J}4Apqh!QSCzqp z9YfnCw#vA(5%usyL+W6v9}J>=XzUIaz;rP%OWURIP5n_9?I;NdT2 zyQ>Y9&k>9j`39Z|3qy116>}>qPnu3t)9LBsqhFd+P|%q%T6Y6ADu`xoRs#v4hm$XE zYyxvfl^@jpNOWj~qV%mW(J1YE8T_M85Z6PJTKyH8D247B!CP-nRw^CLEc3Zl& zGb>O1LA_pO0kR7rj4P4@f}Uz8DzZYfJ1U4fjb^r2xWETPu3r1#Fi;r$T1n5rubwf9 zJ-Uh5I_kK<2TP^BqC=v7xpgLRve(_E1x8FYoE(X#?Mr*~TOmyFQsXl0H>{p#UmXVq z=m~1;!a^rsxX~0b#NTr75|0hYM;0KOU_pQfO8I-=URu60YjM`#w>B`UlPm&YjWI}+ z+9*f8oM1(X@YHb{RKtu9GColf?o@Ae>@V0sO-;m6;1bk`@jCf-4g}KnFAGVz+nF(R z^p`NOGPQOMP%fQ%-=_Q42&#s9V2BF);>F&VScRsf8(kb-CeOdQdjDf2`irzJI`zU3 zta~o!Y`t+#?Te3uXNPcVPiOn|5SmxU+Y1YaGxf4P(uOWE1}l}XYg zHh@*VaxI;k5^0XmA}*fdn#2B2qV<0uw*O;k)s2!hYuAsAAey@9Tm#Hy+ZI$rYj0e? zem(GYZneIM-}d8KY!r-OaIrmZgIBR}+~Bd9g^obdykROcu7(aS#ImZc$)kYav2b&I z_X4v@MA)NA&SQg0BzmL{kYf5F;S8BKxL2wCZS$wJNZD4PcRM*>)2v z!AfN0HvKX1ZoaJV@#a278L^bhX*o7dx=GR93oEyKY}Kk9UN56nsgdc65eYIylfQ&X z$e|5{az-kZQ<o6C{d)N7K1C?%~kRT65x>Ee<8SN68~#qbqN8`&hP(lYdh5 zg&alclxVmEqY&6u*1O~C0QRebOF=rf4@oSC*AvyUNi|6B#fa?4;rc6!dd{IM=m=u? z;QRIdpho~5~FHYi?;V885tS2 zop61ANT1#-7ls)RKuet3zq)F@v6(OFoD@Cx@vP)U-dEnP>*7Ji0C*zT`q`;}ucr>; z+FRgZM%IW!Wny#;A4~(2KnmnGG&Ec&r`%-D8$48pGH5up@Ptk!nnEF*?VMN7ZI(v!Uqn~?C%doA0R{LxqyELIYv9QtSbTZ@0PpVk z{G0LyB_Ba;e$V&30A>&-YfKW4ZXz8Ld?5-dPNh|@Y1U&6vq6{W$7-OMFPM&>;s~OJ(7vDvVe)ZDxITQZy1) z1TbAbCUwB#UJRQm26UFEt@yK#FT-Q$OlFmF1z;ySjZT(kz}E9f%xXVeI12~Ik?qw` z(!-|0)n?CD>kl@cnxTLPN@3O=ghRL$_Qy)JXAS~sg#*d}{*`QuNm~uPMb#|1mT0mE zICo8V%wt?mUM+Y6Nr;7=o&5c+n0SF6Ael-@k876gDH!b(Ndz@DIl#hHa(9g^xrH!q zN0B-LI7D}guGlqarZO~uc$h=e$GTE2Z6~|+p2L|>96ZG0bD_;b$f+On`}auAavj5B zmLT^zlYRN}us_!xAZm!9F-=vXHfsRcaLxE|-m{;xF3>C`406?TLVI#W<3T6}nHaxLxOgRL<5=!xM3X9GFFF)e%ra8@7x zO6Lvt?p0r#4((5eR1540jB|`5dm@_Sp*bY9CStxA9*rC>Brf?_`Llg_4uuZEivFhkO9!9qx`YvhYl(wIka{_CSwcWSf&AQ< z^|+P+sq!%C9~ff6?U` ze;o6EFG50eWz{rYKS1!Ix#t?pBWDz+5rcC=(f`YGBW+H=KOz zH0cYChvnGB)=hbfp{?-MoOQyb-6d)(-Y^KgYe!vRq*?=CMf)6?ngo+lJX7NIG@*=v zN$SgJOP1QJ4Otc2Qwcc!^oye}CwLumJywat_`&poS)YR$A7^H%qXNYI)io|IF4UcZ zN?J41Ii0XSwGd&X6m_#MPE&WqYoyq_%x> z$C1mOv?LgRF@jm2#y4O=e19PLIcd$6=`?%^i@FTak9cHoaiNgXY$r%cg;x=Cb@d$| zf;nX{4?ULTW03!|&K_DJ%z~&X0Od!#;}Aa|-}~?JRmMKhZ4{0nJa-JuFb5w{ z=n@VI%+l7DcC;!+eJ$pjUvmDdfrn!t&w6izQHUJ3xMt?$*a^7`)h9J@ z{8SB0>*;BFXCqMOF!Tara@ra&{Q;w@8Muojkk~MIrZ|mzS>ODN>~rE!^ivcY#pZ=i zOJXr5mPIS->j$GAgqJqY*uA%MXFm^>VyNh8UK8={u!MdClL6~9+A=q6-C7!)35SPU z;q1?u0RnVZ3YP3sf@F0=_Y;}<+8Rb*r9ef8G_?$FU#!Y?E+zPw+)~JQKdy*}* z<(2OX>(R_T6gvkqrzTS|hbfZAVzD;MwTstyu1xPlD@i$M{6S1uYnhtto_g82==a~B zfR&^UI=hK&$>&GCD+nJ^lFR_wNJz8O%`m;P0Ei{~I?$TwW-4Km?QK7LOy;X;STVQPM3s?QNM-mI7 z00KV^9e|(PI;Z=>Zy0t2=6!P5WvbG&2KGW5a&9JmKZ#|06zS|PV8PIR!~m)G+>~tV zG3O<>_JJ%H*~Z0Ye+pbb%@QS`3DV|RjlW};80nHgoCyikZ9_sZwCXhnW}5qVS!++J z;Yned$r>1iQ=7dHKR1`1GgAjWhTSRY=yD}bG->)^qQeoBsXnGe+RM}LQy&Lln?g`8g5u%r;lTrz9GThe%wyN{pW!f}nNAe*;TWN* zBqh)Z;D``NT`ZP!XJrUJAry`fnur0N<{1JqO}++rvMTzGZQT2A`zTqBH8meV;3nrf zIW@C$*-$7vvPNWXwH z+GI{a(?J+!d(;m8z;3NT$lQ*8HJ}j%AR>&wh@=GxbFqu#r|cQ#OX2M%?gEsK4Ojqb z+gY>!b`gwD`mx3@dH;FIZZAD2SN676CFMRq+8_wea82$evqi>e+eW{pqQjf-Z%Qm= zsljZuiwb{^qW_o5nN&AF;Yo(>4v!BtlWS~RBI`SYZRdrP!uR2tpngZd(cIIZo_Qr- zA6(+>H>2+|AOWzLH|D0Gq!E+o7X$HRPxE6jVAX?nv5<}yAoyiI$<5B5Gx~CIUBJBj zW@cuX*4Mjq2OHZ0bF|>V4$_7#i&h%~3IX!3*W5f4=qky4_;3^B{OC+J*BPKUaxqb- z#AV9lSFT@n&B_?!t9Vp7zzA=UPN$B%H^S2t8tTx1nqV zElQ*h#)^?#^1h&;pcv0m>GSubh0<2X zuaMMqe$zaj%8vT$G5c+4oPK=~uwLVzAXC%T#ZA~5Pw zEdr_NXbm7%BvvRj8q*NLbcaFeL~S2JmIir9)+$ewiRjQ8gqItJoANFWA|PMCehmlt znjJfKkf)Ilhju0)@5a@ucmL(_BEKXsXw@K+))h+Ocr7g-Tp0~Z?-oDAcz~&c!0MP< zc+&58?;zwV3I&ldzNp8@dW@BZdqUP4YDd#^>~Kui)u8^$B?6i>#Dz)@(BB=SfgvH} zg`=5&gXx@G_2=oHo6UA^6UdjH(W2{xwA8}xm94-@^v_y#II zF@_t`ban8AEszl`s_JCjmAHL+2Tg?}GrTzB!qedh@F(=m3uG9AJ5-Jt4P_UgG6Hyk zE+g=mT@5ut<3aFa(78h-dJGJJQLB;Doo4M~%bjaCVYfVK;la)s9;Z-lP(ez?m0uCf zw#647e2*T(qF1H%ab#iJ!hU1qGvxeYnnwnM;ivER5fqxBe-QRF*?r;kSm$8ktlKCo zXv`yNyNH?qxN8}-bYTCFLg!u0?dF)AJYmd<_p7A)OR_sA2{1}>xLqP)SjJ+bI-tUz z02YFM1*o!a(eII`FzNkUbaw&1X5|#@&D5)cAkvDXs^Q1LF&7|703Nvb6EbWODv{Hp zGB3*|b`78=)yCe6UETfoVixi27Tk)sbElTf1$MC$~Xg`&yBmyVf zLPJzV@6p4P(ICX2sW93};RE$0h$zO zn6jLl5*nt^z{YJb55}vigfj|fK79D#_5O4T5Ls-eW1`T?A%tLm#sA!c)``exokU~(VOmZN+Y6L zZ--Pjz};|dCLF;8+bOUEO%UHg8FjWY=p-t#u``G+1D2)TP|=A|Kul99J5-)Q#)Ves zqI&!GEm`(3^DZ%A%H~Q|^2lQb6$K@D}WILVzACfG&lcUSMD={{5>b@$FqG{?RXdY*N~Q+73agJackT$gg+YreO>u&<=QkC{mcZK;-J@(t-z=@??Qm z8XUs8_*xI-m{Ih5ceuHrH@JP-%aycoz&xIeEu2Wx-H*tRp=-OKl4aB-AdaD=E$`Vf zjV-q@Y0XTYBYV-(io(PYt%1<}$c;-Z1$}#fz0da;?Cz&{{?r>z`Y^T7Lda{ovQ#HG zArsaYYAvUPiipnYKV@g<1^A;yNMC7!oKIgYf(LMrZYZ%J7&jXBkAVHhE)C4j0fmJj zwa<_$SV;_>sAxTWN(1CDIB?rQ{Vy2zbZ1As5-gbX3mh;U@M09&ZX8V|U9n9L2+4<+ zglM3Y>I|pULAPVTFCf5z&}9<}76^-ktmhO)5k%d#`uxM7{X?Zco1O-z_KyN^i&O+%T6G@HA%CNXAk5RBQ0FrbAXU3CZ~*(Km8{j6XEe}%7On4gbv2q(ngTaTZhk{%PkbHCy%EPDI; zUSbeMLmwt9XeM%tS-t_iEPo4lIq39zrVo^MO?aSZ-QvxMPsjM9#gCp7`9Ob0d>B3a z)>z~i2t~u?`0^+l$BIvw-C-8nfY)teZpxe(@>3W?qTGZ=UOJOos9}TfFny5Ej?s5X z*nNOJQInntcAWJwsF#l8?=R@vJ4$fF@_{Qh!TBW8y$c~|6J{nW12gBtI3_?NY+pvT z>U1B$2!c)c_6FPdfzFyrW%x_|5};}vBgSbqRwce_L=$v|KHbl?*cMED!;IZLHuJ!X zJ>9}wf*G*FN2ExussJ0Bpw!eLj!-B1cIlWtaIxRnA>1}rCzH&?dO(} z?fxgnE|KCjJqGU}$Pkdv#>z8MY39E(;ecqGim%VzR#*0 zLl~?gta%ivP-H8aIyeqZNsgIu!?eGs&;qS}i}~N25nXr^`8OO}T;Uk@&6s|y z1iu~ZTPG69T0q<_7(i46+khj)BqZ7a9?F0kXbvMM*7Y)p)v+;<7zzNml^9py8!K9mP{j|4Ly;_%^!;E3LwqX18`iCRx2t?P@lLol1DFWi1lJzJ-K5q7)KdZSFM4Uj zDY&poeCZiiwNM3CSDszt|0wRw!+KuZw(nmeiAa)EBtxd8ka2IW1z1|=yNWH{QrbcBcu2ZmUh)(9bwmj=%4FtCFuGl_)8 zrL6nrS6}HgR$@#xg_2;psDQ+*oVRUa7bbVJI`ADfMZkGeJ z@nPoy+}N`oCe#3vVyPD_693`1`8M6?IM(<5C-w3b$Cfu>ixK@ew7@rvKXSzFXKzol zon8vWs$l2p%yZP6ODhSjq9+OatB}>l{;+`B2N4T_teelD*XIj}QvfS>iY^4foP#SL zfFtz@lt9Azqu>6da_3bn#1STxGD;NeK?b5Nlf)MBZYoXqTSPmlP~*7rBmB4Bw?t8N zv5G5(5LSpKVv;fk`Yck*zEd2I*8muC_l)32AaWf6RHEyI8s2V70ZViiuJ)|!vmDWj zB{)Ta;en!}Vaap~%7uuEf))j{SurSJANDAb{@#rie7nXpD}F@ugcD|ecRYia@c^`3 zPB#GfJ;_lfs;B(?C4_gB>i?|JohpuVl8bDUS99s#*l!>aF~MG-G;lu^;3;CIct{R& z#Kwk%*?gY}91p!#_{ax@?k6mMLN5`cJJBTUKfdb2<$4UI5kiZYCE@=5u;?N5#mfYD96>x9D6UNuvkckP zPIo_ujDWxQS6J4hu(%1|eo1~_dGf^~&Emk^165xxnhqP*Y0&INkzH=ydO#_ninGdxju;V;m^cz@e>KOezWL#V`B+8BR6a0uwk}_vEfX#ZECh(wQ4j|^D;iQ zF}v=zWQb4c>D4u~Qe4;8*7jI~;N3;?jjAp^g~rp-tdMf|?l(@un&?U1!ZKPe^YolB zdhA$-1?LV-Ql@BgkW>)YJu7b&7FtVS9tzUOP>5+o%&x0{bb3y&YCXqYCw!NuI`!@$ z+x{)t-tyq3OQUgm?Txc|@)3H%P{to%6dF}fULHk-x5sLX!9GQ)T7!4;^6^T;xM{>>Ixp$PLf+wuJ zOiktac_;RLarG?6HE(~7(kz}nWxaCu2tSsB+c8FGkEHq05Q zNG&ggyrDa8oXZ#&2L~-QPd!?L-#)(F`TY6wRTs&8lf6F4LSIFx{j9kZ~p1XqA^g<-&{MNurzPs z!tHdQfABicMPA;<*?A8Hk3)dO3c0#!dG(e0_ZDm_<_U4_;vR0`n(W!U>n>UZ8RVCdfRQ_De?$`D8w;S~1z13ChJiaz*o{x)r26`En z+3l?S#eNOz4*X$i8gTynFc%AMEkq5?1;}NL*=y3CL$uPtVMDHY&!tJ$(=YXP;htBu z>Ti4xr4T(Nn=u*h5B1+`;;~i1$;s*X>C+v~ExLW^(CZg3+Docd@z^r7Tuz@iPl31R zQ63l-)eCP(ctk`80>UX4rLa&IeY>`q^|mHJ$jZs(a138~DXu7Bw;e1ss(5(0LQ%d$ zbGYxwat)o~3bOt7n!MIie4!Dn`g^M^Sg;Li_E4wxOP4Jh`$!B+844pK)46l!ibsz` zD&SWda>P7ak51dt6);#)S;=*v^PT$|*w|?LpE+59#lViDq5^9Y&+bulyQoO&dt=81 zZUbsdU7Xne?auZw8=$;<@3CXu^3Au)&&0p$u&&_#{p9=BJzxiODfC?-t1~7ntE;Qa z3|#bMVp09_mS_n1lSJ+ftsH78317kSa^L~>6pYrvBiU!q$}-V9A6`Lqa#(Se)=Je$ zVFwQ$M1?apH4V*?QIuo^_OqL-Q-Zqsw6U?9o0}W1yAEXhSrmSjeJ-b@9RK_8!Ap*{ zeY?T{B+AvNWEp^Mhp2?t2Nx(!X#!5^jvBS?%o(kbS$^B+7#T?)^J^Q52ySg{?N2Ff z2jMN-wd+gyXO}Nu4v3GBuiC82jXyh?xYCZ;_;UG47YkuSSFM;hapG%?`=^`1@z2m0xz^TXciQ6R z%jL10O!jD!Q2HcYytvI|U=D~>k6W`cg1;9JQj+U=rpV8v{NY1`ehU4t)7Vs(c~rPs zS@p%}XYJ^?8@H4qMEwr7xES7flc~1vEEaBZ4i5B_=&y1P3;`2dymVeaFwr<(-@1L!Areo}r!x<}u>UCFX< zpIq(5Qs+>mV=0kSpRi%a)erNntP*q$jD0ydmPY!cMkz9zVOw0>aNx7#q0CCjTD5-M zLZYju=lOhEirU1N#ThyhYZ~-apgwXkscjtRFJJb2bccu4uh-t4`1tYT__CeCMJVQy zMd@@t`4ctj!-L*-Lq?8#RopW0#j$O_PgMWitD}w`8<} zUH6b5y4Ap+9N!lvIG1v1Z)Btlm7;UTg{Xdm2mgJ;X4s=gj|z*5av}RXOCrx{XeOan zSuTyAJ}hFJp3K69|I7+{nY9Oa|D3Ms@vcuLjX|>)-tVSnhiuoRZgtJ4_w`@>y>Iy` zOanNK+wtf9C!YiT`n9#S+2RNmP%6{&qiXyTGc#pETN5R}+w>X!k9US{D$UYbwtdmC znVUB0pIR`~J#@^?+qdQ5KNqE6U|(?opCYJY2jKliX=zUeuNA8*F5z;lQDgM=@hIO_ zdr3$9ONS^MTib1j2kUct*{im_Js1XD!3ne#iwnyIBFhw`i_e}lT zTdU+Gj4aaZHGjcg$Xy%CA=8(Rd!S3~FjcXD&wXo-O!nMoVYOuadOd=5hSdbapMMPb zK0))vW!V5j4^K}UAbX~6AvAwZtdZ%o)#)iI2U@J=%<1gl;E-uEdlb2_;?k;a>gwuG z8ybo_pPWRFTElwyDDr>30PkzE!-TxtAi*bWV`sOWXTKF-EVK62yjy;+hdwSp zcuHh_EewhGoE~Xa8qAwTcO~djE28|eolywmp`}Ye3O^U zu6aXt@VAK_--Em?+uwZI^yN!F-_SE`_}TO4a}dIci&{RtS(s505fjrpB_+jU(~2Z( zDUi-VpHg3~3K);@efx5}8C5AQfoBfm!wM|w*1e2QgEm{^)s(O{2e@^W7{(EI0;bN7zONe z6r-d|m$s9A;J|b#h{ul~Z+$0P?_OzX9(`0zEDZ`~AKqm6?9;N{x)l&yHA#7g4_|PV zrQcW^cIpFzw(UUVy88Mv=g;p>8M>3msN6^6+@f6^W;@0?4jDFV+p%LBfcftHm;yv2)W&YmrYRr*p;_>60hYyu#)YHVK z6?^)004wP1ylDAFg^zeLpXOAo$nxq*aIW>ct{)W;(18)--Fx>AMq+a<@!%STVZfH{ zT$sTn#l0lDP_7)$@7T0O$Vu!%)&g)CEm_hFhA4-uoAG&BPC*oUHUv$XKCa9{rh_`_a*%kc@9X`H$D77#5Lp?_Sc(W9K5j@&yH_ zJbvOtTi|F?YHB{nqDkyNUPj$Z6YP|IIKp5J1yrmKOpl@LtcBb*Pjl`wF7wa7{_2LH zh)yZ5V)y#txgH{^21xZ@`7C!;@xczr9DKU#4Q;DUdmd66haWj2e|+42g-37Vz&Y{CrbwUT2of4x_P%CPL4(^4NSlwLFtdqSSWy`{=AA zPyKg^Op7|)zPe;Zcg4!YXSfR2fAeqTSbAxBf~YV9?N4h)pJbn&7s-O6wI zTg#(Yo|V`Wz)HZY(Xp|b6fdtHv^3U~96oX+hM(=RJMRt^-VeBzs%9YCC?4j_>C<`8 zfkkZA+o19SNfIEgId$*;{kd3iVrKNWsjsiU^Wee0&Lwv$e&rk^N;H-24;aqnuER!7 znJ^&+&-SRjCO2pF?=HRyGslF1cLQb)huaZL#2S;eZ)H8u`97Z)dLYH^`fnzNtZ)P|buX$u!FwBrmpkxaB1 zv7ocVZRX4lYE!(%>^B+M#ouzsC(>aO78$)slO~-xckb51($b}*j|zm0ocGmPMRi5D zZrPBUMJfNLq2UrHC2`O0t$;v>5a9;61bO%&AsM2~waETi2^YtBH0Udp`H~m!P*uA3 zWd@-~xu#tRF38c;(N>S)zhXG6OL{pvI;H|_%$6>_Ro9!^SJcpx^05;i=~SFZwU zs%T>WrPNdhD)*?O!JW5Er5PY&RPVSS+R(R=fC$!JLal51!Hg z%*m6v$dq{$7fO+>S5)P;Sdo+@N%%eVmkPvPfjAKp8L2$-g_qH@lB=5i7tUq_=%z)mis^X2OGVU$Ir-#;DYh0@KtOumu#}y&D)BT6{I3tEVYlpV7KD?I3RseU`M8&H5E} ztKr0i1v?ZB3)SwS5D0WSPGe{0Q9LA&P?4(he<>2cZo($x{;TN4rukt?n*0J;%^fKU`lt9G@#j+kXEkU3$DcHhDZO*+pv z+V%TiQ!re%6GZv%pC1@L4DbK%pTm_;1pd#z+GXydMM~}4NsWxB`;-SU+1XN z3^F#I)-ZJ(<f^I)>&EmI(lu5t?7yyTvQ)0E7ig{`IRqjt&B@Z$fqv4 z2QRjI&6*qc?kSLlzS20eBI6E{rYef#^5JF8jc=fM%wgW*qOPI~nJ-;Befjb}5On4Y z^0-v)`}6w*bfX~LV%$rN2UckTw*#d_^p zIpJ1pD3gcT+0aUnr=X^`o73P>9u>ai^!J3}2C`Ct?>~E%FP2E&woLk(pSJhVq0WN_ zT?zU)j3y;HVu#hiBS)T0I$s?3hlz>7^yyNVD@LSUIDTA$(a!YktXgOp3nont|Dbk5BexvY1SgNr=|a1 z4QMI@S6AJUSzCVn^!W0&H%NI|TwCKqN@4{KjWD?Hv}G}f*v97ODqJ%c4Ao9y)E-vU zgB5}mIjL3N4ZeW{}7m8tR~OY&asGi_RDg`r_Pk)3I%*5IAFq}*Xjo#2v@ z;(<&BusaGWDgv^Fz;qS$Qc&=x710)K7wgG<<-Qmm>fv z^yw3d8fsu{{P&H|(0qHqDa^N(AuD!}wC~t4x2z1dv%C08>6sYECcu!&QEqlo9V5%B zkSo&G(aEPpD*y-NQ|>~kt>-%TZ7eDzy6d>tI}A1HV&B8v30>h1H{=#|T4TD=B3j|W zFfpV6xM;v0U2slG+%TQv^ZAv@Vl%V;6DO*^eED+Tk|oAS-%so75k^90rA0yS`1<;$ zU%mPq7Rl0U*)mnkcAeL)TemaMRsc^Px2l$*3ZtdjT<)#Jo)|y zJiIQ%+{U*R-G%Njc5FU3F7DvS6vcbvS`Sg5ph!+Z|C9kfD^;HpWIgAAe;;Z~VDo_T zJW)QsT2FKn2XABvM`9in2zklM3snT0QafajvvG0aezn*C{M}8=RL*$EbNAul0?2cp zK7VF2Y-MGwj-tFmKK3S8Ax#>v!h0!ogr?DfVjlEgOIq6=8Y<5LY$8}mOZrasxc)9J zAwh>Z`#X2-lFMkTy0WP=B!AG}z1{h#d)VW?wzkHmr*rN#UJ4JAevf z#*DFEw=Ui_`Z1PKUFaRUo4T@$R4=)>JYFqS?qTlvP)j`*OX$F5UlBIOP!w(s=*?FNKZL?JFZAW1g{L zP}E?e7iV5Yf2|Tz^S4n)i<%Z4nvZw7w5>BWYf*>P)eMemX%Xp-7ucoGL?!CtiZmAu zCa_;uF@pSe$wEhKFC$~istPp1>FKVn9zu}KdGJ8dU|k~hRcAzwt@OA|=cJK$w^IO8 z)uGMZ(`v9Rr$<4Lo87B;#Rv{E66gwP38w9C$2XhIa}xy!MK7Y47U#SPEzlO`g4-ID zg)vKK1;qt9=J=U2fdE$=F2|1_H+Yu051Wpu`h1i_3*d|{K{022Je=8N=r*a#D7RlMzFZB?6Mr1w-M(DW^NBU;B(-UimtUJ zD)Hh)Md^-RcRTr+IzIabvP_c2ho_`s`OeQAs{p{_KI>$ERv8!IVRRl zPF*A(-@hj3y^H(h-fGEES(;m|b4>?jqSNYw3*?p}3 z#tSJaN|@SJu<1j}B;$wR9rj1!B(daS=NDgFJUg8>*AdG!6@w)Y{mc#@KFrAq;Z?>$ zuhG4pC&Ka94|W(QrOv*4_ogy!WP(-JE(+s~^$$A{DxsQZQI9~KJv#9NDJ74LEQ}xt z;}YNBdvo#Ps6|Vbj9a?LWMJ(zW7fE{WJ6XBIup@EoPt?leVHz9d+*yfFw%y)MDyfro!Ymv`thWyDmpcF?8tb( zrGQBrNd?!3cz9AZF6CXMw}AcU4pF*@AIs|6+qX_SDFC`rf z>oBsouyCi8wRo(k&ELMPJ9FYh4k#}@g$0)JXO{c6Im1VeT$P`nKX+Kfh5}n${9Pnh zAx&H+;a~3};qo|@sF!thd8Zbfvz&5uVlwcF}XhsyRjZksndK=p6f z6zAQS9-Kl+5;I#qG=FVtyGS47`o_+3a?u0_3u4AdaoSMtGC#pq=&{E#1i^ff0#W~p z3z^+sQh|Z&_8k08ah5Tk!*W^S=*yS&)yBK_ixO3!M3?o(-*>0z*ntB#B}wq}`=V-) z$fT_Ks@5TTW(yYNNRqIHjEQ@1nk6J&i;?5TMQv)VJ`zQ_7y>u1ew@(1 z%1;wqq_4vpWu#D6>PqJR@kj8HslEs2O!H{!*I1!z_N@=j)0?!=dRD6H*|z_uw6yM8 z9LKd-k|a)_C)n&3WN9=n7Fv?oL_Qt;sKh$dR-3w@;@t;#wln%z9=FlY_c{%gsS zDACvOY*g<#bLLck9_&VMv5Y@$LBlN*V;f~2U2Lnd3n}%}kg~>(QhOS4LOe9*w{-P# zwQt#TP2A>DW4*Y{#(z!|2bkpf``Oa|EEA@#46d-Oe=Jx!?X<0nnpt`~^znL}g&`wjwnMISmefI4I8GcQ`DM!FlP zCS3VuM(~671lk9yft=v#aqNEs}#mLE%-ET_@Kli_KzEEyoKAScCRU|uHWLTcy$c0u z+_N6oVx_Gtu(enz+*0cs%0~Eb=a4{@2J9`x2bqAu#$M7qe@6FwJK z$X-lIak{7}dU>cLx=>yU3`Os`f|Ak>z)W-Vlr2BiiDh7CL2g$~_9s3{OpTL=Qz(#% zlaUp<9PC3R4k78UHQi3VoOLsp%DY+6c!yV* zJaYKV^)xlhl-Ri?c8bZrdlKoAcF*|>7oJSo+TI{RvruJ!(IuxxqrQIomRnV&j(Oz< zv`%;I^Gl3zux8{N{rD*TuxoHgaPSSmYQrSzEWfXRdWhFkKF)gO%J!zFra*a_C@`Ne zU!Y%KzkXc;p_3o3X{4a8en%~bZ`awv!Xo3{m@}kx=&&F}Yvjq?)36X4;xln~X^uPm z#gwkH?UvFO#>O2{77xskzJ6pkx&0}Kq9VygdMw_tq_^;TQfH@Bv-Q#-8hWQvwVT@AqN43VK^>`*gvfP*AP#|^C;SE#73LYf zj_2==xPLD!;^m#<@Z^gZ4KWuu*Iaw@;(IVr$OA|WTLOa;gpP`&Hs8`Rgud16`yYoW z$srP+aXr>B_7q~kfb!Q;{(e8&J|wijZ~A=5>$Nj1?A7NNV_tp7W{^`-(hWJIB7OY; z5JK#U6Wd^}2h!#f|83Yb>jvzhLC6b?mEBF5V*_Juo^0EJ4#b=%Ur{k#%)++wQ04QA z@7%eG<@?3ag%lbcumW&VrbgmoIu>3>k9&1R;!lRR131fl+|1^ixoJ&kiPAjplW`Lo zK%8#lmNDTyENbKm0h%)Nzw=Zdnw}YD!pDS0iq4>b5}*C2^)LGw*&hX%J+dE_KhMhQ z?jfVug@+7;yzRJmW{R-HSVSH=RC!^PC`$j2L{!a%|HmS#@y!2OM3q?E+A>{k$~eH~ z3;EcE8{M;FHFg5?Dzd!CF)2jXr8;v~15hep+qQN-bZuBFbeC+nf3x1IhmdMdT)Co3 z6uUZ0)z;oVV0*`&)1u=4w^Y=P z5mn+(l_V_@LKrpk22Y_~D4Cdx$u9fwFVuu%>FN0t6WeZ5~D3K*VWb4AN$|P7w<$R7#|->3r}Hj zr|>5eBA+Zx@LqCEFOV?Q+}zO6kXu|VPhWAoGSF@p27pl54fCxf~WeZ^43E-!qaS>Jx`321ADwdw1D!N0@{*O|AcOiq?6kF=J;7 zteZO^K)B}d&gom%66?Ph?orWe$5t1C<^ec85YFUAUS3^<*D0hjG;YsLwjs|Lr@0lU z=r|YCm)yQKQquHtt75a(zW@zYSWtQb0jyp&*AXM3HS(Y&6?*mR$a#Ce zXUiOV93<3i*zR&Day9pwBr$M9&`Vh3s*W^G9lVPxdeoQxF=P57nr*o2Knh2?9ycyQ zVR>j4He3W#V6Mo?&9!lH>6UEX1>+T`r2n8n9cZ(0UB7<1wRJyw$3zWz_^|rhs*E0j zjkxABf&62$cI_^~1#%kiJ$PWqYK|LsUT5jfR+`aVZNv>(y9od5r==b&%E@U*%NlOF zn~+@Tt0vQ?*HakIpp0824>XMe4TdPy!W86-s>_}3p zc<^)V_?h!65=R-|zICe;4Ys+FBP^Q88y+4WPkEDHF;xq78TQrHGw%HP;}mcW_t);E zOcz8+j~>$J7RebIq!sE@ybc^1pwAdt zr|Qh!Kwj}AbLY+tkB;6+RI?R@ebdA#Q>F-OH2cgtB$l`P6)3GF^H+F4+fjW-Wh_zR z(>q-9h?uYdmjV+k^Dse*r~xM>IChGQ9ARPII!kmf2yYqY*s4@s7*@0}y!a{@jhFM(2|E=9u^;+qPD{Kb5F=0Ddbvq)NioQ z?xtJncJ< z_oA4SCkG&F&IL$ug?KXc^OxqjIXZtmX?isZoVIH0*s*u+-3zD6cYDK(l2zDd#m@%k z*}i=Jx=;{C&aEg6F(96Q9z3uWA|LBim($F4jz|k-Prk%|JW#RI!X-;qK|pJd8nw?t zr!t%Rx7zEXYaLAsb*HI&N8?V{QVfnZ8EDHvbYe!8Hdden^vt;#&GmuSW-GDj+_`&q z4<(TvB1;PDF$?sc#Vg5O5gnQd1Xb#oS$tCBQx&Z3(?yQZwOY^t=3NkxI1TPfA=KexMT(9T;Z!9Vlqh6 zT|3TG|ko28qp>(hGMb?b`hrn`XwHe<3V`~`tf-~lw-V3Y8?wl*3P zOz6^hc%AUpZD?KjtmM_5m;p<)>@FWWO|>8h6jb^OjP{S<=R69U0Rlo(#m^}ub7*mM zPP{oy8abmZ>H(G7!-nm8R8gTzcop(K85h*8K5_-$TKe8fS?7EexEp0P1RLILZGffA zknf({I`!iAyiZHly!NQZZo!DI?VH|K?B(8UT_WDgXU}fHmGzHuL0!1OUYNUhaRgmG z5n?*rhRSPjgCAR3Rv`_{{p+tVn0p-2n-V_&e3eyIZwq5U`*zc}$KJDGESc$@oSbbW zMoQGt-0L$u%J|Epa~fxUzzV&c_N%oUZvB|GIFK*V3iAwu#6k_MZ z|5S+eGs2X99?sN}vP;yxwpHPQ?L`w;)8hDkJHAg6z!|q9_OeeIE`!+ZL8#N188KLW zOo%i38Hf%jsB|c>v90qw+Op?b^(x*g)YxhP#bZ}w`(?Hoy!g!TvBi$@aA$vc<|7Y6 z|9i6X|9^4#zv#}bmEC3ABS_R%yP)*7+bV%Qvs{L;2HQ<|+KP;Er^t!?HO%t!2T<+( z6lANHpB57JgMM}H%cTrun~IZmxG(P@F(|yYzVV9Oui1=-nvT7RzT-V4Q`|(?iQZfp z14Da#QhX)O0g)APtZZzo{&A!%05!XHaIA1<)mY(;0Zzzpjg0&92xSXe7eJUngbw=S ztl0_C<6Qdoo#qz;18qlHC!cVYBRZM%6k66u;1qnUnOT|IldgQhW)~x2eb1f}J5M%t z-q(b<`?iSsh{Qu7KDHA74DlvdaMjjiE^d|u&B!Mtf}ZT{+YV~6LO=JMHQuGr{CK6C zC%`cTzdAVMq=4+BS9KM}X9=bENbuAdS6|2~?1TE|*Ki1o682lH6tM{jT}fp`X5(j9 zvVZ-Y#LJf_QkV=>Aw$@lq#Wc~pTWk8N|1iY?&!|2mQrW?9CatKaa&wd z*#zIVF$~kub+#;ewTg@|Kv!1*w^Ke+h`=A{b?{S0I*q8Q;m3}J(J3Y54O+0l6*~wo z6x1I`pg9;vMfZ--oEjJt*BgR-CJIzCt&?|-P1E313$0%*zLMp7_Ucu~b?e?KH(D5R zg$T?-7Yy7!bZ|sZa{sM}rDtS3V`F4yk~jsJ;N1iY=QUY`ON0#2OeNRJ`{d>hlDUV* z%EO9Y=aac4t8DG(LE@FiufBsJqZBA;%RCH;kgg2@mpF5Ufe&-89+zee960bP(#P{M zM16mgfnKMNcP37NDPNZ1FCA5G-t_6yi@J5?34KU7h@A?ms=Fu&1>uiKE(&q~$N4ij zVoh*|qU}h686}tyX@hEKjA_B7^>v-fr^(sXf3(20*f|3I~6%xYx zs*K#CNz6{H)}27+*3H-D&pM_bY$g6Qftl9cf;GrB)&#hf|2%f~NRyf^?r;_^$xn&!2_BeO;yWjwP5#T-;zRdoh4 z1PuYt!BG-_)cM}-jn#EtbC+>7$=n1zI~yA*$%c>TLPA458#is-D3SbTltvC8{=B-E zvU05uC66Ap+Fh@uyjx1bI3e*zk7|s8sbb8#_m5CV?UJ?le zMB?&D+-UOHa!{(2$}D)$mTR%T&Y?dBh~1Yh$f;AMFJ8UkO8Wz%@yQ?VMQA++@49`X z+fBGOg}f-(8bIu|4XZ!0v2^o2lz3-Tg-NbcT4kir7z^su95}E&+`V7C zq$3I65MI0G8iFgfn?6_E8!=?(F40L#;MBp2bILR9H+gSHlw41bRdq9eaUe2Mh?f*9 z`z(G@6yu;ZxYVGipdi6;DgFhkLHQ~u2#1zOhB!Gd`>}HiDt$0@lk&S`i-rZv=r35sq<@&0HGnH-LWsD+2!t8OoQ_}1Yiw_k6B5-|8sdA(Xxk6 z@IA$jpk4X#i7O(?3Y!$J)ABiG%%wpHNy@XA^2eh<>AVMXmNlvd?=Oe^dhWMVZOruR ztIPB>T>`XUY$OTStT$b@0=F41-ubi+LH~@6*@BGx6}#vBMT-XZG8Ko$Q|)Il9=giu zZcyqjJ?*;S=9x~AJ=a~B-@>2)Z>K+^zzGuzV1VyoAg~NYSxA8-U z4!wbQfTFmGDchM3eEJHLTzGcpg9Bz3S{T|TcM|`v+kZi1;vfG%{BdHJ{oh#c!n5Ov z)YJPZ96x(@=Nvb8_n@m+zfFo;=*Ad`_TVJI4<04YLA&aw0S`I5Z;q4(^I5HvVg>X3 zE1qhH2K0{PBpe<7TenK7_vs^SB}g@Ht)k#@IIpq%h4R_~;_)_`>3l`&#n^q7@sp+k^9U^1ju zty)!mk(08d`Y!Y@Vv!v383-qB)?!auO?A0$`TY+fgy>;_)4TQ|@W8HJx79`*6RS5k zHrAIeM&+J8g~0~p)AnDdeE|Ld$y16~74%0~;o}Jjdw6nNRHY5}qN|a;BwS1rH@^1g zGz@?HW8N5JhU^Hte*jv@#!Z{!vhFf}#xE5qCCnh}hpMF&Ih4BS=M8T`Dn*GQ|vx`ljU zq(EWELiFZ)p~2_8>n5_I%PawK%|>r{VM6j0ZQre(`;tLZjm7CY!; zGNX_XT$w*8P5@X)7)|IGNutQrYUv}POK`rKnGD^IbFvBCn2*KFCNyJO8!%#oJi%Sq z9mdW5Kof&-iI8aY@Y4tx0T9p;OJ})O+L66`w|1NQRYD`tbqE(JiD>6x1B&Mw6Zcbu zl4(7Kt`~oXfq$E`&L(ikazqmdCtm27^ri*T+$DCz$& zG5%Sdn}{O7BCMu=jJ9S1TDpCRp2Pr#fE2Lxx9xA!ub;lU`SVbI@R-N)^EgJH)>)*x zie}TjM~;MI&zDFT$*@N}G4VpSQ>U$L_uAT8DEJ^|MhR=2M3<%jjI8mu?1&)KaSV!r z$1pbO)_1WQ*`4Iy4FL6%H{G@%?Mt$Mwhb4+jJAbmP;i1eCw_?L5fW!rre_a>b^ZaE z6xN3hkpPqmSo=d&XGnEO_pT%h<ZxJ}%nqb}P_e5@ z&gOd&iV%wS@Qphgyh|oVPD#|nhaXJ6qQA6P@QiJQqkaPhoP6|ou!x!?{)Lfe&YY>L zfjGAg(L`^jO)ImUhEs`zs!dvAd3Cd?P2>L4r|tJwhV0xaVkIG3bSYB})IzYWHuJ@h z8pgxcva>}O8`Uo6Krs}C0VSuG+DpFwaNg?YomS&29(H9^QcVDWyvMCZb{jNq`iCD*Bku?pG{7Ac`tyTEbb_- z1KZAvFk3UE2g!fS5`lZ0XQ`;DT&GaRge#vozyCr!GC$rS3o%o#2Q^ylh+jC}4^{oS zX3fyXEa)j(T=3?hs7ao3VA6eBM~TLlI*OoyV3+<&ssZNnaL}kdhQnlURT3H-5?~l!8bdV zd*ENS`USatJ)GO>@GR)^uPH6gX;*)qYOpB!TUydI_*t-${|XWNCi=Feh_SBBQm3Ky z?4n_7?Coa~x&FaiKIjj&@ok4oVnjYSefhS2Ts`JIZF`MnDypgrhA2H?g*MV|5TBUn z=;voecwV5Za^TY-b)tdUxka0hRB{5cKsYPCS3@+SJgnu;J=m(UOcXAb3%cWJL~q zlHq2Yo15G0F_}?UZSG;dTpmsk{|kb<9KB=UA8-n{>o%yWt3PDQBJ9AK4H*}FrY6!h z7ExsX#|pF8hmIXvGP!R#Zc3%DK@r6cbuHhvj6XwuALTVq_}~Pz-pVFhAYV_hZT%al8Y&CxD4ZsaVIrBy&|cm zzW#h3LqA%5l!IqDZQfi4>*ciXEj@&vf7m8RGdsQQ74cc{yH0?evitXK_`jFmyjk-N zJ=XV_rOv8-+7Yw$A09e-G?m2p7)w7Oq&Vl9ux1rExB3{lARk^t+N*#aHr+S#DkUjbsOYs5VI;A-|)m@`vw(I*zeP6}w zqkTX9+__RFrE9tUckODfqvAh7|K3{lRDl6?{73yiZmlx5IRi{DTyBRQUTXKv}*#X3=OrSaCi2AD{hKDd8BcJ1Z!Y>XafB$Cy8 zXP-V{n|zkPG3a-%esj|94?&p*`yZ~=&+`3dc5K>~$b#p43!XpwlRNB9Z5_^+u06kK z*uH_;HHjtzPXO_h-2`jP0#0t?Vt$5A&WfwY4+1I>{?0vO#2nFz+#Yduu6C(SN=` z#LCu8l<`CT4uiqVP}#9f+bN{un{!BM)7W@-UY?lAb>sE3udUSNdwaVvYSHJQLY37Y zA~dCbu3=YDTJzJk#r|HBz)H<}?HC))q`sRk6$6WxC@T0YX>aaV${GxbUYcvR+2yO% z^R$o6>GA!;E-&-6dpq1;COGmgnLW)6HtOBV9{!u0Y3nqEbSw3x~lOs7QDoQ3{WyT>+ z&!3gXD}{twZ+S!%r-n5QmOoj#*-|I4(?DB$5#Fr+#~rIxl9G~HyNm5O4D9{n75rz0 z@X?(-Fj7`lHfnhv5nR3bxnZ?>R6~-B41h%xa$i((Eeo8XkEt1~C z$4IfNDaji9E&A4ntEfF8tHdnaedo1hn|hvSPilNMPrkTwp};81QrvL1Xx-`+D^_Uf zX=*MM%^DHI+s^v7VfgVq#VfXdKASaf^AgOuB9gwkqQ5))%;aYll|J+2N3LJs@whdE zZB^qTo2gf~X=r*_>xK$6oh`T_SdnO~aPQu|i`T9--L{A~E(%eB&yUOJAe++^KqIt`%*1aPW6=sb836+n48}XSSZ6vwZXV zqs2=~B%3)ev#;HW1zI9$E0AL}|8#3bOa*>oSHS9RmX^ZLzuL}n?f>=eR6K6LKhu49 zArFuGjvZ6j*J$50D-Cn|{qaDacDqHw%bmRQYyW()DoydM$#f48((+DCm6*#d!I+5rg!uoasEA4UR&tidPn!W}jwZ2phhT(5eZOb#-d= zQ^GU3yW`Tr0AXGK-ctGS2U;;lCDq@SzDYEGrSrmR1DBLN@2XYaTY0nJUR$ov;D6&r z+V4+#lD);^mo8uC!*_7wVuxQ}6mC{kQ~MF9ce3uPcnPnVn17OKQF)r3Ss>om)bedo zPE?e@cc+Fqw>O{I)m{JQ(xpogO1|9KROjCw9eGh4F3-bTQEL6xVAH_p{m8lxYgLr$ zL-|z-jeTYREIBuQUbJekSYK_3QUfOq33r znCLz^JT+3u_h?Coq_BSKbdS#=IATIBf6fkQ<_ z#lG#srOhX*yRf3-NAl8w##Rh`}c-Br{e<5ZKn=MYMzw%akeBYuneut8hxM|@c zZqE^$%Zmy|!gF+=9(`ihtoG|gZ^_<6c!0rD4r?6RKQ&cbB&=Ms=JGndltiPPNk2+L zi|z**F8kCk$S*J^wXySS+6|o1CzhYZmTk7occ?VTt9x}Jc#9Rctm~g6zuP{_jC8%y zElIYn+s^IWxZvQkV>$GiKR>@*x_FUc)LMRD|I}yGsQdSA>by8?2b+SPnw4^Yiqn0C zhwOyyq0l^-H9jm~{mfk9;K5}<2B($=9rBPwgfRP29Q^E9+1cmcU9RBW%z`~Ts-CQDEf zz8RBp9`1WI?Z%B83O}V!b+F<>H)Ah%8{zDpwfGb_sv$(nNf;~Lf} z2^qiA_qx9A|Ani6x6z~X`^%gnkqqG_tW(b7PG%*WjYzWO*fhK~4W{FrbNu7Osb^{% z3vN8^v-GZ^i%Vd;^v`D%+iNmr zA2{UhFrQWJ&StC`-uLOq(=Ln8qgTyFde1xL?K#rG6Ch$BrA50{1@XJ5u^>&)NyG))=7D~iZHpaN ztrbzw^-o7#^qrIUPW`No$jr}Ij!$n}gX2_Moou1-GvP(gOliko0vk5?*_B8NRt&a( zdc1plY*aVJaw#6j;5V0{zShavbgCY><9o$-w)o8N80l78wdeMZhPT(!n!{Yh zxDLhXY~Xh9(>R;Ms}-$UU^{mH{FIZab>6~1aTt!`WwfOc6OXpakM>n4c#i+d=bbQ8< zJSS#a$jFirJcUiCTB>$)&A!WOp;8-M|5z?uxG=T;^4ful@Uhp<&7n$&y}nZFr+$=% z^SFFk#m9GcV8H3*i|(Djvc|{u-ZD%&G6R9G@Y#ux1TG0{Ui-&cAD@|5ST>J#JS}A$ ztBn8F`RP{o%kRirDZQS(@3uq?dS+;^%aCus+Ft#{B(Jl?oiF#n0e_DoAP&ivm~f9t zcei`Z{QT!Y?A{f-C5F25cqE;=coF@TWcz=8%TO=04)|ube`#qi=VMX+DcPbm`~ywD zs>?dQzZ|}GysaW;o`U!6y5vg325hZK9%(nU5sic$_fyP4pe;oH+g_TtXzl*1YZG4h zmqjY^beBp=D_oc>TOaSaR4m@1z<;IEH&xyYi^n)5DJJ5Ze-HfTdQo#nKI86~Yw!4< z62Xzq(%$zG3R%+qmsn#ARfg6DeN@q$yJp|RMT{C`nv~Jc$x2E}*fLAc=G1CrJ>38J z_M>EuBTH8tAM9yT8s#<>4jz`R)VG`CHIsejy2pCh(G)p;E*`MZ(9odmnUFPhyEAjx zN9D$J$}AD>MBSG(Y|d)F^%%pCqz^oJwos^hP|&YEx}vLmhIgU#UzrB{!v)=Lrol5#@2 ze)zQ7rqQ9_k;oAOt5zKyHX277!$Fpcw>v7M*H`K}b_4iBGhSa(>Qu)q^Wu~fUv$!J zO#@9l#|F(#b$;LD$EUbm&$Kz9s!GS@ciWrt`@5HJIJ_f8UcAO*c-`{NmJ&bgP9dWs zY<6Q|&4$CG4$0SiyuX{{_=i|ZDWxHjc1f}|SftN5^&3jVL$9v9SL5DqpwnFN$Ytf| zhJj)~M8##Bj;}{j!kO@Bt4t`}cff$%rBnl{@ppTr*_+Gk!9Hn?{=1iKI=-m?n4WcH&%5j}$DdPB~HJ#bKy?%=Xz-1SiQ(y$%y4Mm`9Sl& z(@<-Sq;)Oh+_X9Oj+NaDeoFxARKlYJ3EbV?-5u4*R~)}PUwEwd+_wGkbK7e3n5Moa zF~HBv!5_=HU0PNcy8qsX3)_ZgT$N-x3n|bPCqy+=>Lzv%PsJb~AKxuGcc~NA&msUi zuky%;;kx|7!b%Wz2D@I(QxJW*?%=c4gmsdNL?Us}0E_2wmW=Oh-1pLdYzfMifL92$a%%FU_F-;4Dyvj) zWt+!@zBLUpcx&P-yYtTG6WdEyj#k&^oJ*@cJIe_W7rT`O$AeEw>UQDHwcavQ+V!rJmD7JoF5zRFE5UcS5sL~D3FXU9U z`G+{&8`%10%k&ZqWdlVImE2I5E0FGd@#_2xw$<9P^KEQwHXeI_8XqiNdXy_6Yotlk zvijMjN<-IcdEBm70W->huN&qnIqyq|lK0-aeqj92yLBps`!Y`byoZ1*ARur)g(pxz zW$J}ePJ}a@KE3D@u`DPQ@2& zlCQ7ex^>saN1El`Mt@i0&AM7+!WXi$->fm+yyInId{F(^on~wx zLeSIZaa*qU7kF^Gr-WIa)`|anb!n~lY~#;qc7nalrGj#DVfp#_BO>&=Aq9X=9_;>T zq)@<)p;X^3oLT}TyB~I&@D}Af zU(y}gTd-kU`uiG8C1xtT)lA2CHDlC5-QssrRcxK>`yzG4IjW3}|4o66ul~92n@7-f#ioFFg9h8HUf9tRsQf=s2LEku?BB8*|6`YWmvJF--MwJJf(X00OU2HktSHO!^t`>%JU34FY0KxQf?yT6WnCI8 z@9)CHTf>-?W3>KOjpC7Jl(_d%It1swLpXW`)LR6YQ4pnagRulqj!jUl5%@X^0F&Vc z@c!)Nx7*zfd5E42hNh-wENY&~(`FkL-`c>Ka&8+CZvmJeLaX!}rbsR+Tv-{ZiprhyTW6;}#{?(}AyPxO=*O)P4=y~Qd_XbDS*^N-*U540{M zTU2^ie|PKMgPgu(=1xYCp^FglM87K!%}WOMxPm%*y>)H&UOg}yfO&k9lEL`QAN3!J z=33J-vm+_J1DBLJJ~q5}rmBic9f+H|xKZ7=sN4f2?S7n>6V*$(x^U&Ly$FB84Prxj z&yLx=PV*e7yC7V;0W1IOe1an0OEbq-(<>4L52~l!uDVw&%6s-q>ZCJU18o=pbdqgfQHieHuwjFshyuM2i+%a|NaAVkSY-wiYa-`9 z#7K1PG-RMmd3sfJf1J)S)gwoav?M4kcif2ki^47luKx5`PpLBAz<2yG)=q$3LQqkn zI^AI{Q7qU4TfRH-KeO469LYe=DY8{XMHjsR7&33;FJ*8Wm#$rt?Y=@0Q2Q=>)tseb zBYhR>#ATrX6?6QhPmr~(NM~WtlNY}#HMO(=vS)l-clpW{6QrV%Q+AnGO+4KV>A-u+ zOqJq930wj8B6DoW;=6M*7wV@F)zO=44@A2S_6QrKZ!CIpg+p`|ShYUM6)VmV&rsbu z*9ZVM!h`u9`cpS^;>s^Rici0c$3=BbiTwD8WuAe}8+One(w5(WUO&o@bSdC7+Eq(g z9a_u1r~(35-K)2L$r02$+VT3qfDO&x-!Pwb*US!aB_J3(;I7E3cgW z(zSnD?bvXq88W)QTlbrh-wB?ks2^m;1{y+Kf2H-cSBh>N!*VpId^y>GBBL2J-$wKD z^TFa~J@}`%b5CP~2TL5*E=p_nnK?>_+`6YSsP04gq&p;@tKsG*gIv_?BmJuusCOmm zqPbjBU9Z{Hk9-b2iR4X08=V%L`W+=Qy{c4MmWIjNjh|&c$zx|~tsGas-=O!>WfRyO zQ_vI<92Imt)7Jenh{eTXBJ)`}yB}e@MA$6fa#E%~73gl=yaZJWFx#{Q zvSvx#MpYq>I^?1DgY77rfJh5ZeSMUWdHenekS;RdU{RZ|+FkSUz@a?|8RE&IsFNf$ zpVBtJp><}i5@007vL}ZX14RsW-B`6JiK`M9BYXnP=afk-@tH$y!tP+Gi~lV7+nILJ zH9(ERzIMONuLcl)c?fVSU|b|Yi9F@%MW}n~IX-5+I{+VHe|h!$x|SX9g2gW&0+pi% zu^eV&V-wO%5_}7jRg=gNVkv9L>ycXvP|^^73Iw#c5@L>nl)aPAtbbZf9V>Xf+a6QQVLmXNK2h5 z*_2yGx9Q?Cv>@oi=zj3OSZosMt$>BX`NiCmLBH z&rKDvgJEuNp019 zg?T)3_5SGuE6hRX)q(ZBXZX_P^_w?Mr!LVw&WxQjUR-u-gGm+OGI9}%5keL&Z1Q^W zQuiVw6-)%x!&HfyYPokMNw!k9|Ld#gH6BGPgY>uKtCYYS@_d_{!##gcW%ApCiBYWC z2dp=`^c_P2T{I+P@^<#o;#(|R!A2_7wP1tQ<3Up_yt_A|!|GzPd4)I9ZTCoT=~I)u zxx0cy-l3MwpFB@yD|lDCzhyI>sdAhrzNU8HdK>ESNq5S*oJgOvp|(f#$L{VLTDq9q zT5YHt@HZ)q{~AGyu?SEL&+yl-2b|$GE01VMn%a2;@v7)jNrT1vKaur+Mk2(*{THX5 zMP1g<0B@ng>_R09V6p-{vrYYL;fA3!a&+lUP?R6%wK8k}r zn@27L$9!*=hr7SOzmLRb#1M`{ZX2}p?CtFXZNM2Cuo zV710s6d`8Et)R`s9>SHE^Jtwh-0v7yUtdpT^f9P?h#^OReNDT{B^?N&_(nj$4nMvu z{iDZ^uYq{TJD>H4Bsa+Hr@uUMMSMVO@)#5_dsg8kXn7!+lp) zD0p|hU2b)L^TDbNM^WHmT^~ZUvE#}RVs{@te0c8ub@<5#yppDYA4fldBUeM{9;iJx zZ~F+$HvF_lawjjK)FVDXGLL|r-5|WOZ^K3XgCCBJGm_+aDboRy0lv5(0zh(@lZwzL zIo$D-xjmDeUcU1ZH*La3QhYJSFrt$RXo}E6N1qP1a6eG3=Tlqi+ zzYNZQy~l{tDz%V&3hgNLP#v0u8PY05oxPuZArkD<*sog*&b>cCMBOAWLCkSF z!cqEm`gJXU7Q~ejTp@P_8=C?WW9zq!EfC^YESa5zthw2;DjQFT=*tHOlh;s@v*oMx zWJvPw9=B6b7nR6)K;oh9(^bR$oe(nRMg~hgqe)2SA{t#B9Yw%}j$-N9%Ak=cGW4YC>j`CJ@@=-8j|ScR9gr4I|CS?{-Dx1|>PCzmIuwg0@X76X|dENs{r>N{dY)o^ZR5 z%vBe`(+gS&!4!R0;$1h zx86C>RvU-|2X>d#jAd?%=)~Nz(=3x$>!+@#WDQeFL}&+;SrR%y3w+Mx-A^|#^LAqH zf@}~)I1lbR$k7l3Cf$X)vk{^DnQJJ$7rVWrzv1OYLfi4N$8cWNLe%I-d-Q$IN8)iO zdW^atc5hq1q`2G@JNw7$jr8pR3v3?}jSz2-LKFS$&3Ae$??e>TuJvN~fiygAf4qJH zLixM!u|7dajW@I$Xr(m6X0GLQ?N!3-d)da-F>M4-qTCjI+HF*HbFC6Eh}uWW zttx}7;;ii(u5A%BFBbrx5TNy&xmuZ6Ed%Tcm;>fR4r)$skLL#q4pqu*sUzGO7sRj8 zoi(Lnl}LLU8is3N?UxN_wq{H3rmLU%0^d>MaSzaPn0P~6Q8LUSF>TBT=EnZXdv;dJ z(Ra(hWysPWtji7l=C3bZw;-9^N1lSRFn_}A6}^G*L;dSZq<=#UCKePWm@~*T6?OGe z+mfxk*MC+*k+z-4k%BRazc^Wn!P`myN?J3a&>lAHJPE<*IM^? zu|qsl2e%jqA-W?1dvK%0r>o1YUihJ+S}dZU580JOrNREJ@jU>6_NpMN(*9Iih$lnl ztUce(tTKCMt>vT|b8UR;S-3jHY#Wu2SH?4SaA&w#Qb&4Dz09-!PzXc?-_C86YQAY%tY5|K@ac41wE{QUe6be_~}8WGqcgvt}^maogcr4JJ&w?G?dvC`jVfU500`6jY;&h(yg$Sutfe&O^eIQ;xFcW81 z^WKgbFr7)DV!egT-Wk?OXbs+;;7q(OsKJ4rd?AnVx<_mG$1Op{754OWp!a4ysJ4c7kCjgxD?=Zlv{k47CcLOmB_m53|BiH2yaA2DXxRA~yTM zr6 zUXS+IsKXrsLs;P-!aM(h?X*jAVjcy)IP1PD6JMLL!R896;P8PJ>Y9a4%?3JoQ~%}) z*v%&L#Q1IZCC7nN?V0JRpbdRp1XnA-21oWdB1pqM$0Tua`KRy|GR z^63(73%4BDdh zYMKohs)d~klUi=oObxl0n{H5#&x^D zc#aPr3iB8;2mP;vykna&!8T~?IQpD?`FsBuM2SRPzU8netZ(n5=LjALQ%^Yp516qY zMh`giSe9%&HXqV%J+xUoDlRf8z8LY_r za-wtZQO2*A{ojT2`dcdm7x}=QH%V@^Ps`A)TM_maB?>8c8qTfvgN}WC_;E(%YOHf> zBgcM;YKYL_oaGLCf~!!5oylw_tf2!LCh%Hp2qkh1GKEx}3oc3^^meYxk61|N!L0;yR)~8mg81SD*uq#1n*F8A@57Dxe%kO} zDa~ZUz^{w`>^5qsuXjTDo-yrQL_h0I(L-TddFP+9!PWT(9LSxkv=jgU)fAkT5*s+4d4B;h!<|{-s2hDb&LH=~?`V$oY3T)XxgACg^08f6 zyso}*Z34*}#vFggRMrqBGl+N%>*StTPe>e#)-3w;_m!`H9FNUHmK&Htp2B(w%-@RS5bKE*4zYQ=^sk?b z#7g!ua(obD_xcjsgS`=x7)TKHcy_gLX_xqIrxg?0f((EsT!7Nlw<;(kL=|}GRq%Ft zKs`HrUQStTgcQlxp{S^V5)zc7UC#u_waERxt`5~?Br>~p?A|l}D|b`5Kqd%spTeVM zdv7PLZJ`4|{ZLPlDAsH$Y=BWX*B{~yq(6*dy(o4q7S_o|GPueAhPfUm=~2LYv}ld? zMfd=0-~v3_rU&IQNZ)n_d2_805p@(NUONlOb7ZiWT**(T@Si4%8G7z@?ApKVSQs#Q z*Q{x~I0~-^X+wWG2OsWko6HPUC}f1)X?eoOM+EWUXa6Iz$l3+mx}{9X5!HTcWdaZ6 z%SpAwlQsn%`hlvx5fxhLUsjXyDeY*)-ldz4Ye#3oAwv&BBy*rXaVEi)1z}>c9X16MV9jV3lLS9C)+sT|GwD_} zc6?(xx6|qK=g;&1*)m~Z`AZSuf~Y|r!Ue-{d6Xi{=l*?{>D0B*MuMqcse{lYaQf|q;=MgwKHa1jf?E%BX?vjRPJyCSZde$_|J+Agna^dGu^0bo zAVe~GQHKJ8D2qhHRdGUcRez5q(wwIFcgPu9mvWVMlzNC1ld`e+3Si{i$7CV){Omrk z3g0~L7Ni0<#wxB}xHP0xzykcObpRs&`~?fP0a?MNq z(yRD_KxiK}8ZoiROT`Zk(wBs4kP*UMe@V(g35LO8&%uKS)jra9RGdZPtQYa{l+M}m zH5-XyH=>x3#=R-X>VPA1i8sC zFfg!bqQpv143m^pp+i=pES%x8U%7wW&{#%GLXmWWF?I?f2T&mO-t zri4nr@Wj8P8g4<=PAt>FlUH*g7KtS2bz zJ=%DFWe%TAckEO{itPPS&%CMCdC4nY@%zf;!v5>)>JMn#ozL&f*>3}Aq#dWz?U?s+ z@W&nSpBolkWr!d^0`2)lWN(3bi(9rBfC=2Zs_&r4TguAz)D5svUdXl(`+sEGD-KO@* zb~}nv28=O>0yDwstD4}SKIZzTCJZTaGMI*vsTDS|cG~kzg)bH981`F0{d_$l|L2#i zT8!=Z_(1#7;xopaw}s5aqTUB;-QnE5dS!vorLsVU=IUMcTcuU(KLlP5tv+TYaBu!k z){pz8*9gU^DN6Wn?jh2;;{yh2C#@qF3Wp8&XkHew%gBI?emRbDpsKMXZMY*N) zv;jJ(%s8846z#!4^pMJ75@v;u)dsD#)9uZHM+f1g72l%=R0>XL_R9otQ=V4f0#G9;k$VaUJ{Daf`Lz%yPj5VZfe?|{bLtt@!vT*qfkI|s_% z8!STw`a^CUe2&c>^_BmC{FKGDcpj#Bs1ecy@9>0}7!s9(|MlzHfF8kOrZWjZL(?Xc z>k{tkEe&baIN$sE{3Mi6pB$Re|ubXu(+x zv+Y*w6t%Wfcx#nH1HAVTF3k7gAwV3INxv2hgOc;b*;x;lt>u2ANrXfT{H&kZH0&f| ziHJ)|YPfQkIC(d1x(TRr;SuxQekVC7cpI(HC(#1jNgA#ywLLyV2PPTP%Ly`~ENz-7 z1?>4u*K!i~f}Vlf9-~7WAmJxAPhTk@@E%AJ$uik;0#pkgOvx<+ou~{-n)=t0+;+YP z{Nj-!HUm7SF20SFJa5>NIuFp3;L{Pu#IkCaExjTUs1m0qjFpX#^~(c^2ZipN$R&P{ ztqGB$tbGORA0F?NNet?9B3d8J-I0Xt^yX4ClGuH`XD{6j(=^eW;K}%*WWu6BzCLm+ z6A;8hwtKjr`Fdl#9<|5-z&LfGkQxs~i(1pj+5wb-dTvL~*{T10{pfu1qLK5QIx<3H znUIc;PtT00SO_KL{|SzyW&#q1sIlOX$KcA2ZHZl9<}8DT@Ar*qnk%1i^Rabb+oObX zaQ2i6QLCVLC;vlp?YDMXCZR1w`z4mGH)4=D*Y@E76}mCUafhh=Mf55;Pkc+i3D#=K zL{jg;U5LEs+D;CY-<3KOwf7+yk9wqAXBt8*OKm(l< zJs8llRhuSn2-LFj}Yhms3@?kMAes-m0{~~+JF+6 zAejT&xwIsPP=zrr`uNOs6{VkvNjI6Pg!9%6FcfK&a~4daW~SR9+dfkC1VXj*+!Vaw z$ETygrg4S7rdYh;LZs1pC%y^d_hQff?{pupZM-8v+g41l!#jW&+p3e-4)!+7KRlrQ zVZ*oHZS*R>`c(hj5I@mZuuH9yNmws{niUJLBFUIBOsh!L#cuD}^y>5fvgF3zja}*{(@GxH2nEqPt*_p>Ye__`j_!O z|4XIE>q@yApk2;vt-YQAa(mKyL|EaN=V;!`)g!TAsQezE!B({vhM1sUI*ruYPQz3$`u&jny zBZQU#vdqRkEvY{==0q5)qcj1OG90CZRe5Lf4Y5fu`;M98<21T-PXLOU}b zh~LWaU!h?|^5Ga&w+y&T1e1hmxzhB*W-7rlMePIn?!P}S64qI7S&cfN>ZzH!4jibB za1u|u3*aaCSdIAu>S4?F_fr=kG%_>Es9*4J*MTQycIU#C3{!!j=cof(nzt9SKl=1{ zF8~VGSj5pOYe+&3&})2Py%Fge#*|~zr~1IIKff3Wv%nxDvu z8&R|P49=(>_?O}7e~H`RKQ)%&KfJF-U@a8zxVhj&mb=XDn^8X zIq*QseQu(%xlpl3TLNuz3xG`I8~YI3H6eq8ncIq{J4P(HL(*T!m<|0!LiaxNXH0uu zC#KhYfi)3*8f0cTuWLsI}bX;&_X?QsJ zo(+dzvjTuj&IT$iK`*UB=e)~sRwB&_>!oxg7U2kq;|$NR(91Y=qU0pa!-%?Az*e_f zN(yacY2HBaA>t(x=b$2xG)Qt37N~S8hkiB)NXBY(q(u+z2D=I8`P>X2w1}ch={lQc z7QIn^5nEj>0x5c}c>6LT2|Tl^2_rv00ba8b&hm;1mW!7z6>Rjhw6r`n6hRIyG<2_s zJMptR8Ft>y(^Iz6sz)_lvJSY}*m#7Rp7F8Np^Yk<0cV$>R?W8z#|zOsyD*yqJe6P+ z6B#1Joy_lYMeH#}c{TzB5m9G9{X*sFclh#Vc{xPr0fJpZMBB9fX3e4Ep z)pDR?66KJmO+lHUmrQ*7KIY??)3ZZjsKKC-LFx-lTcBP)APAcA05(wd*7`HhXuXF< zp(Y6+tyKKpfe-Ol6Lqe?9f^kF1t>^S`9XI$3>tx&+YmFaLulUk!3v0uTGC*sLlrAZ zTL>km8b}XFI{T(P|HnFfK%OB>5}UeA4q~mcs%jL{9dz=Gjg5_avp|g!aRy4Flp4`M z3=9?>8q38X0zRFW0z%T&s#aXB3&qUeCHskN^S9^39i{(F`I3r1J`b(1@9{tjX2dg5 z`-4^L`V%copG_zFxc*!Fmo8qmXTfsET+2D5eBip5q@T#v!c8(0H}L;qMB-gjK2_OY zjmwjJpoPrtoxWw#OL%xB#j5U>V$QZ&ZEq;hB)1eZQX-zcg;up*AZn2RhheOIUC!n zswXC|eyA%eOMG6*l;TZkYoeaLwb6Vb;C)024iNJE92zo!x_bU`-^-Wp0Yv$&)?%LI zKp-*r8hC}5uu$6zXsvF@2S?k&{ru>lsO@)Vo<$bt`eKd!@WPO+hod#iq9)mbL-Fz= zV?I!Fwd)+ z#2uJ(6v%P{G7PesF+dviq6+IIo{o)G==^(JyrGeKaG)q{2?qISbive0=W zC(zS@uWr5Xh&>O!xumj<6eOOdT9s*h)yPeBw~XHN2mEaOyGe#$y#T)X6(-vJPopN z=}sg{FN(5gZ7775C5Kwj!r2W! zzips8ePw!BK}zu-%9L@mVhY1~yz1v8F%N+A3Dx+6&GO^U^;f7kw%^rbe+mEnPj|R7 zQ16`XiI<;udpY8=N;+>jJA$v!^B#1{|5v@eZT??Y#9x4G6PlKB7#9BbDh>bUk^SGO z2dQ@BY$dW@!KE00ksu(Jh{gdz<7iEyho@L&&fx=tmQy{>1n4;$FHjIJFAs7_65W43 z_U06l_*Nrhgs4J#ZbCl~2kB{0d>N#VqxM8C)8B>OJf(?4*8R-t&0p=2HQ5HphXR0^TiyQg(e!LY0e+1MCu;4pW&59_y$Qi9 z_`#^r7l2>ud}mmjilLb1R>PY&gJz-FB^EY3lPsTlBxUUY;#K~U=6-2@sA=*YRYX0+ zRX-O+l1m!BBFbm5{fLu354&$We{X7uqD;K+u9MR7c3(Ek;jX-3+hsQht<}e3tYQtd zMxv4F#sk^Sn>TM9-AgRh^%V+9A)jC{2@zUf+RD~CNo~d~okgt+ZL``w@*AAlj(79U z`>oM6@y4%BhE_M-YjD9?g`vLvR}WmZ765F@;O|R3eIR z0B=9j7R~R^-~pgv5h#XL)YN?7ZL~d|mmJXVq#qszH~K432ynF(*u3*R=oJZBGzpD4 zsy~^wNJQcbAzLO&WYwSW6_%z=a@d7;iAFZ`6H(Bd+mjmD!|mH%QQ94{L8|3h`U(XQ z(Yl~xX-JaAI11n8w<$5U_SxgzVY5fr$(bekV*c?jPtE3$RfyzJVAg9!Ornmj70<{K z5>$DF!t`mgmt)r{#_iq>_Aw5VbTX*f+)iMg{jdjW-Bs$*1o9NyKmiPn+60N(H7xo+@D0aco)6Z=y z3|xmsl-kQ{<1cRY@>dJ28OMmHNw43&1&_miGOcMb+;cH|vq09AXS%!6C^yiyG;0lH z?8(3)xDZ9nKR#p!MkgZ=^r|qV<&d~N{%Wp*^{F1PeM`c(f#eebZtW}l5rp{#Ep7zt z4&6WR%)aj&Fr2|3PMVF*FwG}{#7hmw_M=GSt7K(EA-l4rbE_!*K}y#^M;|3A_~x8p zjv!wWw4JGNC`96fQDsF^F4mA_Zsdho)Zq*78t1_hG<9Rp(1sZ^X2|s9^TXD>0aX*p zdhj{5b&G`VkaW<8DLkCUOkfAt!P5;Z@GewpZ=mbaL=*m0uge=>`Sk=)>4o`GUB%+1 z^VxrUj=vB@S1bx6WZCu^2A-%pFhNf?qW!wPy+}rf7*%;&(KJgEuewb46bOa1`j)VZ zLQJ!UyPIJRg8*esj7L+0EIRHL*b8m23#-|aH9qPKILERV12ae!1SP8u;ze!jtxC*; z5Q+FLC#oSp%H+T-Tnedu;lWd2y2uI`jI;_c&{R+((KK`JBdsu7q~;{lZWIQOe?f-} zdiU=mxCIaKK&gufbGZ!ou>g+)O%*CA&fw@elskkGRNNi|#;^e^(1ZUJw-i0ZiqTD` z4)3tQS5O^IM#;pFF#*tEY~d~P4-4boYy~P2Tf*{dsvlwpjr2Qwn2$0z{&&Z9XF1k% zb~bhE<1OIpo3)+iE@S3mVPzcXfyNu^Fk|eA9i{O{*q5wR`A_4T$yd8d9g|WvZ`E9t zXQ%Ipz}iuh6%KfFVO@Leu;*y|A;AYOO*_5$n}le3Aa=wHh{_QuXbyqUe7iM>mpc8@ zfZEc~Ge|MN6(u>O+$bDvYKHjGiCEnTBC(bR;33}E0uM(+hEm$J*%>Q9b7p{##2_ux zoB&w0THs)b&IFc1NcTr0{(5XGnpgldRDu9v9pbv9d>dfjWEwR{{Z6T=sjBX{Q`Y%I z-peC~dyDiqP!KE4d1~IV+L9Sn@yg`;OYX*y@D#gkYQ@lqd!Y^Gj%K zhijR-ym3Ume(Paq0>mCue9hSGCI)`&1muE~cBVnPy)l?anLkw~H1h!dM~t~-kk(G} zk&jL(CIP(iVCirBcm)+abmM$9-thc6(FdU0`r!fFJee*A15@Q9Fo6$lQ6Jp-QHw0) zm^;uSQciv=#LH;x%{b?;@STeToi@2q`Zw}pXW62;F( z5W0J)n-)`c*gRw-qiPyo+qicXm*eYXVvnuChR7Xi?Q0C?Z<0ig5I_X@+w4G4BR89_ zr{m;ZhoXpv(L%BhB_J1~B;ZNUcn28$8QU5&`z%&3Vfkv4$ECEdwDVjU_*#0YIaCz{ z(;}~{nGc^HJZ^7N29)if84M?0#9uX>V%nS;Gkg?As9W^ zSk(W;@hnX^gxRPb3g&%0RR+59#O+)5B9wC1H#D3XAGU3%T^~~Og*sk%R0IvxI4irp zB%g;rs@{9~Nd~Xy*a(Gi((yq2hK|h9iZ_*x$I$S zgK1-)+;E3DL|xJz$r9#~VZlC9W1W8PRlA23EEz~WUspJks|2v+ZQ67)A!y764qOu#t|pJ@paSdlCiFl2z%129XyA^4?r5rfO=dd8g}KA;tiUdvsVUhQ z>i*|*#2@4{&hD!v#M1%xIU_8-I+@)yoYAUsIfQgRluqx5f%@1uIP&1jnlkc-Wepz3 zM&agu>OKe5P(S$C->1aSr@iJ8CI=onk`DzI-EVBTZNW#{P1_CaqGW{72toQCMk(_2 z=uV}r&(D7@YxJj8aP&`vIk1zHv#wc*?7hpt6XaVm+j~gn_XjPdxQ5!`yEUa8v!^*D zp!u4+3WPe*=uWIkgQ(}=EF`>$KSUVPDMCox`-R3W;YM1~g&?r&rhUL%2mNu9w5Y9e zwN9c@w1uaGOLwtfPaIA-&nXvdQc`_DL78A(Xe#EUsWTJdu*GcBT(~@`1z(|b8I<59 z2lQ>G5{5?DF#&2LYvu(5B^2`zS{EYxo8aLs64E>m6WBY8i&3ly#hooxPxv8Y)RBfm z-7>IOhoGH5_}6v}{Mw4opavJ}%RMbW(&{oJJ*8wf4j5atlzM6mWnC znpTz%47UeCdtSx}?sUD~57m;xG}dxuQmXAB&(6?q$m;Jp6nMeTroisHEmAhcar|(j- zQ_p7cbTUnkF+vIdLTdcaGG|Q1AheA}RbryccKC4+9Lt-O{q3vKng#w^3pDt>dm~Pd z8aP;o%+;h$qMei!c&I_pwL3+Rph7ajEK3~gy%?iM27cc87wuJ~6bDt*3cZm800q>x zZ=Zz7-xu^mKxF~>gRt#klOY2?)@?_+K}Ka>l-vELaGrjKBRme6+i#SsFt$45^D z`dp~PcQIIW0Xut>wG=+Gqqe32{{~79&7G~Jxfv63dYXFa=eFbriH2`c(o7k6pjNo* z0zA+G(gRy`|1C$#bpfaB!W;u4_i+aEo?V4&76H&MHK{{7^#thqYxY8dsyIRq;P8to zx-KA8*}Yh={L)4Vj;rq69bOO82H1HRb3!+u{@E#R@u!47jjw8x)jL%a;r;#pVsPS{Q+9!G3{O|^$w)2`Ad5&f-EB9zr68 zyY(=nMW_;W7_!qqQ=V);D^(ww{#ZZFA7@_!Tab<(M&|`S0NQq?o{)lqf(Un46zYk< z8~i6Dv9ilRu}DRc?GbF+^;@^{(b@Mo%Tpfoe?0)bOotn_JfRxt>-c%+Z0=cOuc|I=RbABHB}a?!9R1+>aC=xVoxIWn4k|6u-Aa1auq>02^)09LARMc)j$(=EmQ(`M{ zIt|H&AFuwaU1<|CdNXDZ(nMw$w?<=+2MM%lFJK*7V%(bQ4vfVJK_=C8lz3y6Zw*Ej zY#%4!ha$!jGX_vVK0wa{Fmy!Bbu??Afe=L9EDV}24Oao&o1az$g~GN7Co|;$c#!lF z-KndBCdX`46@~kXiVqZfPaqk>p{LM0{ans_u3o@p@o%ZM=&v$`dD0CvT}1UAz;nDB zSV=MVr*F|RG`iBPRp1*V)a?;+xa$V^P-cC5LmlIhbc*`kE5Iwe{(}|VT;v9Z%r6i( z+E4A32yX`r96oAHzZ#eF1egfjGTw0cb5)ETN52p(nvEbz>rnDer)B~eV8hTueG#Pb zq^wZ(_U|vH@scPU#RyYboK{>5(Jr(O?XMWcgwN zQZrG0I9EACges)s6rkTHH4HPQgFq44((Fkj)Wr56R84sZ9BF7BcqP}OFrf9`lV_R7 z=;$cr2C8KG>`zN?Ptgl zEJg@eO8aJIqhz5N1B>mpXp3;%8n}Ilw$Z73fmu|~ph@zu8`zJCegdMP3@`(FLy;-! zy}{UaIv!wE0~+Y8B9qVr1X7rB+M4Jb0k7adi+>PBsvx!j3_Z!R@fh4`Vt4=|E6tL_ z&@plNHDXY;ljZ}u1o1H!56Q=3ITAFim^l&WsR0B|J(QEe!`#1Ep5q20K$xKuvp)7X zT5-6jwFLXN>5^dBUG$h?aC`43J*#dM;oOy*xy^@bpPz`s@dR!L_*kCQ_|9 zr%GXYPDYyPri~|o@03EaFn1r~3PH2$fq^>#^p?svS?fs5oH9ujnhZ@#L-wGi7j!@_ zyKM;LUG4LBC&c)t`lWxt*37=1QD4Wm(h_4M$0iq0v)Lu1^~yMUC}|2elzP5lKR|6s z#H*5Q@#;?~m=4@v9olR{T*7dPgS%{$hKJ zTUM=y^Gr0Z1Jo&K&DYBj+{l}1}eNbJH0Iy1s zDbYOmpkI1(To~sd{x(OY8M^HoL zJ+q>Mi++^_n&AgvyfsHJC>gncYU;pb5rK}`cJoeCFD{K`K`8bvxw;Qq0a&CAZJZP< zaBy}a<50~{6%V?1X2?oEME1odwrje1pd8JD7;fm){f2{5Cg5DeZJ(o_Tvv#tq+fRt zqt5dkZeseK8)9N&1MrRBl5rNpUbv5PKku0;h8-l|jcP!&Os~a<@fp0GfnPO{YlIYW z4uCJ`X{nsF1Poraa6+HW8vm^U>dy#7w?Ze)J1|0aJd3ND^Y+b~sR&KWIHl~5StAQX z$iW#gZ}A%K)s_YnOv3=Aa-Nn*txW}v**gq%ZGj~`=<6|{Q3tc8neT(%zvIV-fU(b! z`|fRC-t8Y5O`HRrcKUd?ujdin3co)GP(hx>+ZAc= z#^RiVFKnI#_(v;ifEebaf0M(9&*FT~`UEGk1P+PO@#Dv%O_enTLg#u0IUF7g;TKx& z7IqfSwhNv?Hw_H`tE~g}SE+LVanj<^-z$QUzf}Z@Ng+1Y!`K1KS~UaafFP5{6jIdU z%>zjown;rozUE=jKQ&Pob0#$D(64hriw4FS^Bv5)v>uKR6f;aoNhs!}ltUX0{8SEF z!LLQM#vG(v-dJvh4t!xC)HA_`mQZZ@f*g07Faz+G5oN@m9x6axY1 z6^0Oiv+Zj>2K2Wc0S(gwlu;Z|Ws7rQ8|^_8Ge8CtizvA--Ck2sF`XeKI?QyKotbyx znM_@~4!RQudj<`Px~W+y76C#>4`-5J`(1bNdgDn@bek}mvS=e2hiGIrvQ#-L1@4eB-;X(xTMd9F;$y)?%lA1H<*GYhu-5PCN zrFpXXz)SzR=1>wSL4m=bkV^Z-G)vyl4u&o{GVEhjsTKtLywS4iI#^DwQ|`C~LHt4r zKsx`xz{SVIWnDL823Q0l8*wuTbQs$a^}Y|G3sT*5s_QZ21>|&7iF7CUSlJ3Gq^`A? zYO4%VAGw40#jil0!9yp&l;Yrk|Jwy%EbG!|1a>ocXb=2XMAO1$h0CR}5*Q&M-*Jlj z3MxGtglC%Fg`I~O(LnsmhXl+Pr>T($Gv1@ca8=!U(1|xNugR1}oFI!h88T=B?r##& zCVt(}`Q-8LY9G`|G!z!dk7L>5TOlE{v(es5zaEFVZbnmxJ4WK+bjiZIyiY{3UV=N! zgTPxUTq+TVVv_tYZ^zbO#S% zVx~GqF3pK(c!qbQ;qtTF?PA;SjXAB;!ESJd)u9w)UT_BDp13eewXss00UW z(TZJ%EMPNSf*!w^YY84x6xbVTOQh-dnA388x9H1_Mb2_VE&Kq0DqytkehGralL&T^R6%Sss4TmVaZwP%STZt+_d!cRicCED zO^W88fnj~s){2q|_=kR96OCiJifMKDl~CUEc(~h$d*^nlWuc>lNDR(k7P#GeI27ao zjTOmO-?QhPeeVVMJo)>^(Yr__Cmx75tdQg@2bRFh1?V->mYG<88f#1g|De60khl+M zLA}2?oYMz>b`UlF8ng&WgF+ajg24$fC{)Rvjd%QUantyBgs6P{CZ~6px9}P^Sr+(! z)-$YLbpKzqy?0#CZTL3+PD7I-`DmcHNu`}qqD4rAQZzKA5)Fxlw1+gfNo6#JiiXho zv?&@&N>ti|hL$uW&vE(wet$gA@Adk9pYJp7*ZsP2<1?=Jb)DyN9>;N>7YhX==I_H> z?QsPTx#)pVPhhR4;3A*I!=A!%8V7a?n%|1810aaNtV#3^xOIq^eo{G{@Xu&LoB(t3 zcPufwS20bvHYsNeq#?UW0 zIA>8b?#A=O9BXp#6zK^xLEW2pg>;W$UyxXbus?~2I)!GK8yI~>kkZf$KJEvA4AM$N zxHlp^<2EFyQ4diASqxtRLokPVS*u!Y5IB3$&_q_7c%*Qmi9m~fECq5Ef?Ygj%Qa)T z>m7KUqTt?`0ufHewdPn#<&VADbZ3=Qeu={&)obe zCoj*7zTiqclPr8us*B4Q;C7F-wY7i$-i<51yZmt7@|{s!T->Z6Cr2)qj;(7_kes~1 zXQ(3$;aFZrh;^<_TH}p)O?|!1+}xb2=^B+%UR(E#B+nqV{4ExO4Znc!zDa-7b z=Ebkla&lHUE(0BxU~!w9pZ{pQh$tDWC1MdKgNIZild4~R*~{zr@#AZv2eDoU3=O06 z^YXG#HBWuaOiSxoyz+A-Hemr!Rqn>#SLr3e!An0hH}|JBOWH&A=A;nv1*HzDsZsbP z^GtCmx4-{hUpH8`_ns1ejx^ikqipb*Tg zOz!07D&DN=_w&G|N4}S8`IF9`jFvcFt|h`#=-kT^Juoy>jfPbw*L3%__&)Vi7D}dx z$=SKNY=nI0v8l<)n9xvmW28i>lb{k-UVBad0bC`sdi82`q0!ON&)D|EUvlxRWT_Es z>;9BWBcBfIogVES?|IE}+TOkf!%J&YqN1bar~?vwIe-&WlU4A$QzIy>2Kq0#x>{48 z0GifVg{@k(N`^X%vlO1@shNqPIa41!C;Qm!!9HFK2{8}*J=ThFm3GNyu7@= z=L{hqpQ1iNvdZ5fb+Y)>!Gi}6h($fakl?1{+Lg4Ly1G?oRd729o>NMdnscV&j}s7W z?pGbw)btNux2yPF{|k3dPmY}~wDiRhsuceJQXT9BOKd1Sg{SY^Y$dsVpWO67xXDF} zmFMx3C$E42yT-=qRaIFiV`F1CPtOh8x94|ETg$GIG|BU6Z#OYIapL1$MO9UyD_5@I z+DR!uSuR&sSBK-!TGsBm5(e8Sd*z!J!78(Er@jQ{v{^!pY?3Jpy>85tRV1&|~Ib#x9M3`$QIAKQ|t zFZLQUQ!r$~cVy%&DoA;wu}aacTl+n3@N0f{zu@A+XvGP#R$4}e+yLjaFzZan1i~jf z{;rIJ;<~_tT;~7seUl9Jgtu>BGc+_rKbM-8CL$rh zSrxf%uyNxGwx*`0>j3;*!Um^-y~5d>VIkn?_=P0K8zuwx`%#%|CvWV5N)A4A8=Iv< zLPB`=4wGHj?NrzID6AbogovmptAT+5-M*r&O#tL1T6%r1UE=^bWr5F*2YuchZ$obM zVPa8feF_)0Zrg2ICdnIL^CCDcR7>Q|DcSjbOp}>f;TtqU)D9o! ziQDC!@!Z1d+_^92y|w_rG$M6BdD7L@`I3&p$I$N>E4$?FgG`hVk+7 z%M-f4o(l{NG(xopkQ3R{W7+ZS-+xz7e||N7__WvQg@E4t{5+Kw{Csc9k-mWeKbd1T zy#NhQ54SmAyqKZ5z5?Ver~r|BJKT>*52hlvv?!&S+Uzc}UwgB>T!UQA<9GSx%a@c7 zBB+9K?_RN}9zf^tcPI7qe1N`t5DQ%6sw&M^vQOXFK6maM-Xtk4ZNFXeQlA9pD%#3E zVgXsQWQn<*-Bm+x1eCHz)pKd$K{@$o@Ez@ZpOz) zf(l`W{?KEZ?466!i0SQV8diXeM!6368=sARDO{Q`?`Dks z^z!Ib+?j*lzkjbgVP<7(XV;Ca<#%EC2Hnr&s+BZmtMz~OHz|n($g3=0zPxX6&*|$iS8KAASwKMG)albbI7eLXxKbZK_KA*O4WA#81<3mu8w+5ckZ~OX zs*#r2G{~C!XYx}!)4_zv%di9Eo@6kZ5m06V{Gq@SSX>oCJ{Kt=1NHP`( z9|{TzC|N{y?ljB#KKyPA+w$eXB_$YX@Z}n@wxjMaf?60MxV>DpR&>jj!~Cz3wsCQB zWlXF=yg;p5_T`HZGcz-l=Fdkphll$4Es2SZEt-(3cNLndG+9kaWAH~9zrudI4lryA zsgiZhhZvfdXY%JnWMnwxC>%iI4~GoS+PF%%tP|cnGi57IL@)Z>&42}Cz1|RRk%W-G z+jsAZ8-4t=uNfPRhVoC*{c}YHBj!8MD{t)GSdND(1Lpul8YZj)zFXgI@wfpSYwPCi zS{vo$R^pD{PY_bZau1!+uCzxH|0c(;2Tv$M?VvLS=n+1V~oZ1moRzPBqe zzmM&F{yhPY6x#seK{Y7`u1K+X_0Ni{T^#EuTv*6?@#00JXQ!^NZ<5ke-Mnw2U-xtI zF#`i(h*Y@|+1WBCrlzn*inGcsiVZr0pw&Bk*sjSP6_HA4Xed+5q0wPeL&MeQ^G&`@ zcW`GLaRU699r}z0y}UQx;oZHx{@6d08e@j~`sOeN(Jv!Su535Nf;7B;-`(tm?6p@% z$9~*kN--T*qeYO5gT=+f*ePi19^fbxV5;~8Tn-C&Z0S|D6j;G$a}Y=k{wGzLJqdn%WfVPz^nU6@;0G3J{@jE_6D)7@7dN#%u^ zX@Bsp-P+m}h%@k_NTMr`$;HKmmzTE-Wk1rkZR5m^ntn1e7vVP;@n`neFHJlZyzy>; zCj9vTyc|^uVUK&&szB1&TyLaFFDgoW_>kq~$&=8kcyLJefYAsG56>6gud9IhsiKvdc4cJ|VPS^u?rx_G z7XtLf0+H97nwxLjy2T_VC6$+-Zvi&I$;HJV@Z;IDXIb_wlC(8zEKuTR<>m(G=l`7< z#mmR1fdd0L(r%J`hqt@2@WQw9z*8k2(_+Z9ue`R{O8_!pJe{zJ$P&!p>b~Ot70({W zO3TQI2L6GKB5s6NmNH}jDsgZc1|ewxUnFK`MuOWU19I9ubi0R#ui<)WqPLK7K}et# z%%iYxY9v?Vig`7RcHmySHUfjKN?Tj`@dW&&T2X0fL3)!0fAQ(_XLSj_4fwWjPglWG zntwS{c5v_e^^fo;OEm@S5v;>?!J?>F~5|(D$Ha^rw{w9*a+1buFzo$MV zgDjkXd_AnBqC&i_Sub9^MlPKIr`?0Pt^FRkyUGg8H=sp1v~%;*r^)T#05vP1Obi6Q zTtlO+mzL(nXJoSC1n{i@nl0Ckprm=sb^8Q=J*snYq4uPZD=*EL(zyD5-vC zRIHEy+XeGM7n$R&KVjZfODTY91xTq0=v7^qGlz$$1*$V*-L41-JHD&W2tNM)#Q~Wk zaAMP+gWiYqc4#c#T^ttHL{_NA`oSp@N64iF6E`slIpre(ZrwR+s}; zTUi+d(LJo_^4z~QN9AtimIEWj-oGy-w{6?${QP{Ac^EEYfyX~IHd-D@y|rCMC5MRC z##bhKc7Mdzd6=ABK}1Z_aEg3aMP(5U|K*LSsMi2vF?bgko@-lM?{Fy8e)^-~zIxTF ziH}DEI*PG*q*O65C%>>z?y6Qf@N-UfwgonWHByM5?1v8@nERVKH$SOGtrqL=&-kIC z!F*s~V1!gx;1vC&+cs_5w7+6sVSYZF#=60QfoG+qY7ZYhqW7$n`+vOvRq_f7v4GF0 zDfph;y?eKY3`>xgmybaVHO3W|xanY3zy`^!TdnbsZDeD^7`Owbugs1Mlizf4ax!mi zZ9Q=G=uKDDw`AV*2#PwB;wMieKEhhH>Bioe#6)5E8Z9u`J9>Ssj8)kEUEXnA7-n`U zoXqbv&S32~H8H6*&dlKMhc0a4;NU=?qVe%n7A{+3Ra;dRj6!d!5l*Svd-v|;6?|%K zeTG6mXL9nqWIcGV*tod3E?Ry3u9Mu)wnKnFZe%14j6(`v#H3Ac9nAzmI1J|Ed5R)Zoj6wZD)^8YB1GmxL zbJll)yEMWnB71EZ6eF=xGPM<78e7Z3%1TzSwzSkIFevDJyn#aI1X3LzKYumZGFl>s z?^_hzO)9=VqLBMEPy0-#3lh~?#Gcxwrdz4$>2iq79fMAFm(k4)N_+Cf@PgRl@So_n zcQ;#(tf~~|)}R)omYmRZS;Z>+hYQ~h&{yN&!DU}&>P`VUk(hD%^rn%Kk&cV5Z|p-O zBdhRHugAxCK3>P=={1(4IAhm z9n?lbaC&`sa323<6cW0B`pNMry^&5F+RlpxZ|q^3WdQvG>qFCs^~`A7YB0ujjmqG1 z{SIuh|5+fv+ste&{r~{t#IfOtO+>Lp9WaXtc^&xs0vLs2NVs9gLjJx#Y-a>{G~O5! zhd_vyNJ=JHg5uF3g}3m5aSI8V4o)It!(>lt#sIL3u= z1_psRQ5Eg&f(S!IaRXRknuDdyABl!{)hdO8;sCl>bFL|a#$ruJ=kTwW={~;~mv+u> zdzO=vn50pE!i>zu#>suBgMi?s^TM6~I3@hOD&h z?_wl?>|tHDY#;?~5gdpC!9hWLTnzp^RYw0XsRJRBofdaNiecs8r~r9QQX^u(dJqy= zpjoV{fXOaY^uV(ffIq^~HRSpPl3Rh8c0-)Wzf8UevI%h>pf97KLv|4B0t@a6+&TFF zWEyA-EI5?pR)d<=G#c@Xk)dpL9tT(t2k~nMTyOb#a!zA%8|2M(7&741*6g;Sx~7JQ zo7)E$B6NYS@AvXu;sedD8`>k5&%OYYM>i-atiqHV?1{r^X=&_W$pRie9B9j6H!Hq; z6~Zhr6_Uy*dM(g3papCm!%_U&uJ8%YX^+h8YQ~u)ybuJd^Qv+{ZEASn7>g&3mbS0o zzBT7BqJH67w=NQp*Afq#^1)hTUs%1nx1I1<`E)YH94zS)RGDN++}+*x*GNc6yn^At zA0DSJV*eNd#SjFH#>B#Me)$)m7sz4(z~fTdnkP@LLp=YAIs<3T-5uLdc*Rwx z-byYrkI{S~&>7@Dg~_R@d<)-D3wh{SZ>p;`w6p>eESM-zBLoc3EF!UyJ3Wv}IFQUx z&-Ney>^Xis*8B_3yyctg!o<@`zL%9%@{gDMrlG97d1AMe(5D4Cv~ zmbo}$bnEtQ_8}J}&#<+s{D36nVZk=rJvJ7Jl2pBen4FlwHu<7{oM;RW566-vq@*RQae=wU1fSm0w9xAP_9uNc=N`~tP<%1d!92hAsu&AH~mymv|?!G=Ij0^!v5EBx) zh*ihRbN_@p8g%`OX6ZQ1ZAO~N$vJ=Gwzr@|^E$XV;0o*Bo zvG1X!()jt|Z$j6@Dx>GNH@a#fWWkjX9tUz1Wn*K5wd_Hxngx5`)!(0T;XckeAwm&Z zUIQ)XNgYD4@2||PM~;z=<;3pZjV2{hu2F>cfT$P*CvyUxzmn?^FX} z;&)+1J=An{xnKg)0OtoF5J@;W@L(Fn5 zMRJotd_mH%fUjhHe7qFxd;$s#B@Z5GaW5ny#I<+g!E1tRnILvP1A|qFy_$RXveK+r z+1R?DM&G>Y4^xN*Oy{8EIB|M-1O>xj*(pakqvkpmeD|&Z*(ey5td5h0uS%*lSSg9{ zsAx2J2RC+vfY#{-3MFP`1`2qO@OUaxrg<9z97+#5gm!CD;WwTeM$&LDdHFG0D=gx zr9hG-XJPJH4{Z6@_oq;4@o@rc)|5d2$qmQ+gD%8(a=#LR5V#_z3#U=Z^QVNz%=fx} zkOA7DFuT8hk09v<*PT>i?=1ssgevz&4VmqRAWH`N6Sj*uO@QU$*C$=>^mFTWUZ!DN z4@AVKrfxyLAj{DUWq`C6!sZ1Zg%8MO_6T=y5%5dGax72SDXbFVOhHF5fI^^3@zHN> z)3ZNnXgC-Vm60Kd@`waL^g))QhQ}?CTJQm3Tu<4rf4>-!YVPMz8L7-f3*+T$clgA| zufsO;C65m&7xU$4gkwV|qb0gA$>kg*zavZ1S~5=tYl$%Ed1c~-)vyOpENa3YjZ2Th z@F{yxKO$i>>U|NPg3-AfWio!LiR}oNM=-J!j({clbah+n8yfW5i@gN2BLurWzMNV1 zKkAft3e@_qDHqS5_XE|4ttfMOc3ine8EcM3VN$<+7k%+aS?(kEwTY!E|{jqFWY zQGCW4Nt}MMOS!hT_7iI1{29pJmcVb*w|9X{MKwgT3PY2GBg8IQ@E3Tu%6@kYRfSw> z4UKgKDT+Rwo|cwZ0-w(&Q8F?Az{V95Lu1|DWfxps-R)k~VD)(zOTj@_)vV;^E+r46 zNJ>=H9BdFNQlq8hXc6~PUJn$%93XBxv%8xrz5^)iv{xO0m*2WD&oTAqzbS6R(4duFf2|AXowa)FgjY- zcRoKMj$EA}loKS{)&d*6G_W_JwNmm63S{9sQvY%$t_t4=wG#~G^c|pGpWx86|Dw}( zJOs^!Tb#;jqS3k!&>&TWgIxRi^=>_FkW<^>y7QPhhn>zz{Q~%Q{|5BTd+YcwCA!;$ z-)9tnh;!%d`uqDiDd?dih)47Vgk$_^Foo`OU?$g4FEN3ol4b>#hm$hK4B6zNj)rtl ztn_Js*5imj?(V=cMi3s0+R`Imym(=*61Js8@=JR=jp9RZV2Wv;lP`n0n3&iIPN~T} zQoDOqQ1(#0C=3%nA&YgqgblIw$Mkf5eHyuBo57r%zezes0&vzuvyOv|VGwb0{Nx2Z~_u_@Z*!)qVyfBs`@c+-*1uPX{Kk9IY z-AWxHKpzt4=9>>DQCdhJgggJ0W{=*!oosx zTOmE65ViN>j}fdmp$~Vw1U};7;|l^XaIphN59Vy|p+kc~*Tv*80d-H{kRv36u_Dll zz>WlG!RFKrQgRmE>QJO3Vg4yAElntsJsEvqQ?KqwZcdIrz*pb-?U}1l_ApadYlVsJ?|i`I^`zb{g9^q9I(l>G{I~KU2}2(E=w*wpa6B)dCgzLyn^_Jh>9Ial+%rtf;08 zTeG0^J7hUOcbl+8OAR-|m6MZ`8n{GmUfuwdxRaC6(#l#|Rs%YRT)Vb1EWN(Ierbc# zkC%XT=sV_yuN~Jr%Qzm#r8z!-yuR6;-I&+PB?{u0YSujQ;K-KqE-q!X^qh&77?~Nh zY3_gLm&gjLT;xPDC`T4xc8P;(<`$mZ>UylHa6o1Yst??_vH6XSy*&q&hbbgE3K4h} z6lTp|7a(O3=@m#O!s;E--QVwGJ)?QpkflgmBwZX7sMcDAS|WNdo{_Tm-v*{5X)(;-H0i z*bTo0xk1DH)TsbuQY*}uC)Frd{0s2HMMXvCD$`q#)zK%`H98uOI_vy5xXd*$@qZi+ zRkomK#cVJa^2pzr{_&#$1L@THsqZG3U9l9Lb|p9f;!I1i?u7kZZF=J6vu7*LJ{nMM zzSsWc%UyZ|VEEXHv{XpbW!ueNpJv)VD5@5e1ro&zJlbb1TPaFobnkuati@_~aqZf* zZ|du-5H{iBwt!?v2IrB2+A+fy!1(gz%UN)sLMgDsSJ3S^2K-B>(+PwJ(p-x9GS~NR z%yXaG1VUv)%t(ikWww!BK~Ie{b|Z)qjOMvDa4uq9UftWb>gYu>cXW(|ACezawF1F+ zkv?;H{5gI;O;lFgZF*9TU#>;Htg?~?$1M+0+JA}%Q5IM>5L`*^eU;UCKP>Vd3kzYi z>iyYMpKK(tXaD}tMz4&|N@w0D1tthHQXqi)Ovk_4{q>+8@BFpB!k~}dUJ+1S^10zxM?wy1F zn*XwLYO>|Otehq_{P)T!1$f9XdvbXV+#PNeEw@yeGlz9xa#)8>aew4vb4)*&{wTg- z10x{vCydryZOiDhKJ-|febRP#HXt*i(06IsYYL#{G@dZo$@M=covPzR{4YwU$iGYU z7GIap>4K=9!aL&cEM}N7;`za}h9J$BwDRL=Xc4%aQ$2H!*NncdZvG=G0Ue%xdqb-w zjz43r-9lv#6eo)GSsw1x-zfdmH431@o)`n#Q53;B4((45C{#Gh2Rp3 zSM0EPoK)NDDkdR8ipQ1>+HdY^B+}W0mq6X`=;$C8bEyYo*ilXhIV>&S$U0+12=l-o zx0coD85(MXWCFq6H8@zs(TVBFC%+C2Spxp~$@~o6Ob^{$st{i+SBcBj#LTW}YHGT< zxpl!Bw<=)Em8iHVR01KdEg>$keY6Rih$ut3<2*NMosw8OoP4%3^x8G|5`ho;J9q9R z>2XYHm-{3WJQ;o_l7m5JTlj?)yh|P=C282voUdw$%$6^uFg?H@`UXY_i?G&k=-2N% za@qap%S^C#7y~}=lsK0R4Gj@T*vrF8s@(K}1rLNZIt!Umh*Z&4DJ8 znJ%=WUVwP`eK%EWB7BMbQ3!$rI-{b=PKsQNca`b{`N=dB6&T2jTPfXK*y~KK&PsA!BjRmIoW_U+%tAa*VWX%P8acqfq?;;=ji-~*MNHMo%oKp=z=BF zCKI~=1xk><*lrv>O8-xvFUX29`1LP*iqjHN$5R^H%*6|r*O$jrhjXgZad~IMhYu^y z+Jm}-#H$W+JYW9xpg~A;!7U*l3}^uL_Q|mV=-9A51i(Pa$;nAJ z9=iEm5*_WKCXxO?GRKrKhhQWFA@LyFVW`+ua6F}N-?D+;@hL9e3E&e9kYfP?EGs+P z*F;j=%}vRngU*(!ABvj}QYzvxj)PQ|@cpkQ{XGyZ+1c5hV9|qnLH^q;POOA!0WXG_ zXsW9-Ed2hRn4W$OpHgJY7B)f!;N>VHgNKBMYIt}kXWBdw#;ykcK|D~9wL)Ox!BioZ z%F4?2n{yBE-~oa}CRy*>hew*5Zd#z|&qEo3LOpNuM-)YVkdQHHB^;Xt4lFaJ(K{$L zZ=gyAmk4@SLoBKsu!8sw_V4$D4%*({PMTh@?CN;vu!4TVxXG}vFyik9X-UXyh<(EB z92|*gc!GH2ka1Wy)ae`g;JP)QohxH+e9Q(-4+Rfd)?EQNl__hdJLVOZWTX<2>;T-G%4kB%h1a_J< zM4&ZTJ}u10hfJK~f|i_jzMuWc)2H}O>t~a^pac>Ql@O9RSlBaYPjLFP=w)U!Xc+4P z<<$?wj3w6J4b>p}ZTvy~T4KSm6U#th<M z4Xz*pp}PF@=QT(v0Z*T9UAyyg2-%9*IQ;{Hx^X+!;LL02>2X7|VwRJWgG}CyZVPhJ zh2rzN(o)LT*Vicj{691+;<6@LJB5LT*CPFq?%g2J_h5y>IcCSjTp}PtLXN|yI&BUN zL9A57DeO7p0FuyebyzQW;WG4WJAs^aFov85jj0rsxbv@{=djPvw#uyN(9XROcN82i zz#5fMU~v)(;P1HO&|SNRhdB}ow9jD;X3^|JOsqtNmXYDX7OO(fL@CF~W%@HQkWb>u z-EJz5jj@Glzw!Ryz5SM=RhSU?SjhRZ0OlVt0PQ9G-I7hNx-T@{rA!NmFe?XdA!yT> z@Nh27YhL3CDkb{SX;H^OAXPaP6_wLx&(;Ey=Knwll|zh!72-9HoXf?Fr{M!u&0T=d z@dTmbfVOrp?y~)tOfcn;DYUNP>(~Z5&j|F&&9|qB9(vr^I42~8)gj;V4-6-6!&@{Q zu??f5p3I;sti)V~S6dAOCc$0|JT){7nFKNeNvPQ2 z&dI^iYZ@FTGtk>x0n3KF9hmFWsJBf_&z?K?8AbbXclYhlHYoD}whlRkp+1pf{Va$X zwn&OGKw&vAURWcdt?WA*Fc~Lf6_jwGGHon{IGs`9Z@T%GFf5-zFS%7sLjRUPF^x4R zIyns0a1(wpx_p`5D0_DZAqdLozmUvj0FUt(V*q1tYmA`ONz^8c4dzOAl{7Fue8Vui ze1N+raCxTqtxI5PnXE+%4TqHDE`4veAbll3wAQHa$g)m@NXJ;)rFOZeWtkCBJh3?UBq(Ok`nCo12${J_b#2r^O8#3>bZDFg2${ZajXM)uHFY@K`F&YWDr- zlJE^>%(fzuuw#~m)?Swc=)Y!+s)-_yfETxa{UhscN?h506=C57)SE;a77n2PZ~nB; z5fnW4nl*2WIx$=qH4Cy|9;R5fl1sh4z56?^!+ZoK<1B8J(YY2D7O;1thi4S|#34u0 z91E8OEJU9Z%Y1Afkrt+W>=qfA|Af3sj>O{E(dE|$g z@1&K10+5J zwUC?LNY=QGOu~T#r~#~ge;5pshC$TMD{R66*S-0Nwu=Nb?UPX)z=@LCWAhvvP7YEiIE8UA=#eDiZb~C@2VY z0_c43dr}i$-)~@A`d1c15{*IkBF%~}i+Y?uSx~DCcm-*Bd16ok_KEd~)WJQ<07B!C z+b8+=umfgHzq^}e|L#5bhOVz)S#gBnRx^kCNdSdZBz*D^cAQ9p9r?Tx(i`!*BOAW! z-+sQ26_A>uK(Pd(2B`J?`#`yg^?-{XU$wfrIt!N$z<9V`S(zU=XNjJw{CD*JLz3%x z>N5Ys4#VKhn;t)`cdxFidyVPu4KRPe^(ctXPa2^BQkmRIenkHO(04a91k!y8-#&1B zFyuDUe-Dpn6-H$Eq7QlMqtLD^J7D&@3UvwDW+?UBrncLf6 z0~gIm5d$6G)(UJFv>4l$^DD3o|Lm5}@aJw^kMw~NT+1nG*CBo4?K93@2G~Nr7J5y| zh^HY zY*#_aS`CO*-q8GLOHkC-#)qFOH%N8cyGEo=9IG_I;I334%1~HH5F9;qlI$sOCQ5;srwB;04M2V8Z|4Ccnk~T>9j| z{nG<*&VTiZ9D0LW}GI`sBq zGGBPt?APJpsh^P~AnR&Z4B@}WeqGClLl}b+1FwoJ1Qc`*#+@bWnkFUd>a%kVjT!dp}0#A&RbB}oNw|6x)j8hFQ_K{W1tKRc6Ubbh-&KkxFKtfM=MyR|#qd{B_VysMG52!!Q~@kM)D{(;94djx(Azm9d4H2k54ug`dS zU5d_V7i_@u-us1Ahm8p+vwBG2AQ2Ge7Icj1ahul;_;#Hv%E4w5 z?f<|Xc{YR#-PT?CCd*(tK!33soVy^vfduJm)v)FPh#_aFbU{RdQ>do zSjG0(4bv-;1J#S6=fRHqD~eHu_&_Wn8s0{l7r{8?CHq`s5ofVavd|ckp_mRB zhYl~tn>9-)5awswUUH%Esz5D-XXOiMMp_p%SW|;T-gBC1n8k`*IFA6@d0w|8?9H3K zi1Ufy$MMI`O8-H7=r|lFNeKyR+`A+2m)t-6hxtz11xcn3&0)}r$$+Zlpuae0G0N6R zNVxCC4zGD$g0xUe18HSrY-z6PKV-?S8)~2t>5cssF|l&o0l)s{+i^redgB7^@9%mq zff?_OeL{8}Hqe`0OVQ(&d2Iku1Ai6oPL9@pz$xl~Ku`v2=WT zFx<884G*H6AoA)t!~Q7I^gpl)BElEu9@~r$&=>DV%uavqp{55WW+XH&>@+uS@IYlH zMv@)wlL0lka8Kik21Re3`)JtWS+gefLhRm+Jw<>WKpLuo7{KkADnPzOT59nqJMhiL z4-ZD$GL{^0F!OFRu)PHrs-n(zB1L0=9zJm*6s;J9O@;zp3b~PN0n8&{R#sM~(gd`@ z6vS+IIh|ZH11|!&o5YNajt3s7ldK?7yKcJyb2 zymJOQ1JTJNxotf@&?490(Dm=5L-86SH{?C@bii%{8B<`2&TrK_upX~RE6FYgq@G#mK9;jpFn{P_WKjgk3=}MUTn85?VRKWxrP$0NG+^+$kP1qyQ zFH|WyUmD<31?}cE2eTG25_N5ZNiqO2(+WL~WR3u!pVQ)kJK=G$&W~MGL5P5}w!}fF zsd`+vKQ~Yx9|=7IHX%l0RHq#B?w4bRSVBey>2QD$o%GJ^5pIm*q)AhR><1l!c{*WW zle^J{N8Un1L&FJ{C$s{Sei^RsDikaq33+)m^tD7^K^iASk;n~J3kwSZfcNibhujg_ z*m%StqzgOsJ*J?bslXC>)(w~>3rYzY-_GTrvUMvLvHrs~5{^7cQ5g0V0CGYIb6ITl zfB!}G8~!2*xLh`yMdFG&FU;r(ML`DQn33Y<=VwQ+CX}6EdqGmEfjCt{e-KV70|g2x zC?&<(zYXVb{mAHOHfY67&8}DmLJi?t`s1E)P6PztNguGdD7Eu0!%v3YqE3W9C1YJU zE0r*XwJ1Cv02mcb0tOe57EP#|w`D3R3i~SOnt^ zGp@f1dELV@_4jD4B1fw+0fM57_b#M|l7&f*pk+d#V3cro?=RAhgzQW?Iy%D5*n?IS zbi{F!izDo53_kD=?uiim+_ep@!q~vRSVqOv3|Pv_QP@elPo4sRutL#BuEQmSBu2$# zlr_?@kPcbV!{EL0;Nl15e~TBhb^2Myet*a6odo6(?FY#;2wL zcT|3&MI#u{3(C+q3IG7!pV(*v3K`E|ypX||0G449WC!#IV}~geERuxa{nug5C99}7 zxcC<|al(d)&DB=~ng-Lv;WwwcaMt&GUIjHN&^`e1G9fvc5zGR~y@1KHKq3MLoj}`G z;K3o!5sxqW8@aG`h@guP4?&9ZE*VaWS=ras)$sLv)Eaci;%O2#*&(9^RDMFzWJ%;V zq=6kUTarEmt6eYwQVUj&c+j>93Im^D)FobJJbi7DlJFlXL3}L!xJVM&AC~`kTz85# zhnlkkW4P`)oRgL1HhK=>OhiRR08{eRU}*z`N(55!sgSqB#WXjrD8p+)q#mE14n(+s zEtBx%@@IZm)`p*vgs?=^Ns59-Ks=A=;wNDOrm7{go!RR!;o<&NDu#Q^wz|s5%A&$% zK^!6&7A7lm7~n`XfU?c3@bcee7fO=dCNkv4XK1`WRBFmqMov!C!69S4s)|bC;kJMG zZX^~c0HYv8ZxMh&yha7xed>4|0_sAtwnAsrD=kh zqq^mi~K3(KXQBUHIn2Hba?1og_^`=#LEs zgW;4&udA&^pr7-|{p+n15U8lD<06L_G7_1JJv%?I0obbM>i-Cl4vtG6kqsLdD4@eC z(E6}wQ|OLEmDURkOp;h)YU))ooTe=ZdJV?LExina&I$f9XnF93-MJuvw1W#|Jn&=^ zk)BA$>j|{UqO|YDBtqi-&D~0o5*U=IFxCQwG>@9KusRasBEmT-M5%F1nH5%5{r#u4 z(DQcXOX<*F@vAb$>q9cx-WwU&30qp(sJ-7ma#q4J*@!87zff5uw*a%a&J=@0fc(pc zS&=IQ_67K?59xY)r$@F!fbO)W;(DygpOeue_jBTf%C{dE{JHaliAmG&*}6hSFd6OrNJt8 z=DPZNd(?Zu#p@{Qia5)-b=3aXQTtOqSTD%WUGDgI0l6Xd>5){Wn{SoYMyl|KE5{!2 z@bdcd+A=$(b9D6Shi}h4Jv{Cm*yMHLLLUZ+=uH0p{ad@_g7WAE<=+$YeSNRs>xFaS zq6FX6j~~xn@_}CBcg^40|Buf3o$-!qP$IiqSd<|!eIFV+FLGmiVBld*|9PErXxi&& zZJq4uO8Ce9%$cWjUgdwY3}Ha8nA=j6;QDe1(fmE>h#H##;p*3sEH z^Y!av&BW_}+~=eE&TjiwA#={&DX_p5d`$mpwdMCqJKg zF$5i9W_sHF)TvkK!zrfI7cS5-lp`{~?P9jkkJi>ZRS?9!d>Pdj>xbUAYv;~I`*3PJ zeYWIe^u$!)$huE0d}`NVG}`B#S;0V2_bXi2j9S+5Q1~ariHZF1FRM2BVg1$r&o7^c zbFE{bW)+-t_Y|^fs|l2MXZfw~I>kw;1%E!q&BPu&=Xou&w}tUJw%HceCC$UGOIA>n zkfrgohp9TZDBm^L0>On!H&%&;pwl_2pp}Y>i^W!46~({2?5+in!WU_sV>D*SN-&$j literal 46900 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%0DK7wA)OxyjMCY43MaP}fJ>-=2^g<7T{Llmg^Oe6 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 literal 48147 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 diff --git a/docs/_static/djangocache-delete.png b/docs/_static/djangocache-delete.png index dd3e7d0939b78938111875d5000ef1fae84f763f..59d60579d1ed7d7c4c4e05c1d86fbfd4031fc47d 100644 GIT binary patch literal 30412 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 HQ$iB}T{U8w literal 33677 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 C+|f<| literal 32506 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 diff --git a/docs/_static/djangocache-set.png b/docs/_static/djangocache-set.png index 7aae1b1c4ddd945a6f4f1511001af5701e7bb25e..bd51a62ded3ce07f59076702d91ca83253856536 100644 GIT binary patch literal 33357 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 Uu3Q<1 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. Even without concurrent access, a slight benefit is -observable. +`. Increasing the number of shards from four to eight +has a negligible impact on performance. ========= ========= ========= ========= ========= ========= ========= ========= Timings for pylibmc.Client ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 25.988us 30.041us 41.962us 269.890us 2.407s - set 9021 0 28.133us 31.948us 45.061us 88.930us 262.482ms - delete 1012 104 25.988us 29.087us 39.101us 65.804us 27.031ms - Total 98999 2.697s + get 88966 9705 25.988us 29.802us 41.008us 139.952us 2.388s + set 9021 0 27.895us 30.994us 40.054us 97.990us 254.248ms + delete 1012 104 25.988us 29.087us 38.147us 89.169us 27.159ms + Total 98999 2.669s ========= ========= ========= ========= ========= ========= ========= ========= Memcached performance is low latency and very stable. @@ -123,14 +123,14 @@ Timings for redis.StrictRedis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 88966 9705 45.061us 49.114us 77.009us 197.887us 4.171s - set 9021 0 46.015us 50.068us 77.963us 179.052us 429.199ms - delete 1012 104 44.823us 56.982us 77.009us 104.189us 47.746ms - Total 98999 4.648s + get 88966 9705 44.107us 54.121us 73.910us 204.086us 4.125s + set 9021 0 45.061us 56.028us 75.102us 237.942us 427.197ms + delete 1012 104 44.107us 54.836us 72.002us 126.839us 46.771ms + Total 98999 4.599s ========= ========= ========= ========= ========= ========= ========= ========= Redis performance is roughly half that of Memcached. :doc:`DiskCache ` -performs better than Redis for get operations through the 99th percentile. +performs better than Redis for get operations through the Max percentile. Concurrent Access ----------------- @@ -176,10 +176,10 @@ Timings for diskcache.Cache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72929 16.928us 29.802us 45.061us 517.130us 13.617s - set 71530 0 303.030us 360.966us 36.302ms 6.251s 269.090s - delete 7916 773 265.837us 330.925us 35.141ms 1.339s 17.652s - Total 791992 300.358s + get 712546 71214 15.974us 23.127us 40.054us 4.953ms 12.349s + set 71530 0 94.891us 1.328ms 21.307ms 1.846s 131.728s + delete 7916 807 65.088us 1.278ms 19.610ms 1.244s 13.811s + Total 791992 157.888s ========= ========= ========= ========= ========= ========= ========= ========= Notice the unacceptably high maximum store and delete latency. Without @@ -191,10 +191,10 @@ Timings for diskcache.FanoutCache(shards=4, timeout=1.0) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72975 17.166us 34.094us 73.195us 8.381ms 15.575s - set 71530 0 228.882us 1.421ms 19.039ms 333.486ms 79.159s - delete 7916 784 198.126us 1.385ms 19.165ms 107.130ms 8.838s - Total 791992 103.572s + get 712546 71623 19.073us 35.048us 59.843us 12.980ms 16.849s + set 71530 0 108.004us 1.313ms 9.176ms 333.361ms 50.821s + delete 7916 767 73.195us 1.264ms 9.033ms 108.232ms 4.964s + Total 791992 72.634s ========= ========= ========= ========= ========= ========= ========= ========= Here :class:`FanoutCache ` uses four shards to @@ -207,10 +207,10 @@ Timings for diskcache.FanoutCache(shards=8, timeout=0.010) ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 71626 24.080us 46.015us 72.956us 6.997ms 20.241s - set 71530 106 253.916us 1.400ms 8.787ms 15.915ms 47.683s - delete 7916 779 216.961us 1.345ms 8.602ms 11.446ms 4.516s - Total 791992 72.440s + get 712546 71106 25.034us 47.922us 101.089us 9.015ms 22.336s + set 71530 39 134.945us 1.324ms 5.763ms 16.027ms 33.347s + delete 7916 775 88.930us 1.267ms 5.017ms 13.732ms 3.308s + Total 791992 58.991s ========= ========= ========= ========= ========= ========= ========= ========= With one shard allocated per worker and a low timeout, the maximum latency is @@ -224,16 +224,16 @@ Timings for pylibmc.Client ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72146 83.208us 105.143us 120.878us 520.945us 61.320s - set 71530 0 85.115us 107.050us 123.024us 458.002us 6.285s - delete 7916 792 82.016us 103.951us 119.925us 298.977us 673.505ms - Total 791992 68.279s + get 712546 72043 83.923us 107.050us 123.978us 617.027us 61.824s + set 71530 0 84.877us 108.004us 124.931us 312.090us 6.283s + delete 7916 796 82.970us 105.858us 123.024us 288.963us 680.970ms + Total 791992 68.788s ========= ========= ========= ========= ========= ========= ========= ========= Memcached performance is low latency and stable even under heavy load. Notice -that cache gets are half as fast in total as compared with :class:`FanoutCache -`. The superior performance of get operations put the -overall performance of :doc:`DiskCache ` within ten percent of +that cache gets are three times slower in total as compared with +:class:`FanoutCache `. The superior performance of get +operations put the overall performance of :doc:`DiskCache ` ahead of Memcached. ========= ========= ========= ========= ========= ========= ========= ========= @@ -241,10 +241,10 @@ Timings for redis.StrictRedis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 72652 141.144us 174.999us 210.047us 931.978us 103.515s - set 71530 0 142.097us 174.999us 211.000us 623.941us 10.457s - delete 7916 811 139.952us 172.138us 205.994us 288.963us 1.138s - Total 791992 115.110s + get 712546 72093 138.044us 169.039us 212.908us 151.121ms 101.197s + set 71530 0 138.998us 169.992us 216.007us 1.200ms 10.173s + delete 7916 752 136.137us 167.847us 211.954us 1.059ms 1.106s + Total 791992 112.476s ========= ========= ========= ========= ========= ========= ========= ========= Redis performance is roughly half that of Memcached. Beware the impact of diff --git a/docs/djangocache-benchmarks.rst b/docs/djangocache-benchmarks.rst index 7bfc0ce..4f791ff 100644 --- a/docs/djangocache-benchmarks.rst +++ b/docs/djangocache-benchmarks.rst @@ -136,10 +136,10 @@ Timings for locmem ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 140750 35.048us 56.982us 59.128us 8.609ms 28.325s - set 71530 0 36.955us 38.147us 46.015us 6.582ms 2.670s - delete 7916 0 31.948us 34.809us 36.955us 2.065ms 255.893ms - Total 791992 31.252s + get 712546 140750 36.001us 57.936us 60.081us 10.202ms 28.962s + set 71530 0 36.955us 39.101us 45.061us 2.784ms 2.709s + delete 7916 0 32.902us 35.048us 37.193us 1.524ms 265.399ms + Total 791992 31.936s ========= ========= ========= ========= ========= ========= ========= ========= Notice the high cache miss rate. This reflects the isolation of local memory @@ -151,10 +151,10 @@ Timings for memcached ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 69192 88.930us 102.043us 123.978us 917.912us 63.269s - set 71530 0 92.030us 106.096us 127.077us 804.901us 6.604s - delete 7916 0 87.023us 100.136us 122.070us 201.941us 687.053ms - Total 791992 70.560s + get 712546 69185 87.023us 99.182us 110.865us 576.973us 61.758s + set 71530 0 89.169us 102.043us 114.202us 259.876us 6.395s + delete 7916 0 85.115us 97.990us 108.957us 201.941us 672.212ms + Total 791992 68.825s ========= ========= ========= ========= ========= ========= ========= ========= Memcached performance is low latency and very stable. @@ -164,13 +164,13 @@ Timings for redis ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 68891 174.046us 213.146us 251.055us 1.084ms 126.502s - set 71530 0 179.052us 216.007us 252.962us 478.983us 13.056s - delete 7916 770 156.879us 193.119us 227.213us 293.970us 1.268s - Total 791992 140.826s + get 712546 69526 160.933us 195.980us 239.134us 1.365ms 116.816s + set 71530 0 166.178us 200.987us 242.949us 587.940us 12.143s + delete 7916 791 143.051us 177.860us 217.915us 330.925us 1.165s + Total 791992 130.124s ========= ========= ========= ========= ========= ========= ========= ========= -Redis performance is roughtly half that of Memcached. Beware the impact of +Redis performance is roughly half that of Memcached. Beware the impact of persistence settings on your Redis performance. Depending on your use of logging and snapshotting, maximum latency may increase significantly. @@ -179,17 +179,17 @@ Timings for diskcache ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712546 69423 36.001us 59.128us 92.983us 7.305ms 28.354s - set 71530 0 300.169us 1.451ms 8.877ms 39.359ms 51.403s - delete 7916 0 239.134us 1.378ms 8.740ms 14.397ms 4.926s - Total 791992 84.683s + get 712546 69509 33.855us 56.982us 79.155us 11.908ms 30.078s + set 71530 0 178.814us 1.355ms 5.032ms 26.620ms 34.461s + delete 7916 0 107.050us 1.280ms 4.738ms 17.217ms 3.303s + Total 791992 67.842s ========= ========= ========= ========= ========= ========= ========= ========= :class:`DjangoCache ` defaults to using eight shards with a 10 millisecond timeout. Notice that cache get operations are in aggregate more than twice as fast as Memcached. And total cache time for all -operations is only 20% slower. The higher set and delete latencies are due to -the retry behavior of :class:`DjangoCache ` objects. If +operations is comparable. The higher set and delete latencies are due to the +retry behavior of :class:`DjangoCache ` objects. If lower latency is required then the retry behavior can be disabled. ========= ========= ========= ========= ========= ========= ========= ========= @@ -197,12 +197,12 @@ Timings for filebased ------------------------------------------------------------------------------- Action Count Miss Median P90 P99 Max Total ========= ========= ========= ========= ========= ========= ========= ========= - get 712598 99964 101.805us 171.900us 365.973us 5.407ms 83.088s - set 71557 0 7.903ms 10.250ms 12.787ms 34.464ms 578.779s - delete 7837 0 200.987us 346.899us 596.046us 1.250ms 1.736s - Total 791992 663.603s + get 712749 103843 112.772us 193.119us 423.908us 18.428ms 92.428s + set 71431 0 8.893ms 11.742ms 14.790ms 44.201ms 646.879s + delete 7812 0 223.875us 389.099us 679.016us 15.058ms 1.940s + Total 791992 741.247s ========= ========= ========= ========= ========= ========= ========= ========= Notice the higher cache miss rate. That's a result of the cache's random -culling strategy. Get and set operations also take two to seven times longer in -aggregate as compared with :class:`DjangoCache `. +culling strategy. Get and set operations also take three to twenty times longer +in aggregate as compared with :class:`DjangoCache `. From 1f0391062dad8a0480d74bebc9345b5cc41339b7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 17:34:18 -0800 Subject: [PATCH 206/550] Bump version to 3.0.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 15b858f..26cfd29 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '2.9.0' -__build__ = 0x020900 +__version__ = '3.0.0' +__build__ = 0x030000 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 12969484f45c205701b3fbcc4217ee4d4f10c69e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 17:52:54 -0800 Subject: [PATCH 207/550] Remove SQLite page_size pragma setting --- diskcache/core.py | 1 - docs/api.rst | 2 -- docs/tutorial.rst | 1 - 3 files changed, 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index ed83522..b36010b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -70,7 +70,6 @@ def __repr__(self): u'sqlite_cache_size': 2 ** 13, # 8,192 pages u'sqlite_journal_mode': u'wal', u'sqlite_mmap_size': 2 ** 26, # 64mb - u'sqlite_page_size': 4096, u'sqlite_synchronous': 1, # NORMAL u'disk_min_file_size': 2 ** 15, # 32kb u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, diff --git a/docs/api.rst b/docs/api.rst index 5cc2267..90f15f9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -56,8 +56,6 @@ Read the :ref:`Settings tutorial ` for details. * `sqlite_journal_mode` (str) default "wal" - SQLite journal mode pragma. * `sqlite_mmap_size` (int, in bytes) default 64 megabytes - SQLite mmap size pragma. - * `sqlite_page_size` (int, in bytes) default 4 kilobytes - SQLite page size - pragma. * `sqlite_synchronous` (int) default 1, "NORMAL" - SQLite synchronous pragma. * `disk_min_file_size` (int, in bytes) default one kilobyte - values with diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 3a06d5f..4dc32c3 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -547,7 +547,6 @@ pragma documentation`_ for more details. * `sqlite_cache_size`, default 8,192 pages. * `sqlite_journal_mode`, default "wal". * `sqlite_mmap_size`, default 64 megabytes. -* `sqlite_page_size`, default 4 kilobytes. * `sqlite_synchronous`, default 1, "NORMAL". Each of these settings can passed to :class:`DjangoCache From f51d29ad7da758ad72cf0c1e67dfc9d1bd4b1899 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 17:54:21 -0800 Subject: [PATCH 208/550] Fix test for slow filesystem on Travis --- tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 80e4db0..38c15d5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1481,7 +1481,7 @@ def test_custom_eviction_policy(cache): time.sleep(1.1) - assert cache.cull() == 20 + assert cache.cull() > 0 assert cache.volume() < size_limit From 70b320b4803d6ad1159d42ba99712529023b2114 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 14 Dec 2017 17:54:43 -0800 Subject: [PATCH 209/550] Bump version to 3.0.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 26cfd29..c7619c3 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '3.0.0' -__build__ = 0x030000 +__version__ = '3.0.1' +__build__ = 0x030001 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 3b9dfbff60ae3b6865497fe89c19804b3361bac6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 13:16:09 -0700 Subject: [PATCH 210/550] Add caveat about Parallels shared folders --- docs/tutorial.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 4dc32c3..f4ff046 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -663,12 +663,14 @@ will define equality. threads and processes and as such inherits all SQLite caveats. Most notably SQLite is `not recommended`_ for use with Network File System (NFS) mounts. For this reason, :doc:`DiskCache ` currently `performs poorly`_ on `Python -Anywhere`_. +Anywhere`_. Users have also reported issues running inside of `Parallels`_ +shared folders. .. _`hash protocol`: https://docs.python.org/library/functions.html#hash .. _`not recommended`: https://www.sqlite.org/faq.html#q5 .. _`performs poorly`: https://www.pythonanywhere.com/forums/topic/1847/ .. _`Python Anywhere`: https://www.pythonanywhere.com/ +.. _`Parallels`: https://www.parallels.com/ Implementation Notes -------------------- From e45cc787ce9e4b7cbb8c65ec396b2e4d783c3123 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 13:33:30 -0700 Subject: [PATCH 211/550] Remove pickletools.optimize from Cache value serialization (prefer faster/bigger for values) --- diskcache/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index b36010b..8c3b7c9 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -239,8 +239,7 @@ def store(self, value, read, key=UNKNOWN): return size, MODE_BINARY, filename, None else: - data = pickle.dumps(value, protocol=self.pickle_protocol) - result = pickletools.optimize(data) + result = pickle.dumps(value, protocol=self.pickle_protocol) if len(result) < min_file_size: return 0, MODE_PICKLE, None, sqlite3.Binary(result) From f61404d6b58dd308d659a6f0ad10f141eb4081c4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 14:09:10 -0700 Subject: [PATCH 212/550] Update Django cache tests to 1.11 series --- requirements.txt | 2 +- tests/test_djangocache.py | 152 +++++++++++++++++++++++--------------- tox.ini | 2 +- 3 files changed, 93 insertions(+), 63 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0a47640..f59ec75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ mock==1.3.0 nose==1.3.7 -django==1.9.13 +django>=1.11,<1.12 diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 530a9d5..9b2a4a2 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- # Most of this file was copied from: -# https://raw.githubusercontent.com/django/django/master/tests/cache/tests.py +# https://raw.githubusercontent.com/django/django/1.11.12/tests/cache/tests.py # Unit tests for cache framework # Uses whatever cache backend is set in the test settings file. from __future__ import unicode_literals import copy +import io import os import re import shutil @@ -23,8 +24,10 @@ DEFAULT_CACHE_ALIAS, CacheKeyWarning, cache, caches, ) from django.core.cache.utils import make_template_fragment_key -from django.db import connection, connections -from django.http import HttpRequest, HttpResponse, StreamingHttpResponse +from django.db import close_old_connections, connection, connections +from django.http import ( + HttpRequest, HttpResponse, HttpResponseNotModified, StreamingHttpResponse, +) from django.middleware.cache import ( CacheMiddleware, FetchFromCacheMiddleware, UpdateCacheMiddleware, ) @@ -34,7 +37,7 @@ from django.template.response import TemplateResponse from django.test import ( RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, - override_settings, + ignore_warnings, mock, override_settings, ) from django.test.signals import setting_changed from django.utils import six, timezone, translation @@ -42,6 +45,7 @@ get_cache_key, learn_cache_key, patch_cache_control, patch_response_headers, patch_vary_headers, ) +from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_text from django.views.decorators.cache import cache_page @@ -97,25 +101,33 @@ def custom_key_func(key, key_prefix, version): return 'CUSTOM-' + '-'.join([key_prefix, str(version), key]) +def custom_key_func2(key, key_prefix, version): + "Another customized cache key function" + return '-'.join(['CUSTOM', key_prefix, str(version), key]) + + _caches_setting_base = { 'default': {}, 'prefix': {'KEY_PREFIX': 'cacheprefix{}'.format(os.getpid())}, 'v2': {'VERSION': 2}, 'custom_key': {'KEY_FUNCTION': custom_key_func}, - 'custom_key2': {'KEY_FUNCTION': 'cache.tests.custom_key_func'}, + 'custom_key2': {'KEY_FUNCTION': custom_key_func2}, 'cull': {'OPTIONS': {'MAX_ENTRIES': 30}}, 'zero_cull': {'OPTIONS': {'CULL_FREQUENCY': 0, 'MAX_ENTRIES': 30}}, } -def caches_setting_for_tests(base=None, **params): +def caches_setting_for_tests(base=None, exclude=None, **params): # `base` is used to pull in the memcached config from the original settings, + # `exclude` is a set of cache names denoting which `_caches_setting_base` keys + # should be omitted. # `params` are test specific overrides and `_caches_settings_base` is the # base config for the tests. # This results in the following search order: # params -> _caches_setting_base -> base base = base or {} - setting = {k: base.copy() for k in _caches_setting_base.keys()} + exclude = exclude or set() + setting = {k: base.copy() for k in _caches_setting_base.keys() if k not in exclude} for key, cache_params in setting.items(): cache_params.update(_caches_setting_base[key]) cache_params.update(params) @@ -217,6 +229,7 @@ def test_decr(self): def test_close(self): self.assertTrue(hasattr(cache, 'close')) + cache.close() def test_data_types(self): # Many different data types can be cached @@ -368,11 +381,11 @@ def test_clear(self): self.assertIsNone(cache.get("key2")) def test_long_timeout(self): - ''' - Using a timeout greater than 30 days makes memcached think - it is an absolute expiration timestamp instead of a relative - offset. Test that we honour this convention. Refs #12399. - ''' + """ + Followe memcached's convention where a timeout greater than 30 days is + treated as an absolute expiration timestamp instead of a relative + offset (#12399). + """ cache.set('key1', 'eggs', 60 * 60 * 24 * 30 + 1) # 30 days + 1 second self.assertEqual(cache.get('key1'), 'eggs') @@ -384,16 +397,16 @@ def test_long_timeout(self): self.assertEqual(cache.get('key4'), 'lobster bisque') def test_forever_timeout(self): - ''' + """ Passing in None into timeout results in a value that is cached forever - ''' + """ cache.set('key1', 'eggs', None) self.assertEqual(cache.get('key1'), 'eggs') cache.add('key2', 'ham', None) self.assertEqual(cache.get('key2'), 'ham') added = cache.add('key1', 'new eggs', None) - self.assertEqual(added, False) + self.assertIs(added, False) self.assertEqual(cache.get('key1'), 'eggs') cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, None) @@ -401,9 +414,9 @@ def test_forever_timeout(self): self.assertEqual(cache.get('key4'), 'lobster bisque') def test_zero_timeout(self): - ''' + """ Passing in zero into timeout results in a value that is not cached - ''' + """ cache.set('key1', 'eggs', 0) self.assertIsNone(cache.get('key1')) @@ -428,7 +441,7 @@ def _perform_cull_test(self, cull_cache, initial_count, final_count): # Count how many keys are left in the cache. for i in range(1, initial_count): if cull_cache.has_key('cull%d' % i): - count = count + 1 + count += 1 self.assertEqual(count, final_count) def test_cull(self): @@ -437,7 +450,7 @@ def test_cull(self): def test_zero_cull(self): self._perform_cull_test(caches['zero_cull'], 50, 19) - def test_invalid_keys(self): + def _perform_invalid_key_test(self, key, expected_warning): """ All the builtin backends (except memcached, see below) should warn on keys that would be refused by memcached. This encourages portable @@ -455,19 +468,31 @@ def func(key, *args): try: with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") - # memcached does not allow whitespace or control characters in keys - cache.set('key with spaces', 'value') - self.assertEqual(len(w), 2) - self.assertIsInstance(w[0].message, CacheKeyWarning) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - # memcached limits key length to 250 - cache.set('a' * 251, 'value') + cache.set(key, 'value') self.assertEqual(len(w), 1) self.assertIsInstance(w[0].message, CacheKeyWarning) + self.assertEqual(str(w[0].message.args[0]), expected_warning) finally: cache.key_func = old_func + def test_invalid_key_characters(self): + # memcached doesn't allow whitespace or control characters in keys. + key = 'key with spaces and 清' + expected_warning = ( + "Cache key contains characters that will cause errors if used " + "with memcached: %r" % key + ) + self._perform_invalid_key_test(key, expected_warning) + + def test_invalid_key_length(self): + # memcached limits key length to 250. + key = ('a' * 250) + '清' + expected_warning = ( + 'Cache key will cause errors if used with memcached: ' + '%r (longer than %s)' % (key, 250) + ) + self._perform_invalid_key_test(key, expected_warning) + def test_cache_versioning_get_set(self): # set, using default version = 1 cache.set('answer1', 42) @@ -627,54 +652,42 @@ def test_cache_versioning_incr_decr(self): def test_cache_versioning_get_set_many(self): # set, using default version = 1 cache.set_many({'ford1': 37, 'arthur1': 42}) - self.assertDictEqual(cache.get_many(['ford1', 'arthur1']), - {'ford1': 37, 'arthur1': 42}) - self.assertDictEqual(cache.get_many(['ford1', 'arthur1'], version=1), - {'ford1': 37, 'arthur1': 42}) + self.assertDictEqual(cache.get_many(['ford1', 'arthur1']), {'ford1': 37, 'arthur1': 42}) + self.assertDictEqual(cache.get_many(['ford1', 'arthur1'], version=1), {'ford1': 37, 'arthur1': 42}) self.assertDictEqual(cache.get_many(['ford1', 'arthur1'], version=2), {}) self.assertDictEqual(caches['v2'].get_many(['ford1', 'arthur1']), {}) - self.assertDictEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=1), - {'ford1': 37, 'arthur1': 42}) + self.assertDictEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=1), {'ford1': 37, 'arthur1': 42}) self.assertDictEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=2), {}) # set, default version = 1, but manually override version = 2 cache.set_many({'ford2': 37, 'arthur2': 42}, version=2) self.assertDictEqual(cache.get_many(['ford2', 'arthur2']), {}) self.assertDictEqual(cache.get_many(['ford2', 'arthur2'], version=1), {}) - self.assertDictEqual(cache.get_many(['ford2', 'arthur2'], version=2), - {'ford2': 37, 'arthur2': 42}) + self.assertDictEqual(cache.get_many(['ford2', 'arthur2'], version=2), {'ford2': 37, 'arthur2': 42}) - self.assertDictEqual(caches['v2'].get_many(['ford2', 'arthur2']), - {'ford2': 37, 'arthur2': 42}) + self.assertDictEqual(caches['v2'].get_many(['ford2', 'arthur2']), {'ford2': 37, 'arthur2': 42}) self.assertDictEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=1), {}) - self.assertDictEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=2), - {'ford2': 37, 'arthur2': 42}) + self.assertDictEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=2), {'ford2': 37, 'arthur2': 42}) # v2 set, using default version = 2 caches['v2'].set_many({'ford3': 37, 'arthur3': 42}) self.assertDictEqual(cache.get_many(['ford3', 'arthur3']), {}) self.assertDictEqual(cache.get_many(['ford3', 'arthur3'], version=1), {}) - self.assertDictEqual(cache.get_many(['ford3', 'arthur3'], version=2), - {'ford3': 37, 'arthur3': 42}) + self.assertDictEqual(cache.get_many(['ford3', 'arthur3'], version=2), {'ford3': 37, 'arthur3': 42}) - self.assertDictEqual(caches['v2'].get_many(['ford3', 'arthur3']), - {'ford3': 37, 'arthur3': 42}) + self.assertDictEqual(caches['v2'].get_many(['ford3', 'arthur3']), {'ford3': 37, 'arthur3': 42}) self.assertDictEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=1), {}) - self.assertDictEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=2), - {'ford3': 37, 'arthur3': 42}) + self.assertDictEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=2), {'ford3': 37, 'arthur3': 42}) # v2 set, default version = 2, but manually override version = 1 caches['v2'].set_many({'ford4': 37, 'arthur4': 42}, version=1) - self.assertDictEqual(cache.get_many(['ford4', 'arthur4']), - {'ford4': 37, 'arthur4': 42}) - self.assertDictEqual(cache.get_many(['ford4', 'arthur4'], version=1), - {'ford4': 37, 'arthur4': 42}) + self.assertDictEqual(cache.get_many(['ford4', 'arthur4']), {'ford4': 37, 'arthur4': 42}) + self.assertDictEqual(cache.get_many(['ford4', 'arthur4'], version=1), {'ford4': 37, 'arthur4': 42}) self.assertDictEqual(cache.get_many(['ford4', 'arthur4'], version=2), {}) self.assertDictEqual(caches['v2'].get_many(['ford4', 'arthur4']), {}) - self.assertDictEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=1), - {'ford4': 37, 'arthur4': 42}) + self.assertDictEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=1), {'ford4': 37, 'arthur4': 42}) self.assertDictEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=2), {}) def test_incr_version(self): @@ -784,24 +797,43 @@ def test_get_or_set(self): self.assertIsNone(cache.get('projector')) self.assertEqual(cache.get_or_set('projector', 42), 42) self.assertEqual(cache.get('projector'), 42) + self.assertEqual(cache.get_or_set('null', None), None) def test_get_or_set_callable(self): def my_callable(): return 'value' self.assertEqual(cache.get_or_set('mykey', my_callable), 'value') + self.assertEqual(cache.get_or_set('mykey', my_callable()), 'value') + + def test_get_or_set_callable_returning_none(self): + self.assertIsNone(cache.get_or_set('mykey', lambda: None)) + # Previous get_or_set() doesn't store None in the cache. + self.assertEqual(cache.get('mykey', 'default'), 'default') def test_get_or_set_version(self): + msg = ( + "get_or_set() missing 1 required positional argument: 'default'" + if six.PY3 + else 'get_or_set() takes at least 3 arguments' + ) cache.get_or_set('brian', 1979, version=2) - with self.assertRaisesMessage(ValueError, 'You need to specify a value.'): + with self.assertRaisesMessage(TypeError, msg): cache.get_or_set('brian') - with self.assertRaisesMessage(ValueError, 'You need to specify a value.'): + with self.assertRaisesMessage(TypeError, msg): cache.get_or_set('brian', version=1) self.assertIsNone(cache.get('brian', version=1)) self.assertEqual(cache.get_or_set('brian', 42, version=1), 42) self.assertEqual(cache.get_or_set('brian', 1979, version=2), 1979) self.assertIsNone(cache.get('brian', version=3)) + def test_get_or_set_racing(self): + with mock.patch('%s.%s' % (settings.CACHES['default']['BACKEND'], 'add')) as cache_add: + # Simulate cache.add() failing to add a value. In that case, the + # default value should be returned. + cache_add.return_value = False + self.assertEqual(cache.get_or_set('key', 'default'), 'default') + class PicklingSideEffect(object): @@ -853,19 +885,17 @@ def test_cache_write_unpicklable_type(self): # This fails if not using the highest pickling protocol on Python 2. cache.set('unpicklable', UnpicklableType()) - def test_custom_key_func(self): - # GrantJ 2016-02-22 Disable test in BaseCacheTests. Fails for unknown - # reason. - pass - def test_cull(self): cache.cull() def test_zero_cull(self): - pass # DiskCache has its own cull strategy. + pass # DiskCache has its own cull strategy. + + def test_invalid_key_characters(self): + pass # DiskCache supports any Pickle-able value as a cache key. - def test_invalid_keys(self): - pass # DiskCache supports any Pickleable value as a key. + def test_invalid_key_length(self): + pass # DiskCache supports any Pickle-able value as a cache key. def test_directory(self): self.assertTrue('tmp' in cache.directory) diff --git a/tox.ini b/tox.ini index df548ab..9b13a3d 100644 --- a/tox.ini +++ b/tox.ini @@ -3,5 +3,5 @@ envlist=py27,py34,py35,py36 [testenv] deps=nose mock - django + django>=1.11,<1.12 commands=nosetests From 426d431280db4c7d0249498f4624f99ae84d1533 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 14:16:50 -0700 Subject: [PATCH 213/550] Bump version to 3.0.2 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index c7619c3..e6cb2f0 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '3.0.1' -__build__ = 0x030001 +__version__ = '3.0.2' +__build__ = 0x030002 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From bef9a7955421b377f834b244a146103e2f08c20d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 14:36:04 -0700 Subject: [PATCH 214/550] Fixes for pylint on python 3.6 --- .pylintrc | 499 ++++++++++++++++++++++++++------------- diskcache/core.py | 18 +- diskcache/djangocache.py | 3 +- diskcache/persistent.py | 4 +- 4 files changed, 346 insertions(+), 178 deletions(-) diff --git a/.pylintrc b/.pylintrc index 58fbeae..7e716ec 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,43 +1,43 @@ [MASTER] -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS -# Pickle collected data for later comparisons. -persistent=yes +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +jobs=1 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= -# Use multiple processes to speed up Pylint. -jobs=1 +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages +suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. -optimize-ast=no - [MESSAGES CONTROL] @@ -45,11 +45,6 @@ optimize-ast=no # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= - # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration @@ -59,22 +54,91 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -#disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating - -[REPORTS] +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + locally-enabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + no-else-return, + inconsistent-return-statements, + not-callable -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no -# Tells whether to display a full report or only the messages -reports=yes +[REPORTS] # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which @@ -87,130 +151,172 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # used to format the message information. See doc for all details #msg-template= +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text -[BASIC] +# Tells whether to display a full report or only the messages +reports=no -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,input +# Activate the evaluation score. +score=yes -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata +[REFACTORING] -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= +# Maximum number of nested blocks for function / method body +max-nested-blocks=6 -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=optparse.Values,sys.exit -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ +[BASIC] -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ +# Naming style matching correct argument names +argument-naming-style=snake_case -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Regular expression matching correct argument names. Overrides argument- +# naming-style +#argument-rgx= -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ +# Naming style matching correct attribute names +attr-naming-style=snake_case -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ +# Regular expression matching correct attribute names. Overrides attr-naming- +# style +#attr-rgx= -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ +# Bad variable names which should always be refused, separated by a comma +bad-names=foo, + bar, + baz, + toto, + tutu, + tata -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Naming style matching correct class attribute names +class-attribute-naming-style=any -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style +#class-attribute-rgx= -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Naming style matching correct class names +class-naming-style=PascalCase -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ +# Regular expression matching correct class names. Overrides class-naming-style +#class-rgx= -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ +# Naming style matching correct constant names +const-naming-style=UPPER_CASE -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ +# Regular expression matching correct constant names. Overrides const-naming- +# style +#const-rgx= -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ +# Naming style matching correct function names +function-naming-style=snake_case -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ +# Regular expression matching correct function names. Overrides function- +# naming-style +#function-rgx= -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# Good variable names which should always be accepted, separated by a comma +good-names=i, + j, + k, + ex, + Run, + _ -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ +# Naming style matching correct inline iteration names +inlinevar-naming-style=any -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style +#inlinevar-rgx= + +# Naming style matching correct method names +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style +#method-rgx= + +# Naming style matching correct module names +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty +# Naming style matching correct variable names +variable-naming-style=snake_case -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 +# Regular expression matching correct variable names. Overrides variable- +# naming-style +#variable-rgx= [FORMAT] -# Maximum number of characters on a single line. -max-line-length=100 +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 # Maximum number of lines in a module max-module-lines=2000 -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no [LOGGING] @@ -223,14 +329,13 @@ logging-modules=logging [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO +notes=FIXME, + XXX, + TODO [SIMILARITIES] -# Minimum lines number of a similarity. -min-similarity-lines=20 - # Ignore comments when computing similarities. ignore-comments=yes @@ -240,9 +345,15 @@ ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no +# Minimum lines number of a similarity. +min-similarity-lines=4 + [SPELLING] +# Limits count of emitted suggestions for spelling mistakes +max-spelling-suggestions=4 + # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= @@ -260,49 +371,100 @@ spelling-store-unknown-words=no [TYPECHECK] +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=eviction_policy, + statistics, + count, + size, + cull_limit + # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). This supports can work -# with qualified names. -ignored-classes= +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members=statistics,eviction_policy,size_limit,cull_limit,large_value_threshold,sqlite_cache_size,sqlite_mmap_size,sqlite_synchronous,sqlite_journal_mode,count,size,hits,misses +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 -[VARIABLES] -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_$|dummy +[VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. -callbacks=cb_,_cb +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls @@ -310,65 +472,72 @@ valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - [DESIGN] # Maximum number of arguments for function / method max-args=8 -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=30 +# Maximum number of attributes for a class (see R0902). +max-attributes=7 -# Maximum number of return / yield for function / method body -max-returns=10 +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 # Maximum number of branch for function / method body -max-branches=25 +max-branches=20 -# Maximum number of statements in function / method body -max-statements=70 +# Maximum number of locals for function / method body +max-locals=30 # Maximum number of parents for a class (see R0901). max-parents=7 -# Maximum number of attributes for a class (see R0902). -max-attributes=10 +# Maximum number of public methods for a class (see R0904). +max-public-methods=25 + +# Maximum number of return / yield for function / method body +max-returns=8 + +# Maximum number of statements in function / method body +max-statements=60 # Minimum number of public methods for a class (see R0903). min-public-methods=2 -# Maximum number of public methods for a class (see R0904). -max-public-methods=25 -# Maximum number of boolean expressions in a if statement -max-bool-expr=10 +[IMPORTS] +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no -[IMPORTS] +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= +deprecated-modules=optparse,tkinter.tix # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + [EXCEPTIONS] diff --git a/diskcache/core.py b/diskcache/core.py index 8c3b7c9..e449636 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -19,20 +19,20 @@ import zlib if sys.hexversion < 0x03000000: - import cPickle as pickle + import cPickle as pickle # pylint: disable=import-error # ISSUE #25 Fix for http://bugs.python.org/issue10211 - from cStringIO import StringIO as BytesIO - TextType = unicode + from cStringIO import StringIO as BytesIO # pylint: disable=import-error + TextType = unicode # pylint: disable=invalid-name,undefined-variable BytesType = str - INT_TYPES = int, long - range = xrange # pylint: disable=redefined-builtin,invalid-name + INT_TYPES = int, long # pylint: disable=undefined-variable + range = xrange # pylint: disable=redefined-builtin,invalid-name,undefined-variable io_open = io.open # pylint: disable=invalid-name else: import pickle from io import BytesIO # pylint: disable=ungrouped-imports TextType = str BytesType = bytes - INT_TYPES = int, + INT_TYPES = (int,) io_open = open # pylint: disable=invalid-name try: @@ -584,9 +584,9 @@ def _transact(self, filename=None): raise else: sql('COMMIT') - for filename in filenames: - if filename is not None: - _disk_remove(filename) + for name in filenames: + if name is not None: + _disk_remove(name) def set(self, key, value, expire=None, read=False, tag=None): diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index fedd93c..3ec07da 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -303,9 +303,8 @@ def cull(self): return self._cache.cull() - def clear(self, **kwargs): + def clear(self): "Remove *all* values from the cache at once." - # pylint: disable=unused-argument return self._cache.clear() diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 9e4d7f0..499c350 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -14,8 +14,8 @@ from .core import BytesType, Cache, ENOVAL, TextType, Timeout if sys.hexversion < 0x03000000: - from itertools import izip as zip # pylint: disable=redefined-builtin,ungrouped-imports,wrong-import-order - range = xrange # pylint: disable=redefined-builtin,invalid-name + from itertools import izip as zip # pylint: disable=redefined-builtin,no-name-in-module,ungrouped-imports + range = xrange # pylint: disable=redefined-builtin,invalid-name,undefined-variable def _make_compare(seq_op, doc): From f6ef7e677435dce47e56ed9b4f3cdf2ef572abd5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 14:36:31 -0700 Subject: [PATCH 215/550] Bump version to 3.0.3 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index e6cb2f0..7ff361a 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '3.0.2' -__build__ = 0x030002 +__version__ = '3.0.3' +__build__ = 0x030003 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016 Grant Jenks' From 161000ed03537fec41291cd8d54785a3250be65c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 14:48:25 -0700 Subject: [PATCH 216/550] Update copyright to 2018 --- LICENSE | 2 +- README.rst | 4 ++-- diskcache/__init__.py | 2 +- docs/index.rst | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index 3b481fd..ff2e17b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2016 Grant Jenks +Copyright 2016-2018 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.rst b/README.rst index 158e752..f6cf18c 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ DiskCache: Disk Backed Cache `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. -The cloud-based computing of 2016 puts a premium on memory. Gigabytes of empty +The cloud-based computing of 2018 puts a premium on memory. Gigabytes of empty space is left on disks as processes vie for memory. Among these processes is Memcached (and sometimes Redis) which is used as a cache. Wouldn't it be nice to leverage empty disk space for caching? @@ -127,7 +127,7 @@ Reference and Indices DiskCache License ----------------- -Copyright 2016 Grant Jenks +Copyright 2016-2018 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 7ff361a..338bdb8 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -33,4 +33,4 @@ __build__ = 0x030003 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2016 Grant Jenks' +__copyright__ = 'Copyright 2016-2018 Grant Jenks' diff --git a/docs/index.rst b/docs/index.rst index 23c7c8c..ed0357a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ DiskCache: Disk Backed Cache `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. -The cloud-based computing of 2016 puts a premium on memory. Gigabytes of empty +The cloud-based computing of 2018 puts a premium on memory. Gigabytes of empty space is left on disks as processes vie for memory. Among these processes is Memcached (and sometimes Redis) which is used as a cache. Wouldn't it be nice to leverage empty disk space for caching? From bc1ca5489606d5ce34406a4321c057474143e844 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 14:48:41 -0700 Subject: [PATCH 217/550] Bump version to 3.0.4 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 338bdb8..8645e3a 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '3.0.3' -__build__ = 0x030003 +__version__ = '3.0.4' +__build__ = 0x030004 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2018 Grant Jenks' From 7c0acc2e5f7c6663803642ee34aaf2040ce34f36 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 15:06:41 -0700 Subject: [PATCH 218/550] Fix test for PyPy (different error messages) --- tests/test_djangocache.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 9b2a4a2..b8e554f 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -812,15 +812,10 @@ def test_get_or_set_callable_returning_none(self): self.assertEqual(cache.get('mykey', 'default'), 'default') def test_get_or_set_version(self): - msg = ( - "get_or_set() missing 1 required positional argument: 'default'" - if six.PY3 - else 'get_or_set() takes at least 3 arguments' - ) cache.get_or_set('brian', 1979, version=2) - with self.assertRaisesMessage(TypeError, msg): + with self.assertRaises(TypeError): cache.get_or_set('brian') - with self.assertRaisesMessage(TypeError, msg): + with self.assertRaises(TypeError): cache.get_or_set('brian', version=1) self.assertIsNone(cache.get('brian', version=1)) self.assertEqual(cache.get_or_set('brian', 42, version=1), 42) From 1f0f06a379bc3c456444c08f3e7fbc1059bc2c2b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 18 Apr 2018 15:07:01 -0700 Subject: [PATCH 219/550] Bump version to 3.0.5 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 8645e3a..b3e147e 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '3.0.4' -__build__ = 0x030004 +__version__ = '3.0.5' +__build__ = 0x030005 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2018 Grant Jenks' From e36f3f00f502d364ce8964e5510801c4a6033b13 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 19 Apr 2018 16:40:05 -0700 Subject: [PATCH 220/550] Update appveyor to use Django 1.11 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1e66e98..4afd5c8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,7 @@ environment: install: - - "%PYTHON%\\python.exe -m pip install nose mock django==1.9.13" + - "%PYTHON%\\python.exe -m pip install nose mock django==1.11.12" build: off From 63972690e6f21b0cd8b69057b2e53a358e6ee124 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 19 Apr 2018 16:40:24 -0700 Subject: [PATCH 221/550] Bump version to 3.0.6 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index b3e147e..ca58f14 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '3.0.5' -__build__ = 0x030005 +__version__ = '3.0.6' +__build__ = 0x030006 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2018 Grant Jenks' From dc9b97a615610ecd874376b098dd98372b0deb7b Mon Sep 17 00:00:00 2001 From: Sraw Date: Sun, 22 Jul 2018 02:47:48 +0800 Subject: [PATCH 222/550] Add information about when to close instance (#72) Add information about when to close instance --- docs/tutorial.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f4ff046..679dd17 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -86,6 +86,15 @@ thread that accesses a cache is also responsible for calling :meth:`close >>> cache.close() >>> with Cache('/tmp/mycachedir') as reference: ... pass + +A closed instance will automatically re-open when needed, but re-openning a closed +instance is relatively slow, and as all operations are atomic, so you can safely leave +it open. + + >>> cache.set(b'key') = b'value' + >>> cache.close() + >>> cache.get(b'key') # automatically re-open, but slowly. + 'value' Set an item, get a value, and delete a key using the usual operators: From b9e1408521ab6f1fedb95267b715b5353f1617fb Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 26 Oct 2018 10:02:47 -0700 Subject: [PATCH 223/550] Add test script for issue 85 --- tests/issue_85.py | 105 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 tests/issue_85.py diff --git a/tests/issue_85.py b/tests/issue_85.py new file mode 100644 index 0000000..455dc75 --- /dev/null +++ b/tests/issue_85.py @@ -0,0 +1,105 @@ +"""Test Script for Issue #85 + +$ export PYTHONPATH=`pwd` +$ python tests/issue_85.py + +""" + +print('REMOVING CACHE DIRECTORY') +import shutil +shutil.rmtree('.cache', ignore_errors=True) + +print('INITIALIZING DJANGO') +import os +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') + +import django +django.setup() + +print('RUNNING MULTI-THREADING INIT TEST') +from django.core.cache import cache +def run(): + cache.get('key') + +import threading +threads = [threading.Thread(target=run) for _ in range(50)] +_ = [thread.start() for thread in threads] +_ = [thread.join() for thread in threads] + +print('SQLITE COMPILE OPTIONS') +c = cache._cache._shards[0] +options = c._sql('pragma compile_options').fetchall() +print('\n'.join(val for val, in options)) + +print('CREATING DATA TABLE') +c._con.execute('create table data (x)') +nums = [(num,) for num in range(1000)] +c._con.executemany('insert into data values (?)', nums) +c._timeout = 60 + +commands = { + 'read/write': [ + 'SELECT MAX(x) FROM data', + 'UPDATE data SET x = x + 1', + ], + 'write/read': [ + 'UPDATE data SET x = x + 1', + 'SELECT MAX(x) FROM data', + ], + 'begin/read/write': [ + 'BEGIN', + 'SELECT MAX(x) FROM data', + 'UPDATE data SET x = x + 1', + 'COMMIT', + ], + 'begin/write/read': [ + 'BEGIN', + 'UPDATE data SET x = x + 1', + 'SELECT MAX(x) FROM data', + 'COMMIT', + ], + 'begin immediate/read/write': [ + 'BEGIN IMMEDIATE', + 'SELECT MAX(x) FROM data', + 'UPDATE data SET x = x + 1', + 'COMMIT', + ], + 'begin immediate/write/read': [ + 'BEGIN IMMEDIATE', + 'UPDATE data SET x = x + 1', + 'SELECT MAX(x) FROM data', + 'COMMIT', + ], + 'begin exclusive/read/write': [ + 'BEGIN EXCLUSIVE', + 'SELECT MAX(x) FROM data', + 'UPDATE data SET x = x + 1', + 'COMMIT', + ], + 'begin exclusive/write/read': [ + 'BEGIN EXCLUSIVE', + 'UPDATE data SET x = x + 1', + 'SELECT MAX(x) FROM data', + 'COMMIT', + ], +} + +import collections +errors = collections.deque() + +import sqlite3 + +def run(): + try: + for statement in statements: + c._sql(statement) + except sqlite3.OperationalError: + errors.append(True) + +for key, statements in commands.items(): + print(f'RUNNING {key}') + errors.clear() + threads = [threading.Thread(target=run) for _ in range(100)] + _ = [thread.start() for thread in threads] + _ = [thread.join() for thread in threads] + print('Error count:', len(errors)) From d0d58d2fc3864d7d07ca825b347a9db6c9a7c728 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 26 Oct 2018 10:03:17 -0700 Subject: [PATCH 224/550] Add core.Cache._con for thread-safe connection shortcut --- diskcache/core.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index e449636..05ec0c4 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -537,7 +537,7 @@ def disk(self): @property - def _sql(self): + def _con(self): con = getattr(self._local, 'con', None) if con is None: @@ -561,7 +561,12 @@ def _sql(self): if key.startswith('sqlite_'): self.reset(key, value, update=False) - return con.execute + return con + + + @property + def _sql(self): + return self._con.execute @cl.contextmanager From 71cbe4facbca56bf8fb53ad2c7002f8f7e6bd568 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 27 Oct 2018 22:50:08 -0700 Subject: [PATCH 225/550] Refactor script into functions, add transaction serialization analysis --- tests/issue_85.py | 132 +++++++++++++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/tests/issue_85.py b/tests/issue_85.py index 455dc75..a52de97 100644 --- a/tests/issue_85.py +++ b/tests/issue_85.py @@ -5,47 +5,56 @@ """ -print('REMOVING CACHE DIRECTORY') +import collections +import django +import os +import random import shutil -shutil.rmtree('.cache', ignore_errors=True) +import sqlite3 +import threading +import time -print('INITIALIZING DJANGO') -import os -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') -import django -django.setup() +def remove_cache_dir(): + print('REMOVING CACHE DIRECTORY') + shutil.rmtree('.cache', ignore_errors=True) -print('RUNNING MULTI-THREADING INIT TEST') -from django.core.cache import cache -def run(): - cache.get('key') -import threading -threads = [threading.Thread(target=run) for _ in range(50)] -_ = [thread.start() for thread in threads] -_ = [thread.join() for thread in threads] +def init_django(): + global shard + print('INITIALIZING DJANGO') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') + django.setup() + from django.core.cache import cache + shard = cache._cache._shards[0] + + +def multi_threading_init_test(): + print('RUNNING MULTI-THREADING INIT TEST') + from django.core.cache import cache + + def run(): + cache.get('key') + + threads = [threading.Thread(target=run) for _ in range(50)] + _ = [thread.start() for thread in threads] + _ = [thread.join() for thread in threads] + + +def show_sqlite_compile_options(): + print('SQLITE COMPILE OPTIONS') + options = shard._sql('pragma compile_options').fetchall() + print('\n'.join(val for val, in options)) -print('SQLITE COMPILE OPTIONS') -c = cache._cache._shards[0] -options = c._sql('pragma compile_options').fetchall() -print('\n'.join(val for val, in options)) -print('CREATING DATA TABLE') -c._con.execute('create table data (x)') -nums = [(num,) for num in range(1000)] -c._con.executemany('insert into data values (?)', nums) -c._timeout = 60 +def create_data_table(): + print('CREATING DATA TABLE') + shard._con.execute('create table data (x)') + nums = [(num,) for num in range(1000)] + shard._con.executemany('insert into data values (?)', nums) + commands = { - 'read/write': [ - 'SELECT MAX(x) FROM data', - 'UPDATE data SET x = x + 1', - ], - 'write/read': [ - 'UPDATE data SET x = x + 1', - 'SELECT MAX(x) FROM data', - ], 'begin/read/write': [ 'BEGIN', 'SELECT MAX(x) FROM data', @@ -84,22 +93,49 @@ def run(): ], } -import collections -errors = collections.deque() -import sqlite3 +values = collections.deque() + -def run(): +def run(statements): + ident = threading.get_ident() try: - for statement in statements: - c._sql(statement) - except sqlite3.OperationalError: - errors.append(True) - -for key, statements in commands.items(): - print(f'RUNNING {key}') - errors.clear() - threads = [threading.Thread(target=run) for _ in range(100)] - _ = [thread.start() for thread in threads] - _ = [thread.join() for thread in threads] - print('Error count:', len(errors)) + for index, statement in enumerate(statements): + if index == (len(statements) - 1): + values.append(('COMMIT', ident)) + time.sleep(random.random() / 10.0) + shard._sql(statement) + if index == 0: + values.append(('BEGIN', ident)) + except sqlite3.OperationalError as exc: + values.append(('ERROR', ident)) + + +def test_transaction_errors(): + for key, statements in commands.items(): + print(f'RUNNING {key}') + values.clear() + threads = [] + for _ in range(100): + thread = threading.Thread(target=run, args=(statements,)) + threads.append(thread) + _ = [thread.start() for thread in threads] + _ = [thread.join() for thread in threads] + errors = [pair for pair in values if pair[0] == 'ERROR'] + begins = [pair for pair in values if pair[0] == 'BEGIN'] + commits = [pair for pair in values if pair[0] == 'COMMIT'] + print('Error count:', len(errors)) + print('Begin count:', len(begins)) + print('Commit count:', len(commits)) + begin_idents = [ident for _, ident in begins] + commit_idents = [ident for _, ident in commits] + print('Serialized:', begin_idents == commit_idents) + + +if __name__ == '__main__': + remove_cache_dir() + init_django() + multi_threading_init_test() + show_sqlite_compile_options() + create_data_table() + test_transaction_errors() From 43029175ed9579e2e8cf89167c2039caa8e1584c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 27 Oct 2018 23:05:26 -0700 Subject: [PATCH 226/550] Fix typo in comment --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index 05ec0c4..02ed192 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1873,7 +1873,7 @@ def reset(self, key, value=ENOVAL, update=True): if key.startswith('sqlite_'): - # 2016-02-17 GrantJ - PRAGMA and autocommit_level=None + # 2016-02-17 GrantJ - PRAGMA and isolation_level=None # don't always play nicely together. Retry setting the # PRAGMA. I think some PRAGMA statements expect to # immediately take an EXCLUSIVE lock on the database. I From f17c945d0a651eda8ce5fb6fe1fe07b1bc5cb21b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 1 Nov 2018 22:12:13 -0700 Subject: [PATCH 227/550] Add _sql_retry attribute and use in __init__ and reset methods --- diskcache/core.py | 97 ++++++++++++++++++++++++++-------------------- tests/test_core.py | 19 --------- 2 files changed, 55 insertions(+), 61 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 02ed192..d7de9a2 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -388,7 +388,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): ' and could not be created' % self._directory ) - sql = self._sql + sql = self._sql_retry # Setup Settings table. @@ -569,6 +569,34 @@ def _sql(self): return self._con.execute + @property + def _sql_retry(self): + con = self._con + + # 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() + while True: + try: + return con.execute(statement, *args, **kwargs) + except sqlite3.OperationalError as exc: + if exc.args[0] != '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, filename=None): sql = self._sql @@ -1858,51 +1886,36 @@ def reset(self, key, value=ENOVAL, update=True): :raises Timeout: if database timeout expires """ + sql = self._sql_retry + if value is ENOVAL: select = 'SELECT value FROM Settings WHERE key = ?' - (value,), = self._sql(select, (key,)).fetchall() + (value,), = sql(select, (key,)).fetchall() setattr(self, key, value) return value - else: - if update: - with self._transact() as (sql, _): - statement = 'UPDATE Settings SET value = ? WHERE key = ?' - sql(statement, (value, key)) - else: - sql = self._sql - - if key.startswith('sqlite_'): - - # 2016-02-17 GrantJ - PRAGMA and isolation_level=None - # don't always play nicely together. Retry setting the - # PRAGMA. I think some PRAGMA statements expect to - # immediately take an EXCLUSIVE lock on the database. I - # can't find any documentation for this but without the - # retry, stress will intermittently fail with multiple - # processes. - - pause = 0.001 - count = 60000 # 60 / 0.001 - error = sqlite3.OperationalError - pragma = key[7:] - - for _ in range(count): - try: - args = pragma, value - sql('PRAGMA %s = %s' % args).fetchall() - except sqlite3.OperationalError as exc: - error = exc - time.sleep(pause) - else: - break - else: - raise error - del error + if update: + statement = 'UPDATE Settings SET value = ? WHERE key = ?' + sql(statement, (value, key)) - elif key.startswith('disk_'): - attr = key[5:] - setattr(self._disk, attr, value) + if key.startswith('sqlite_'): - setattr(self, key, value) - return value + # 2016-02-17 GrantJ - PRAGMA and isolation_level=None + # don't always play nicely together. Retry setting the + # PRAGMA. I think some PRAGMA statements expect to + # immediately take an EXCLUSIVE lock on the database. I + # can't find any documentation for this but without the + # retry, stress will intermittently fail with multiple + # processes. + + # 2018-11-01 GrantJ - Retry logic moved to + # ``_execute_with_retry`` in ``self._sql_retry``. + + pragma = key[7:] + sql('PRAGMA %s = %s' % (pragma, value)).fetchall() + elif key.startswith('disk_'): + attr = key[5:] + setattr(self._disk, attr, value) + + setattr(self, key, value) + return value diff --git a/tests/test_core.py b/tests/test_core.py index 38c15d5..d58e013 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -177,25 +177,6 @@ def test_init_makedirs(): raise -@setup_cache -def test_pragma(cache): - local = mock.Mock() - con = mock.Mock() - execute = mock.Mock() - cursor = mock.Mock() - fetchall = mock.Mock() - - local.con = con - con.execute = execute - execute.return_value = cursor - cursor.fetchall = fetchall - fetchall.side_effect = [sqlite3.OperationalError, None] - - size = 2 ** 28 - - with mock.patch.object(cache, '_local', local): - assert cache.reset('sqlite_mmap_size', size) == size - @setup_cache @nt.raises(sqlite3.OperationalError) def test_pragma_error(cache): From 6373b641bc2273d9abfb9648b883d61151ead7ac Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 4 Nov 2018 14:55:18 -0800 Subject: [PATCH 228/550] Remove ENOENT check, IOError means get() fails --- diskcache/core.py | 9 +++------ tests/test_core.py | 11 ----------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index d7de9a2..b5273a4 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -975,12 +975,9 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): try: value = self._disk.fetch(mode, filename, db_value, read) - except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - return default - else: - raise + except IOError: + # Key was deleted before we could retrieve result. + return default else: # Slow path, transaction required. cache_hit = ( diff --git a/tests/test_core.py b/tests/test_core.py index d58e013..efeb90b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -280,17 +280,6 @@ def test_get_keyerror4(cache): cache[0] -@nt.raises(IOError) -@setup_cache -def test_get_keyerror5(cache): - func = mock.Mock(side_effect=IOError(errno.EACCES, '')) - - cache[0] = b'abcd' * 2 ** 20 - - with mock.patch('diskcache.core.open', func): - cache[0] - - @setup_cache def test_read(cache): cache.set(0, b'abcd' * 2 ** 20) From d3cb88ada7865dd3550f51ea7010f6e00e19406a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 4 Nov 2018 15:04:47 -0800 Subject: [PATCH 229/550] Change OperationalError exception check for missing arguments --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index b5273a4..1f78a2d 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -587,7 +587,7 @@ def _execute_with_retry(statement, *args, **kwargs): try: return con.execute(statement, *args, **kwargs) except sqlite3.OperationalError as exc: - if exc.args[0] != 'database is locked': + if str(exc) != 'database is locked': raise diff = time.time() - start if diff > 60: From 9ed818969277045ad9503650fafec0a37abb9b38 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 4 Nov 2018 15:24:20 -0800 Subject: [PATCH 230/550] Simplify pragma-setting code before tables exist --- diskcache/core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 1f78a2d..54d7544 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -409,10 +409,8 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): # Chance to set pragmas before any tables are created. for key, value in sorted(sets.items()): - if not key.startswith('sqlite_'): - continue - - self.reset(key, value, update=False) + if key.startswith('sqlite_'): + self.reset(key, value, update=False) sql('CREATE TABLE IF NOT EXISTS Settings (' ' key TEXT NOT NULL UNIQUE,' From b5f5ef6634cdfd21f88437030ba597593720592c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 4 Nov 2018 15:24:41 -0800 Subject: [PATCH 231/550] Improve comment formatting --- diskcache/core.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 54d7544..df40908 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -545,9 +545,10 @@ def _con(self): 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. + # 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' From 32da631ad777156e11e5d190cddcb30489f42d04 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 4 Nov 2018 15:31:50 -0800 Subject: [PATCH 232/550] Add process ID check to support process forking --- diskcache/core.py | 10 ++++++++++ tests/test_core.py | 3 +++ 2 files changed, 13 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index df40908..fb646ef 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -536,6 +536,16 @@ def disk(self): @property def _con(self): + # Check process ID to support process forking. If the process + # ID changes, close the connection and update the process ID. + + local_pid = getattr(self._local, 'pid', None) + pid = os.getpid() + + if local_pid != pid: + self.close() + self._local.pid = pid + con = getattr(self._local, 'con', None) if con is None: diff --git a/tests/test_core.py b/tests/test_core.py index efeb90b..80c54df 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -186,6 +186,7 @@ def test_pragma_error(cache): cursor = mock.Mock() fetchall = mock.Mock() + local.pid = os.getpid() local.con = con con.execute = execute execute.return_value = cursor @@ -324,6 +325,7 @@ def test_set_timeout(cache): con = mock.Mock() execute = mock.Mock() + local.pid = os.getpid() local.con = con con.execute = execute execute.side_effect = sqlite3.OperationalError @@ -942,6 +944,7 @@ def test_add_timeout(cache): con = mock.Mock() execute = mock.Mock() + local.pid = os.getpid() local.con = con con.execute = execute execute.side_effect = sqlite3.OperationalError From 4c4b3305a632655a43892e585ddbbcd99180478e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 5 Nov 2018 08:58:30 -0800 Subject: [PATCH 233/550] Move test_add_concurrent from core to fanout --- tests/test_core.py | 30 ------------------------------ tests/test_fanout.py | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 80c54df..cd56fd6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -907,36 +907,6 @@ def test_add_large_value(cache): cache.check() -def stress_add(cache, limit, results): - total = 0 - for num in range(limit): - if cache.add(num, num): - total += 1 - # Stop one thread from running ahead of others. - time.sleep(0.001) - results.append(total) - - -@setup_cache -def test_add_concurrent(cache): - results = co.deque() - limit = 1000 - - threads = [ - threading.Thread(target=stress_add, args=(cache, limit, results)) - for _ in range(16) - ] - - for thread in threads: - thread.start() - - for thread in threads: - thread.join() - - assert sum(results) == limit - cache.check() - - @setup_cache @nt.raises(dc.Timeout) def test_add_timeout(cache): diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 6d43f95..386bdb4 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -2,6 +2,7 @@ from __future__ import print_function +import collections as co import errno import functools as ft import hashlib @@ -32,13 +33,17 @@ if sys.hexversion < 0x03000000: range = xrange -def setup_cache(func): +def setup_cache(func=None, **options): + if func is None: + return lambda func: setup_cache(func, **options) + @ft.wraps(func) def wrapper(): shutil.rmtree('tmp', ignore_errors=True) - with dc.FanoutCache('tmp') as cache: + with dc.FanoutCache('tmp', **options) as cache: func(cache) shutil.rmtree('tmp', ignore_errors=True) + return wrapper @@ -163,6 +168,36 @@ def test_add_timeout_retry(cache): assert cache.add(0, 0, retry=True) +def stress_add(cache, limit, results): + total = 0 + for num in range(limit): + if cache.add(num, num, retry=True): + total += 1 + # Stop one thread from running ahead of others. + time.sleep(0.001) + results.append(total) + + +@setup_cache(shards=1) +def test_add_concurrent(cache): + results = co.deque() + limit = 1000 + + threads = [ + threading.Thread(target=stress_add, args=(cache, limit, results)) + for _ in range(16) + ] + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + + assert sum(results) == limit + cache.check() + + @setup_cache def test_incr(cache): cache.incr('key', delta=3) == 3 From 21ae73fca23a833a6ddbdcfa94088a3c4e8c2db7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 5 Nov 2018 09:20:19 -0800 Subject: [PATCH 234/550] Simplify and shorten duration of test_incr_concurrent --- tests/test_fanout.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 386bdb4..e302bed 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -242,27 +242,24 @@ def stress_incr(cache, limit): time.sleep(0.001) -def test_incr_concurrent(): +@setup_cache(shards=1, timeout=0.001) +def test_incr_concurrent(cache): count = 16 - limit = 500 + limit = 50 - with dc.FanoutCache('tmp', timeout=0.001) as cache: - threads = [ - threading.Thread(target=stress_incr, args=(cache, limit)) - for _ in range(count) - ] - - for thread in threads: - thread.start() + threads = [ + threading.Thread(target=stress_incr, args=(cache, limit)) + for _ in range(count) + ] - for thread in threads: - thread.join() + for thread in threads: + thread.start() - with dc.FanoutCache('tmp') as cache: - assert cache.get(b'key') == count * limit - cache.check() + for thread in threads: + thread.join() - shutil.rmtree('tmp', ignore_errors=True) + assert cache.get(b'key') == count * limit + cache.check() @setup_cache From fb3e78568d47802199df8a83aa1d3742ec566711 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 5 Nov 2018 10:18:39 -0800 Subject: [PATCH 235/550] Avoid settings pragmas values that are already set --- diskcache/core.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index fb646ef..3e3bcc0 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1905,6 +1905,7 @@ def reset(self, key, value=ENOVAL, update=True): sql(statement, (value, key)) if key.startswith('sqlite_'): + pragma = key[7:] # 2016-02-17 GrantJ - PRAGMA and isolation_level=None # don't always play nicely together. Retry setting the @@ -1917,8 +1918,14 @@ def reset(self, key, value=ENOVAL, update=True): # 2018-11-01 GrantJ - Retry logic moved to # ``_execute_with_retry`` in ``self._sql_retry``. - pragma = key[7:] - sql('PRAGMA %s = %s' % (pragma, value)).fetchall() + # 2018-11-05 GrantJ - Avoid setting pragma values that + # are already set. Pragma settings like auto_vacuum and + # journal_mode can take a long time or may not work after + # tables have been created. + + (old_value,), = sql('PRAGMA %s' % (pragma)).fetchall() + if old_value != value: + sql('PRAGMA %s = %s' % (pragma, value)).fetchall() elif key.startswith('disk_'): attr = key[5:] setattr(self._disk, attr, value) From 76735e03a386ac2741e39755330271f3def99faa Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 5 Nov 2018 10:38:47 -0800 Subject: [PATCH 236/550] Catch Timeout exceptions during stress --- tests/stress_test_core.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index a3e7c8b..bce0d16 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -3,7 +3,7 @@ from __future__ import print_function import collections as co -from diskcache import Cache, UnknownFileWarning, EmptyDirWarning +from diskcache import Cache, UnknownFileWarning, EmptyDirWarning, Timeout import multiprocessing as mp import os import random @@ -124,19 +124,24 @@ def all_ops(): def worker(queue, eviction_policy, processes, threads): - timings = {'get': [], 'set': [], 'delete': []} + timings = co.defaultdict(list) cache = Cache('tmp', eviction_policy=eviction_policy) for index, (action, key, value) in enumerate(iter(queue.get, None)): start = time.time() - if action == 'set': - cache.set(key, value, expire=EXPIRE) - elif action == 'get': - result = cache.get(key) + try: + if action == 'set': + cache.set(key, value, expire=EXPIRE) + elif action == 'get': + result = cache.get(key) + else: + assert action == 'delete' + cache.delete(key) + except Timeout: + miss = True else: - assert action == 'delete' - cache.delete(key) + miss = False stop = time.time() @@ -144,7 +149,10 @@ def worker(queue, eviction_policy, processes, threads): assert result == value if index > WARMUP: - timings[action].append(stop - start) + delta = stop - start + timings[action].append(delta) + if miss: + timings[action + '-miss'].append(delta) queue.put(timings) @@ -179,7 +187,7 @@ def dispatch(num, eviction_policy, processes, threads): stop = time.time() - timings = {'get': [], 'set': [], 'delete': [], 'self': (stop - start)} + timings = co.defaultdict(list) for thread_queue in thread_queues: data = thread_queue.get() @@ -243,7 +251,7 @@ def stress_test(create=True, delete=True, warnings.simplefilter('ignore', category=EmptyDirWarning) cache.check() - timings = {'get': [], 'set': [], 'delete': [], 'self': 0.0} + timings = co.defaultdict(list) for num in range(processes): with open('output-%s.pkl' % num, 'rb') as reader: From c1514c6343801d4ec1a7aecdff7fae9aebb49d21 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 6 Nov 2018 14:19:37 -0800 Subject: [PATCH 237/550] Remove couple of old and lousy multi-threading tests --- tests/test_core.py | 57 ---------------------------------------------- 1 file changed, 57 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index cd56fd6..8348fea 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -810,63 +810,6 @@ def test_tag(cache): assert cache.get(3, tag=True) == (None, b'three') -@setup_cache -def test_multiple_threads(cache): - values = list(range(100)) - - cache[0] = 0 - cache[1] = 1 - cache[2] = 2 - - cache = dc.Cache('tmp') - - def worker(): - sets = list(values) - random.shuffle(sets) - - with dc.Cache('tmp') as thread_cache: - for value in sets: - thread_cache[value] = value - - threads = [threading.Thread(target=worker) for _ in range(10)] - - for thread in threads: - thread.start() - - for thread in threads: - thread.join() - - for value in values: - assert cache[value] == value - - assert len(cache.check()) == 0 - - -@setup_cache -def test_thread_safe(cache): - values = list(range(100)) - - def worker(): - with cache: - sets = list(values) - random.shuffle(sets) - for value in sets: - cache[value] = value - - threads = [threading.Thread(target=worker) for _ in range(10)] - - for thread in threads: - thread.start() - - for thread in threads: - thread.join() - - for value in values: - assert cache[value] == value - - assert len(cache.check()) == 0 - - @setup_cache def test_with(cache): with dc.Cache('tmp') as tmp: From ce0c2ae09357d59609164e47d4fb9ef5540db724 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 6 Nov 2018 14:59:30 -0800 Subject: [PATCH 238/550] Set timeout to 0 during initialization and retry pragma with old value check --- diskcache/core.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 3e3bcc0..b2275d8 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -374,7 +374,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): raise ValueError('disk must subclass diskcache.Disk') self._directory = directory - self._timeout = 60 # Use 1 minute timeout for initialization. + self._timeout = 0 # Manually handle retries during initialization. self._local = threading.local() if not op.isdir(directory): @@ -580,7 +580,7 @@ def _sql(self): @property def _sql_retry(self): - con = self._con + sql = self._sql # 2018-11-01 GrantJ - Some SQLite builds/versions handle # the SQLITE_BUSY return value and connection parameter @@ -594,7 +594,7 @@ def _execute_with_retry(statement, *args, **kwargs): start = time.time() while True: try: - return con.execute(statement, *args, **kwargs) + return sql(statement, *args, **kwargs) except sqlite3.OperationalError as exc: if str(exc) != 'database is locked': raise @@ -1892,17 +1892,18 @@ def reset(self, key, value=ENOVAL, update=True): :raises Timeout: if database timeout expires """ - sql = self._sql_retry + sql = self._sql + sql_retry = self._sql_retry if value is ENOVAL: select = 'SELECT value FROM Settings WHERE key = ?' - (value,), = sql(select, (key,)).fetchall() + (value,), = sql_retry(select, (key,)).fetchall() setattr(self, key, value) return value if update: statement = 'UPDATE Settings SET value = ? WHERE key = ?' - sql(statement, (value, key)) + sql_retry(statement, (value, key)) if key.startswith('sqlite_'): pragma = key[7:] @@ -1915,17 +1916,25 @@ def reset(self, key, value=ENOVAL, update=True): # retry, stress will intermittently fail with multiple # processes. - # 2018-11-01 GrantJ - Retry logic moved to - # ``_execute_with_retry`` in ``self._sql_retry``. - # 2018-11-05 GrantJ - Avoid setting pragma values that # are already set. Pragma settings like auto_vacuum and # journal_mode can take a long time or may not work after # tables have been created. - (old_value,), = sql('PRAGMA %s' % (pragma)).fetchall() - if old_value != value: - sql('PRAGMA %s = %s' % (pragma, value)).fetchall() + start = time.time() + while True: + try: + (old_value,), = sql('PRAGMA %s' % (pragma)).fetchall() + if old_value != value: + sql('PRAGMA %s = %s' % (pragma, value)).fetchall() + break + except sqlite3.OperationalError as exc: + if str(exc) != 'database is locked': + raise + diff = time.time() - start + if diff > 60: + raise + time.sleep(0.001) elif key.startswith('disk_'): attr = key[5:] setattr(self._disk, attr, value) From a43b45700a7527675ab421ee051d010d2e0b1674 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 6 Nov 2018 16:34:08 -0800 Subject: [PATCH 239/550] Change body min-width to 240px for mobile --- docs/_static/custom.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 50b1356..998ebea 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -6,3 +6,7 @@ table { th.head { text-align: center; } + +div.body { + min-width: 240px; +} From 3effaa9b80858fd65fcae1d322b97cafb46a891f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 6 Nov 2018 21:18:56 -0800 Subject: [PATCH 240/550] Change docs around process forking (now supported, yay) --- docs/tutorial.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 679dd17..67823d8 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -64,7 +64,7 @@ Cache ----- The core of :doc:`DiskCache ` is :class:`diskcache.Cache` which -represents a disk and file backed cache. As a Cache it supports a familiar +represents a disk and file backed cache. As a Cache, it supports a familiar Python Mapping interface with additional cache and performance parameters. >>> from diskcache import Cache @@ -77,23 +77,24 @@ Cache objects may also reference the same directory from separate threads or processes. In this way, they are also process-safe and support cross-process communication. -When created, Cache objects open and maintain a file handle. As such, they do -not survive process forking but they may be serialized using Pickle. Each -thread that accesses a cache is also responsible for calling :meth:`close -` on the cache. You can use a Cache reference in a -`with` statement to safeguard calling :meth:`close `. +Cache objects open and maintain one or more file handles. But unlike files, all +Cache operations are atomic and Cache objects support process-forking and may +be serialized using Pickle. Each thread that accesses a cache should also call +:meth:`close ` on the cache. Cache objects can be used +in a `with` statement to safeguard calling :meth:`close +`. >>> cache.close() >>> with Cache('/tmp/mycachedir') as reference: ... pass -A closed instance will automatically re-open when needed, but re-openning a closed -instance is relatively slow, and as all operations are atomic, so you can safely leave -it open. +Closed Cache objects will automatically re-open when accessed. But opening Cache +objects is relatively slow, and since all operations are atomic, you can safely +leave Cache objects open. >>> cache.set(b'key') = b'value' >>> cache.close() - >>> cache.get(b'key') # automatically re-open, but slowly. + >>> cache.get(b'key') # Automatically opens, but slower. 'value' Set an item, get a value, and delete a key using the usual operators: From 6511693b4a534203d1f39828a9854f80a3d04451 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Nov 2018 16:36:11 -0800 Subject: [PATCH 241/550] Change flask-cache link to flask-caching --- docs/development.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development.rst b/docs/development.rst index a37c3a7..404c1da 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -29,7 +29,7 @@ Requests for Contributions #. Backend Compatibility - #. `Flask-Cache `_ + #. `Flask-Caching `_ #. `Beaker `_ #. `dogpile.cache `_ From 1d466a38b3be39a822cc2e7c2f1fc286dc454f4f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Nov 2018 16:39:09 -0800 Subject: [PATCH 242/550] Update disk_min_file_size in docs to 32 kb --- docs/api.rst | 2 +- docs/tutorial.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 90f15f9..660d094 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -58,7 +58,7 @@ Read the :ref:`Settings tutorial ` for details. pragma. * `sqlite_synchronous` (int) default 1, "NORMAL" - SQLite synchronous pragma. - * `disk_min_file_size` (int, in bytes) default one kilobyte - values with + * `disk_min_file_size` (int, in bytes) default 32 kilobytes - values with greater size are stored in files. * `disk_pickle_protocol` (int) default highest Pickle protocol - the Pickle protocol to use for data types that are not natively supported. diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 67823d8..15d8cd2 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -529,7 +529,7 @@ are updated lazily. Prefer idioms like :meth:`len >>> cache.size_limit 4000000000 >>> cache.disk_min_file_size - 1024 + 32768 >>> cache.reset('cull_limit', 0) # Disable automatic evictions. 0 >>> cache.set(b'key', 1.234) @@ -544,7 +544,7 @@ these may be specified when initializing the :ref:`Cache `. Changing these values will update the unprefixed attribute on the :class:`Disk ` object. -* `disk_min_file_size`, default one kilobyte. The minimum size to store a value +* `disk_min_file_size`, default 32 kilobytes. The minimum size to store a value in a file. * `disk_pickle_protocol`, default highest Pickle protocol. The Pickle protocol to use for data types that are not natively supported. From ea45793c8ca1b51d0c41878921eac63f5db9d6d9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 7 Nov 2018 17:05:45 -0800 Subject: [PATCH 243/550] Add more docs about FanoutCache retry behavior for Mapping operators --- diskcache/fanout.py | 15 +++++++++++++++ docs/tutorial.rst | 20 ++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 565537a..cfa2ccf 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -58,6 +58,9 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + :param key: key for item :param value: value for item :param float expire: seconds until the key expires @@ -84,6 +87,8 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): def __setitem__(self, key, value): """Set `key` and `value` item in cache. + Calls :func:`FanoutCache.set` internally with `retry` set to `True`. + :param key: key for item :param value: value for item @@ -190,6 +195,9 @@ 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`. + If database timeout occurs then returns `default` unless `retry` is set + to `True` (default `False`). + :param key: key for item :param default: return value if key is missing (default None) :param bool read: if True, return file handle to value @@ -220,6 +228,8 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, def __getitem__(self, key): """Return corresponding value for `key` from cache. + Calls :func:`FanoutCache.get` internally with `retry` set to `True`. + :param key: key for item :return: value for item :raises KeyError: if key is not found @@ -295,6 +305,9 @@ def delete(self, key, retry=False): Missing keys are ignored. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + :param key: key for item :param bool retry: retry if database timeout expires (default False) :return: True if item was deleted @@ -318,6 +331,8 @@ def delete(self, key, retry=False): def __delitem__(self, key): """Delete corresponding item for `key` from cache. + Calls :func:`FanoutCache.delete` internally with `retry` set to `True`. + :param key: key for item :raises KeyError: if key is not found diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 15d8cd2..a9e94f8 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -312,18 +312,22 @@ suggested. This will depend on your scenario. The default value is 8. Another parameter, `timeout`, sets a limit on how long to wait for database transactions. Transactions are used for every operation that writes to the -database. The `timeout` parameter is also present on -:class:`diskcache.Cache`. When a :exc:`diskcache.Timeout` error occurs in -:class:`Cache ` methods, the exception is raised to the -caller. In contrast, :class:`FanoutCache ` catches +database. When the timeout expires, a :exc:`diskcache.Timeout` error is raised +internally. This `timeout` parameter is also present on +:class:`diskcache.Cache`. When a :exc:`Timeout ` error +occurs in :class:`Cache ` methods, the exception is raised to +the caller. In contrast, :class:`FanoutCache ` catches timeout errors and aborts the operation. As a result, :meth:`set ` and :meth:`delete ` methods may silently fail. Most methods that handle :exc:`Timeout ` exceptions also include a `retry` keyword parameter -(default ``False``) to automatically repeat attempts that -timeout. :class:`FanoutCache ` will never raise a -:exc:`Timeout ` exception. The default `timeout` is 0.010 -(10 milliseconds). +(default ``False``) to automatically repeat attempts that timeout. The Mapping +interface operators: :meth:`cache[key] `, +:meth:`cache[key] = value `, and :meth:`del +cache[key] ` automatically retry operations +when :exc:`Timeout ` errors occur. :class:`FanoutCache +` will never raise a :exc:`Timeout ` +exception. The default `timeout` is 0.010 (10 milliseconds). >>> from diskcache import FanoutCache >>> cache = FanoutCache('/tmp/mycachedir', shards=4, timeout=1) From a08bf815964a134a87cfa18b47ac72a1e7a88766 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 8 Nov 2018 16:44:50 -0800 Subject: [PATCH 244/550] Bump version to 3.1.0 --- diskcache/__init__.py | 4 ++-- docs/tutorial.rst | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index ca58f14..6ac1d4d 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '3.0.6' -__build__ = 0x030006 +__version__ = '3.1.0' +__build__ = 0x030100 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2018 Grant Jenks' diff --git a/docs/tutorial.rst b/docs/tutorial.rst index a9e94f8..b6b245d 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -87,10 +87,10 @@ in a `with` statement to safeguard calling :meth:`close >>> cache.close() >>> with Cache('/tmp/mycachedir') as reference: ... pass - -Closed Cache objects will automatically re-open when accessed. But opening Cache -objects is relatively slow, and since all operations are atomic, you can safely -leave Cache objects open. + +Closed Cache objects will automatically re-open when accessed. But opening +Cache objects is relatively slow, and since all operations are atomic, you can +safely leave Cache objects open. >>> cache.set(b'key') = b'value' >>> cache.close() From 769b36aa5d6e259e4926332a1d0a37406f964d83 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 16 Nov 2018 11:34:07 -0800 Subject: [PATCH 245/550] Catch ValueError when unpacking old pragma value. --- diskcache/core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index b2275d8..af60a1d 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1924,8 +1924,12 @@ def reset(self, key, value=ENOVAL, update=True): start = time.time() while True: try: - (old_value,), = sql('PRAGMA %s' % (pragma)).fetchall() - if old_value != value: + try: + (old_value,), = sql('PRAGMA %s' % (pragma)).fetchall() + update = old_value != value + except ValueError: + update = True + if update: sql('PRAGMA %s = %s' % (pragma, value)).fetchall() break except sqlite3.OperationalError as exc: From 2e3af05c435023213353fa6084252059b03acd92 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 22 Nov 2018 10:44:52 -0800 Subject: [PATCH 246/550] Bump version to 3.1.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 6ac1d4d..26532cc 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -29,8 +29,8 @@ __title__ = 'diskcache' -__version__ = '3.1.0' -__build__ = 0x030100 +__version__ = '3.1.1' +__build__ = 0x030101 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2018 Grant Jenks' From 05cac6ab51dc723200a158ecb7b58e0ddcd662ce Mon Sep 17 00:00:00 2001 From: Mateusz Konieczny Date: Wed, 28 Nov 2018 20:09:43 +0100 Subject: [PATCH 247/550] fix syntax error in tutorial (#95) fix syntax error in tutorial (#95) --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index b6b245d..7ca2e41 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -92,7 +92,7 @@ Closed Cache objects will automatically re-open when accessed. But opening Cache objects is relatively slow, and since all operations are atomic, you can safely leave Cache objects open. - >>> cache.set(b'key') = b'value' + >>> cache.set(b'key', b'value') >>> cache.close() >>> cache.get(b'key') # Automatically opens, but slower. 'value' From 50a80fe1662e80b14be1f28db34d22a6ab86aa04 Mon Sep 17 00:00:00 2001 From: George Tantiras Date: Thu, 17 Jan 2019 01:11:14 +0200 Subject: [PATCH 248/550] Clarified timout settings for DjangoCache (#98) --- docs/tutorial.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 7ca2e41..d594731 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -390,7 +390,8 @@ DjangoCache 'BACKEND': 'diskcache.DjangoCache', 'LOCATION': '/path/to/cache/directory', 'SHARDS': 4, - 'DATABASE_TIMEOUT': 1.0, + 'DATABASE_TIMEOUT': 1.0, # Timeout for each DjangoCache database transaction + 'TIMEOUT': 300, # Default timeout for each key 'OPTIONS': { 'size_limit': 2 ** 32 # 4 gigabytes }, From 2774689c60bac3ebd06246943bca2014779ee2c6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 16 Jan 2019 15:22:45 -0800 Subject: [PATCH 249/550] Improve comments around DjangoCache settings example --- docs/tutorial.rst | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index d594731..faf7e28 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -389,19 +389,23 @@ DjangoCache 'default': { 'BACKEND': 'diskcache.DjangoCache', 'LOCATION': '/path/to/cache/directory', - 'SHARDS': 4, - 'DATABASE_TIMEOUT': 1.0, # Timeout for each DjangoCache database transaction - 'TIMEOUT': 300, # Default timeout for each key + 'TIMEOUT': 300, + # ^-- Django setting for default timeout of each key. + 'SHARDS': 8, + 'DATABASE_TIMEOUT': 0.010, # 10 milliseconds + # ^-- Timeout for each DjangoCache database transaction. 'OPTIONS': { - 'size_limit': 2 ** 32 # 4 gigabytes + 'size_limit': 2 ** 30 # 1 gigabyte }, }, } As with :class:`FanoutCache ` above, these settings -create a Django-compatible cache with four shards and a one second timeout. You -can pass further settings via the ``OPTIONS`` mapping as shown in the Django -documentation. :class:`DjangoCache ` will never raise a +create a Django-compatible cache with eight shards and a 10ms timeout. You can +pass further settings via the ``OPTIONS`` mapping as shown in the Django +documentation. Only the ``BACKEND`` and ``LOCATION`` keys are necessary in the +above example. The other keys simply display their default +value. :class:`DjangoCache ` will never raise a :exc:`Timeout ` exception. But unlike :class:`FanoutCache `, the keyword parameter `retry` defaults to ``True`` for :class:`DjangoCache ` methods. From 445afdc53c73c65a929bb8b780de2f2e63cc2775 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 12 Mar 2019 22:23:43 -0700 Subject: [PATCH 250/550] Fix Cache.incr docstring --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index af60a1d..0064c79 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -865,7 +865,7 @@ def incr(self, key, delta=1, default=0): :param key: key for item :param int delta: amount to increment (default 1) - :param int default: value if key is missing (default None) + :param int default: value if key is missing (default 0) :return: new value for item :raises KeyError: if key is not found and default is None :raises Timeout: if database timeout expires From 5458892614dd9c1a844c1daa797c24872d1eee28 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 13 Mar 2019 22:01:25 -0700 Subject: [PATCH 251/550] Remove __del__ methods from Deque and Index (error prone, what's the point?) --- diskcache/persistent.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 499c350..d17cd2f 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -618,10 +618,6 @@ def rotate(self, steps=1): self.append(value) - def __del__(self): - self._cache.close() - - __hash__ = None @@ -1342,7 +1338,3 @@ def __repr__(self): """ name = type(self).__name__ return '{0}({1!r})'.format(name, self.directory) - - - def __del__(self): - self._cache.close() From bf407f1fd3cbaf62d2c15783866ab70def731ecd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 16 Nov 2018 12:05:19 -0800 Subject: [PATCH 252/550] Fix abc imports for Python 3.7 warning. --- diskcache/persistent.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index d17cd2f..1bd0388 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -5,18 +5,32 @@ import operator as op import sys -from collections import MutableMapping, OrderedDict, Sequence -from collections import KeysView, ValuesView, ItemsView +from collections import OrderedDict from itertools import islice from shutil import rmtree from tempfile import mkdtemp from .core import BytesType, Cache, ENOVAL, TextType, Timeout +############################################################################ +# BEGIN Python 2/3 Shims +############################################################################ + +try: + from collections.abc import MutableMapping, Sequence + from collections.abc import KeysView, ValuesView, ItemsView +except ImportError: + from collections import MutableMapping, Sequence + from collections import KeysView, ValuesView, ItemsView + if sys.hexversion < 0x03000000: from itertools import izip as zip # pylint: disable=redefined-builtin,no-name-in-module,ungrouped-imports range = xrange # pylint: disable=redefined-builtin,invalid-name,undefined-variable +############################################################################ +# END Python 2/3 Shims +############################################################################ + def _make_compare(seq_op, doc): "Make compare method with Sequence semantics." From f921f6981a177a3af5580ab0b2069de9007b1c90 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 22 Nov 2018 09:30:13 -0800 Subject: [PATCH 253/550] Update references to Python 3.7 --- .travis.yml | 1 + README.rst | 2 +- docs/development.rst | 3 ++- docs/index.rst | 4 ++-- setup.py | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index c72311a..6b50cb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: - "3.4" - "3.5" - "3.6" + - "3.7" - "pypy" install: - pip install -r requirements.txt diff --git a/README.rst b/README.rst index f6cf18c..341ccba 100644 --- a/README.rst +++ b/README.rst @@ -63,7 +63,7 @@ Features - Supports multiple eviction policies (LRU and LFU included) - Keys support "tag" metadata and eviction - Developed on Python 2.7 -- Tested on CPython 2.7, 3.4, 3.5, 3.6 and PyPy +- Tested on CPython 2.7, 3.4, 3.5, 3.6, 3.7 and PyPy - Tested on Linux, Mac OS X, and Windows - Tested using Travis CI and AppVeyor CI diff --git a/docs/development.rst b/docs/development.rst index 404c1da..6d16d44 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -65,12 +65,13 @@ counterparts are necessary for some benchmarks. Testing ------- -:doc:`DiskCache ` currently tests against four versions of Python: +:doc:`DiskCache ` currently tests against five versions of Python: * CPython 2.7 * CPython 3.4 * CPython 3.5 * CPython 3.6 +* CPython 3.7 * PyPy2 Testing uses `tox `_. If you don't want to diff --git a/docs/index.rst b/docs/index.rst index ed0357a..68576df 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -62,8 +62,8 @@ Features - Thread-safe and process-safe - Supports multiple eviction policies (LRU and LFU included) - Keys support "tag" metadata and eviction -- Developed on Python 2.7 -- Tested on CPython 2.7, 3.4, 3.5, 3.6 and PyPy +- Developed on Python 3.7 +- Tested on CPython 2.7, 3.4, 3.5, 3.6, 3.7 and PyPy - Tested on Linux, Mac OS X, and Windows - Tested using Travis CI and AppVeyor CI diff --git a/setup.py b/setup.py index 0f00554..56150dc 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ def run_tests(self): 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ), From 936a086919499089b3f0d1ea595d227c12f7820d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 22 Nov 2018 09:35:03 -0800 Subject: [PATCH 254/550] Update travis/reqs --- .travis.yml | 23 ++++++++++++----------- requirements.txt | 6 +++--- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6b50cb2..7d20288 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,13 @@ +sudo: false language: python -python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - - "3.7" - - "pypy" -install: - - pip install -r requirements.txt -script: - - nosetests -v +install: pip install -r requirements.txt +script: nosetests -v +matrix: + include: + - python: 2.7 + - python: 3.4 + - python: 3.5 + - python: 3.6 + - python: 3.7 + dist: xenial + - python: pypy diff --git a/requirements.txt b/requirements.txt index f59ec75..34bc2f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -mock==1.3.0 -nose==1.3.7 -django>=1.11,<1.12 +mock +nose +django<2 From d66035887ff5ada119f216c110216f794d2f6897 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 22 Nov 2018 10:13:12 -0800 Subject: [PATCH 255/550] Start transition from nose to pytest --- .travis.yml | 13 +++++++++++-- MANIFEST.in | 2 +- appveyor.yml | 6 ++++-- requirements.txt | 13 ++++++++++++- setup.py | 19 +++++++++---------- tox.ini | 32 +++++++++++++++++++++++++++----- 6 files changed, 64 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7d20288..2c15404 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,22 @@ sudo: false language: python -install: pip install -r requirements.txt -script: nosetests -v +install: pip install tox +script: tox matrix: include: - python: 2.7 + env: TOXENV=py27 - python: 3.4 + env: TOXENV=py34 - python: 3.5 + env: TOXENV=py35 - python: 3.6 + env: TOXENV=py36 - python: 3.7 dist: xenial + env: TOXENV=py37 - python: pypy + env: TOXENV=pypy + - python: 3.7 + dist: xenial + env: TOXENV=lint diff --git a/MANIFEST.in b/MANIFEST.in index 645a28c..0c73842 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include README.rst LICENSE requirements.txt +include README.rst LICENSE diff --git a/appveyor.yml b/appveyor.yml index 4afd5c8..dc376f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,17 +6,19 @@ environment: - PYTHON: "C:\\Python34" - PYTHON: "C:\\Python35" - PYTHON: "C:\\Python36" + - PYTHON: "C:\\Python37" - PYTHON: "C:\\Python27-x64" - PYTHON: "C:\\Python34-x64" - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36-x64" + - PYTHON: "C:\\Python37-x64" install: - - "%PYTHON%\\python.exe -m pip install nose mock django==1.11.12" + - "%PYTHON%\\python.exe -m pip install tox" build: off test_script: - - "%PYTHON%\\python.exe -m nose -v" + - "%PYTHON%\\python.exe -m tox -e py" diff --git a/requirements.txt b/requirements.txt index 34bc2f0..bfbdb82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,14 @@ +coverage +django<2 +doc8 +gj mock nose -django<2 +pylint +pytest +pytest-cov +pytest-django +sphinx +tox +twine +wheel diff --git a/setup.py b/setup.py index 56150dc..64da622 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,9 @@ -import io -from setuptools import setup, find_packages +from setuptools import setup from setuptools.command.test import test as TestCommand -import sys import diskcache + class Tox(TestCommand): def finalize_options(self): TestCommand.finalize_options(self) @@ -13,24 +12,24 @@ def finalize_options(self): def run_tests(self): import tox errno = tox.cmdline(self.test_args) - sys.exit(errno) + exit(errno) + -with io.open('README.rst', encoding='UTF-8') as reader: +with open('README.rst') as reader: readme = reader.read() setup( - name='diskcache', + name=diskcache.__title__, version=diskcache.__version__, - description='Disk and file backed cache.', + description='Disk Cache -- Disk and file backed persistent cache.', long_description=readme, author='Grant Jenks', author_email='contact@grantjenks.com', url='http://www.grantjenks.com/docs/diskcache/', - packages=find_packages(exclude=('tests', 'docs')), - package_data={'': ['LICENSE', 'README.rst']}, + license='Apache 2.0', + packages=['diskcache'], tests_require=['tox'], cmdclass={'test': Tox}, - license='Apache 2.0', install_requires=[], classifiers=( 'Development Status :: 5 - Production/Stable', diff --git a/tox.ini b/tox.ini index 9b13a3d..9013024 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,29 @@ [tox] -envlist=py27,py34,py35,py36 +envlist=py27,py34,py35,py36,py37,pypy,lint +skip_missing_interpreters=True + [testenv] -deps=nose - mock - django>=1.11,<1.12 -commands=nosetests +deps= + django<2 + mock + pytest + pytest-django +commands=python -m pytest +setenv = + DJANGO_SETTINGS_MODULE=tests.settings + PYTHONPATH={toxinidir}:{toxinidir}/tests + +[pytest] +addopts= + --doctest-modules + --doctest-glob "*.rst" + --ignore tests/benchmark_core.py + --ignore tests/benchmark_djangocache.py + --ignore tests/benchmark_glob.py + --ignore tests/plot.py +norecursedirs=site-packages +testpaths=docs diskcache tests + +[testenv:lint] +deps=pylint +commands=pylint diskcache From e2c5c464c77507d7234b108cccf1535099d35d83 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 18 Mar 2019 21:58:42 -0700 Subject: [PATCH 256/550] Change Django version to 1.11.* --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bfbdb82..c1cc167 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ coverage -django<2 +django==1.11.* doc8 gj mock From 4dbd291964673606b33ec52bb6bdd5855936d77d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 18 Mar 2019 22:30:03 -0700 Subject: [PATCH 257/550] Remove old talk files --- tests/talk/benchmark.py | 35 --------- tests/talk/crawler.py | 52 ------------- tests/talk/manage.py | 22 ------ tests/talk/talk/__init__.py | 0 tests/talk/talk/settings.py | 141 ------------------------------------ tests/talk/talk/urls.py | 25 ------- tests/talk/talk/views.py | 25 ------- tests/talk/talk/wsgi.py | 16 ---- 8 files changed, 316 deletions(-) delete mode 100644 tests/talk/benchmark.py delete mode 100644 tests/talk/crawler.py delete mode 100755 tests/talk/manage.py delete mode 100644 tests/talk/talk/__init__.py delete mode 100644 tests/talk/talk/settings.py delete mode 100644 tests/talk/talk/urls.py delete mode 100644 tests/talk/talk/views.py delete mode 100644 tests/talk/talk/wsgi.py diff --git a/tests/talk/benchmark.py b/tests/talk/benchmark.py deleted file mode 100644 index e115162..0000000 --- a/tests/talk/benchmark.py +++ /dev/null @@ -1,35 +0,0 @@ -import random, requests, signal, time, threading - -signal.signal(signal.SIGINT, lambda signum, frame: exit()) - - -count = 0 - -def monitor(): - global count - while True: - time.sleep(1) - print(f"{'*' * (count // 8)}") - count = 0 - -thread = threading.Thread(target=monitor) -thread.daemon = True -thread.start() - - -# Histogram of expovariate values: -# value | count -# ----- | ----- -# 64 | ************************************************************* -# 127 | ******************************** -# 191 | *************** -# 254 | ****** -# 318 | *** -# 382 | ** -# 445 | * -# 509 | - -while True: - value = int(random.expovariate(1) * 100) - response = requests.get(f'http://127.0.0.1:8000/echo/{value}') - count += 1 diff --git a/tests/talk/crawler.py b/tests/talk/crawler.py deleted file mode 100644 index fc36154..0000000 --- a/tests/talk/crawler.py +++ /dev/null @@ -1,52 +0,0 @@ -import bs4, requests, signal, urllib.parse - -signal.signal(signal.SIGINT, lambda signum, frame: exit()) - -root='http://127.0.0.1:8000/' - - -def get(url): - "Get url and return response text." - print(url) - response = requests.get(url) - return response.text - - -def parse(url, text): - "Parse url with given text and yield links." - soup = bs4.BeautifulSoup(text, 'lxml') - - for anchor in soup.find_all('a', href=True): - full_url = urllib.parse.urljoin(url, anchor['href']) - href, _ = urllib.parse.urldefrag(full_url) - - if href.startswith(root): - yield href - - -from collections import deque - -def crawl(): - "Crawl root url." - urls = deque([root]) - results = dict() - - while True: - try: - url = urls.popleft() - except IndexError: - break - - if url in results: - continue - - text = get(url) - - for link in parse(url, text): - urls.append(link) - - results[url] = text - - -if __name__ == '__main__': - crawl() diff --git a/tests/talk/manage.py b/tests/talk/manage.py deleted file mode 100755 index 0c84391..0000000 --- a/tests/talk/manage.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk.settings") - try: - from django.core.management import execute_from_command_line - except ImportError: - # The above import may fail for some other reason. Ensure that the - # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. - try: - import django - except ImportError: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) - raise - execute_from_command_line(sys.argv) diff --git a/tests/talk/talk/__init__.py b/tests/talk/talk/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/talk/talk/settings.py b/tests/talk/talk/settings.py deleted file mode 100644 index aa4165f..0000000 --- a/tests/talk/talk/settings.py +++ /dev/null @@ -1,141 +0,0 @@ -""" -Django settings for talk project. - -Generated by 'django-admin startproject' using Django 1.10.6. - -For more information on this file, see -https://docs.djangoproject.com/en/1.10/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.10/ref/settings/ -""" - -import os - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '_lzt+2b46g)@x%set-4u7j-vjw-_%sq4xdco990z(l4o2$^_)*' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False - -ALLOWED_HOSTS = ['127.0.0.1'] - - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'talk.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'talk.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/1.10/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} - - -# Password validation -# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/1.10/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.10/howto/static-files/ - -STATIC_URL = '/static/' - - -CACHES = { - 'filebased': { - 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', - 'LOCATION': '/tmp/filebased', - 'OPTIONS': { - 'MAX_ENTRIES': 1000, - } - }, - 'memcached': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': [ - '127.0.0.1:11211', - ], - }, - 'diskcache': { - 'BACKEND': 'diskcache.DjangoCache', - 'LOCATION': '/tmp/diskcache', - } -} diff --git a/tests/talk/talk/urls.py b/tests/talk/talk/urls.py deleted file mode 100644 index ac79390..0000000 --- a/tests/talk/talk/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -"""talk URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf.urls import url -from django.contrib import admin - -from . import views - -urlpatterns = [ - url(r'^echo/(?P.*)$', views.echo), - url(r'^$', views.index), - url(r'^crawl/(?P.*)$', views.crawl), -] diff --git a/tests/talk/talk/views.py b/tests/talk/talk/views.py deleted file mode 100644 index 675b31e..0000000 --- a/tests/talk/talk/views.py +++ /dev/null @@ -1,25 +0,0 @@ -import random, time - -from django.http import HttpResponse -from django.views.decorators.cache import cache_page - -# @cache_page(3600, cache='filebased') -# @cache_page(3600, cache='memcached') -# @cache_page(3600, cache='diskcache') -def echo(request, value): - time.sleep(0.1) - return HttpResponse(value, content_type='text/plain') - - -def index(request): - return HttpResponse('0') - - -def crawl(request, value): - time.sleep(random.random()) - value = int(value) - random.seed(value) - nums = random.sample(range(100), 5) - link = '{0}
' - links = ''.join(link.format(num) for num in nums) - return HttpResponse('{}'.format(links)) diff --git a/tests/talk/talk/wsgi.py b/tests/talk/talk/wsgi.py deleted file mode 100644 index a636d03..0000000 --- a/tests/talk/talk/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for talk project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talk.settings") - -application = get_wsgi_application() From 7ca8e375691c6ffc56b0816558edebf489153316 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 18 Mar 2019 22:30:29 -0700 Subject: [PATCH 258/550] Exclude built docs --- .gitignore | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 69c7ee7..7af3b5a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,13 +4,16 @@ # virutalenv directories /env*/ -# coverage files +# test files/directories .coverage - -# setup sdist, test and upload directories +.pytest_cache/ /.tox/ + +# setup and upload directories /build/ /dist/ /diskcache.egg-info/ +/docs/_build/ +# macOS metadata .DS_Store From 02f9902b84fafa6bc825ce1335da8d632c462258 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 18 Mar 2019 22:30:41 -0700 Subject: [PATCH 259/550] Update pylint --- .pylintrc | 494 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 259 insertions(+), 235 deletions(-) diff --git a/.pylintrc b/.pylintrc index 7e716ec..39d96b0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,7 +2,7 @@ # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code +# run arbitrary code. extension-pkg-whitelist= # Add files or directories to the blacklist. They should be base names, not @@ -17,9 +17,15 @@ ignore-patterns= # pygtk.require(). #init-hook= -# Use multiple processes to speed up Pylint. +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. jobs=1 +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= @@ -31,7 +37,7 @@ persistent=yes #rcfile= # When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages +# user-friendly hints instead of false-positive error messages. suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the @@ -42,18 +48,18 @@ unsafe-load-any-extension=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. confidence= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to +# file where it should appear only once). You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". disable=print-statement, parameter-unpacking, unpacking-in-except, @@ -67,11 +73,11 @@ disable=print-statement, raw-checker-failed, bad-inline-option, locally-disabled, - locally-enabled, file-ignored, suppressed-message, useless-suppression, deprecated-pragma, + use-symbolic-message-instead, apply-builtin, basestring-builtin, buffer-builtin, @@ -127,9 +133,12 @@ disable=print-statement, dict-items-not-iterating, dict-keys-not-iterating, dict-values-not-iterating, - no-else-return, - inconsistent-return-statements, - not-callable + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -148,15 +157,15 @@ enable=c-extension-no-member evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details +# used to format the message information. See doc for all details. #msg-template= # Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg +# and msvs (visual studio). You can also give a reporter class, e.g. # mypackage.mymodule.MyReporterClass. output-format=text -# Tells whether to display a full report or only the messages +# Tells whether to display a full report or only the messages. reports=no # Activate the evaluation score. @@ -166,120 +175,134 @@ score=yes [REFACTORING] # Maximum number of nested blocks for function / method body -max-nested-blocks=6 +max-nested-blocks=5 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. -never-returning-functions=optparse.Values,sys.exit +never-returning-functions=sys.exit -[BASIC] +[LOGGING] -# Naming style matching correct argument names -argument-naming-style=snake_case +# Format style used to check logging format string. `old` means using % +# formatting, while `new` is for `{}` formatting. +logging-format-style=old -# Regular expression matching correct argument names. Overrides argument- -# naming-style -#argument-rgx= +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging -# Naming style matching correct attribute names -attr-naming-style=snake_case -# Regular expression matching correct attribute names. Overrides attr-naming- -# style -#attr-rgx= +[SPELLING] -# Bad variable names which should always be refused, separated by a comma -bad-names=foo, - bar, - baz, - toto, - tutu, - tata +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 -# Naming style matching correct class attribute names -class-attribute-naming-style=any +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package.. +spelling-dict= -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style -#class-attribute-rgx= +# List of comma separated words that should not be checked. +spelling-ignore-words= -# Naming style matching correct class names -class-naming-style=PascalCase +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= -# Regular expression matching correct class names. Overrides class-naming-style -#class-rgx= +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no -# Naming style matching correct constant names -const-naming-style=UPPER_CASE -# Regular expression matching correct constant names. Overrides const-naming- -# style -#const-rgx= +[MISCELLANEOUS] -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO -# Naming style matching correct function names -function-naming-style=snake_case -# Regular expression matching correct function names. Overrides function- -# naming-style -#function-rgx= +[TYPECHECK] -# Good variable names which should always be accepted, separated by a comma -good-names=i, - j, - k, - ex, - Run, - _ +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= -# Naming style matching correct inline iteration names -inlinevar-naming-style=any +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style -#inlinevar-rgx= +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes -# Naming style matching correct method names -method-naming-style=snake_case +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes -# Regular expression matching correct method names. Overrides method-naming- -# style -#method-rgx= +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local -# Naming style matching correct module names -module-naming-style=snake_case +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= -# Regular expression matching correct module names. Overrides module-naming- -# style -#module-rgx= +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty -# Naming style matching correct variable names -variable-naming-style=snake_case +[VARIABLES] -# Regular expression matching correct variable names. Overrides variable- -# naming-style -#variable-rgx= +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io [FORMAT] @@ -290,7 +313,7 @@ expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ -# Number of spaces of indent required inside a hanging or continued line. +# Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 @@ -300,8 +323,8 @@ indent-string=' ' # Maximum number of characters on a single line. max-line-length=100 -# Maximum number of lines in a module -max-module-lines=2000 +# Maximum number of lines in a module. +max-module-lines=1000 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. @@ -319,21 +342,6 @@ single-line-class-stmt=no single-line-if-stmt=no -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - [SIMILARITIES] # Ignore comments when computing similarities. @@ -349,106 +357,154 @@ ignore-imports=no min-similarity-lines=4 -[SPELLING] +[BASIC] -# Limits count of emitted suggestions for spelling mistakes -max-spelling-suggestions=4 +# Naming style matching correct argument names. +argument-naming-style=snake_case -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= -# List of comma separated words that should not be checked. -spelling-ignore-words= +# Naming style matching correct attribute names. +attr-naming-style=snake_case -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata +# Naming style matching correct class attribute names. +class-attribute-naming-style=any -[TYPECHECK] +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager +# Naming style matching correct class names. +class-naming-style=PascalCase -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members=eviction_policy, - statistics, - count, - size, - cull_limit +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= +# Naming style matching correct function names. +function-naming-style=snake_case -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any -[VARIABLES] +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= +# Naming style matching correct method names. +method-naming-style=snake_case -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb +# Naming style matching correct module names. +module-naming-style=snake_case -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= -# Tells whether we should check for unused import in __init__ files. -init-import=no +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant [CLASSES] @@ -470,77 +526,45 @@ exclude-protected=_asdict, valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs +valid-metaclass-classmethod-first-arg=cls [DESIGN] -# Maximum number of arguments for function / method -max-args=8 +# Maximum number of arguments for function / method. +max-args=5 # Maximum number of attributes for a class (see R0902). max-attributes=7 -# Maximum number of boolean expressions in a if statement +# Maximum number of boolean expressions in an if statement. max-bool-expr=5 -# Maximum number of branch for function / method body -max-branches=20 +# Maximum number of branch for function / method body. +max-branches=12 -# Maximum number of locals for function / method body -max-locals=30 +# Maximum number of locals for function / method body. +max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). -max-public-methods=25 +max-public-methods=20 -# Maximum number of return / yield for function / method body -max-returns=8 +# Maximum number of return / yield for function / method body. +max-returns=6 -# Maximum number of statements in function / method body -max-statements=60 +# Maximum number of statements in function / method body. +max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=2 -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception From 63e3d77c35bbc928e201ce9c1bde0303680ba8fe Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 18 Mar 2019 22:31:01 -0700 Subject: [PATCH 260/550] Update dependencies for testing --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 9013024..a0588f1 100644 --- a/tox.ini +++ b/tox.ini @@ -4,8 +4,7 @@ skip_missing_interpreters=True [testenv] deps= - django<2 - mock + django==1.11.* pytest pytest-django commands=python -m pytest From 85a79b0149d20ffa728a65e1c2d0992c43308b7c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 18 Mar 2019 23:04:45 -0700 Subject: [PATCH 261/550] Add nose back as dep (for Python 2.7) --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index a0588f1..7864ed4 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ skip_missing_interpreters=True [testenv] deps= django==1.11.* + mock pytest pytest-django commands=python -m pytest From 452d956898190685be18131a2ab0f1a0a1010d0c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 18 Mar 2019 23:05:17 -0700 Subject: [PATCH 262/550] Update tests for pytest framework --- tests/test_core.py | 202 +++++++++++++++------------------------------ 1 file changed, 65 insertions(+), 137 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 8348fea..76b5568 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -9,9 +9,9 @@ import io import json import mock -import nose.tools as nt import os import os.path as op +import pytest import random import shutil import sqlite3 @@ -31,23 +31,19 @@ import diskcache import diskcache as dc -warnings.simplefilter('error') -warnings.simplefilter('ignore', category=dc.EmptyDirWarning) +pytestmark = pytest.mark.filterwarnings('ignore', category=dc.EmptyDirWarning) if sys.hexversion < 0x03000000: range = xrange -def setup_cache(func): - @ft.wraps(func) - def wrapper(): - shutil.rmtree('tmp', ignore_errors=True) - with dc.Cache('tmp') as cache: - func(cache) - shutil.rmtree('tmp', ignore_errors=True) - return wrapper +@pytest.fixture +def cache(): + shutil.rmtree('tmp', ignore_errors=True) + with dc.Cache('tmp') as cache: + yield cache + shutil.rmtree('tmp', ignore_errors=True) -@setup_cache def test_init(cache): for key, value in dc.DEFAULT_SETTINGS.items(): assert getattr(cache, key) == value @@ -93,10 +89,10 @@ def test_disk_reset(): shutil.rmtree('tmp', ignore_errors=True) -@nt.raises(ValueError) def test_disk_valueerror(): - with dc.Cache('tmp', disk=dc.Disk('tmp')) as cache: - pass + with pytest.raises(ValueError): + with dc.Cache('tmp', disk=dc.Disk('tmp')) as cache: + pass class JSONDisk(diskcache.Disk): @@ -164,21 +160,19 @@ def test_custom_filename_disk(): shutil.rmtree('tmp', ignore_errors=True) -@nt.raises(EnvironmentError) def test_init_makedirs(): shutil.rmtree('tmp', ignore_errors=True) makedirs = mock.Mock(side_effect=OSError(errno.EACCES)) - try: - with mock.patch('os.makedirs', makedirs): - cache = dc.Cache('tmp') - except EnvironmentError: - shutil.rmtree('tmp') - raise + with pytest.raises(EnvironmentError): + try: + with mock.patch('os.makedirs', makedirs): + cache = dc.Cache('tmp') + except EnvironmentError: + shutil.rmtree('tmp') + raise -@setup_cache -@nt.raises(sqlite3.OperationalError) def test_pragma_error(cache): local = mock.Mock() con = mock.Mock() @@ -197,10 +191,10 @@ def test_pragma_error(cache): with mock.patch('time.sleep', lambda num: 0): with mock.patch.object(cache, '_local', local): - cache.reset('sqlite_mmap_size', size) + with pytest.raises(sqlite3.OperationalError): + cache.reset('sqlite_mmap_size', size) -@setup_cache def test_close_error(cache): class LocalTest(object): def __init__(self): @@ -216,7 +210,6 @@ def __getattr__(self, name): cache.close() -@setup_cache def test_getsetdel(cache): values = [ (None, False), @@ -263,14 +256,11 @@ def test_getsetdel(cache): cache.check() -@nt.raises(KeyError) -@setup_cache def test_get_keyerror1(cache): - cache[0] + with pytest.raises(KeyError): + cache[0] -@nt.raises(IOError, KeyError) -@setup_cache def test_get_keyerror4(cache): func = mock.Mock(side_effect=IOError(errno.ENOENT, '')) @@ -278,24 +268,22 @@ def test_get_keyerror4(cache): cache[0] = b'abcd' * 2 ** 20 with mock.patch('diskcache.core.open', func): - cache[0] + with pytest.raises((IOError, KeyError, OSError)): + cache[0] -@setup_cache def test_read(cache): cache.set(0, b'abcd' * 2 ** 20) with cache.read(0) as reader: assert reader is not None -@nt.raises(KeyError) -@setup_cache def test_read_keyerror(cache): - with cache.read(0) as reader: - pass + with pytest.raises(KeyError): + with cache.read(0) as reader: + pass -@setup_cache def test_set_twice(cache): large_value = b'abcd' * 2 ** 20 @@ -318,8 +306,6 @@ def test_set_twice(cache): cache.check() -@setup_cache -@nt.raises(dc.Timeout) def test_set_timeout(cache): local = mock.Mock() con = mock.Mock() @@ -330,20 +316,19 @@ def test_set_timeout(cache): con.execute = execute execute.side_effect = sqlite3.OperationalError - try: - with mock.patch.object(cache, '_local', local): - cache.set('a', 'b' * 2 ** 20) - finally: - cache.check() + with pytest.raises(dc.Timeout): + try: + with mock.patch.object(cache, '_local', local): + cache.set('a', 'b' * 2 ** 20) + finally: + cache.check() -@setup_cache def test_raw(cache): assert cache.set(0, io.BytesIO(b'abcd'), read=True) assert cache[0] == b'abcd' -@setup_cache def test_get(cache): assert cache.get(0) is None assert cache.get(1, 'dne') == 'dne' @@ -356,14 +341,12 @@ def test_get(cache): assert cache.get(0, tag=True) == (0, u'number') assert cache.get(0, expire_time=True, tag=True) == (0, None, u'number') -@setup_cache def test_get_expired_fast_path(cache): assert cache.set(0, 0, expire=0.001) time.sleep(0.01) assert cache.get(0) is None -@setup_cache def test_get_ioerror_fast_path(cache): assert cache.set(0, 0) @@ -382,7 +365,6 @@ def test_get_ioerror_fast_path(cache): assert cache.get(0) is None -@setup_cache def test_get_expired_slow_path(cache): cache.stats(enable=True) cache.reset('eviction_policy', 'least-recently-used') @@ -391,8 +373,6 @@ def test_get_expired_slow_path(cache): assert cache.get(0) is None -@setup_cache -@nt.raises(IOError) def test_get_ioerror_slow_path(cache): cache.reset('eviction_policy', 'least-recently-used') cache.set(0, 0) @@ -409,10 +389,10 @@ def test_get_ioerror_slow_path(cache): fetch.side_effect = io_error with mock.patch.object(cache, '_disk', disk): - cache.get(0) + with pytest.raises(IOError): + cache.get(0) -@setup_cache def test_pop(cache): assert cache.incr('alpha') == 1 assert cache.pop('alpha') == 1 @@ -438,7 +418,6 @@ def test_pop(cache): assert cache.pop('epsilon') == '0' * 2 ** 20 -@setup_cache def test_pop_ioerror(cache): assert cache.set(0, 0) @@ -457,8 +436,6 @@ def test_pop_ioerror(cache): assert cache.pop(0) is None -@setup_cache -@nt.raises(IOError) def test_pop_ioerror_eacces(cache): assert cache.set(0, 0) @@ -474,10 +451,10 @@ def test_pop_ioerror_eacces(cache): fetch.side_effect = io_error with mock.patch.object(cache, '_disk', disk): - cache.pop(0) + with pytest.raises(IOError): + cache.pop(0) -@setup_cache def test_delete(cache): cache[0] = 0 assert cache.delete(0) @@ -486,21 +463,18 @@ def test_delete(cache): assert len(cache.check()) == 0 -@nt.raises(KeyError) -@setup_cache def test_del(cache): - del cache[0] + with pytest.raises(KeyError): + del cache[0] -@nt.raises(KeyError) -@setup_cache def test_del_expired(cache): cache.set(0, 0, expire=0.001) time.sleep(0.01) - del cache[0] + with pytest.raises(KeyError): + del cache[0] -@setup_cache def test_stats(cache): cache[0] = 0 @@ -525,7 +499,6 @@ def test_stats(cache): assert len(cache.check()) == 0 -@setup_cache def test_path(cache): cache[0] = u'abc' large_value = b'abc' * 2 ** 20 @@ -545,7 +518,6 @@ def test_path(cache): assert len(cache.check()) == 0 -@setup_cache def test_expire_rows(cache): cache.reset('cull_limit', 0) @@ -566,7 +538,6 @@ def test_expire_rows(cache): assert len(cache.check()) == 0 -@setup_cache def test_least_recently_stored(cache): cache.reset('eviction_policy', u'least-recently-stored') cache.reset('size_limit', int(10.1e6)) @@ -602,7 +573,6 @@ def test_least_recently_stored(cache): assert len(cache.check()) == 0 -@setup_cache def test_least_recently_used(cache): cache.reset('eviction_policy', u'least-recently-used') cache.reset('size_limit', int(10.1e6)) @@ -633,7 +603,6 @@ def test_least_recently_used(cache): assert len(cache.check()) == 0 -@setup_cache def test_least_frequently_used(cache): cache.reset('eviction_policy', u'least-frequently-used') cache.reset('size_limit', int(10.1e6)) @@ -662,16 +631,14 @@ def test_least_frequently_used(cache): assert len(cache.check()) == 0 -@nt.raises(OSError) -@setup_cache def test_filename_error(cache): func = mock.Mock(side_effect=OSError(errno.EACCES)) with mock.patch('os.makedirs', func): - cache._disk.filename() + with pytest.raises(OSError): + cache._disk.filename() -@setup_cache def test_remove_error(cache): func = mock.Mock(side_effect=OSError(errno.EACCES)) @@ -687,7 +654,6 @@ def test_remove_error(cache): raise Exception('test_remove_error failed') -@setup_cache def test_check(cache): blob = b'a' * 2 ** 20 keys = (0, 1, 1234, 56.78, u'hello', b'world', None) @@ -717,7 +683,6 @@ def test_check(cache): assert len(cache.check()) == 0 # Should display no warnings. -@setup_cache def test_integrity_check(cache): for value in range(1000): cache[value] = value @@ -738,7 +703,6 @@ def test_integrity_check(cache): assert len(cache.check()) == 0 -@setup_cache def test_expire(cache): cache.reset('cull_limit', 0) # Disable expiring keys on `set`. now = time.time() @@ -765,7 +729,6 @@ def test_tag_index(): shutil.rmtree('tmp', ignore_errors=True) -@setup_cache def test_evict(cache): colors = ('red', 'blue', 'yellow') @@ -778,7 +741,6 @@ def test_evict(cache): assert len(cache.check()) == 0 -@setup_cache def test_clear(cache): for value in range(100): cache[value] = value @@ -788,16 +750,14 @@ def test_clear(cache): assert len(cache.check()) == 0 -@setup_cache -@nt.raises(dc.Timeout) def test_clear_timeout(cache): transact = mock.Mock() transact.side_effect = dc.Timeout with mock.patch.object(cache, '_transact', transact): - cache.clear() + with pytest.raises(dc.Timeout): + cache.clear() -@setup_cache def test_tag(cache): assert cache.set(0, None, tag=u'zero') assert cache.set(1, None, tag=1234) @@ -810,7 +770,6 @@ def test_tag(cache): assert cache.get(3, tag=True) == (None, b'three') -@setup_cache def test_with(cache): with dc.Cache('tmp') as tmp: tmp[u'a'] = 0 @@ -820,14 +779,12 @@ def test_with(cache): assert cache[u'b'] == 1 -@setup_cache def test_contains(cache): assert 0 not in cache cache[0] = 0 assert 0 in cache -@setup_cache def test_add(cache): assert cache.add(1, 1) assert cache.get(1) == 1 @@ -840,7 +797,6 @@ def test_add(cache): cache.check() -@setup_cache def test_add_large_value(cache): value = b'abcd' * 2 ** 20 assert cache.add(b'test-key', value) @@ -850,8 +806,6 @@ def test_add_large_value(cache): cache.check() -@setup_cache -@nt.raises(dc.Timeout) def test_add_timeout(cache): local = mock.Mock() con = mock.Mock() @@ -862,14 +816,14 @@ def test_add_timeout(cache): con.execute = execute execute.side_effect = sqlite3.OperationalError - try: - with mock.patch.object(cache, '_local', local): - cache.add(0, 0) - finally: - cache.check() + with pytest.raises(dc.Timeout): + try: + with mock.patch.object(cache, '_local', local): + cache.add(0, 0) + finally: + cache.check() -@setup_cache def test_incr(cache): assert cache.incr('key', default=5) == 6 assert cache.incr('key', 2) == 8 @@ -881,22 +835,19 @@ def test_incr(cache): assert cache.incr('key') == 1 -@setup_cache -@nt.raises(KeyError) def test_incr_insert_keyerror(cache): - cache.incr('key', default=None) + with pytest.raises(KeyError): + cache.incr('key', default=None) -@setup_cache -@nt.raises(KeyError) def test_incr_update_keyerror(cache): assert cache.set('key', 100, expire=0.100) assert cache.get('key') == 100 time.sleep(0.120) - cache.incr('key', default=None) + with pytest.raises(KeyError): + cache.incr('key', default=None) -@setup_cache def test_decr(cache): assert cache.decr('key', default=5) == 4 assert cache.decr('key', 2) == 2 @@ -908,8 +859,6 @@ def test_decr(cache): assert cache.decr('key') == -1 -@setup_cache -@nt.raises(StopIteration) def test_iter(cache): sequence = list('abcdef') + [('g',)] @@ -922,10 +871,10 @@ def test_iter(cache): cache['h'] = 7 - next(iterator) + with pytest.raises(StopIteration): + next(iterator) -@setup_cache def test_iter_expire(cache): cache.reset('cull_limit', 0) for num in range(100): @@ -934,13 +883,11 @@ def test_iter_expire(cache): assert list(cache) == list(range(100)) -@setup_cache -@nt.raises(StopIteration) def test_iter_error(cache): - next(iter(cache)) + with pytest.raises(StopIteration): + next(iter(cache)) -@setup_cache def test_reversed(cache): sequence = 'abcdef' @@ -960,13 +907,11 @@ def test_reversed(cache): assert False, 'StopIteration expected' -@setup_cache -@nt.raises(StopIteration) def test_reversed_error(cache): - next(reversed(cache)) + with pytest.raises(StopIteration): + next(reversed(cache)) -@setup_cache def test_push_pull(cache): for value in range(10): cache.push(value) @@ -978,7 +923,6 @@ def test_push_pull(cache): assert len(cache) == 0 -@setup_cache def test_push_pull_prefix(cache): for value in range(10): cache.push(value, prefix='key') @@ -992,7 +936,6 @@ def test_push_pull_prefix(cache): assert len(cache.check()) == 0 -@setup_cache def test_push_pull_extras(cache): cache.push('test') assert cache.pull() == (500000000000000, 'test') @@ -1025,7 +968,6 @@ def test_push_pull_extras(cache): assert len(cache.check()) == 0 -@setup_cache def test_push_pull_expire(cache): cache.push(0, expire=0.1) cache.push(0, expire=0.1) @@ -1037,7 +979,6 @@ def test_push_pull_expire(cache): assert len(cache.check()) == 0 -@setup_cache def test_push_pull_large_value(cache): value = b'test' * (2 ** 20) cache.push(value) @@ -1046,7 +987,6 @@ def test_push_pull_large_value(cache): assert len(cache.check()) == 0 -@setup_cache def test_pull_ioerror(cache): assert cache.push(0) == 500000000000000 @@ -1065,8 +1005,6 @@ def test_pull_ioerror(cache): assert cache.pull() == (None, None) -@setup_cache -@nt.raises(IOError) def test_pull_ioerror_eacces(cache): assert cache.push(0) == 500000000000000 @@ -1082,15 +1020,14 @@ def test_pull_ioerror_eacces(cache): fetch.side_effect = io_error with mock.patch.object(cache, '_disk', disk): - cache.pull() + with pytest.raises(IOError): + cache.pull() -@setup_cache def test_iterkeys(cache): assert list(cache.iterkeys()) == [] -@setup_cache def test_pickle(cache): for num, val in enumerate('abcde'): cache[val] = num @@ -1102,7 +1039,6 @@ def test_pickle(cache): assert other[key] == cache[key] -@setup_cache def test_pragmas(cache): results = [] @@ -1139,7 +1075,6 @@ def compare_pragmas(): assert all(results) -@setup_cache def test_size_limit_with_files(cache): cache.reset('cull_limit', 0) size_limit = 30 * cache.disk_min_file_size @@ -1154,7 +1089,6 @@ def test_size_limit_with_files(cache): assert cache.volume() <= size_limit -@setup_cache def test_size_limit_with_database(cache): cache.reset('cull_limit', 0) size_limit = 2 * cache.disk_min_file_size @@ -1170,7 +1104,6 @@ def test_size_limit_with_database(cache): assert cache.volume() <= size_limit -@setup_cache def test_cull_eviction_policy_none(cache): cache.reset('eviction_policy', 'none') size_limit = 2 * cache.disk_min_file_size @@ -1186,7 +1119,6 @@ def test_cull_eviction_policy_none(cache): assert cache.volume() > size_limit -@setup_cache def test_cull_size_limit_0(cache): cache.reset('cull_limit', 0) size_limit = 2 * cache.disk_min_file_size @@ -1202,8 +1134,6 @@ def test_cull_size_limit_0(cache): assert cache.volume() <= size_limit -@setup_cache -@nt.raises(dc.Timeout) def test_cull_timeout(cache): transact = mock.Mock() transact.side_effect = [dc.Timeout] @@ -1211,10 +1141,10 @@ def test_cull_timeout(cache): with mock.patch.object(cache, 'expire', lambda now: 0): with mock.patch.object(cache, 'volume', lambda: int(1e12)): with mock.patch.object(cache, '_transact', transact): - cache.cull() + with pytest.raises(dc.Timeout): + cache.cull() -@setup_cache def test_key_roundtrip(cache): key_part_0 = u"part0" key_part_1 = u"part1" @@ -1334,7 +1264,6 @@ def test_rsync(): shutil.rmtree('tmp', ignore_errors=True) -@setup_cache def test_custom_eviction_policy(cache): dc.EVICTION_POLICY['lru-gt-1s'] = { 'init': ( @@ -1371,7 +1300,6 @@ def test_custom_eviction_policy(cache): assert cache.volume() < size_limit -@setup_cache def test_lru_incr(cache): cache.reset('eviction_policy', 'least-recently-used') cache.incr(0) From 2aa9ee2722d3632f05847672f8fbf4507edb36a2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 17:02:15 -0700 Subject: [PATCH 263/550] Update Deque tests for pytest --- tests/test_deque.py | 82 +++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 59 deletions(-) diff --git a/tests/test_deque.py b/tests/test_deque.py index 14329d1..f9ba35b 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -2,8 +2,8 @@ import functools as ft import mock -import nose.tools as nt import pickle +import pytest import shutil import diskcache as dc @@ -17,17 +17,14 @@ def rmdir(directory): pass -def setup_deque(func): - @ft.wraps(func) - def wrapper(): - deque = dc.Deque() - try: - func(deque) - except Exception: - rmdir(deque.directory) - raise - - return wrapper +@pytest.fixture +def deque(): + deque = dc.Deque() + try: + yield deque + except Exception: + rmdir(deque.directory) + raise def test_init(): @@ -55,7 +52,6 @@ def test_init(): rmdir(directory) -@setup_deque def test_getsetdel(deque): sequence = list('abcde') assert len(deque) == 0 @@ -83,14 +79,12 @@ def test_getsetdel(deque): assert len(deque) == 0 -@setup_deque def test_reversed(deque): sequence = list('abcde') deque += sequence assert list(reversed(deque)) == list(reversed(sequence)) -@setup_deque def test_state(deque): sequence = list('abcde') deque.extend(sequence) @@ -100,7 +94,6 @@ def test_state(deque): assert values == sequence -@setup_deque def test_compare(deque): assert not (deque == {}) assert not (deque == [0]) @@ -110,30 +103,26 @@ def test_compare(deque): assert deque <= [1] -@nt.raises(IndexError) -@setup_deque def test_indexerror_negative(deque): - deque[-1] + with pytest.raises(IndexError): + deque[-1] -@nt.raises(IndexError) -@setup_deque def test_indexerror(deque): - deque[0] + with pytest.raises(IndexError): + deque[0] -@nt.raises(IndexError) -@setup_deque def test_indexerror_islice(deque): islice = mock.Mock(side_effect=StopIteration) deque.append(0) with mock.patch('diskcache.persistent.islice', islice): - deque[0] + with pytest.raises(IndexError): + deque[0] -@setup_deque def test_get_timeout(deque): cache = mock.MagicMock() cache.__len__.return_value = 1 @@ -146,7 +135,6 @@ def test_get_timeout(deque): deque[0] -@setup_deque def test_set_timeout(deque): cache = mock.MagicMock() cache.__len__.return_value = 1 @@ -159,7 +147,6 @@ def test_set_timeout(deque): deque[0] = 0 -@setup_deque def test_del_timeout(deque): cache = mock.MagicMock() cache.__len__.return_value = 1 @@ -178,7 +165,6 @@ def test_repr(): assert repr(deque) == 'Deque(directory=%r)' % directory -@setup_deque def test_iter_timeout(deque): cache = mock.MagicMock() cache.iterkeys.side_effect = [iter([0, 1])] @@ -188,7 +174,6 @@ def test_iter_timeout(deque): assert list(deque) == [0] -@setup_deque def test_reversed_timeout(deque): cache = mock.MagicMock() cache.iterkeys.side_effect = [iter([0, 1])] @@ -198,7 +183,6 @@ def test_reversed_timeout(deque): assert list(reversed(deque)) == [0] -@setup_deque def test_append_timeout(deque): cache = mock.MagicMock() cache.push.side_effect = [dc.Timeout, None] @@ -207,7 +191,6 @@ def test_append_timeout(deque): deque.append(0) -@setup_deque def test_appendleft_timeout(deque): cache = mock.MagicMock() cache.push.side_effect = [dc.Timeout, None] @@ -216,7 +199,6 @@ def test_appendleft_timeout(deque): deque.appendleft(0) -@setup_deque def test_count(deque): deque += 'abbcccddddeeeee' @@ -224,21 +206,18 @@ def test_count(deque): assert deque.count(value) == index -@setup_deque def test_extend(deque): sequence = list('abcde') deque.extend(sequence) assert deque == sequence -@setup_deque def test_extendleft(deque): sequence = list('abcde') deque.extendleft(sequence) assert deque == list(reversed(sequence)) -@setup_deque def test_pop(deque): sequence = list('abcde') deque.extend(sequence) @@ -247,13 +226,11 @@ def test_pop(deque): assert deque.pop() == sequence.pop() -@nt.raises(IndexError) -@setup_deque def test_pop_indexerror(deque): - deque.pop() + with pytest.raises(IndexError): + deque.pop() -@setup_deque def test_pop_timeout(deque): cache = mock.MagicMock() cache.pull.side_effect = [dc.Timeout, (None, 0)] @@ -262,7 +239,6 @@ def test_pop_timeout(deque): assert deque.pop() == 0 -@setup_deque def test_popleft(deque): sequence = list('abcde') deque.extend(sequence) @@ -273,13 +249,11 @@ def test_popleft(deque): del sequence[0] -@nt.raises(IndexError) -@setup_deque def test_popleft_indexerror(deque): - deque.popleft() + with pytest.raises(IndexError): + deque.popleft() -@setup_deque def test_popleft_timeout(deque): cache = mock.MagicMock() cache.pull.side_effect = [dc.Timeout, (None, 0)] @@ -288,7 +262,6 @@ def test_popleft_timeout(deque): assert deque.popleft() == 0 -@setup_deque def test_remove(deque): deque.extend('abaca') deque.remove('a') @@ -299,7 +272,6 @@ def test_remove(deque): assert deque == 'bc' -@setup_deque def test_remove_timeout(deque): cache = mock.MagicMock() cache.iterkeys.side_effect = [iter([0, 1, 2, 3, 4])] @@ -310,26 +282,22 @@ def test_remove_timeout(deque): deque.remove(3) -@nt.raises(ValueError) -@setup_deque def test_remove_valueerror(deque): - deque.remove(0) + with pytest.raises(ValueError): + deque.remove(0) -@setup_deque def test_reverse(deque): deque += 'abcde' deque.reverse() assert deque == 'edcba' -@nt.raises(TypeError) -@setup_deque def test_rotate_typeerror(deque): - deque.rotate(0.5) + with pytest.raises(TypeError): + deque.rotate(0.5) -@setup_deque def test_rotate(deque): deque.rotate(1) deque.rotate(-1) @@ -338,14 +306,12 @@ def test_rotate(deque): assert deque == 'cdeab' -@setup_deque def test_rotate_negative(deque): deque += 'abcde' deque.rotate(-2) assert deque == 'cdeab' -@setup_deque def test_rotate_indexerror(deque): deque += 'abc' @@ -357,7 +323,6 @@ def test_rotate_indexerror(deque): deque.rotate(1) -@setup_deque def test_rotate_indexerror_negative(deque): deque += 'abc' @@ -369,7 +334,6 @@ def test_rotate_indexerror_negative(deque): deque.rotate(-1) -@setup_deque def test_clear_timeout(deque): cache = mock.MagicMock() cache.clear.side_effect = [dc.Timeout, None] From a455fe83b172942e0de863c67e50eb806f95ae66 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 17:02:33 -0700 Subject: [PATCH 264/550] Add pytest-env and env vars for pytest --- requirements.txt | 1 + tox.ini | 3 +++ 2 files changed, 4 insertions(+) diff --git a/requirements.txt b/requirements.txt index c1cc167..ef3d63a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ pylint pytest pytest-cov pytest-django +pytest-env sphinx tox twine diff --git a/tox.ini b/tox.ini index 7864ed4..f5e0916 100644 --- a/tox.ini +++ b/tox.ini @@ -23,6 +23,9 @@ addopts= --ignore tests/plot.py norecursedirs=site-packages testpaths=docs diskcache tests +env = + DJANGO_SETTINGS_MODULE=tests.settings + PYTHONPATH={PWD}:{PWD}/tests [testenv:lint] deps=pylint From faf962ae990b8c21ce7f7e09d9d2197d9d49a024 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 17:06:18 -0700 Subject: [PATCH 265/550] Update Index tests for pytest --- tests/test_index.py | 49 ++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/tests/test_index.py b/tests/test_index.py index 423c3c9..e5cd71d 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -2,8 +2,8 @@ import functools as ft import mock -import nose.tools as nt import pickle +import pytest import shutil import sys @@ -17,17 +17,14 @@ def rmdir(directory): pass -def setup_index(func): - @ft.wraps(func) - def wrapper(): - index = dc.Index() - try: - func(index) - except Exception: - rmdir(index.directory) - raise - - return wrapper +@pytest.fixture +def index(): + index = dc.Index() + try: + yield index + except Exception: + rmdir(index.directory) + raise def test_init(): @@ -64,7 +61,6 @@ def test_init(): assert index == mapping -@setup_index def test_getsetdel(index): letters = 'abcde' assert len(index) == 0 @@ -81,7 +77,6 @@ def test_getsetdel(index): assert len(index) == 0 -@setup_index def test_get_timeout(index): cache = mock.MagicMock() cache.__getitem__.side_effect = [dc.Timeout, 0] @@ -90,7 +85,6 @@ def test_get_timeout(index): assert index[0] == 0 -@setup_index def test_set_timeout(index): cache = mock.MagicMock() cache.__setitem__.side_effect = [dc.Timeout, None] @@ -99,7 +93,6 @@ def test_set_timeout(index): index[0] = 0 -@setup_index def test_del_timeout(index): cache = mock.MagicMock() cache.__delitem__.side_effect = [dc.Timeout, None] @@ -108,7 +101,6 @@ def test_del_timeout(index): del index[0] -@setup_index def test_pop(index): letters = 'abcde' assert len(index) == 0 @@ -124,13 +116,11 @@ def test_pop(index): assert len(index) == 0 -@nt.raises(KeyError) -@setup_index def test_pop_keyerror(index): - index.pop('a') + with pytest.raises(KeyError): + index.pop('a') -@setup_index def test_pop_timeout(index): cache = mock.MagicMock() cache.pop.side_effect = [dc.Timeout, 1] @@ -139,7 +129,6 @@ def test_pop_timeout(index): assert index.pop(0) == 1 -@setup_index def test_popitem(index): letters = 'abcde' @@ -152,13 +141,11 @@ def test_popitem(index): assert len(index) == 2 -@nt.raises(KeyError) -@setup_index def test_popitem_keyerror(index): - index.popitem() + with pytest.raises(KeyError): + index.popitem() -@setup_index def test_popitem_timeout(index): cache = mock.MagicMock() cache.__reversed__ = mock.Mock() @@ -170,13 +157,11 @@ def test_popitem_timeout(index): assert value == (0, 1) -@setup_index def test_setdefault(index): assert index.setdefault('a', 0) == 0 assert index.setdefault('a', 1) == 0 -@setup_index def test_setdefault_timeout(index): cache = mock.MagicMock() cache.__getitem__ = mock.Mock() @@ -189,7 +174,6 @@ def test_setdefault_timeout(index): assert value == 0 -@setup_index def test_iter(index): letters = 'abcde' @@ -200,7 +184,6 @@ def test_iter(index): assert index[key] == num -@setup_index def test_reversed(index): letters = 'abcde' @@ -211,7 +194,6 @@ def test_reversed(index): assert index[key] == (len(letters) - num - 1) -@setup_index def test_state(index): mapping = {'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1} index.update(mapping) @@ -221,7 +203,6 @@ def test_state(index): assert values == mapping -@setup_index def test_push_timeout(index): cache = mock.MagicMock() cache.push.side_effect = [dc.Timeout, None] @@ -230,7 +211,6 @@ def test_push_timeout(index): index.push(0) -@setup_index def test_pull_timeout(index): cache = mock.MagicMock() cache.pull.side_effect = [dc.Timeout, None] @@ -239,7 +219,6 @@ def test_pull_timeout(index): index.pull(0) -@setup_index def test_clear_timeout(index): cache = mock.MagicMock() cache.clear.side_effect = [dc.Timeout, None] @@ -249,7 +228,6 @@ def test_clear_timeout(index): if sys.hexversion < 0x03000000: - @setup_index def test_itervalues_timeout(index): cache = mock.MagicMock() cache.__iter__.side_effect = [iter([0, 1, 2])] @@ -259,7 +237,6 @@ def test_itervalues_timeout(index): assert list(index.itervalues()) == [1, 2] - @setup_index def test_iteritems_timeout(index): cache = mock.MagicMock() cache.__iter__.side_effect = [iter([0, 1, 2])] From fdee183bb71c56ec002b38351cfa4bc93537b20e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 17:24:25 -0700 Subject: [PATCH 266/550] Update FanoutCache tests for pytest --- tests/test_fanout.py | 132 +++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 86 deletions(-) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index e302bed..6d5b4f1 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -8,9 +8,9 @@ import hashlib import io import mock -import nose.tools as nt import os import os.path as op +import pytest import random import shutil import sqlite3 @@ -33,21 +33,15 @@ if sys.hexversion < 0x03000000: range = xrange -def setup_cache(func=None, **options): - if func is None: - return lambda func: setup_cache(func, **options) - @ft.wraps(func) - def wrapper(): - shutil.rmtree('tmp', ignore_errors=True) - with dc.FanoutCache('tmp', **options) as cache: - func(cache) - shutil.rmtree('tmp', ignore_errors=True) - - return wrapper +@pytest.fixture +def cache(): + shutil.rmtree('tmp', ignore_errors=True) + with dc.FanoutCache('tmp') as cache: + yield cache + shutil.rmtree('tmp', ignore_errors=True) -@setup_cache def test_init(cache): assert cache.directory == 'tmp' @@ -65,7 +59,6 @@ def test_init(cache): cache.check() -@setup_cache def test_set_get_delete(cache): for value in range(100): cache.set(value, value) @@ -104,7 +97,6 @@ def test_set_get_delete(cache): cache.check() -@setup_cache def test_set_timeout(cache): shards = mock.Mock() shard = mock.Mock() @@ -118,7 +110,6 @@ def test_set_timeout(cache): assert not cache.set(0, 0) -@setup_cache def test_set_timeout_retry(cache): shards = mock.Mock() shard = mock.Mock() @@ -133,14 +124,12 @@ def test_set_timeout_retry(cache): cache[1] = 1 -@setup_cache def test_add(cache): assert cache.add(0, 0) assert not cache.add(0, 1) assert cache.get(0) == 0 -@setup_cache def test_add_timeout(cache): shards = mock.Mock() shard = mock.Mock() @@ -154,7 +143,6 @@ def test_add_timeout(cache): assert not cache.add(0, 0) -@setup_cache def test_add_timeout_retry(cache): shards = mock.Mock() shard = mock.Mock() @@ -178,32 +166,32 @@ def stress_add(cache, limit, results): results.append(total) -@setup_cache(shards=1) -def test_add_concurrent(cache): - results = co.deque() - limit = 1000 +def test_add_concurrent(): + shutil.rmtree('tmp', ignore_errors=True) + with dc.FanoutCache('tmp', shards=1) as cache: + results = co.deque() + limit = 1000 - threads = [ - threading.Thread(target=stress_add, args=(cache, limit, results)) - for _ in range(16) - ] + threads = [ + threading.Thread(target=stress_add, args=(cache, limit, results)) + for _ in range(16) + ] - for thread in threads: - thread.start() + for thread in threads: + thread.start() - for thread in threads: - thread.join() + for thread in threads: + thread.join() - assert sum(results) == limit - cache.check() + assert sum(results) == limit + cache.check() + shutil.rmtree('tmp', ignore_errors=True) -@setup_cache def test_incr(cache): cache.incr('key', delta=3) == 3 -@setup_cache def test_incr_timeout(cache): shards = mock.Mock() shard = mock.Mock() @@ -217,7 +205,6 @@ def test_incr_timeout(cache): assert cache.incr('key', 1) is None -@setup_cache def test_incr_timeout_retry(cache): shards = mock.Mock() shard = mock.Mock() @@ -231,7 +218,6 @@ def test_incr_timeout_retry(cache): assert cache.incr('key', retry=True) == 1 -@setup_cache def test_decr(cache): cache.decr('key', delta=2) == -2 @@ -242,27 +228,28 @@ def stress_incr(cache, limit): time.sleep(0.001) -@setup_cache(shards=1, timeout=0.001) -def test_incr_concurrent(cache): - count = 16 - limit = 50 +def test_incr_concurrent(): + shutil.rmtree('tmp', ignore_errors=True) + with dc.FanoutCache('tmp', shards=1, timeout=0.001) as cache: + count = 16 + limit = 50 - threads = [ - threading.Thread(target=stress_incr, args=(cache, limit)) - for _ in range(count) - ] + threads = [ + threading.Thread(target=stress_incr, args=(cache, limit)) + for _ in range(count) + ] - for thread in threads: - thread.start() + for thread in threads: + thread.start() - for thread in threads: - thread.join() + for thread in threads: + thread.join() - assert cache.get(b'key') == count * limit - cache.check() + assert cache.get(b'key') == count * limit + cache.check() + shutil.rmtree('tmp', ignore_errors=True) -@setup_cache def test_get_timeout(cache): cache.set(0, 0) @@ -278,7 +265,6 @@ def test_get_timeout(cache): assert cache.get(0) is None -@setup_cache def test_get_timeout_retry(cache): shards = mock.Mock() shard = mock.Mock() @@ -292,7 +278,6 @@ def test_get_timeout_retry(cache): assert cache.get(0, retry=True) == 0 -@setup_cache def test_pop(cache): for num in range(100): cache[num] = num @@ -301,7 +286,6 @@ def test_pop(cache): assert cache.pop(num) == num -@setup_cache def test_pop_timeout(cache): shards = mock.Mock() shard = mock.Mock() @@ -315,7 +299,6 @@ def test_pop_timeout(cache): assert cache.pop(0) is None -@setup_cache def test_pop_timeout_retry(cache): shards = mock.Mock() shard = mock.Mock() @@ -329,7 +312,6 @@ def test_pop_timeout_retry(cache): assert cache.pop(0, retry=True) == 0 -@setup_cache def test_delete_timeout(cache): shards = mock.Mock() shard = mock.Mock() @@ -343,7 +325,6 @@ def test_delete_timeout(cache): assert not cache.delete(0) -@setup_cache def test_delete_timeout_retry(cache): shards = mock.Mock() shard = mock.Mock() @@ -357,20 +338,17 @@ def test_delete_timeout_retry(cache): assert cache.delete(0, retry=True) -@setup_cache def test_delitem(cache): cache[0] = 0 assert cache[0] == 0 del cache[0] -@setup_cache -@nt.raises(KeyError) def test_delitem_keyerror(cache): - del cache[0] + with pytest.raises(KeyError): + del cache[0] -@setup_cache def test_delitem_timeout(cache): shards = mock.Mock() shard = mock.Mock() @@ -384,7 +362,6 @@ def test_delitem_timeout(cache): del cache[0] -@setup_cache def test_tag_index(cache): assert cache.tag_index == 0 cache.create_tag_index() @@ -393,27 +370,23 @@ def test_tag_index(cache): assert cache.tag_index == 0 -@setup_cache def test_read(cache): cache.set(0, b'abcd' * 2 ** 20) with cache.read(0) as reader: assert reader is not None -@nt.raises(KeyError) -@setup_cache def test_read_keyerror(cache): - with cache.read(0) as reader: - pass + with pytest.raises(KeyError): + with cache.read(0) as reader: + pass -@nt.raises(KeyError) -@setup_cache def test_getitem_keyerror(cache): - cache[0] + with pytest.raises(KeyError): + cache[0] -@setup_cache def test_expire(cache): cache.reset('cull_limit', 0) @@ -428,7 +401,6 @@ def test_expire(cache): assert cache.expire() == 100 -@setup_cache def test_evict(cache): colors = ('red', 'blue', 'yellow') @@ -441,7 +413,6 @@ def test_evict(cache): assert len(cache.check()) == 0 -@setup_cache def test_size_limit_with_files(cache): shards = 8 cache.reset('cull_limit', 0) @@ -457,7 +428,6 @@ def test_size_limit_with_files(cache): assert (cache.volume() // shards) <= size_limit -@setup_cache def test_size_limit_with_database(cache): shards = 8 cache.reset('cull_limit', 0) @@ -474,7 +444,6 @@ def test_size_limit_with_database(cache): assert (cache.volume() // shards) <= size_limit -@setup_cache def test_clear(cache): for value in range(100): cache[value] = value @@ -484,7 +453,6 @@ def test_clear(cache): assert len(cache.check()) == 0 -@setup_cache def test_remove_timeout(cache): shard = mock.Mock() clear = mock.Mock() @@ -496,7 +464,6 @@ def test_remove_timeout(cache): assert cache.clear() == 5 -@setup_cache def test_reset_timeout(cache): shard = mock.Mock() reset = mock.Mock() @@ -508,7 +475,6 @@ def test_reset_timeout(cache): assert cache.reset('blah', 1) == 0 -@setup_cache def test_stats(cache): for value in range(100): cache[value] = value @@ -534,20 +500,17 @@ def test_stats(cache): assert len(cache.check()) == 0 -@setup_cache def test_volume(cache): volume = sum(shard.volume() for shard in cache._shards) assert volume == cache.volume() -@setup_cache def test_iter(cache): for num in range(100): cache[num] = num assert set(cache) == set(range(100)) -@setup_cache def test_iter_expire(cache): """Test iteration with expiration. @@ -563,7 +526,6 @@ def test_iter_expire(cache): assert set(cache) == set() -@setup_cache def test_reversed(cache): for num in range(100): cache[num] = num @@ -571,7 +533,6 @@ def test_reversed(cache): assert list(cache) == list(reversed(reverse)) -@setup_cache def test_pickle(cache): for num, val in enumerate('abcde'): cache[val] = num @@ -583,7 +544,6 @@ def test_pickle(cache): assert other[key] == cache[key] -@setup_cache def test_memoize(cache): count = 1000 From 24de717f406966e2d57a399df0e4059a53a9a82b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 20:16:02 -0700 Subject: [PATCH 267/550] Exclude default cache dir from testing --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7af3b5a..0ef1c27 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /env*/ # test files/directories +/.cache/ .coverage .pytest_cache/ /.tox/ From dd58adc7b0ff5168b5392c3d9d64b77073235f4f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 20:16:21 -0700 Subject: [PATCH 268/550] Add pylibmc and django_redis for Django tests --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index ef3d63a..c982310 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,11 @@ coverage django==1.11.* +django_redis doc8 gj mock nose +pylibmc pylint pytest pytest-cov From 787f856c535a3dff2597b49bcd81a7ad080110bb Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 21:58:51 -0700 Subject: [PATCH 269/550] Update pylint settings --- .pylintrc | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.pylintrc b/.pylintrc index 39d96b0..1750d67 100644 --- a/.pylintrc +++ b/.pylintrc @@ -138,7 +138,12 @@ disable=print-statement, xreadlines-attribute, deprecated-sys-function, exception-escape, - comprehension-escape + comprehension-escape, + no-else-return, + no-member, + useless-object-inheritance, + inconsistent-return-statements, + ungrouped-imports # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -175,7 +180,7 @@ score=yes [REFACTORING] # Maximum number of nested blocks for function / method body -max-nested-blocks=5 +max-nested-blocks=6 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then @@ -324,7 +329,7 @@ indent-string=' ' max-line-length=100 # Maximum number of lines in a module. -max-module-lines=1000 +max-module-lines=2000 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. @@ -532,7 +537,7 @@ valid-metaclass-classmethod-first-arg=cls [DESIGN] # Maximum number of arguments for function / method. -max-args=5 +max-args=8 # Maximum number of attributes for a class (see R0902). max-attributes=7 @@ -541,22 +546,22 @@ max-attributes=7 max-bool-expr=5 # Maximum number of branch for function / method body. -max-branches=12 +max-branches=20 # Maximum number of locals for function / method body. -max-locals=15 +max-locals=30 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). -max-public-methods=20 +max-public-methods=25 # Maximum number of return / yield for function / method body. -max-returns=6 +max-returns=8 # Maximum number of statements in function / method body. -max-statements=50 +max-statements=60 # Minimum number of public methods for a class (see R0903). min-public-methods=2 From c91272d6b78cf3f7178e24d418d8d82e22c3192f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 22:02:58 -0700 Subject: [PATCH 270/550] Update tutorial and add doctests --- docs/tutorial.rst | 112 ++++++++++++++++++++++++------------------ tests/test_doctest.py | 10 ++++ 2 files changed, 75 insertions(+), 47 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index faf7e28..52a95a6 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -93,16 +93,17 @@ Cache objects is relatively slow, and since all operations are atomic, you can safely leave Cache objects open. >>> cache.set(b'key', b'value') + True >>> cache.close() >>> cache.get(b'key') # Automatically opens, but slower. - 'value' + b'value' Set an item, get a value, and delete a key using the usual operators: >>> cache = Cache('/tmp/mycachedir') >>> cache[b'key'] = b'value' >>> cache[b'key'] - 'value' + b'value' >>> b'key' in cache True >>> del cache[b'key'] @@ -111,7 +112,7 @@ There's also a :meth:`set ` method with additional keyword parameters: `expire`, `read`, and `tag`. >>> from io import BytesIO - >>> cache.set(b'key', BytesIO('value'), expire=5, read=True, tag=u'data') + >>> cache.set(b'key', BytesIO(b'value'), expire=5, read=True, tag='data') True In the example above: the key expires in 5 seconds, the value is read as a @@ -119,11 +120,14 @@ file-like object, and tag metadata is stored with the key. Another method, :meth:`get ` supports querying extra information with `default`, `read`, `expire_time`, and `tag` keyword parameters. - >>> cache.get(b'key', default=b'', read=True, expire_time=True, tag=True) - (<_io.BufferedReader - name=u'/tmp/mycachedir/1d/6e/128a921c3b8a9027c1f69989f3ac.val'>, - 1457066214.784396, - u'data') + >>> result = cache.get(b'key', default=b'', read=True, expire_time=True, tag=True) + >>> reader, timestamp, tag = result + >>> type(reader) + + >>> type(timestamp) + + >>> tag + 'data' The return value is a tuple containing the value, expire time (seconds from epoch), and tag. Because we passed ``read=True`` the value is returned as a @@ -154,14 +158,14 @@ Increment and decrement methods also support a keyword parameter, `default`, which will be used for missing keys. When ``None``, incrementing or decrementing a missing key will raise a :exc:`KeyError`. - >>> cache.incr(u'alice') + >>> cache.incr('alice') 1 - >>> cache.decr(u'bob', default=-9) + >>> cache.decr('bob', default=-9) -10 - >>> cache.incr(u'carol', default=None) + >>> cache.incr('carol', default=None) Traceback (most recent call last): ... - KeyError: u'carol' + KeyError: 'carol' Increment and decrement operations are atomic and assume the value may be stored in a SQLite column. Most builds that target machines with 64-bit pointer @@ -171,13 +175,14 @@ Like :meth:`delete ` and :meth:`get `, the method :meth:`pop ` can be used to delete an item in the cache and return its value. - >>> cache.pop(u'alice') + >>> cache.pop('alice') 1 - >>> cache.pop(u'dave', default=u'does not exist') - u'does not exist' - >>> cache.set(u'dave', 0, expire=None, tag=u'admin') - >>> cache.pop(u'dave', expire_time=True, tag=True) - (0, None, u'admin') + >>> cache.pop('dave', default='does not exist') + 'does not exist' + >>> cache.set('dave', 0, expire=None, tag='admin') + True + >>> cache.pop('dave', expire_time=True, tag=True) + (0, None, 'admin') The :meth:`pop ` operation is atomic and using :meth:`incr ` together is an accurate method for counting and dumping @@ -186,9 +191,12 @@ the `read` argument is not supported. Another four methods remove items from the cache. + >>> cache.clear() + 3 >>> cache.reset('cull_limit', 0) # Disable automatic evictions. + 0 >>> for num in range(10): - ... cache.set(num, num, expire=0) # Expire immediately. + ... _ = cache.set(num, num, expire=0) # Expire immediately. >>> len(cache) 10 >>> list(cache) @@ -206,8 +214,9 @@ items you must explicitly call :meth:`expire ` which works regardless of the :ref:`cull_limit `. >>> for num in range(100): - ... cache.set(num, num, tag=u'odd' if num % 2 else u'even') - >>> cache.evict(u'even') + ... _ = cache.set(num, num, tag='odd' if num % 2 else 'even') + >>> cache.evict('even') + 50 .. _tutorial-tag-index: @@ -218,8 +227,9 @@ can be created. To do so, initialize the cache with ``tag_index=True``. >>> cache = Cache('/tmp/mycachedir', tag_index=True) >>> for num in range(100): - ... cache.set(num, num, tag=(num % 2)) + ... _ = cache.set(num, num, tag=(num % 2)) >>> cache.evict(0) + 50 Likewise, the tag index may be created or dropped using methods:: @@ -239,16 +249,19 @@ removing expired items from the cache and then uses the eviction policy to remove items until the cache volume is less than the size limit. >>> cache.clear() + 50 >>> cache.reset('size_limit', int(1e6)) + 1000000 >>> cache.reset('cull_limit', 0) + 0 >>> for count in range(1000): - >>> cache[count] = b'A' * 1000 - >>> cache.volume() - 1437696 - >>> cache.cull() - 320 - >>> cache.volume() - 999424 + ... cache[count] = b'A' * 1000 + >>> cache.volume() > int(1e6) + True + >>> cache.cull() > 0 + True + >>> cache.volume() < int(1e6) + True Some users may defer all culling to a cron-like process by setting the :ref:`cull_limit ` to zero and calling :meth:`cull @@ -259,7 +272,8 @@ Some users may defer all culling to a cron-like process by setting the :meth:`Clear ` simply removes all items from the cache. - >>> cache.clear() + >>> cache.clear() > 0 + True Each of these methods is designed to work concurrent to others. None of them block readers or writers in other threads or processes. @@ -268,8 +282,8 @@ Lastly, three methods support metadata about the cache. The first is :meth:`volume ` which returns the estimated total size in bytes of the cache directory on disk. - >>> cache.volume() - 9216 + >>> cache.volume() < int(1e5) + True .. _tutorial-statistics: @@ -279,11 +293,12 @@ and misses. Cache statistics must first be enabled. >>> cache.stats(enable=True) (0, 0) >>> for num in range(100): - ... cache.set(num, num) + ... _ = cache.set(num, num) >>> for num in range(150): - ... cache.get(num) - >>> cache.stats(enable=False, reset=True) - (100, 50) # 100 hits, 50 misses + ... _ = cache.get(num) + >>> hits, misses = cache.stats(enable=False, reset=True) + >>> (hits, misses) + (100, 50) Cache statistics are useful when evaluating different :ref:`eviction policies `. By default, statistics are disabled as they @@ -293,8 +308,9 @@ are not counted in cache statistics. The third is :meth:`check ` which verifies cache consistency. It can also fix inconsistencies and reclaim unused space. - >>> cache.check(fix=True) - [] + >>> warning, = cache.check(fix=True) + >>> type(warning.message) + The return value is a list of warnings. @@ -457,8 +473,8 @@ access and editing at both front and back sides. :class:`Deque >>> deque.appendleft('foo') >>> len(deque) 4 - >>> deque.directory - '/tmp/...' + >>> type(deque.directory) + >>> other = Deque(directory=deque.directory) >>> len(other) 4 @@ -535,6 +551,8 @@ are updated lazily. Prefer idioms like :meth:`len ` directly. >>> cache = Cache('/tmp/mycachedir', size_limit=int(4e9)) + >>> cache.clear() + 100 >>> cache.size_limit 4000000000 >>> cache.disk_min_file_size @@ -544,7 +562,7 @@ are updated lazily. Prefer idioms like :meth:`len >>> cache.set(b'key', 1.234) True >>> cache.count # Stale attribute. - 0 + 100 >>> cache.reset('count') # Prefer: len(cache) 1 @@ -602,12 +620,12 @@ policy. The policy can be set during initialization using a keyword argument. >>> cache = Cache('/tmp/mydir') >>> cache.eviction_policy - u'least-recently-stored' - >>> cache = Cache('/tmp/mydir', eviction_policy=u'least-frequently-used') + 'least-recently-stored' + >>> cache = Cache('/tmp/mydir', eviction_policy='least-frequently-used') >>> cache.eviction_policy - u'least-frequently-used' - >>> cache.reset('eviction_policy', u'least-recently-used') - u'least-recently-used' + 'least-frequently-used' + >>> cache.reset('eviction_policy', 'least-recently-used') + 'least-recently-used' Though the eviction policy is changed, the previously created indexes will not be dropped. Prefer to always specify the eviction policy as a keyword argument @@ -658,7 +676,7 @@ example below uses compressed JSON. data = json.loads(zlib.decompress(data).decode('utf-8')) return data - with Cache('/tmp/dir', disk=JSONDisk, disk_compress_level=6) as cache: + with Cache('/tmp/mydir', disk=JSONDisk, disk_compress_level=6) as cache: pass Four data types can be stored natively in the cache metadata database: diff --git a/tests/test_doctest.py b/tests/test_doctest.py index ba8eb6b..b523e46 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -1,5 +1,6 @@ import doctest import shutil +import sys import diskcache.core import diskcache.djangocache @@ -43,3 +44,12 @@ def test_persistent(): rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.persistent) assert failures == 0 + + +def test_tutorial(): + if sys.hexversion < 0x03000000: + return + rmdir('/tmp/mycachedir') + rmdir('/tmp/mydir') + failures, _ = doctest.testfile('../docs/tutorial.rst') + assert failures == 0 From 8e8f556c08e37781fd7bc880777894b7f71c0cc8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 22:04:34 -0700 Subject: [PATCH 271/550] Add django_redis and pylibmc for tests --- tox.ini | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index f5e0916..86126ce 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,9 @@ skip_missing_interpreters=True [testenv] deps= django==1.11.* + django_redis mock + pylibmc pytest pytest-django commands=python -m pytest @@ -15,11 +17,10 @@ setenv = [pytest] addopts= - --doctest-modules - --doctest-glob "*.rst" --ignore tests/benchmark_core.py --ignore tests/benchmark_djangocache.py --ignore tests/benchmark_glob.py + --ignore tests/issue_85.py --ignore tests/plot.py norecursedirs=site-packages testpaths=docs diskcache tests @@ -28,5 +29,7 @@ env = PYTHONPATH={PWD}:{PWD}/tests [testenv:lint] -deps=pylint +deps= + django==1.11.* + pylint commands=pylint diskcache From 6a797078d51e36a8da5b281ec3f39611fc4cde68 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 22:05:47 -0700 Subject: [PATCH 272/550] Remove unnecessary pass statements --- diskcache/core.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 0064c79..452d61c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -40,7 +40,6 @@ except NameError: class WindowsError(Exception): "Windows error place-holder on platforms without support." - pass class Constant(tuple): "Pretty display of immutable constant." @@ -343,17 +342,14 @@ def remove(self, filename): class Timeout(Exception): "Database timeout expired." - pass class UnknownFileWarning(UserWarning): "Warning used by Cache.check for unknown files." - pass class EmptyDirWarning(UserWarning): "Warning used by Cache.check for empty directories." - pass class Cache(object): From 8d33162fda890384eb58a1725c075eb7efd790a3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 22:13:33 -0700 Subject: [PATCH 273/550] Change index to copy README and update license --- LICENSE | 17 +++--- README.rst | 27 +++++----- docs/index.rst | 137 +------------------------------------------------ 3 files changed, 23 insertions(+), 158 deletions(-) diff --git a/LICENSE b/LICENSE index ff2e17b..3259b98 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,12 @@ -Copyright 2016-2018 Grant Jenks +Copyright 2016-2019 Grant Jenks -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/README.rst b/README.rst index 341ccba..eae044c 100644 --- a/README.rst +++ b/README.rst @@ -62,7 +62,7 @@ Features - Thread-safe and process-safe - Supports multiple eviction policies (LRU and LFU included) - Keys support "tag" metadata and eviction -- Developed on Python 2.7 +- Developed on Python 3.7 - Tested on CPython 2.7, 3.4, 3.5, 3.6, 3.7 and PyPy - Tested on Linux, Mac OS X, and Windows - Tested using Travis CI and AppVeyor CI @@ -111,8 +111,8 @@ introduction, benchmarks, development, and API. .. _`DiskCache API Reference`: http://www.grantjenks.com/docs/diskcache/api.html .. _`DiskCache Development`: http://www.grantjenks.com/docs/diskcache/development.html -Reference and Indices ---------------------- +Reference +--------- * `DiskCache Documentation`_ * `DiskCache at PyPI`_ @@ -124,21 +124,20 @@ Reference and Indices .. _`DiskCache at GitHub`: https://github.com/grantjenks/python-diskcache/ .. _`DiskCache Issue Tracker`: https://github.com/grantjenks/python-diskcache/issues/ -DiskCache License ------------------ +License +------- -Copyright 2016-2018 Grant Jenks +Copyright 2016-2019 Grant Jenks -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. .. _`DiskCache`: http://www.grantjenks.com/docs/diskcache/ diff --git a/docs/index.rst b/docs/index.rst index 68576df..9fe19ac 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,102 +1,7 @@ -DiskCache: Disk Backed Cache -============================ - -`DiskCache`_ is an Apache2 licensed disk and file backed cache library, written -in pure-Python, and compatible with Django. - -The cloud-based computing of 2018 puts a premium on memory. Gigabytes of empty -space is left on disks as processes vie for memory. Among these processes is -Memcached (and sometimes Redis) which is used as a cache. Wouldn't it be nice -to leverage empty disk space for caching? - -Django is Python's most popular web framework and ships with several caching -backends. Unfortunately the file-based cache in Django is essentially -broken. The culling method is random and large caches repeatedly scan a cache -directory which slows linearly with growth. Can you really allow it to take -sixty milliseconds to store a key in a cache with a thousand items? - -In Python, we can do better. And we can do it in pure-Python! - -:: - - In [1]: import pylibmc - In [2]: client = pylibmc.Client(['127.0.0.1'], binary=True) - In [3]: client[b'key'] = b'value' - In [4]: %timeit client[b'key'] - - 10000 loops, best of 3: 25.4 µs per loop - - In [5]: import diskcache as dc - In [6]: cache = dc.Cache('tmp') - In [7]: cache[b'key'] = b'value' - In [8]: %timeit cache[b'key'] - - 100000 loops, best of 3: 11.8 µs per loop - -**Note:** Micro-benchmarks have their place but are not a substitute for real -measurements. DiskCache offers cache benchmarks to defend its performance -claims. Micro-optimizations are avoided but your mileage may vary. - -DiskCache efficiently makes gigabytes of storage space available for -caching. By leveraging rock-solid database libraries and memory-mapped files, -cache performance can match and exceed industry-standard solutions. There's no -need for a C compiler or running another process. Performance is a feature and -testing has 100% coverage with unit tests and hours of stress. - -Testimonials ------------- - -Does your company or website use `DiskCache`_? Send us a `message -`_ and let us know. - -Features --------- - -- Pure-Python -- Fully Documented -- Benchmark comparisons (alternatives, Django cache backends) -- 100% test coverage -- Hours of stress testing -- Performance matters -- Django compatible API -- Thread-safe and process-safe -- Supports multiple eviction policies (LRU and LFU included) -- Keys support "tag" metadata and eviction -- Developed on Python 3.7 -- Tested on CPython 2.7, 3.4, 3.5, 3.6, 3.7 and PyPy -- Tested on Linux, Mac OS X, and Windows -- Tested using Travis CI and AppVeyor CI - -.. image:: https://api.travis-ci.org/grantjenks/python-diskcache.svg?branch=master - :target: http://www.grantjenks.com/docs/diskcache/ - -.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-diskcache?branch=master&svg=true - :target: http://www.grantjenks.com/docs/diskcache/ - -Quickstart ----------- - -Installing DiskCache is simple with -`pip `_:: - - $ pip install diskcache - -You can access documentation in the interpreter with Python's built-in help -function:: - - >>> from diskcache import Cache, FanoutCache, DjangoCache - >>> help(Cache) - >>> help(FanoutCache) - >>> help(DjangoCache) - -User Guide ----------- - -For those wanting more details, this part of the documentation describes -introduction, benchmarks, development, and API. +.. include:: ../README.rst .. toctree:: - :maxdepth: 1 + :hidden: tutorial cache-benchmarks @@ -105,41 +10,3 @@ introduction, benchmarks, development, and API. sf-python-2017-meetup-talk api development - -Reference and Indices ---------------------- - -* `DiskCache Documentation`_ -* `DiskCache at PyPI`_ -* `DiskCache at GitHub`_ -* `DiskCache Issue Tracker`_ -* :ref:`search` -* :ref:`genindex` - -.. _`DiskCache Documentation`: http://www.grantjenks.com/docs/diskcache/ -.. _`DiskCache at PyPI`: https://pypi.python.org/pypi/diskcache/ -.. _`DiskCache at GitHub`: https://github.com/grantjenks/python-diskcache/ -.. _`DiskCache Issue Tracker`: https://github.com/grantjenks/python-diskcache/issues/ - -Apache2 License ---------------- - -A large number of open source projects you find today are `GPL Licensed`_. -A project that is released as GPL cannot be used in any commercial product -without the product itself also being offered as open source. - -The MIT, BSD, ISC, and Apache2 licenses are great alternatives to the GPL -that allow your open-source software to be used freely in proprietary, -closed-source software. - -DiskCache is released under terms of the `Apache2 License`_. - -.. _`GPL Licensed`: http://www.opensource.org/licenses/gpl-license.php -.. _`Apache2 License`: http://opensource.org/licenses/Apache-2.0 - -DiskCache License ------------------ - -.. include:: ../LICENSE - -.. _`DiskCache`: http://www.grantjenks.com/docs/diskcache/ From 20345385e6c01c49b59cbedb27eea05009f0f324 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 19 Mar 2019 22:43:13 -0700 Subject: [PATCH 274/550] Move benchmark settings to separate file --- tests/benchmark_djangocache.py | 2 +- tests/settings.py | 31 --------------------------- tests/settings_benchmark.py | 39 ++++++++++++++++++++++++++++++++++ tox.ini | 2 -- 4 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 tests/settings_benchmark.py diff --git a/tests/benchmark_djangocache.py b/tests/benchmark_djangocache.py index 4a0c81b..898188f 100644 --- a/tests/benchmark_djangocache.py +++ b/tests/benchmark_djangocache.py @@ -32,7 +32,7 @@ def setup(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings_benchmark') import django django.setup() diff --git a/tests/settings.py b/tests/settings.py index 12b65e2..1a2f569 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -131,35 +131,4 @@ 'BACKEND': 'diskcache.DjangoCache', 'LOCATION': CACHE_DIR, }, - 'memcached': { - 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', - 'LOCATION': '127.0.0.1:11211', - }, - 'redis': { - 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': 'redis://127.0.0.1:6379/1', - 'OPTIONS': { - 'CLIENT_CLASS': 'django_redis.client.DefaultClient', - } - }, - 'filebased': { - 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', - 'LOCATION': '/tmp/django_cache', - 'OPTIONS': { - 'CULL_FREQUENCY': 10, - 'MAX_ENTRIES': 1000, - } - }, - 'locmem': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'diskcache', - 'OPTIONS': { - 'CULL_FREQUENCY': 10, - 'MAX_ENTRIES': 1000, - } - }, - 'diskcache': { - 'BACKEND': 'diskcache.DjangoCache', - 'LOCATION': 'tmp', - }, } diff --git a/tests/settings_benchmark.py b/tests/settings_benchmark.py new file mode 100644 index 0000000..5b614a5 --- /dev/null +++ b/tests/settings_benchmark.py @@ -0,0 +1,39 @@ +from .settings import * + +CACHES = { + 'default': { + 'BACKEND': 'diskcache.DjangoCache', + 'LOCATION': CACHE_DIR, + }, + 'memcached': { + 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', + 'LOCATION': '127.0.0.1:11211', + }, + 'redis': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'redis://127.0.0.1:6379/1', + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + } + }, + 'filebased': { + 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + 'LOCATION': '/tmp/django_cache', + 'OPTIONS': { + 'CULL_FREQUENCY': 10, + 'MAX_ENTRIES': 1000, + } + }, + 'locmem': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'diskcache', + 'OPTIONS': { + 'CULL_FREQUENCY': 10, + 'MAX_ENTRIES': 1000, + } + }, + 'diskcache': { + 'BACKEND': 'diskcache.DjangoCache', + 'LOCATION': 'tmp', + }, +} diff --git a/tox.ini b/tox.ini index 86126ce..080f92d 100644 --- a/tox.ini +++ b/tox.ini @@ -5,9 +5,7 @@ skip_missing_interpreters=True [testenv] deps= django==1.11.* - django_redis mock - pylibmc pytest pytest-django commands=python -m pytest From 08f3afa4f119436bef8b77f8e7ed0ef2adfcb38e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 15 Mar 2019 15:30:04 -0700 Subject: [PATCH 275/550] Initial prototype/testing for transactions --- diskcache/core.py | 97 +++++++++++++---------------------------------- 1 file changed, 26 insertions(+), 71 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 452d61c..b0b8b92 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -372,6 +372,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): self._directory = directory self._timeout = 0 # Manually handle retries during initialization. self._local = threading.local() + self._transaction_identifier = None if not op.isdir(directory): try: @@ -607,21 +608,34 @@ def _transact(self, filename=None): sql = self._sql filenames = [] _disk_remove = self._disk.remove + thread_identifier = threading.get_ident() + transaction_identifier = self._transaction_identifier - try: - sql('BEGIN IMMEDIATE') - except sqlite3.OperationalError: - if filename is not None: - _disk_remove(filename) - raise Timeout + if thread_identifier == transaction_identifier: + begin = False + else: + try: + sql('BEGIN IMMEDIATE') + begin = True + self._transaction_identifier = thread_identifier + except sqlite3.OperationalError: + if filename is not None: + _disk_remove(filename) + raise Timeout try: yield sql, filenames.append except BaseException: - sql('ROLLBACK') + if begin: + assert self._transaction_identifier == thread_identifier + self._transaction_identifier = None + sql('ROLLBACK') raise else: - sql('COMMIT') + if begin: + assert self._transaction_identifier == thread_identifier + self._transaction_identifier = None + sql('COMMIT') for name in filenames: if name is not None: _disk_remove(name) @@ -847,70 +861,11 @@ def add(self, key, value, expire=None, read=False, tag=None): def incr(self, key, delta=1, default=0): - """Increment value by delta for item with key. - - If key is missing and default is None then raise KeyError. Else if key - is missing and default is not None 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. - - :param key: key for item - :param int delta: amount to increment (default 1) - :param int default: value if key is missing (default 0) - :return: new value for item - :raises KeyError: if key is not found and default is None - :raises Timeout: if database timeout expires - - """ - 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() as (sql, cleanup): - rows = sql(select, (db_key, raw)).fetchall() - - if not rows: - if default is None: - raise KeyError(key) - - value = default + delta - columns = (None, None) + self._disk.store(value, False, key=key) - self._row_insert(db_key, raw, now, columns) - self._cull(now, sql, cleanup) - return value - - (rowid, expire_time, filename, value), = rows - - if expire_time is not None and expire_time < now: - if default is None: - raise KeyError(key) - - value = default + delta - columns = (None, None) + self._disk.store(value, False, key=key) - self._row_update(rowid, now, columns) - self._cull(now, sql, cleanup) - cleanup(filename) - return value - + with self._transact(): + value = self.get(key, default=default) value += delta - - columns = 'store_time = ?, value = ?' - update_column = EVICTION_POLICY[self.eviction_policy]['get'] - - if update_column is not None: - columns += ', ' + update_column.format(now=now) - - update = 'UPDATE Cache SET %s WHERE rowid = ?' % columns - sql(update, (now, value, rowid)) - + time.sleep(0.001) + self.set(key, value) return value From e967a66435582f696881b9c1f6c48af1a73996b3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 20 Mar 2019 20:10:59 -0700 Subject: [PATCH 276/550] Replace incr with faster implementation --- diskcache/core.py | 67 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index b0b8b92..9702502 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -861,11 +861,70 @@ def add(self, key, value, expire=None, read=False, tag=None): def incr(self, key, delta=1, default=0): - with self._transact(): - value = self.get(key, default=default) + """Increment value by delta for item with key. + + If key is missing and default is None then raise KeyError. Else if key + is missing and default is not None 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. + + :param key: key for item + :param int delta: amount to increment (default 1) + :param int default: value if key is missing (default 0) + :return: new value for item + :raises KeyError: if key is not found and default is None + :raises Timeout: if database timeout expires + + """ + 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() as (sql, cleanup): + rows = sql(select, (db_key, raw)).fetchall() + + if not rows: + if default is None: + raise KeyError(key) + + value = default + delta + columns = (None, None) + self._disk.store(value, False, key=key) + self._row_insert(db_key, raw, now, columns) + self._cull(now, sql, cleanup) + return value + + (rowid, expire_time, filename, value), = rows + + if expire_time is not None and expire_time < now: + if default is None: + raise KeyError(key) + + value = default + delta + columns = (None, None) + self._disk.store(value, False, key=key) + self._row_update(rowid, now, columns) + self._cull(now, sql, cleanup) + cleanup(filename) + return value + value += delta - time.sleep(0.001) - self.set(key, value) + + columns = 'store_time = ?, value = ?' + update_column = EVICTION_POLICY[self.eviction_policy]['get'] + + if update_column is not None: + columns += ', ' + update_column.format(now=now) + + update = 'UPDATE Cache SET %s WHERE rowid = ?' % columns + sql(update, (now, value, rowid)) + return value From b41c965c9bde17f6065987247fb7eda42909ad9b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 20 Mar 2019 21:47:57 -0700 Subject: [PATCH 277/550] Add getsetdel test to fanout cache --- tests/test_fanout.py | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 6d5b4f1..63d7d61 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -250,6 +250,52 @@ def test_incr_concurrent(): shutil.rmtree('tmp', ignore_errors=True) +def test_getsetdel(cache): + values = [ + (None, False), + ((None,) * 2 ** 10, False), + (1234, False), + (2 ** 512, False), + (56.78, False), + (u'hello', False), + (u'hello' * 2 ** 10, False), + (b'world', False), + (b'world' * 2 ** 10, False), + (io.BytesIO(b'world' * 2 ** 10), True), + ] + + for key, (value, file_like) in enumerate(values): + assert cache.set(key, value, read=file_like) + + assert len(cache) == len(values) + + for key, (value, file_like) in enumerate(values): + if file_like: + assert cache[key] == value.getvalue() + else: + assert cache[key] == value + + for key, _ in enumerate(values): + del cache[key] + + assert len(cache) == 0 + + for value, (key, _) in enumerate(values): + cache[key] = value + + assert len(cache) == len(values) + + for value, (key, _) in enumerate(values): + assert cache[key] == value + + for _, (key, _) in enumerate(values): + del cache[key] + + assert len(cache) == 0 + + cache.check() + + def test_get_timeout(cache): cache.set(0, 0) From 88c1da99d94bcb7e72dd18c052661d9adebb107b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 20 Mar 2019 21:55:09 -0700 Subject: [PATCH 278/550] Add transact API with retry --- diskcache/core.py | 53 +++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 9702502..d2d8081 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -372,7 +372,7 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): self._directory = directory self._timeout = 0 # Manually handle retries during initialization. self._local = threading.local() - self._transaction_identifier = None + self._txn_id = None if not op.isdir(directory): try: @@ -604,37 +604,50 @@ def _execute_with_retry(statement, *args, **kwargs): @cl.contextmanager - def _transact(self, filename=None): + def transact(self, retry=False): + """Lock the cache to perform a transaction. + + """ + with self._transact(retry=retry): + yield + + + @cl.contextmanager + def _transact(self, retry=False, filename=None): sql = self._sql filenames = [] _disk_remove = self._disk.remove - thread_identifier = threading.get_ident() - transaction_identifier = self._transaction_identifier + tid = threading.get_ident() + txn_id = self._txn_id - if thread_identifier == transaction_identifier: + if tid == txn_id: begin = False else: - try: - sql('BEGIN IMMEDIATE') - begin = True - self._transaction_identifier = thread_identifier - except sqlite3.OperationalError: - if filename is not None: - _disk_remove(filename) - raise Timeout + while True: + try: + sql('BEGIN IMMEDIATE') + begin = True + self._txn_id = tid + break + except sqlite3.OperationalError: + if retry: + continue + if filename is not None: + _disk_remove(filename) + raise Timeout try: yield sql, filenames.append except BaseException: if begin: - assert self._transaction_identifier == thread_identifier - self._transaction_identifier = None + assert self._txn_id == tid + self._txn_id = None sql('ROLLBACK') raise else: if begin: - assert self._transaction_identifier == thread_identifier - self._transaction_identifier = None + assert self._txn_id == tid + self._txn_id = None sql('COMMIT') for name in filenames: if name is not None: @@ -684,7 +697,7 @@ def set(self, key, value, expire=None, read=False, tag=None): # INSERT OR REPLACE aka UPSERT is not used because the old filename may # need cleanup. - with self._transact(filename) as (sql, cleanup): + with self._transact(False, filename) as (sql, cleanup): rows = sql( 'SELECT rowid, filename FROM Cache' ' WHERE key = ? AND raw = ?', @@ -836,7 +849,7 @@ def add(self, key, value, expire=None, read=False, tag=None): size, mode, filename, db_value = self._disk.store(value, read, key=key) columns = (expire_time, tag, size, mode, filename, db_value) - with self._transact(filename) as (sql, cleanup): + with self._transact(False, filename) as (sql, cleanup): rows = sql( 'SELECT rowid, filename, expire_time FROM Cache' ' WHERE key = ? AND raw = ?', @@ -1261,7 +1274,7 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, ' ORDER BY key %s LIMIT 1' ) % order[side] - with self._transact(filename) as (sql, cleanup): + with self._transact(False, filename) as (sql, cleanup): rows = sql(select, (min_key, max_key, raw)).fetchall() if rows: From e4589f925ae6d1119f046e267cc1be8a993d0bfa Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 20 Mar 2019 22:16:44 -0700 Subject: [PATCH 279/550] Change 'timeout expires' to 'timeout occurs' --- diskcache/core.py | 40 ++++++++++++++++++++-------------------- diskcache/djangocache.py | 18 +++++++++--------- diskcache/fanout.py | 22 +++++++++++----------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index d2d8081..90ec58f 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -667,7 +667,7 @@ def set(self, key, value, expire=None, read=False, tag=None): :param bool read: read value as bytes from file (default False) :param str tag: text to associate with key (default None) :return: True if item was set - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ now = time.time() @@ -840,7 +840,7 @@ def add(self, key, value, expire=None, read=False, tag=None): :param bool read: read value as bytes from file (default False) :param str tag: text to associate with key (default None) :return: True if item was added - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ now = time.time() @@ -891,7 +891,7 @@ def incr(self, key, delta=1, default=0): :param int default: value if key is missing (default 0) :return: new value for item :raises KeyError: if key is not found and default is None - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ now = time.time() @@ -962,7 +962,7 @@ def decr(self, key, delta=1, default=0): :param int default: value if key is missing (default 0) :return: new value for item :raises KeyError: if key is not found and default is None - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ return self.incr(key, -delta, default) @@ -979,7 +979,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): (default False) :param bool tag: if True, return tag in tuple (default False) :return: value for item or default if key not found - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ db_key, raw = self._disk.put(key) @@ -1066,7 +1066,7 @@ def __getitem__(self, key): :param key: key matching item :return: corresponding value :raises KeyError: if key is not found - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ value = self.get(key, default=ENOVAL) @@ -1081,7 +1081,7 @@ def read(self, key): :param key: key matching item :return: file open for reading in binary mode :raises KeyError: if key is not found - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ handle = self.get(key, default=ENOVAL, read=True) @@ -1123,7 +1123,7 @@ def pop(self, key, default=None, expire_time=False, tag=False): (default False) :param bool tag: if True, return tag in tuple (default False) :return: value for item or default if key not found - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ db_key, raw = self._disk.put(key) @@ -1175,7 +1175,7 @@ def __delitem__(self, key): :param key: key matching item :raises KeyError: if key is not found - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ db_key, raw = self._disk.put(key) @@ -1205,7 +1205,7 @@ def delete(self, key): :param key: key matching item :return: True if item was deleted - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ try: @@ -1252,7 +1252,7 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, :param bool read: read value as bytes from file (default False) :param str tag: text to associate with key (default None) :return: key for item in cache - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ if prefix is None: @@ -1352,7 +1352,7 @@ def pull(self, prefix=None, default=(None, None), side='front', (default False) :param bool tag: if True, return tag in tuple (default False) :return: key and value item pair or default if queue is empty - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ if prefix is None: @@ -1425,7 +1425,7 @@ def check(self, fix=False): :param bool fix: correct inconsistencies :return: list of warnings - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ # pylint: disable=access-member-before-definition,W0201 @@ -1543,7 +1543,7 @@ def create_tag_index(self): It is better to initialize cache with `tag_index=True` than use this. - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ sql = self._sql @@ -1554,7 +1554,7 @@ def create_tag_index(self): def drop_tag_index(self): """Drop tag index on cache database. - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ sql = self._sql @@ -1574,7 +1574,7 @@ def evict(self, tag): :param str tag: tag identifying items :return: count of rows removed - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ select = ( @@ -1598,7 +1598,7 @@ def expire(self, now=None): :param float now: current time (default None, ``time.time()`` used) :return: count of items removed - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ select = ( @@ -1621,7 +1621,7 @@ def cull(self): exception occurred. :return: count of items removed - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ now = time.time() @@ -1673,7 +1673,7 @@ def clear(self): exception occurred. :return: count of rows removed - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ select = ( @@ -1912,7 +1912,7 @@ def reset(self, key, value=ENOVAL, update=True): :param value: value for item (optional) :param bool update: update database Settings table (default True) :return: updated value for item - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ sql = self._sql diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 3ec07da..e15c576 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -70,7 +70,7 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, :param int version: key version number (default None, cache parameter) :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 expires (default True) + :param bool retry: retry if database timeout occurs (default True) :return: True if item was added """ @@ -93,7 +93,7 @@ def get(self, key, default=None, version=None, read=False, :param float expire_time: if True, return expire_time in tuple (default False) :param tag: if True, return tag in tuple (default False) - :param bool retry: retry if database timeout expires (default False) + :param bool retry: retry if database timeout occurs (default False) :return: value for item if key is found else default """ @@ -127,7 +127,7 @@ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, :param int version: key version number (default None, cache parameter) :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 expires (default True) + :param bool retry: retry if database timeout occurs (default True) :return: True if item was set """ @@ -151,7 +151,7 @@ def pop(self, key, default=None, version=None, expire_time=False, :param float expire_time: if True, return expire_time in tuple (default False) :param tag: if True, return tag in tuple (default False) - :param bool retry: retry if database timeout expires (default True) + :param bool retry: retry if database timeout occurs (default True) :return: value for item if key is found else default """ @@ -164,7 +164,7 @@ def delete(self, key, version=None, retry=True): :param key: key for item :param int version: key version number (default None, cache parameter) - :param bool retry: retry if database timeout expires (default True) + :param bool retry: retry if database timeout occurs (default True) :return: True if item was deleted """ @@ -190,7 +190,7 @@ def incr(self, key, delta=1, version=None, default=None, retry=True): :param int delta: amount to increment (default 1) :param int version: key version number (default None, cache parameter) :param int default: value if key is missing (default None) - :param bool retry: retry if database timeout expires (default True) + :param bool retry: retry if database timeout occurs (default True) :return: new value for item on success else None :raises ValueError: if key is not found and default is None @@ -223,7 +223,7 @@ def decr(self, key, delta=1, version=None, default=None, retry=True): :param int delta: amount to decrement (default 1) :param int version: key version number (default None, cache parameter) :param int default: value if key is missing (default None) - :param bool retry: retry if database timeout expires (default True) + :param bool retry: retry if database timeout occurs (default True) :return: new value for item on success else None :raises ValueError: if key is not found and default is None @@ -269,7 +269,7 @@ def create_tag_index(self): It is better to initialize cache with `tag_index=True` than use this. - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ self._cache.create_tag_index() @@ -278,7 +278,7 @@ def create_tag_index(self): def drop_tag_index(self): """Drop tag index on cache database. - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ self._cache.drop_tag_index() diff --git a/diskcache/fanout.py b/diskcache/fanout.py index cfa2ccf..948497d 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -67,7 +67,7 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): (default None, no expiry) :param bool read: read value as raw bytes from file (default False) :param str tag: text to associate with key (default None) - :param bool retry: retry if database timeout expires (default False) + :param bool retry: retry if database timeout occurs (default False) :return: True if item was set """ @@ -113,7 +113,7 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): (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 expires (default False) + :param bool retry: retry if database timeout occurs (default False) :return: True if item was added """ @@ -146,7 +146,7 @@ def incr(self, key, delta=1, default=0, retry=False): :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 expires (default False) + :param bool retry: retry if database timeout occurs (default False) :return: new value for item on success else None :raises KeyError: if key is not found and default is None @@ -183,7 +183,7 @@ def decr(self, key, delta=1, default=0, retry=False): :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 expires (default False) + :param bool retry: retry if database timeout occurs (default False) :return: new value for item on success else None :raises KeyError: if key is not found and default is None @@ -205,7 +205,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, :param float expire_time: if True, return expire_time in tuple (default False) :param tag: if True, return tag in tuple (default False) - :param bool retry: retry if database timeout expires (default False) + :param bool retry: retry if database timeout occurs (default False) :return: value for item if key is found else default """ @@ -281,7 +281,7 @@ def pop(self, key, default=None, expire_time=False, tag=False, :param float expire_time: if True, return expire_time in tuple (default False) :param tag: if True, return tag in tuple (default False) - :param bool retry: retry if database timeout expires (default False) + :param bool retry: retry if database timeout occurs (default False) :return: value for item if key is found else default """ @@ -309,7 +309,7 @@ def delete(self, key, retry=False): `True` (default `False`). :param key: key for item - :param bool retry: retry if database timeout expires (default False) + :param bool retry: retry if database timeout occurs (default False) :return: True if item was deleted """ @@ -359,7 +359,7 @@ def check(self, fix=False): :param bool fix: correct inconsistencies :return: list of warnings - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ return sum((shard.check(fix=fix) for shard in self._shards), []) @@ -379,7 +379,7 @@ def create_tag_index(self): It is better to initialize cache with `tag_index=True` than use this. - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ for shard in self._shards: @@ -389,7 +389,7 @@ def create_tag_index(self): def drop_tag_index(self): """Drop tag index on cache database. - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ for shard in self._shards: @@ -518,7 +518,7 @@ def reset(self, key, value=ENOVAL): :param str key: Settings key for item :param value: value for item (optional) :return: updated value for item - :raises Timeout: if database timeout expires + :raises Timeout: if database timeout occurs """ for shard in self._shards: From 7487054fab527bf8cde02420faac923389e499ae Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 20 Mar 2019 22:46:31 -0700 Subject: [PATCH 280/550] Add retry param to Cache methods --- diskcache/core.py | 150 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 113 insertions(+), 37 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 90ec58f..8fb2211 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -607,6 +607,8 @@ def _execute_with_retry(statement, *args, **kwargs): def transact(self, retry=False): """Lock the cache to perform a transaction. + # TODO + """ with self._transact(retry=retry): yield @@ -654,18 +656,22 @@ def _transact(self, retry=False, filename=None): _disk_remove(name) - def set(self, key, value, expire=None, read=False, tag=None): + 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: True if item was set :raises Timeout: if database timeout occurs @@ -697,7 +703,7 @@ def set(self, key, value, expire=None, read=False, tag=None): # INSERT OR REPLACE aka UPSERT is not used because the old filename may # need cleanup. - with self._transact(False, filename) as (sql, cleanup): + with self._transact(retry, filename) as (sql, cleanup): rows = sql( 'SELECT rowid, filename FROM Cache' ' WHERE key = ? AND raw = ?', @@ -716,7 +722,16 @@ def set(self, key, value, expire=None, read=False, tag=None): return True - __setitem__ = set + def __setitem__(self, key, value): + """Set corresponding `value` for `key` in cache. + + :param key: key for item + :param value: value for item + :return: corresponding value + :raises KeyError: if key is not found + + """ + self.set(key, value, retry=True) def _row_update(self, rowid, now, columns): @@ -822,7 +837,7 @@ def _cull(self, now, sql, cleanup, limit=None): cleanup(filename) - def add(self, key, value, expire=None, read=False, tag=None): + 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. @@ -833,12 +848,16 @@ def add(self, key, value, expire=None, read=False, tag=None): 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: True if item was added :raises Timeout: if database timeout occurs @@ -849,7 +868,7 @@ def add(self, key, value, expire=None, read=False, tag=None): size, mode, filename, db_value = self._disk.store(value, read, key=key) columns = (expire_time, tag, size, mode, filename, db_value) - with self._transact(False, filename) as (sql, cleanup): + with self._transact(retry, filename) as (sql, cleanup): rows = sql( 'SELECT rowid, filename, expire_time FROM Cache' ' WHERE key = ? AND raw = ?', @@ -873,7 +892,7 @@ def add(self, key, value, expire=None, read=False, tag=None): return True - def incr(self, key, delta=1, default=0): + def incr(self, key, delta=1, default=0, retry=False): """Increment value by delta for item with key. If key is missing and default is None then raise KeyError. Else if key @@ -886,9 +905,13 @@ def incr(self, key, delta=1, default=0): 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 is not found and default is None :raises Timeout: if database timeout occurs @@ -901,7 +924,7 @@ def incr(self, key, delta=1, default=0): ' WHERE key = ? AND raw = ?' ) - with self._transact() as (sql, cleanup): + with self._transact(retry) as (sql, cleanup): rows = sql(select, (db_key, raw)).fetchall() if not rows: @@ -941,7 +964,7 @@ def incr(self, key, delta=1, default=0): return value - def decr(self, key, delta=1, default=0): + def decr(self, key, delta=1, default=0, retry=False): """Decrement value by delta for item with key. If key is missing and default is None then raise KeyError. Else if key @@ -957,20 +980,28 @@ def decr(self, key, delta=1, default=0): 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 is not found and default is None :raises Timeout: if database timeout occurs """ - return self.incr(key, -delta, default) + return self.incr(key, -delta, default, retry) - def get(self, key, default=None, read=False, expire_time=False, tag=False): + 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 return if key is missing (default None) :param bool read: if True, return file handle to value @@ -978,6 +1009,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): :param bool expire_time: if True, return expire_time in tuple (default False) :param bool tag: if True, 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 @@ -1019,7 +1051,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False): 'UPDATE Settings SET value = value + 1 WHERE key = "misses"' ) - with self._transact() as (sql, _): + with self._transact(retry) as (sql, _): rows = sql(select, (db_key, raw, time.time())).fetchall() if not rows: @@ -1066,25 +1098,28 @@ def __getitem__(self, key): :param key: key matching item :return: corresponding value :raises KeyError: if key is not found - :raises Timeout: if database timeout occurs """ - value = self.get(key, default=ENOVAL) + value = self.get(key, default=ENOVAL, retry=True) if value is ENOVAL: raise KeyError(key) return value - def read(self, key): + 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 is not found :raises Timeout: if database timeout occurs """ - handle = self.get(key, default=ENOVAL, read=True) + handle = self.get(key, default=ENOVAL, read=True, retry=retry) if handle is ENOVAL: raise KeyError(key) return handle @@ -1110,18 +1145,22 @@ def __contains__(self, key): return bool(rows) - def pop(self, key, default=None, expire_time=False, tag=False): + def pop(self, key, default=None, expire_time=False, tag=False, retry=False): """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 return if key is missing (default None) :param bool expire_time: if True, return expire_time in tuple (default False) :param bool tag: if True, 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 @@ -1138,7 +1177,7 @@ def pop(self, key, default=None, expire_time=False, tag=False): elif expire_time or tag: default = default, None - with self._transact() as (sql, _): + with self._transact(retry) as (sql, _): rows = sql(select, (db_key, raw, time.time())).fetchall() if not rows: @@ -1170,17 +1209,21 @@ def pop(self, key, default=None, expire_time=False, tag=False): return value - def __delitem__(self, key): + 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 is not found :raises Timeout: if database timeout occurs """ db_key, raw = self._disk.put(key) - with self._transact() as (sql, cleanup): + with self._transact(retry) as (sql, cleanup): rows = sql( 'SELECT rowid, filename FROM Cache' ' WHERE key = ? AND raw = ?' @@ -1198,24 +1241,28 @@ def __delitem__(self, key): return True - def delete(self, key): + 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: True if item was deleted :raises Timeout: if database timeout occurs """ try: - return self.__delitem__(key) + return self.__delitem__(key, retry=retry) except KeyError: return False def push(self, value, prefix=None, side='back', expire=None, read=False, - tag=None): + tag=None, retry=False): """Push `value` onto `side` of queue identified by `prefix` in cache. When prefix is None, integer keys are used. Otherwise, string keys are @@ -1229,6 +1276,9 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, 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). + See also `Cache.pull`. >>> cache = Cache('/tmp/test') @@ -1251,6 +1301,7 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, (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 @@ -1274,7 +1325,7 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, ' ORDER BY key %s LIMIT 1' ) % order[side] - with self._transact(False, filename) as (sql, cleanup): + with self._transact(retry, filename) as (sql, cleanup): rows = sql(select, (min_key, max_key, raw)).fetchall() if rows: @@ -1305,7 +1356,7 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, def pull(self, prefix=None, default=(None, None), side='front', - expire_time=False, tag=False): + expire_time=False, tag=False, retry=False): """Pull key and value item pair from `side` of queue in cache. When prefix is None, integer keys are used. Otherwise, string keys are @@ -1319,6 +1370,9 @@ def pull(self, prefix=None, default=(None, None), side='front', 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('/tmp/test') @@ -1351,6 +1405,7 @@ def pull(self, prefix=None, default=(None, None), side='front', :param bool expire_time: if True, return expire_time in tuple (default False) :param bool tag: if True, 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 @@ -1375,7 +1430,7 @@ def pull(self, prefix=None, default=(None, None), side='front', default = default, None while True: - with self._transact() as (sql, cleanup): + with self._transact(retry) as (sql, cleanup): rows = sql(select, (min_key, max_key)).fetchall() if not rows: @@ -1412,7 +1467,7 @@ def pull(self, prefix=None, default=(None, None), side='front', return key, value - def check(self, fix=False): + def check(self, fix=False, retry=False): """Check database and file system consistency. Intended for use in testing and post-mortem error analysis. @@ -1423,7 +1478,11 @@ def check(self, fix=False): 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 @@ -1443,7 +1502,7 @@ def check(self, fix=False): if fix: sql('VACUUM') - with self._transact() as (sql, _): + with self._transact(retry) as (sql, _): # Check Cache.filename against file system. @@ -1562,7 +1621,7 @@ def drop_tag_index(self): self.reset('tag_index', 0) - def evict(self, tag): + def evict(self, tag, retry=False): """Remove items with matching `tag` from cache. Removing items is an iterative process. In each iteration, a subset of @@ -1572,7 +1631,11 @@ def evict(self, tag): `args` attribute will be the number of items removed before the exception occurred. + Raises :exc:`Timeout` error when database timeout occurs and `retry` is + `False` (default). + :param str tag: tag identifying items + :param bool retry: retry if database timeout occurs (default False) :return: count of rows removed :raises Timeout: if database timeout occurs @@ -1583,10 +1646,10 @@ def evict(self, tag): ' ORDER BY rowid LIMIT ?' ) args = [tag, 0, 100] - return self._select_delete(select, args, arg_index=1) + return self._select_delete(select, args, arg_index=1, retry=retry) - def expire(self, now=None): + def expire(self, now=None, retry=False): """Remove expired items from cache. Removing items is an iterative process. In each iteration, a subset of @@ -1596,7 +1659,11 @@ def expire(self, now=None): `args` attribute will be the number of items removed before the exception occurred. + Raises :exc:`Timeout` error when database timeout occurs and `retry` is + `False` (default). + :param float now: current time (default None, ``time.time()`` used) + :param bool retry: retry if database timeout occurs (default False) :return: count of items removed :raises Timeout: if database timeout occurs @@ -1607,10 +1674,10 @@ def expire(self, now=None): ' ORDER BY expire_time LIMIT ?' ) args = [0, now or time.time(), 100] - return self._select_delete(select, args, row_index=1) + return self._select_delete(select, args, row_index=1, retry=retry) - def cull(self): + def cull(self, retry=False): """Cull items from cache until volume is less than size limit. Removing items is an iterative process. In each iteration, a subset of @@ -1620,6 +1687,10 @@ def cull(self): `args` attribute will be the number of items removed before the exception occurred. + Raises :exc:`Timeout` error when database timeout occurs and `retry` is + `False` (default). + + :param bool retry: retry if database timeout occurs (default False) :return: count of items removed :raises Timeout: if database timeout occurs @@ -1641,7 +1712,7 @@ def cull(self): try: while self.volume() > self.size_limit: - with self._transact() as (sql, cleanup): + with self._transact(retry) as (sql, cleanup): rows = sql(select_filename, (10,)).fetchall() if not rows: @@ -1662,7 +1733,7 @@ def cull(self): return count - def clear(self): + def clear(self, retry=False): """Remove all items from cache. Removing items is an iterative process. In each iteration, a subset of @@ -1672,6 +1743,10 @@ def clear(self): `args` attribute will be the number of items removed before the exception occurred. + Raises :exc:`Timeout` error when database timeout occurs and `retry` is + `False` (default). + + :param bool retry: retry if database timeout occurs (default False) :return: count of rows removed :raises Timeout: if database timeout occurs @@ -1682,16 +1757,17 @@ def clear(self): ' ORDER BY rowid LIMIT ?' ) args = [0, 100] - return self._select_delete(select, args) + return self._select_delete(select, args, retry=retry) - def _select_delete(self, select, args, row_index=0, arg_index=0): + def _select_delete(self, select, args, row_index=0, arg_index=0, + retry=False): count = 0 delete = 'DELETE FROM Cache WHERE rowid IN (%s)' try: while True: - with self._transact() as (sql, cleanup): + with self._transact(retry) as (sql, cleanup): rows = sql(select, args).fetchall() if not rows: From aeda91ea4215c652a2f4b321bbdeaf4ca0bbe33d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 21 Mar 2019 16:25:10 -0700 Subject: [PATCH 281/550] Import get_ident from thread for Python 2 --- diskcache/core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index 8fb2211..3019482 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -22,6 +22,7 @@ import cPickle as pickle # pylint: disable=import-error # ISSUE #25 Fix for http://bugs.python.org/issue10211 from cStringIO import StringIO as BytesIO # pylint: disable=import-error + from thread import get_ident TextType = unicode # pylint: disable=invalid-name,undefined-variable BytesType = str INT_TYPES = int, long # pylint: disable=undefined-variable @@ -30,6 +31,7 @@ else: import pickle from io import BytesIO # pylint: disable=ungrouped-imports + from threading import get_ident TextType = str BytesType = bytes INT_TYPES = (int,) @@ -619,7 +621,7 @@ def _transact(self, retry=False, filename=None): sql = self._sql filenames = [] _disk_remove = self._disk.remove - tid = threading.get_ident() + tid = get_ident() txn_id = self._txn_id if tid == txn_id: From ffd46ba18ea76baad03e148fb9143baa20bdc975 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 21 Mar 2019 20:01:03 -0700 Subject: [PATCH 282/550] Refactor FanoutCache to use retry param from Cache --- diskcache/fanout.py | 195 ++++++++++++++++++++++--------------------- tests/test_fanout.py | 94 +-------------------- 2 files changed, 102 insertions(+), 187 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 948497d..c3bb14c 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -1,10 +1,16 @@ "Fanout cache automatically shards keys and values." import itertools as it +import operator import os.path as op import sqlite3 import time +try: + from functools import reduce +except ImportError: + reduce + from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout from .memo import memoize from .persistent import Deque, Index @@ -72,16 +78,11 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): """ index = self._hash(key) % self._count - set_func = self._shards[index].set - - while True: - try: - return set_func(key, value, expire, read, tag) - except Timeout: - if retry: - continue - else: - return False + shard = self._shards[index] + try: + return shard.set(key, value, expire, read, tag, retry) + except Timeout: + return False def __setitem__(self, key, value): @@ -93,7 +94,9 @@ def __setitem__(self, key, value): :param value: value for item """ - self.set(key, value, retry=True) + index = self._hash(key) % self._count + shard = self._shards[index] + shard[key] = value def add(self, key, value, expire=None, read=False, tag=None, retry=False): @@ -107,6 +110,9 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + :param key: key for item :param value: value for item :param float expire: seconds until the key expires @@ -118,16 +124,11 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): """ index = self._hash(key) % self._count - add_func = self._shards[index].add - - while True: - try: - return add_func(key, value, expire, read, tag) - except Timeout: - if retry: - continue - else: - return False + shard = self._shards[index] + try: + return shard.add(key, value, expire, read, tag, retry) + except Timeout: + return False def incr(self, key, delta=1, default=0, retry=False): @@ -143,6 +144,9 @@ def incr(self, key, delta=1, default=0, retry=False): machines with 64-bit pointer widths will support 64-bit signed integers. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + :param key: key for item :param int delta: amount to increment (default 1) :param int default: value if key is missing (default 0) @@ -152,16 +156,11 @@ def incr(self, key, delta=1, default=0, retry=False): """ index = self._hash(key) % self._count - incr_func = self._shards[index].incr - - while True: - try: - return incr_func(key, delta, default) - except Timeout: - if retry: - continue - else: - return None + shard = self._shards[index] + try: + return shard.incr(key, delta, default, retry) + except Timeout: + return None def decr(self, key, delta=1, default=0, retry=False): @@ -180,6 +179,9 @@ def decr(self, key, delta=1, default=0, retry=False): machines with 64-bit pointer widths will support 64-bit signed integers. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + :param key: key for item :param int delta: amount to decrement (default 1) :param int default: value if key is missing (default 0) @@ -188,7 +190,12 @@ def decr(self, key, delta=1, default=0, retry=False): :raises KeyError: if key is not found and default is None """ - return self.incr(key, -delta, default, retry) + index = self._hash(key) % self._count + shard = self._shards[index] + try: + return shard.decr(key, delta, default, retry) + except Timeout: + return None def get(self, key, default=None, read=False, expire_time=False, tag=False, @@ -210,19 +217,11 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, """ index = self._hash(key) % self._count - get_func = self._shards[index].get - - while True: - try: - return get_func( - key, default=default, read=read, expire_time=expire_time, - tag=tag, - ) - except (Timeout, sqlite3.OperationalError): - if retry: - continue - else: - return default + shard = self._shards[index] + try: + return shard.get(key, default, read, expire_time, tag, retry) + except (Timeout, sqlite3.OperationalError): + return default def __getitem__(self, key): @@ -235,12 +234,9 @@ def __getitem__(self, key): :raises KeyError: if key is not found """ - value = self.get(key, default=ENOVAL, retry=True) - - if value is ENOVAL: - raise KeyError(key) - - return value + index = self._hash(key) % self._count + shard = self._shards[index] + return shard[key] def read(self, key): @@ -265,7 +261,8 @@ def __contains__(self, key): """ index = self._hash(key) % self._count - return key in self._shards[index] + shard = self._shards[index] + return key in shard def pop(self, key, default=None, expire_time=False, tag=False, @@ -276,6 +273,9 @@ def pop(self, key, default=None, expire_time=False, tag=False, Operation is atomic. Concurrent operations will be serialized. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + :param key: key for item :param default: return value if key is missing (default None) :param float expire_time: if True, return expire_time in tuple @@ -286,18 +286,11 @@ def pop(self, key, default=None, expire_time=False, tag=False, """ index = self._hash(key) % self._count - pop_func = self._shards[index].pop - - while True: - try: - return pop_func( - key, default=default, expire_time=expire_time, tag=tag, - ) - except Timeout: - if retry: - continue - else: - return default + shard = self._shards[index] + try: + return shard.pop(key, default, expire_time, tag, retry) + except Timeout: + return default def delete(self, key, retry=False): @@ -314,18 +307,11 @@ def delete(self, key, retry=False): """ index = self._hash(key) % self._count - del_func = self._shards[index].__delitem__ - - while True: - try: - return del_func(key) - except Timeout: - if retry: - continue - else: - return False - except KeyError: - return False + shard = self._shards[index] + try: + return shard.delete(key, retry) + except Timeout: + return False def __delitem__(self, key): @@ -337,16 +323,15 @@ def __delitem__(self, key): :raises KeyError: if key is not found """ - deleted = self.delete(key, retry=True) - - if not deleted: - raise KeyError(key) + index = self._hash(key) % self._count + shard = self._shards[index] + del shard[key] memoize = memoize - def check(self, fix=False): + def check(self, fix=False, retry=False): """Check database and file system consistency. Intended for use in testing and post-mortem error analysis. @@ -357,21 +342,30 @@ def check(self, fix=False): held for a long time. For example, local benchmarking shows that a cache with 1,000 file references takes ~60ms to check. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + :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 """ - return sum((shard.check(fix=fix) for shard in self._shards), []) + warnings = (shard.check(fix, retry) for shard in self._shards) + return reduce(operator.iadd, warnings, []) - def expire(self): + def expire(self, retry=False): """Remove expired items from cache. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + + :param bool retry: retry if database timeout occurs (default False) :return: count of items removed """ - return self._remove('expire', args=(time.time(),)) + return self._remove('expire', args=(time.time(),), retry=retry) def create_tag_index(self): @@ -396,41 +390,53 @@ def drop_tag_index(self): shard.drop_tag_index() - def evict(self, tag): + def evict(self, tag, retry=False): """Remove items with matching `tag` from cache. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + :param str tag: tag identifying items + :param bool retry: retry if database timeout occurs (default False) :return: count of items removed """ - return self._remove('evict', args=(tag,)) + return self._remove('evict', args=(tag,), retry=retry) - def cull(self): + def cull(self, retry=False): """Cull items from cache until volume is less than size limit. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + + :param bool retry: retry if database timeout occurs (default False) :return: count of items removed """ - return self._remove('cull') + return self._remove('cull', retry=retry) - def clear(self): + def clear(self, retry=False): """Remove all items from cache. + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + + :param bool retry: retry if database timeout occurs (default False) :return: count of items removed """ - return self._remove('clear') + return self._remove('clear', retry=retry) - def _remove(self, name, args=()): + def _remove(self, name, args=(), retry=False): total = 0 for shard in self._shards: method = getattr(shard, name) while True: try: - count = method(*args) + count = method(*args, retry=retry) total += count except Timeout as timeout: total += timeout.args[0] @@ -448,8 +454,9 @@ def stats(self, enable=True, reset=False): """ results = [shard.stats(enable, reset) for shard in self._shards] - return (sum(result[0] for result in results), - sum(result[1] for result in results)) + total_hits = sum(hits for hits, _ in results) + total_misses = sum(misses for _, misses in results) + return total_hits, total_misses def volume(self): diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 63d7d61..77f7384 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -110,20 +110,6 @@ def test_set_timeout(cache): assert not cache.set(0, 0) -def test_set_timeout_retry(cache): - shards = mock.Mock() - shard = mock.Mock() - set_func = mock.Mock() - - shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) - shard.set = set_func - set_func.side_effect = [dc.Timeout, True, dc.Timeout, True] - - with mock.patch.object(cache, '_shards', shards): - assert cache.set(0, 0, retry=True) - cache[1] = 1 - - def test_add(cache): assert cache.add(0, 0) assert not cache.add(0, 1) @@ -143,19 +129,6 @@ def test_add_timeout(cache): assert not cache.add(0, 0) -def test_add_timeout_retry(cache): - shards = mock.Mock() - shard = mock.Mock() - add_func = mock.Mock() - - shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) - shard.add = add_func - add_func.side_effect = [dc.Timeout, True] - - with mock.patch.object(cache, '_shards', shards): - assert cache.add(0, 0, retry=True) - - def stress_add(cache, limit, results): total = 0 for num in range(limit): @@ -205,19 +178,6 @@ def test_incr_timeout(cache): assert cache.incr('key', 1) is None -def test_incr_timeout_retry(cache): - shards = mock.Mock() - shard = mock.Mock() - incr_func = mock.Mock() - - shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) - shard.incr = incr_func - incr_func.side_effect = [dc.Timeout, 1] - - with mock.patch.object(cache, '_shards', shards): - assert cache.incr('key', retry=True) == 1 - - def test_decr(cache): cache.decr('key', delta=2) == -2 @@ -311,19 +271,6 @@ def test_get_timeout(cache): assert cache.get(0) is None -def test_get_timeout_retry(cache): - shards = mock.Mock() - shard = mock.Mock() - get_func = mock.Mock() - - shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) - shard.get = get_func - get_func.side_effect = [dc.Timeout, 0] - - with mock.patch.object(cache, '_shards', shards): - assert cache.get(0, retry=True) == 0 - - def test_pop(cache): for num in range(100): cache[num] = num @@ -345,45 +292,19 @@ def test_pop_timeout(cache): assert cache.pop(0) is None -def test_pop_timeout_retry(cache): - shards = mock.Mock() - shard = mock.Mock() - pop_func = mock.Mock() - - shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) - shard.pop = pop_func - pop_func.side_effect = [dc.Timeout, 0] - - with mock.patch.object(cache, '_shards', shards): - assert cache.pop(0, retry=True) == 0 - - def test_delete_timeout(cache): shards = mock.Mock() shard = mock.Mock() delete_func = mock.Mock() shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) - shard.__delitem__ = delete_func + shard.delete = delete_func delete_func.side_effect = dc.Timeout with mock.patch.object(cache, '_shards', shards): assert not cache.delete(0) -def test_delete_timeout_retry(cache): - shards = mock.Mock() - shard = mock.Mock() - delete_func = mock.Mock() - - shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) - shard.__delitem__ = delete_func - delete_func.side_effect = [dc.Timeout, True] - - with mock.patch.object(cache, '_shards', shards): - assert cache.delete(0, retry=True) - - def test_delitem(cache): cache[0] = 0 assert cache[0] == 0 @@ -395,19 +316,6 @@ def test_delitem_keyerror(cache): del cache[0] -def test_delitem_timeout(cache): - shards = mock.Mock() - shard = mock.Mock() - delete_func = mock.Mock() - - shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) - shard.__delitem__ = delete_func - delete_func.side_effect = [dc.Timeout, True] - - with mock.patch.object(cache, '_shards', shards): - del cache[0] - - def test_tag_index(cache): assert cache.tag_index == 0 cache.create_tag_index() From 10ba4e6773a19db490a898c4782e511267815c3d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 21 Mar 2019 21:38:10 -0700 Subject: [PATCH 283/550] Update persistent data types to use retry param of Cache --- diskcache/persistent.py | 195 +++++++++------------------------------- tests/test_deque.py | 104 --------------------- tests/test_index.py | 98 -------------------- 3 files changed, 41 insertions(+), 356 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 1bd0388..a8d7485 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -10,7 +10,7 @@ from shutil import rmtree from tempfile import mkdtemp -from .core import BytesType, Cache, ENOVAL, TextType, Timeout +from .core import BytesType, Cache, ENOVAL, TextType ############################################################################ # BEGIN Python 2/3 Shims @@ -193,7 +193,7 @@ def __getitem__(self, index): try: key = _key(index) return _cache[key] - except (KeyError, Timeout): + except KeyError: continue @@ -218,14 +218,8 @@ def __setitem__(self, index, value): """ _key = self._key _cache = self._cache - - while True: - try: - key = _key(index) - _cache[key] = value - return - except Timeout: - continue + key = _key(index) + _cache[key] = value def __delitem__(self, index): @@ -254,7 +248,7 @@ def __delitem__(self, index): key = _key(index) del _cache[key] return - except (KeyError, Timeout): + except KeyError: continue @@ -297,7 +291,7 @@ def __iter__(self): for key in _cache.iterkeys(): try: yield _cache[key] - except (KeyError, Timeout): + except KeyError: pass @@ -330,7 +324,7 @@ def __reversed__(self): for key in _cache.iterkeys(reverse=True): try: yield _cache[key] - except (KeyError, Timeout): + except KeyError: pass @@ -356,14 +350,7 @@ def append(self, value): :param value: value to add to back of deque """ - _cache_push = self._cache.push - - while True: - try: - _cache_push(value) - return - except Timeout: - continue + self._cache.push(value, retry=True) def appendleft(self, value): @@ -380,28 +367,14 @@ def appendleft(self, value): :param value: value to add to front of deque """ - _cache_push = self._cache.push - - while True: - try: - _cache_push(value, side='front') - return - except Timeout: - continue + self._cache.push(value, side='front', retry=True) def clear(self): """Remove all elements from deque. """ - _cache_clear = self._cache.clear - - while True: - try: - _cache_clear() - return - except Timeout: - continue + self._cache.clear(retry=True) def count(self, value): @@ -469,18 +442,11 @@ def pop(self): :raises IndexError: if deque is empty """ - _cache_pull = self._cache.pull - - while True: - try: - default = None, ENOVAL - _, value = _cache_pull(default=default, side='back') - except Timeout: - continue - else: - if value is ENOVAL: - raise IndexError('pop from an empty deque') - return value + default = None, ENOVAL + _, value = self._cache.pull(default=default, side='back', retry=True) + if value is ENOVAL: + raise IndexError('pop from an empty deque') + return value def popleft(self): @@ -499,18 +465,11 @@ def popleft(self): IndexError: pop from an empty deque """ - _cache_pull = self._cache.pull - - while True: - try: - default = None, ENOVAL - _, value = _cache_pull(default=default) - except Timeout: - continue - else: - if value is ENOVAL: - raise IndexError('pop from an empty deque') - return value + default = None, ENOVAL + _, value = self._cache.pull(default=default, retry=True) + if value is ENOVAL: + raise IndexError('pop from an empty deque') + return value def remove(self, value): @@ -538,27 +497,16 @@ def remove(self, value): for key in _cache.iterkeys(): try: - while True: - try: - item = _cache[key] - except Timeout: - continue - else: - break + item = _cache[key] except KeyError: continue else: if value == item: try: - while True: - try: - del _cache[key] - except Timeout: - continue - else: - return + del _cache[key] except KeyError: continue + return raise ValueError('deque.remove(value): value not in deque') @@ -743,13 +691,7 @@ def __getitem__(self, key): :raises KeyError: if key is not found """ - _cache = self._cache - - while True: - try: - return _cache[key] - except Timeout: - continue + return self._cache[key] def __setitem__(self, key, value): @@ -768,15 +710,7 @@ def __setitem__(self, key, value): :param value: value for item """ - _cache = self._cache - - while True: - try: - _cache[key] = value - except Timeout: - continue - else: - return + self._cache[key] = value def __delitem__(self, key): @@ -800,15 +734,7 @@ def __delitem__(self, key): :raises KeyError: if key is not found """ - _cache = self._cache - - while True: - try: - del _cache[key] - except Timeout: - continue - else: - return + del self._cache[key] def setdefault(self, key, default=None): @@ -830,18 +756,11 @@ def setdefault(self, key, default=None): """ _cache = self._cache - while True: try: - return self[key] + return _cache[key] except KeyError: - while True: - try: - _cache.add(key, default) - except Timeout: - continue - else: - break + _cache.add(key, default, retry=True) def pop(self, key, default=ENOVAL): @@ -868,18 +787,11 @@ def pop(self, key, default=ENOVAL): :raises KeyError: if key is not found and default is ENOVAL """ - _cache = self._cache - - while True: - try: - value = _cache.pop(key, default=default) - except Timeout: - continue - else: - if value is ENOVAL: - raise KeyError(key) - return value + value = _cache.pop(key, default=default, retry=True) + if value is ENOVAL: + raise KeyError(key) + return value def popitem(self, last=True): @@ -921,8 +833,8 @@ def popitem(self, last=True): raise KeyError try: - value = _cache.pop(key) - except (KeyError, Timeout): + value = _cache.pop(key, retry=True) + except KeyError: continue else: return key, value @@ -958,13 +870,7 @@ def push(self, value, prefix=None, side='back'): :return: key for item in cache """ - _cache_push = self._cache.push - - while True: - try: - return _cache_push(value, prefix, side) - except Timeout: - continue + return self._cache.push(value, prefix, side, retry=True) def pull(self, prefix=None, default=(None, None), side='front'): @@ -1006,27 +912,14 @@ def pull(self, prefix=None, default=(None, None), side='front'): :return: key and value item pair or default if queue is empty """ - _cache_pull = self._cache.pull - - while True: - try: - return _cache_pull(prefix, default, side) - except Timeout: - continue + return self._cache.pull(prefix, default, side, retry=True) def clear(self): """Remove all items from index. """ - _cache_clear = self._cache.clear - - while True: - try: - _cache_clear() - return - except Timeout: - continue + self._cache.clear(retry=True) def __iter__(self): @@ -1145,11 +1038,8 @@ def itervalues(self): try: yield _cache[key] except KeyError: - break - except Timeout: - continue - else: - break + pass + break def iteritems(self): @@ -1171,11 +1061,8 @@ def iteritems(self): try: yield key, _cache[key] except KeyError: - break - except Timeout: - continue - else: - break + pass + break def viewkeys(self): diff --git a/tests/test_deque.py b/tests/test_deque.py index f9ba35b..59d9819 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -123,82 +123,12 @@ def test_indexerror_islice(deque): deque[0] -def test_get_timeout(deque): - cache = mock.MagicMock() - cache.__len__.return_value = 1 - cache.iterkeys.side_effect = [iter([0]), iter([0])] - cache.__getitem__.side_effect = [dc.Timeout, 0] - - deque.append(0) - - with mock.patch.object(deque, '_cache', cache): - deque[0] - - -def test_set_timeout(deque): - cache = mock.MagicMock() - cache.__len__.return_value = 1 - cache.iterkeys.side_effect = [iter([0]), iter([0])] - cache.__setitem__.side_effect = [dc.Timeout, None] - - deque.append(0) - - with mock.patch.object(deque, '_cache', cache): - deque[0] = 0 - - -def test_del_timeout(deque): - cache = mock.MagicMock() - cache.__len__.return_value = 1 - cache.iterkeys.side_effect = [iter([0]), iter([0])] - cache.__delitem__.side_effect = [dc.Timeout, None] - - deque.append(0) - - with mock.patch.object(deque, '_cache', cache): - del deque[0] - - def test_repr(): directory = '/tmp/diskcache/deque' deque = dc.Deque(directory=directory) assert repr(deque) == 'Deque(directory=%r)' % directory -def test_iter_timeout(deque): - cache = mock.MagicMock() - cache.iterkeys.side_effect = [iter([0, 1])] - cache.__getitem__.side_effect = [dc.Timeout, 0] - - with mock.patch.object(deque, '_cache', cache): - assert list(deque) == [0] - - -def test_reversed_timeout(deque): - cache = mock.MagicMock() - cache.iterkeys.side_effect = [iter([0, 1])] - cache.__getitem__.side_effect = [dc.Timeout, 0] - - with mock.patch.object(deque, '_cache', cache): - assert list(reversed(deque)) == [0] - - -def test_append_timeout(deque): - cache = mock.MagicMock() - cache.push.side_effect = [dc.Timeout, None] - - with mock.patch.object(deque, '_cache', cache): - deque.append(0) - - -def test_appendleft_timeout(deque): - cache = mock.MagicMock() - cache.push.side_effect = [dc.Timeout, None] - - with mock.patch.object(deque, '_cache', cache): - deque.appendleft(0) - - def test_count(deque): deque += 'abbcccddddeeeee' @@ -231,14 +161,6 @@ def test_pop_indexerror(deque): deque.pop() -def test_pop_timeout(deque): - cache = mock.MagicMock() - cache.pull.side_effect = [dc.Timeout, (None, 0)] - - with mock.patch.object(deque, '_cache', cache): - assert deque.pop() == 0 - - def test_popleft(deque): sequence = list('abcde') deque.extend(sequence) @@ -254,14 +176,6 @@ def test_popleft_indexerror(deque): deque.popleft() -def test_popleft_timeout(deque): - cache = mock.MagicMock() - cache.pull.side_effect = [dc.Timeout, (None, 0)] - - with mock.patch.object(deque, '_cache', cache): - assert deque.popleft() == 0 - - def test_remove(deque): deque.extend('abaca') deque.remove('a') @@ -272,16 +186,6 @@ def test_remove(deque): assert deque == 'bc' -def test_remove_timeout(deque): - cache = mock.MagicMock() - cache.iterkeys.side_effect = [iter([0, 1, 2, 3, 4])] - cache.__getitem__.side_effect = [0, dc.Timeout, KeyError, 3, 3] - cache.__delitem__.side_effect = [KeyError, dc.Timeout, None] - - with mock.patch.object(deque, '_cache', cache): - deque.remove(3) - - def test_remove_valueerror(deque): with pytest.raises(ValueError): deque.remove(0) @@ -332,11 +236,3 @@ def test_rotate_indexerror_negative(deque): with mock.patch.object(deque, '_cache', cache): deque.rotate(-1) - - -def test_clear_timeout(deque): - cache = mock.MagicMock() - cache.clear.side_effect = [dc.Timeout, None] - - with mock.patch.object(deque, '_cache', cache): - deque.clear() diff --git a/tests/test_index.py b/tests/test_index.py index e5cd71d..38fb9d2 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -77,30 +77,6 @@ def test_getsetdel(index): assert len(index) == 0 -def test_get_timeout(index): - cache = mock.MagicMock() - cache.__getitem__.side_effect = [dc.Timeout, 0] - - with mock.patch.object(index, '_cache', cache): - assert index[0] == 0 - - -def test_set_timeout(index): - cache = mock.MagicMock() - cache.__setitem__.side_effect = [dc.Timeout, None] - - with mock.patch.object(index, '_cache', cache): - index[0] = 0 - - -def test_del_timeout(index): - cache = mock.MagicMock() - cache.__delitem__.side_effect = [dc.Timeout, None] - - with mock.patch.object(index, '_cache', cache): - del index[0] - - def test_pop(index): letters = 'abcde' assert len(index) == 0 @@ -121,14 +97,6 @@ def test_pop_keyerror(index): index.pop('a') -def test_pop_timeout(index): - cache = mock.MagicMock() - cache.pop.side_effect = [dc.Timeout, 1] - - with mock.patch.object(index, '_cache', cache): - assert index.pop(0) == 1 - - def test_popitem(index): letters = 'abcde' @@ -146,34 +114,11 @@ def test_popitem_keyerror(index): index.popitem() -def test_popitem_timeout(index): - cache = mock.MagicMock() - cache.__reversed__ = mock.Mock() - cache.__reversed__.side_effect = [iter([0]), iter([0])] - cache.pop.side_effect = [dc.Timeout, 1] - - with mock.patch.object(index, '_cache', cache): - value = index.popitem() - assert value == (0, 1) - - def test_setdefault(index): assert index.setdefault('a', 0) == 0 assert index.setdefault('a', 1) == 0 -def test_setdefault_timeout(index): - cache = mock.MagicMock() - cache.__getitem__ = mock.Mock() - cache.__getitem__.side_effect = [KeyError, 0] - cache.add = mock.Mock() - cache.add.side_effect = [dc.Timeout, 0] - - with mock.patch.object(index, '_cache', cache): - value = index.setdefault('a', 0) - assert value == 0 - - def test_iter(index): letters = 'abcde' @@ -201,46 +146,3 @@ def test_state(index): state = pickle.dumps(index) values = pickle.loads(state) assert values == mapping - - -def test_push_timeout(index): - cache = mock.MagicMock() - cache.push.side_effect = [dc.Timeout, None] - - with mock.patch.object(index, '_cache', cache): - index.push(0) - - -def test_pull_timeout(index): - cache = mock.MagicMock() - cache.pull.side_effect = [dc.Timeout, None] - - with mock.patch.object(index, '_cache', cache): - index.pull(0) - - -def test_clear_timeout(index): - cache = mock.MagicMock() - cache.clear.side_effect = [dc.Timeout, None] - - with mock.patch.object(index, '_cache', cache): - index.clear() - - -if sys.hexversion < 0x03000000: - def test_itervalues_timeout(index): - cache = mock.MagicMock() - cache.__iter__.side_effect = [iter([0, 1, 2])] - cache.__getitem__.side_effect = [dc.Timeout, KeyError, 1, 2] - - with mock.patch.object(index, '_cache', cache): - assert list(index.itervalues()) == [1, 2] - - - def test_iteritems_timeout(index): - cache = mock.MagicMock() - cache.__iter__.side_effect = [iter([0, 1, 2])] - cache.__getitem__.side_effect = [dc.Timeout, KeyError, 1, 2] - - with mock.patch.object(index, '_cache', cache): - assert list(index.iteritems()) == [(1, 1), (2, 2)] From d05369b66ae5319859ce1308b718fa6cda16ba73 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 21 Mar 2019 22:09:15 -0700 Subject: [PATCH 284/550] Add memoize method to Cache and Index data types --- diskcache/core.py | 5 +++++ diskcache/memo.py | 8 ++++---- diskcache/persistent.py | 2 ++ tests/test_core.py | 36 ++++++++++++++++++++++++++++++++++++ tests/test_index.py | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 3019482..48fdd5d 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -37,6 +37,8 @@ INT_TYPES = (int,) io_open = open # pylint: disable=invalid-name +from .memo import memoize + try: WindowsError except NameError: @@ -1469,6 +1471,9 @@ def pull(self, prefix=None, default=(None, None), side='front', return key, value + memoize = memoize + + def check(self, fix=False, retry=False): """Check database and file system consistency. diff --git a/diskcache/memo.py b/diskcache/memo.py index 3a2243a..d8f81ee 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -4,7 +4,7 @@ from functools import wraps -from .core import ENOVAL +MARK = object() def memoize(cache, name=None, typed=False, expire=None, tag=None): """Memoizing cache decorator. @@ -80,7 +80,7 @@ def wrapper(*args, **kwargs): key = reference + args if kwargs: - key += (ENOVAL,) + key += (MARK,) sorted_items = sorted(kwargs.items()) for item in sorted_items: @@ -92,9 +92,9 @@ def wrapper(*args, **kwargs): if kwargs: key += tuple(type(value) for _, value in sorted_items) - result = cache.get(key, default=ENOVAL, retry=True) + result = cache.get(key, default=MARK, retry=True) - if result is ENOVAL: + if result is MARK: result = function(*args, **kwargs) cache.set(key, result, expire=expire, tag=tag, retry=True) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index a8d7485..3d8f89e 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -634,6 +634,8 @@ def __init__(self, *args, **kwargs): directory = mkdtemp(prefix='diskcache-') self._cache = Cache(directory, eviction_policy='none') self.update(*args, **kwargs) + self.memoize = self._cache.memoize + self.stats = self._cache.stats @classmethod diff --git a/tests/test_core.py b/tests/test_core.py index 76b5568..f096485 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1307,6 +1307,42 @@ def test_lru_incr(cache): assert cache[0] == 0 +def test_memoize(cache): + count = 1000 + + def fibiter(num): + alpha, beta = 0, 1 + + for _ in range(num): + alpha, beta = beta, alpha + beta + + return alpha + + @cache.memoize() + def fibrec(num): + if num == 0: + return 0 + elif num == 1: + return 1 + else: + return fibrec(num - 1) + fibrec(num - 2) + + cache.stats(enable=True) + + for value in range(count): + assert fibrec(value) == fibiter(value) + + hits1, misses1 = cache.stats() + + for value in range(count): + assert fibrec(value) == fibiter(value) + + hits2, misses2 = cache.stats() + + assert hits2 == (hits1 + count) + assert misses2 == misses1 + + if __name__ == '__main__': import nose nose.runmodule() diff --git a/tests/test_index.py b/tests/test_index.py index 38fb9d2..5bcbfdc 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -146,3 +146,39 @@ def test_state(index): state = pickle.dumps(index) values = pickle.loads(state) assert values == mapping + + +def test_memoize(index): + count = 1000 + + def fibiter(num): + alpha, beta = 0, 1 + + for _ in range(num): + alpha, beta = beta, alpha + beta + + return alpha + + @index.memoize() + def fibrec(num): + if num == 0: + return 0 + elif num == 1: + return 1 + else: + return fibrec(num - 1) + fibrec(num - 2) + + index.stats(enable=True) + + for value in range(count): + assert fibrec(value) == fibiter(value) + + hits1, misses1 = index.stats() + + for value in range(count): + assert fibrec(value) == fibiter(value) + + hits2, misses2 = index.stats() + + assert hits2 == (hits1 + count) + assert misses2 == misses1 From a8c9259377072cf0ac15d7c7b72e57ce58cbb464 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 09:36:25 -0700 Subject: [PATCH 285/550] Move memoize to DjangoCache type --- diskcache/djangocache.py | 85 +++++++++++++++++++++++++++++++++++++++- diskcache/memo.py | 1 + 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index e15c576..108247a 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -1,5 +1,6 @@ "Django-compatible disk and file backed cache." +from functools import wraps from django.core.cache.backends.base import BaseCache try: @@ -10,6 +11,8 @@ from .fanout import FanoutCache +MARK = object() + class DjangoCache(BaseCache): "Django-compatible disk and file backed cache." @@ -26,7 +29,6 @@ def __init__(self, directory, params): options = params.get('OPTIONS', {}) self._directory = directory self._cache = FanoutCache(directory, shards, timeout, **options) - self.memoize = self._cache.memoize @property @@ -327,3 +329,84 @@ def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): # ticket 21147 - avoid time.time() related precision issues timeout = -1 return None if timeout is None else timeout + + + def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, + typed=False, tag=None): + """Memoizing cache decorator. + + 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. + + 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, + or for rewrapping the function with a different cache. + + Remember to call memoize when decorating a callable. If you forget, then a + TypeError will occur. + + :param str name: name given for callable (default None, automatic) + :param float timeout: seconds until the item expires + (default 300 seconds) + :param int version: key version number (default None, cache parameter) + :param bool typed: cache different types separately (default False) + :param str tag: text to associate with arguments (default None) + :return: callable decorator + + """ + # Caution: Nearly identical code exists in memo.memoize + if callable(name): + raise TypeError('name cannot be callable') + + def decorator(function): + "Decorator created by memoize call for callable." + if name is None: + try: + reference = function.__qualname__ + except AttributeError: + reference = function.__name__ + + reference = function.__module__ + reference + else: + reference = name + + reference = (reference,) + + @wraps(function) + def wrapper(*args, **kwargs): + "Wrapper for callable to cache arguments and return values." + + key = reference + args + + if kwargs: + key += (MARK,) + 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) + + result = self.get(key, MARK, version, retry=True) + + if result is MARK: + result = function(*args, **kwargs) + self.set(key, result, timeout, version, tag=tag, retry=True) + + return result + + return wrapper + + return decorator diff --git a/diskcache/memo.py b/diskcache/memo.py index d8f81ee..20c6404 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -56,6 +56,7 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): :return: callable decorator """ + # Caution: Nearly identical code exists in DjangoCache.memoize if callable(name): raise TypeError('name cannot be callable') From e2eb35dd09c086a36183d692a3b14575eb6c82b4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 09:36:45 -0700 Subject: [PATCH 286/550] Move memoize to Index type --- diskcache/persistent.py | 52 +++++++++++++++++++++++++++++++++++++++-- tests/test_index.py | 6 ++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 3d8f89e..bf33207 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -634,8 +634,6 @@ def __init__(self, *args, **kwargs): directory = mkdtemp(prefix='diskcache-') self._cache = Cache(directory, eviction_policy='none') self.update(*args, **kwargs) - self.memoize = self._cache.memoize - self.stats = self._cache.stats @classmethod @@ -1233,6 +1231,56 @@ def __ne__(self, other): return not self == other + def memoize(self, name=None, typed=False): + """Memoizing cache decorator. + + 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. + + 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, + or for rewrapping the function with a different cache. + + >>> from diskcache import Index + >>> mapping = Index('/tmp/diskcache/index') + >>> @mapping.memoize(typed=True) + ... def fibonacci(number): + ... if number == 0: + ... return 0 + ... elif number == 1: + ... return 1 + ... else: + ... return fibonacci(number - 1) + fibonacci(number - 2) + >>> print(sum(fibonacci(number=value) for value in range(100))) + 573147844013817084100 + + Remember to call memoize when decorating a callable. If you forget, + then a TypeError will occur. Note the lack of parenthenses after + memoize below: + + >>> @mapping.memoize + ... def test(): + ... pass + Traceback (most recent call last): + ... + TypeError: name cannot be callable + + :param str name: name given for callable (default None, automatic) + :param bool typed: cache different types separately (default False) + :return: callable decorator + + """ + return self._cache.memoize(name, typed) + + def __repr__(self): """index.__repr__() <==> repr(index) diff --git a/tests/test_index.py b/tests/test_index.py index 5bcbfdc..2b4aa14 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -168,17 +168,17 @@ def fibrec(num): else: return fibrec(num - 1) + fibrec(num - 2) - index.stats(enable=True) + index._cache.stats(enable=True) for value in range(count): assert fibrec(value) == fibiter(value) - hits1, misses1 = index.stats() + hits1, misses1 = index._cache.stats() for value in range(count): assert fibrec(value) == fibiter(value) - hits2, misses2 = index.stats() + hits2, misses2 = index._cache.stats() assert hits2 == (hits1 + count) assert misses2 == misses1 From 661de4afa7cf327899db7ba1d1b328ce3a9279b4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 10:16:15 -0700 Subject: [PATCH 287/550] Add transact method to Deque and Index and improve docs --- diskcache/core.py | 30 +++++++++++++++--- diskcache/persistent.py | 67 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 48fdd5d..c5c683a 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -609,9 +609,31 @@ def _execute_with_retry(statement, *args, **kwargs): @cl.contextmanager def transact(self, retry=False): - """Lock the cache to perform a transaction. + """Context manager to perform a transaction by locking the cache. - # TODO + 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('/tmp/diskcache') + >>> _ = cache.clear() + >>> 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 """ with self._transact(retry=retry): @@ -1285,7 +1307,7 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, See also `Cache.pull`. - >>> cache = Cache('/tmp/test') + >>> cache = Cache('/tmp/diskcache') >>> _ = cache.clear() >>> print(cache.push('first value')) 500000000000000 @@ -1379,7 +1401,7 @@ def pull(self, prefix=None, default=(None, None), side='front', See also `Cache.push` and `Cache.get`. - >>> cache = Cache('/tmp/test') + >>> cache = Cache('/tmp/diskcache') >>> _ = cache.clear() >>> cache.pull() (None, None) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index bf33207..77f44d0 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -6,6 +6,7 @@ import sys from collections import OrderedDict +from contextlib import contextmanager from itertools import islice from shutil import rmtree from tempfile import mkdtemp @@ -583,6 +584,39 @@ def rotate(self, steps=1): __hash__ = None + @contextmanager + def transact(self): + """Context manager to perform a transaction by locking the deque. + + While the deque 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). + + >>> from diskcache import Deque + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += range(5) + >>> with deque.transact(): # Atomically rotate elements. + ... value = deque.pop() + ... deque.appendleft(value) + >>> list(deque) + [4, 0, 1, 2, 3] + + :param bool retry: retry if database timeout occurs (default False) + :return: context manager for use in `with` statement + :raises Timeout: if database timeout occurs + + """ + with self._cache.transact(retry=True): + yield + + class Index(MutableMapping): """Persistent mutable mapping with insertion order iteration. @@ -1281,6 +1315,39 @@ def memoize(self, name=None, typed=False): return self._cache.memoize(name, typed) + @contextmanager + def transact(self): + """Context manager to perform a transaction by locking the index. + + While the index 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). + + >>> from diskcache import Index + >>> mapping = Index('/tmp/diskcache/index') + >>> with mapping.transact(): # Atomically increment two keys. + ... mapping['total'] = mapping.get('total', 0) + 123.4 + ... mapping['count'] = mapping.get('count', 0) + 1 + >>> with mapping.transact(): # Atomically calculate average. + ... average = mapping['total'] / mapping['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 + + """ + with self._cache.transact(retry=True): + yield + + def __repr__(self): """index.__repr__() <==> repr(index) From 26c2768f8da5a0d436688b6180106c0e039637e7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 14:21:23 -0700 Subject: [PATCH 288/550] Add cache property to Deque and Index --- diskcache/persistent.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 77f44d0..b015438 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -135,6 +135,12 @@ def fromcache(cls, cache, iterable=()): return self + @property + def cache(self): + "Cache used by deque." + return self._cache + + @property def directory(self): "Directory path where deque is stored." @@ -697,6 +703,12 @@ def fromcache(cls, cache, *args, **kwargs): return self + @property + def cache(self): + "Cache used by index." + return self._cache + + @property def directory(self): "Directory path where items are stored." From 89a6fe4a05c569f1d5fea88b5845fc4eef6a718e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 14:22:06 -0700 Subject: [PATCH 289/550] Change get/set/del-item to use faster algorithm. --- diskcache/persistent.py | 69 +++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index b015438..2cd7b06 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -147,30 +147,36 @@ def directory(self): return self._cache.directory - def _key(self, index): + def _index(self, index, func): len_self = len(self) - if index < 0: - index += len_self - if index < 0: + if index >= 0: + if index >= len_self: raise IndexError('deque index out of range') - elif index >= len_self: - raise IndexError('deque index out of range') - diff = len_self - index - 1 - _cache_iterkeys = self._cache.iterkeys + for key in self._cache.iterkeys(): + if index == 0: + try: + return func(key) + except KeyError: + continue + index -= 1 + else: + if index < -len_self: + raise IndexError('deque index out of range') - try: - if index <= diff: - iter_keys = _cache_iterkeys() - key = next(islice(iter_keys, index, index + 1)) - else: - iter_keys = _cache_iterkeys(reverse=True) - key = next(islice(iter_keys, diff, diff + 1)) - except StopIteration: - raise IndexError('deque index out of range') + index += 1 + + for key in self._cache.iterkeys(reverse=True): + if index == 0: + try: + return func(key) + except KeyError: + continue + index += 1 + + raise IndexError('deque index out of range') - return key def __getitem__(self, index): @@ -193,15 +199,7 @@ def __getitem__(self, index): :raises IndexError: if index out of range """ - _key = self._key - _cache = self._cache - - while True: - try: - key = _key(index) - return _cache[key] - except KeyError: - continue + return self._index(index, self._cache.__getitem__) def __setitem__(self, index, value): @@ -223,10 +221,8 @@ def __setitem__(self, index, value): :raises IndexError: if index out of range """ - _key = self._key - _cache = self._cache - key = _key(index) - _cache[key] = value + set_value = lambda key: self._cache.__setitem__(key, value) + self._index(index, set_value) def __delitem__(self, index): @@ -247,16 +243,7 @@ def __delitem__(self, index): :raises IndexError: if index out of range """ - _key = self._key - _cache = self._cache - - while True: - try: - key = _key(index) - del _cache[key] - return - except KeyError: - continue + self._index(index, self._cache.__delitem__) def __repr__(self): From a81c393b2a85e9dd99282ca4884fc4a0da81ffc8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 14:22:52 -0700 Subject: [PATCH 290/550] Remove test_indexerror_islice --- tests/test_deque.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/test_deque.py b/tests/test_deque.py index 59d9819..cdb97ed 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -113,16 +113,6 @@ def test_indexerror(deque): deque[0] -def test_indexerror_islice(deque): - islice = mock.Mock(side_effect=StopIteration) - - deque.append(0) - - with mock.patch('diskcache.persistent.islice', islice): - with pytest.raises(IndexError): - deque[0] - - def test_repr(): directory = '/tmp/diskcache/deque' deque = dc.Deque(directory=directory) From 45aaad0facb56d3882b8a952699bf2db79394dd0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 14:23:34 -0700 Subject: [PATCH 291/550] Add items from iterable in transaction during initialization. --- diskcache/persistent.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 2cd7b06..769337f 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -106,7 +106,8 @@ def __init__(self, iterable=(), directory=None): if directory is None: directory = mkdtemp() self._cache = Cache(directory, eviction_policy='none') - self.extend(iterable) + with self.transact(): + self.extend(iterable) @classmethod From 78692a8cc879d370ac7e4ac31b152018b4270442 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 14:23:56 -0700 Subject: [PATCH 292/550] Compare value to item (rather than item to value) --- diskcache/persistent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 769337f..b1a4c35 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -388,7 +388,7 @@ def count(self, value): :param value: value to count in deque """ - return sum(1 for item in self if item == value) + return sum(1 for item in self if value == item) def extend(self, iterable): From 2446f4dd1f8927590ac9ab410a2e49066764dd09 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 14:24:18 -0700 Subject: [PATCH 293/550] Simplify Deque.reverse and add comment about Cache.swap idea --- diskcache/persistent.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index b1a4c35..2b2394b 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -509,20 +509,25 @@ def remove(self, value): def reverse(self): """Reverse deque in place. + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque += 'abc' + >>> deque.reverse() + >>> list(deque) + ['c', 'b', 'a'] + """ - # pylint: disable=protected-access - directory = mkdtemp() - temp = None - - try: - temp = Deque(iterable=reversed(self), directory=directory) - self.clear() - self.extend(temp) - finally: - if temp is not None: - temp._cache.close() - del temp - rmtree(directory) + # GrantJ 2019-03-22 Consider using an algorithm that swaps the values + # at two keys. Like self._cache.swap(key1, key2, retry=True) The swap + # method would exchange the values at two given keys. Then, using a + # forward iterator and a reverse iterator, the reversis method could + # avoid making copies of the values. + temp = Deque(iterable=reversed(self)) + self.clear() + self.extend(temp) + directory = temp.directory + del temp + rmtree(directory) def rotate(self, steps=1): From e1171b259f9cd3203dd1b38183c9650421844f9d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 14:25:00 -0700 Subject: [PATCH 294/550] Remove docstring comments about timeout and retry --- diskcache/persistent.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 2b2394b..046b463 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -594,9 +594,6 @@ def transact(self): Transactions may be nested and may not be shared between threads. - Raises :exc:`Timeout` error when database timeout occurs and `retry` is - `False` (default). - >>> from diskcache import Deque >>> deque = Deque(directory='/tmp/diskcache/deque') >>> deque.clear() @@ -607,7 +604,6 @@ def transact(self): >>> list(deque) [4, 0, 1, 2, 3] - :param bool retry: retry if database timeout occurs (default False) :return: context manager for use in `with` statement :raises Timeout: if database timeout occurs @@ -1331,9 +1327,6 @@ def transact(self): Transactions may be nested and may not be shared between threads. - Raises :exc:`Timeout` error when database timeout occurs and `retry` is - `False` (default). - >>> from diskcache import Index >>> mapping = Index('/tmp/diskcache/index') >>> with mapping.transact(): # Atomically increment two keys. @@ -1344,7 +1337,6 @@ def transact(self): >>> 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 From c17698806708913313973f834e426d3ba9633bd0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 16:42:30 -0700 Subject: [PATCH 295/550] Add Cache.peek, Deque.peek and Deque.peekleft --- diskcache/core.py | 107 ++++++++++++++++++++++++++++++++++++++++ diskcache/persistent.py | 67 ++++++++++++++++++++++--- 2 files changed, 168 insertions(+), 6 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index c5c683a..79c6740 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1493,6 +1493,113 @@ def pull(self, prefix=None, default=(None, None), side='front', 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 is None, 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('/tmp/diskcache') + >>> _ = cache.clear() + >>> 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 return if key is missing + (default (None, None)) + :param str side: either 'front' or 'back' (default 'front') + :param bool expire_time: if True, return expire_time in tuple + (default False) + :param bool tag: if True, 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 prefix is None: + min_key = 0 + max_key = 999999999999999 + else: + min_key = prefix + '-000000000000000' + max_key = prefix + '-999999999999999' + + order = {'front': 'ASC', 'back': 'DESC'} + select = ( + 'SELECT rowid, key, expire_time, tag, mode, filename, value' + ' FROM Cache WHERE ? < key AND key < ? AND raw = 1' + ' ORDER BY key %s LIMIT 1' + ) % order[side] + + if expire_time and tag: + default = default, None, None + elif expire_time or tag: + default = default, None + + while True: + with self._transact(retry) as (sql, cleanup): + rows = sql(select, (min_key, max_key)).fetchall() + + if not rows: + return default + + (rowid, key, db_expire, db_tag, mode, name, db_value), = rows + + if db_expire is not None and 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 as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + return default + else: + raise + finally: + if name is not None: + self._disk.remove(name) + + 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 + + memoize = memoize diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 046b463..e59b1ab 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -185,15 +185,16 @@ def __getitem__(self, index): Return corresponding item for `index` in deque. + See also `Deque.peekleft` and `Deque.peek` for indexing deque at index + ``0`` or ``-1``. + >>> deque = Deque(directory='/tmp/diskcache/deque') >>> deque.clear() >>> deque.extend('abcde') - >>> deque[0] - 'a' - >>> deque[-1] - 'e' - >>> deque[2] - 'c' + >>> deque[1] + 'b' + >>> deque[-2] + 'd' :param int index: index of item :return: corresponding item @@ -417,6 +418,60 @@ def extendleft(self, iterable): self.appendleft(value) + def peek(self): + """Peek at value at back of deque. + + Faster than indexing deque at -1. + + If deque is empty then raise IndexError. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.peek() + Traceback (most recent call last): + ... + IndexError: peek from an empty deque + >>> deque += 'abc' + >>> deque.peek() + 'c' + + :raises IndexError: if deque is empty + + """ + default = None, ENOVAL + _, value = self._cache.peek(default=default, side='back', retry=True) + if value is ENOVAL: + raise IndexError('peek from an empty deque') + return value + + + def peekleft(self): + """Peek at value at back of deque. + + Faster than indexing deque at 0. + + If deque is empty then raise IndexError. + + >>> deque = Deque(directory='/tmp/diskcache/deque') + >>> deque.clear() + >>> deque.peekleft() + Traceback (most recent call last): + ... + IndexError: peek from an empty deque + >>> deque += 'abc' + >>> deque.peekleft() + 'a' + + :raises IndexError: if deque is empty + + """ + default = None, ENOVAL + _, value = self._cache.peek(default=default, side='front', retry=True) + if value is ENOVAL: + raise IndexError('peek from an empty deque') + return value + + def pop(self): """Remove and return value at back of deque. From 27af7e9f377a9b7fb6e5f9ef1bf9c528aebc3204 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 21:15:04 -0700 Subject: [PATCH 296/550] Add comment about peek and pull similarities --- diskcache/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index 79c6740..b8b46b7 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1436,6 +1436,7 @@ def pull(self, prefix=None, default=(None, None), side='front', :raises Timeout: if database timeout occurs """ + # Caution: Nearly identical code exists in Cache.peek if prefix is None: min_key = 0 max_key = 999999999999999 @@ -1544,6 +1545,7 @@ def peek(self, prefix=None, default=(None, None), side='front', :raises Timeout: if database timeout occurs """ + # Caution: Nearly identical code exists in Cache.pull if prefix is None: min_key = 0 max_key = 999999999999999 From a2b707ed813b9203e66ed15bec5b27493fb83333 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 21:15:43 -0700 Subject: [PATCH 297/550] Add Cache.peekitem --- diskcache/core.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index b8b46b7..20d48dd 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1602,6 +1602,79 @@ def peek(self, prefix=None, default=(None, None), side='front', 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('/tmp/diskcache') + >>> _ = cache.clear() + >>> for index, letter in enumerate('abc'): + ... cache[letter] = index + >>> cache.peekitem() + ('c', 2) + >>> cache.peekitem(last=False) + ('a', 0) + + :param bool last: last item in iteration order (default True) + :param bool expire_time: if True, return expire_time in tuple + (default False) + :param bool tag: if True, 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] + + while True: + with self._transact(retry) as (sql, cleanup): + rows = sql(select).fetchall() + + if not rows: + raise KeyError('dictionary is empty') + + (rowid, key, raw, db_expire, db_tag, mode, name, value), = rows + + if db_expire is not None and db_expire < time.time(): + sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) + cleanup(name) + else: + break + + py_key = self._disk.get(key, raw) + + try: + py_value = self._disk.fetch(mode, name, value, False) + except IOError as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + return default + else: + raise + finally: + if name is not None: + self._disk.remove(name) + + if expire_time and tag: + return (py_key, py_value), db_expire, db_tag + elif expire_time: + return (py_key, py_value), db_expire + elif tag: + return (py_key, py_value), db_tag + else: + return py_key, py_value + + memoize = memoize From 1dfae1c397e4e2f2e6cc2474678c20ee35736aa3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 21:25:41 -0700 Subject: [PATCH 298/550] Fix typo in docstring --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index 20d48dd..0d01f74 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1603,7 +1603,7 @@ def peek(self, prefix=None, default=(None, None), side='front', 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. + """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. From e1b24a18eed2971eeef7dd57282bff8d64954d2c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 21:26:01 -0700 Subject: [PATCH 299/550] Update doctest for consistency --- diskcache/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 0d01f74..5bd0635 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1613,8 +1613,8 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): >>> cache = Cache('/tmp/diskcache') >>> _ = cache.clear() - >>> for index, letter in enumerate('abc'): - ... cache[letter] = index + >>> for num, letter in enumerate('abc'): + ... cache[letter] = num >>> cache.peekitem() ('c', 2) >>> cache.peekitem(last=False) From f22dba80df105605df6f9585a7f9e29db598a782 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 21:27:04 -0700 Subject: [PATCH 300/550] Add Index.peekitem --- diskcache/persistent.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index e59b1ab..d7cfb89 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -853,6 +853,26 @@ def setdefault(self, key, default=None): _cache.add(key, default, retry=True) + def peekitem(self, last=True): + """Peek at key and value item pair in index based on iteration order. + + >>> index = Index('/tmp/diskcache/index') + >>> index.clear() + >>> for num, letter in enumerate('xyz'): + ... index[letter] = num + >>> index.peekitem() + ('z', 2) + >>> index.peekitem(last=False) + ('x', 0) + + :param bool last: last item in iteration order (default True) + :return: key and value item pair + :raises KeyError: if cache is empty + + """ + return self._cache.peekitem(last, retry=True) + + def pop(self, key, default=ENOVAL): """Remove corresponding item for `key` from index and return value. From 75ee2a44a8f83d53aea62375ccc9344fd44b020b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 22:00:29 -0700 Subject: [PATCH 301/550] Add sphinx doc directives to persistent data types --- diskcache/persistent.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index d7cfb89..d564098 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -271,6 +271,9 @@ def __iadd__(self, iterable): Extend back side of deque with items from iterable. + :param iterable: iterable of items to append to deque + :return: deque with added items + """ self.extend(iterable) return self @@ -387,6 +390,7 @@ def count(self, value): 4 :param value: value to count in deque + :return: count of items equal to value in deque """ return sum(1 for item in self if value == item) @@ -435,6 +439,7 @@ def peek(self): >>> deque.peek() 'c' + :return: value at back of deque :raises IndexError: if deque is empty """ @@ -462,6 +467,7 @@ def peekleft(self): >>> deque.peekleft() 'a' + :return: value at front of deque :raises IndexError: if deque is empty """ @@ -489,6 +495,7 @@ def pop(self): ... IndexError: pop from an empty deque + :return: value at back of deque :raises IndexError: if deque is empty """ @@ -514,6 +521,9 @@ def popleft(self): ... IndexError: pop from an empty deque + :return: value at front of deque + :raises IndexError: if deque is empty + """ default = None, ENOVAL _, value = self._cache.pull(default=default, retry=True) @@ -1303,6 +1313,7 @@ def __eq__(self, other): True :param other: other mapping in equality comparison + :return: True if index equals other """ if len(self) != len(other): @@ -1336,6 +1347,7 @@ def __ne__(self, other): True :param other: other mapping in inequality comparison + :return: True if index does not equal other """ return not self == other From 7c0a54621ff52ee52204a8eff5a7c0d9b19a0324 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Mar 2019 23:11:08 -0700 Subject: [PATCH 302/550] Fixes for pylint --- .pylintrc | 8 ++- diskcache/core.py | 151 +++++++++++++++++++++------------------- diskcache/fanout.py | 2 +- diskcache/persistent.py | 1 - 4 files changed, 86 insertions(+), 76 deletions(-) diff --git a/.pylintrc b/.pylintrc index 1750d67..3ce5d57 100644 --- a/.pylintrc +++ b/.pylintrc @@ -143,7 +143,9 @@ disable=print-statement, no-member, useless-object-inheritance, inconsistent-return-statements, - ungrouped-imports + ungrouped-imports, + not-callable, + duplicate-code # PyCQA/pylint #1055 # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -329,7 +331,7 @@ indent-string=' ' max-line-length=100 # Maximum number of lines in a module. -max-module-lines=2000 +max-module-lines=2500 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. @@ -555,7 +557,7 @@ max-locals=30 max-parents=7 # Maximum number of public methods for a class (see R0904). -max-public-methods=25 +max-public-methods=30 # Maximum number of return / yield for function / method body. max-returns=8 diff --git a/diskcache/core.py b/diskcache/core.py index 5bd0635..2c423f5 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -18,11 +18,13 @@ import warnings import zlib +from .memo import memoize + if sys.hexversion < 0x03000000: import cPickle as pickle # pylint: disable=import-error # ISSUE #25 Fix for http://bugs.python.org/issue10211 from cStringIO import StringIO as BytesIO # pylint: disable=import-error - from thread import get_ident + from thread import get_ident # pylint: disable=import-error TextType = unicode # pylint: disable=invalid-name,undefined-variable BytesType = str INT_TYPES = int, long # pylint: disable=undefined-variable @@ -37,8 +39,6 @@ INT_TYPES = (int,) io_open = open # pylint: disable=invalid-name -from .memo import memoize - try: WindowsError except NameError: @@ -1457,32 +1457,35 @@ def pull(self, prefix=None, default=(None, None), side='front', default = default, None while True: - with self._transact(retry) as (sql, cleanup): - rows = sql(select, (min_key, max_key)).fetchall() + while True: + with self._transact(retry) as (sql, cleanup): + rows = sql(select, (min_key, max_key)).fetchall() - if not rows: - return default + if not rows: + return default - (rowid, key, db_expire, db_tag, mode, name, db_value), = rows + (rowid, key, db_expire, db_tag, mode, name, + db_value), = rows - sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) + sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) - if db_expire is not None and db_expire < time.time(): - cleanup(name) - else: - break + if db_expire is not None and db_expire < time.time(): + cleanup(name) + else: + break - try: - value = self._disk.fetch(mode, name, db_value, False) - except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - return default - else: - raise - finally: - if name is not None: - self._disk.remove(name) + try: + value = self._disk.fetch(mode, name, db_value, False) + except IOError as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + continue + else: + raise + finally: + if name is not None: + self._disk.remove(name) + break if expire_time and tag: return (key, value), db_expire, db_tag @@ -1566,31 +1569,34 @@ def peek(self, prefix=None, default=(None, None), side='front', default = default, None while True: - with self._transact(retry) as (sql, cleanup): - rows = sql(select, (min_key, max_key)).fetchall() + while True: + with self._transact(retry) as (sql, cleanup): + rows = sql(select, (min_key, max_key)).fetchall() - if not rows: - return default + if not rows: + return default - (rowid, key, db_expire, db_tag, mode, name, db_value), = rows + (rowid, key, db_expire, db_tag, mode, name, + db_value), = rows - if db_expire is not None and db_expire < time.time(): - sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) - cleanup(name) - else: - break + if db_expire is not None and 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 as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - return default - else: - raise - finally: - if name is not None: - self._disk.remove(name) + try: + value = self._disk.fetch(mode, name, db_value, False) + except IOError as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + continue + else: + raise + finally: + if name is not None: + self._disk.remove(name) + break if expire_time and tag: return (key, value), db_expire, db_tag @@ -1637,42 +1643,45 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): ) % order[last] while True: - with self._transact(retry) as (sql, cleanup): - rows = sql(select).fetchall() + while True: + with self._transact(retry) as (sql, cleanup): + rows = sql(select).fetchall() - if not rows: - raise KeyError('dictionary is empty') + if not rows: + raise KeyError('dictionary is empty') - (rowid, key, raw, db_expire, db_tag, mode, name, value), = rows + (rowid, db_key, raw, db_expire, db_tag, mode, name, + db_value), = rows - if db_expire is not None and db_expire < time.time(): - sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) - cleanup(name) - else: - break + if db_expire is not None and db_expire < time.time(): + sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) + cleanup(name) + else: + break - py_key = self._disk.get(key, raw) + key = self._disk.get(db_key, raw) - try: - py_value = self._disk.fetch(mode, name, value, False) - except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - return default - else: - raise - finally: - if name is not None: - self._disk.remove(name) + try: + value = self._disk.fetch(mode, name, db_value, False) + except IOError as error: + if error.errno == errno.ENOENT: + # Key was deleted before we could retrieve result. + continue + else: + raise + finally: + if name is not None: + self._disk.remove(name) + break if expire_time and tag: - return (py_key, py_value), db_expire, db_tag + return (key, value), db_expire, db_tag elif expire_time: - return (py_key, py_value), db_expire + return (key, value), db_expire elif tag: - return (py_key, py_value), db_tag + return (key, value), db_tag else: - return py_key, py_value + return key, value memoize = memoize diff --git a/diskcache/fanout.py b/diskcache/fanout.py index c3bb14c..886e27f 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -9,7 +9,7 @@ try: from functools import reduce except ImportError: - reduce + reduce # pylint: disable=pointless-statement from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout from .memo import memoize diff --git a/diskcache/persistent.py b/diskcache/persistent.py index d564098..a8bbffe 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -7,7 +7,6 @@ from collections import OrderedDict from contextlib import contextmanager -from itertools import islice from shutil import rmtree from tempfile import mkdtemp From ac07b05e7e70ba95a357abe560296afd28a7799c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 26 Mar 2019 20:20:55 -0700 Subject: [PATCH 303/550] Disable no-name-in-module for pylint --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index 2c423f5..660bc9d 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -24,7 +24,7 @@ import cPickle as pickle # pylint: disable=import-error # ISSUE #25 Fix for http://bugs.python.org/issue10211 from cStringIO import StringIO as BytesIO # pylint: disable=import-error - from thread import get_ident # pylint: disable=import-error + from thread import get_ident # pylint: disable=import-error,no-name-in-module TextType = unicode # pylint: disable=invalid-name,undefined-variable BytesType = str INT_TYPES = int, long # pylint: disable=undefined-variable From 987184f48bf3cee0fe29d7698a80eaabaf1a61f3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 26 Mar 2019 21:12:33 -0700 Subject: [PATCH 304/550] Use the full 80 characters --- diskcache/fanout.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 886e27f..e588766 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -265,8 +265,7 @@ def __contains__(self, key): return key in shard - def pop(self, key, default=None, expire_time=False, tag=False, - retry=False): + def pop(self, key, default=None, expire_time=False, tag=False, retry=False): """Remove corresponding item for `key` from cache and return value. If `key` is missing, return `default`. From 2b22f38de9c8a74b2bd293baa4ce381c64651513 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 26 Mar 2019 21:15:11 -0700 Subject: [PATCH 305/550] Change iterators to generator, not list --- diskcache/fanout.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index e588766..97bbc38 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -493,14 +493,14 @@ def __setstate__(self, state): def __iter__(self): "Iterate keys in cache including expired items." - iterators = [iter(shard) for shard in self._shards] + iterators = (iter(shard) for shard in self._shards) return it.chain.from_iterable(iterators) def __reversed__(self): "Reverse iterate keys in cache including expired items." - iterators = [reversed(shard) for shard in self._shards] - return it.chain.from_iterable(reversed(iterators)) + iterators = (reversed(shard) for shard in reversed(self._shards)) + return it.chain.from_iterable(iterators) def __len__(self): From 7c5d398e26f4c84e752da17bc67a1b35efe86ba4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 26 Mar 2019 21:15:44 -0700 Subject: [PATCH 306/550] Add delay before expire --- docs/tutorial.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 52a95a6..8b05c0d 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -201,6 +201,8 @@ Another four methods remove items from the cache. 10 >>> list(cache) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> import time + >>> time.sleep(1) >>> cache.expire() 10 From 6f7db0a33f7c08c319f0126eadf8d00f9156b895 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 26 Mar 2019 22:05:27 -0700 Subject: [PATCH 307/550] Remove cache.check() from doctests --- docs/tutorial.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 8b05c0d..c168cf7 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -308,13 +308,8 @@ incur an extra overhead on cache lookups. Increment and decrement operations are not counted in cache statistics. The third is :meth:`check ` which verifies cache -consistency. It can also fix inconsistencies and reclaim unused space. - - >>> warning, = cache.check(fix=True) - >>> type(warning.message) - - -The return value is a list of warnings. +consistency. It can also fix inconsistencies and reclaim unused space. The +return value is a list of warnings. .. _tutorial-fanoutcache: From 9820c701b4addd67d2d721371e8c82de8e92009b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 27 Mar 2019 09:28:51 -0700 Subject: [PATCH 308/550] Add Cache.touch, FanoutCache.touch, and DjangoCache.touch with tests --- diskcache/core.py | 37 +++++++++++++++++++++++++++++++++++++ diskcache/djangocache.py | 18 ++++++++++++++++++ diskcache/fanout.py | 21 +++++++++++++++++++++ tests/test_core.py | 7 +++++++ tests/test_djangocache.py | 17 +++++++++++++++++ tests/test_fanout.py | 7 +++++++ 6 files changed, 107 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index 660bc9d..70c9f05 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -863,6 +863,43 @@ def _cull(self, now, sql, cleanup, limit=None): 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: True if key was touched + :raises Timeout: if database timeout occurs + + """ + now = time.time() + db_key, raw = self._disk.put(key) + expire_time = None if expire is None else now + expire + + with self._transact(retry) as (sql, cleanup): + 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 is None or old_expire_time > now: + sql('UPDATE Cache SET expire_time = ? WHERE rowid = ?', + (expire_time, rowid), + ) + return True + + return False + + def add(self, key, value, expire=None, read=False, tag=None, retry=False): """Add `key` and `value` item to cache. diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 108247a..e2c223f 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -139,6 +139,24 @@ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, return self._cache.set(key, value, timeout, read, tag, retry) + def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None, retry=True): + """Touch a key in the cache. If timeout is given, that timeout will be + used for the key; otherwise the default cache timeout will be used. + + :param key: key for item + :param float timeout: seconds until the item expires + (default 300 seconds) + :param int version: key version number (default None, cache parameter) + :param bool retry: retry if database timeout occurs (default True) + :return: True if key was touched + + """ + # pylint: disable=arguments-differ + key = self.make_key(key, version=version) + timeout = self.get_backend_timeout(timeout=timeout) + return self._cache.touch(key, timeout, retry) + + def pop(self, key, default=None, version=None, expire_time=False, tag=False, retry=True): """Remove corresponding item for `key` from cache and return value. diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 97bbc38..7691c8b 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -99,6 +99,27 @@ def __setitem__(self, key, value): shard[key] = value + def touch(self, key, expire=None, retry=False): + """Touch `key` in cache and update `expire` time. + + If database timeout occurs then fails silently unless `retry` is set to + `True` (default `False`). + + :param key: key for item + :param float expire: seconds until the key expires + (default None, no expiry) + :param bool retry: retry if database timeout occurs (default False) + :return: True if key was touched + + """ + index = self._hash(key) % self._count + shard = self._shards[index] + try: + return shard.touch(key, expire, retry) + except Timeout: + return False + + def add(self, key, value, expire=None, read=False, tag=None, retry=False): """Add `key` and `value` item to cache. diff --git a/tests/test_core.py b/tests/test_core.py index f096485..422a065 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -785,6 +785,13 @@ def test_contains(cache): assert 0 in cache +def test_touch(cache): + assert cache.set(0, None, expire=60) + assert cache.touch(0, expire=None) + assert cache.touch(0, expire=0) + assert not cache.touch(0) + + def test_add(cache): assert cache.add(1, 1) assert cache.get(1) == 1 diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index b8e554f..113156c 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -286,6 +286,23 @@ def test_cache_read_for_model_instance_with_deferred(self): # We only want the default expensive calculation run on creation and set self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read) + def test_touch(self): + # cache.touch() updates the timeout. + cache.set('expire1', 'very quickly', timeout=1) + self.assertTrue(cache.touch('expire1', timeout=2)) + time.sleep(1) + self.assertTrue(cache.has_key('expire1')) + time.sleep(2) + self.assertFalse(cache.has_key('expire1')) + + # cache.touch() works without the timeout argument. + cache.set('expire1', 'very quickly', timeout=1) + self.assertTrue(cache.touch('expire1')) + time.sleep(2) + self.assertTrue(cache.has_key('expire1')) + + self.assertFalse(cache.touch('nonexistent')) + def test_expiration(self): # Cache values can be set to expire cache.set('expire1', 'very quickly', 1) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 77f7384..65cb1fb 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -110,6 +110,13 @@ def test_set_timeout(cache): assert not cache.set(0, 0) +def test_touch(cache): + assert cache.set(0, None, expire=60) + assert cache.touch(0, expire=None) + assert cache.touch(0, expire=0) + assert not cache.touch(0) + + def test_add(cache): assert cache.add(0, 0) assert not cache.add(0, 1) From 85be791009bdb0ed730fba5fe8822d495c4f2ba7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 27 Mar 2019 11:19:20 -0700 Subject: [PATCH 309/550] Provide access to FanoutCache used by DjangoCache --- diskcache/djangocache.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index e2c223f..4c75d47 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -31,6 +31,12 @@ def __init__(self, directory, params): self._cache = FanoutCache(directory, shards, timeout, **options) + @property + def cache(self): + "FanoutCache used by DjangoCache." + return self._cache + + @property def directory(self): """Cache directory.""" From 2649ac9054e5761a487e8488640c091ce14b5bb2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 27 Mar 2019 13:44:24 -0700 Subject: [PATCH 310/550] Pylint fixes --- diskcache/core.py | 2 +- diskcache/recipes.py | 181 ++++++++++++++++++++++++++++++++++++++++++ tests/test_doctest.py | 9 +++ 3 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 diskcache/recipes.py diff --git a/diskcache/core.py b/diskcache/core.py index 70c9f05..7c7a7a7 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -881,7 +881,7 @@ def touch(self, key, expire=None, retry=False): db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire - with self._transact(retry) as (sql, cleanup): + with self._transact(retry) as (sql, _): rows = sql( 'SELECT rowid, expire_time FROM Cache' ' WHERE key = ? AND raw = ?', diff --git a/diskcache/recipes.py b/diskcache/recipes.py new file mode 100644 index 0000000..939786c --- /dev/null +++ b/diskcache/recipes.py @@ -0,0 +1,181 @@ +"""Cache Recipes + + +""" + +import os +import threading +import time + + +class Averager(object): + """Recipe for calculating a running average. + + Sometimes known as "online statistics," the running average maintains the + total and count. The average can then be calculated at any time. + + >>> import diskcache + >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> ave = Averager(cache, 'latency') + >>> ave.add(0.080) + >>> ave.add(0.120) + >>> ave.get() + 0.1 + >>> ave.add(0.160) + >>> ave.get() + 0.12 + + """ + def __init__(self, cache, key): + self._cache = cache + self._key = key + + def add(self, value): + "Add `value` to average." + with self._cache.transact(): + total, count = self._cache.get(self._key, default=(0.0, 0)) + total += value + count += 1 + self._cache.set(self._key, (total, count)) + + def get(self): + "Get current average." + total, count = self._cache.get(self._key, default=(0.0, 0), retry=True) + return 0.0 if count == 0 else total / count + + def pop(self): + "Return current average and reset average to 0.0." + total, count = self._cache.pop(self._key, default=(0.0, 0), retry=True) + return 0.0 if count == 0 else total / count + + +class Lock(object): + """Recipe for cross-process and cross-thread lock. + + >>> import diskcache + >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> lock = Lock(cache, 'report-123') + >>> lock.acquire() + >>> lock.release() + >>> with lock: + ... pass + + """ + def __init__(self, cache, key): + self._cache = cache + self._key = key + + def acquire(self): + "Acquire lock using spin-lock algorithm." + while True: + if self._cache.add(self._key, None, retry=True): + return + time.sleep(0.001) + + def release(self): + "Release lock by deleting key." + self._cache.delete(self._key, retry=True) + + def __enter__(self): + self.acquire() + + def __exit__(self, *exc_info): + self.release() + + +class RLock(object): + """Recipe for cross-process and cross-thread re-entrant lock. + + >>> import diskcache + >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> rlock = RLock(cache, 'user-123') + >>> rlock.acquire() + >>> rlock.acquire() + >>> rlock.release() + >>> rlock.release() + >>> with rlock: + ... pass + >>> rlock.release() + Traceback (most recent call last): + ... + AssertionError: cannot release un-acquired lock + + """ + def __init__(self, cache, key): + self._cache = cache + self._key = key + pid = os.getpid() + tid = threading.get_ident() + self._value = '{}-{}'.format(pid, tid) + + def acquire(self): + "Acquire lock by incrementing count using spin-lock algorithm." + while True: + with self._cache.transact(): + value, count = self._cache.get(self._key, default=(None, 0)) + if self._value == value or count == 0: + self._cache.set(self._key, (self._value, count + 1)) + return + time.sleep(0.001) + + def release(self): + "Release lock by decrementing count." + with self._cache.transact(): + value, count = self._cache.get(self._key, default=(None, 0)) + is_owned = self._value == value and count > 0 + assert is_owned, 'cannot release un-acquired lock' + self._cache.set(self._key, (value, count - 1)) + + def __enter__(self): + self.acquire() + + def __exit__(self, *exc_info): + self.release() + + +class BoundedSemaphore(object): + """Recipe for cross-process and cross-thread bounded semaphore. + + >>> import diskcache + >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> semaphore = BoundedSemaphore(cache, 'max-connections', value=2) + >>> semaphore.acquire() + >>> semaphore.acquire() + >>> semaphore.release() + >>> with semaphore: + ... pass + >>> semaphore.release() + >>> semaphore.release() + Traceback (most recent call last): + ... + AssertionError: cannot release un-acquired semaphore + + """ + def __init__(self, cache, key, value=1): + self._cache = cache + self._key = key + self._value = value + + def acquire(self): + "Acquire semaphore by decrementing value using spin-lock algorithm." + while True: + with self._cache.transact(): + value = self._cache.get(self._key, default=self._value) + if value > 0: + self._cache.set(self._key, value - 1) + return + time.sleep(0.001) + + def release(self): + "Release semaphore by incrementing value." + with self._cache.transact(): + value = self._cache.get(self._key, default=self._value) + assert self._value > value, 'cannot release un-acquired semaphore' + value += 1 + self._cache.set(self._key, value) + + def __enter__(self): + self.acquire() + + def __exit__(self, *exc_info): + self.release() diff --git a/tests/test_doctest.py b/tests/test_doctest.py index b523e46..d90d548 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -7,6 +7,7 @@ import diskcache.fanout import diskcache.memo import diskcache.persistent +import diskcache.recipes def rmdir(directory): @@ -53,3 +54,11 @@ def test_tutorial(): rmdir('/tmp/mydir') failures, _ = doctest.testfile('../docs/tutorial.rst') assert failures == 0 + + +def test_recipes(): + if sys.hexversion < 0x03000000: + return + rmdir('/tmp/diskcache') + failures, _ = doctest.testmod(diskcache.recipes) + assert failures == 0 From 303099dcbe0dce170bce943f744c238b52d520b5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 28 Mar 2019 17:21:48 -0700 Subject: [PATCH 311/550] Add throttle and barrier decorators to recipes --- diskcache/recipes.py | 103 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 939786c..25445a2 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -3,6 +3,7 @@ """ +import functools import os import threading import time @@ -67,9 +68,7 @@ def __init__(self, cache, key): def acquire(self): "Acquire lock using spin-lock algorithm." - while True: - if self._cache.add(self._key, None, retry=True): - return + while not self._cache.add(self._key, None, retry=True): time.sleep(0.001) def release(self): @@ -92,10 +91,10 @@ class RLock(object): >>> rlock.acquire() >>> rlock.acquire() >>> rlock.release() - >>> rlock.release() >>> with rlock: ... pass >>> rlock.release() + >>> rlock.release() Traceback (most recent call last): ... AssertionError: cannot release un-acquired lock @@ -179,3 +178,99 @@ def __enter__(self): def __exit__(self, *exc_info): self.release() + + +def throttle(cache, count, seconds, name=None, time=time.time, + sleep=time.sleep): + """Decorator to throttle calls to function. + + >>> import diskcache, time + >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> @throttle(cache, 1, 1) + ... def int_time(): + ... return int(time.time()) + >>> times = [int_time() for _ in range(4)] + >>> [times[i] - times[i - 1] for i in range(1, 4)] + [1, 1, 1] + + """ + def decorator(func): + rate = count / float(seconds) + + if name is None: + try: + key = func.__qualname__ + except AttributeError: + key = func.__name__ + + key = func.__module__ + '.' + key + else: + key = name + + cache.set(key, (time(), count), retry=True) + + @functools.wraps(func) + def wrapper(*args, **kwargs): + while True: + with cache.transact(): + last, tally = cache.get(key, retry=True) + now = time() + tally += (now - last) * rate + delay = 0 + + if tally > count: + cache.set(key, (now, count - 1), retry=True) + elif tally >= 1: + cache.set(key, (now, tally - 1), retry=True) + else: + delay = (1 - tally) / rate + + if delay: + sleep(delay) + else: + break + + return func(*args, **kwargs) + + return wrapper + + return decorator + + +def barrier(cache, lock_factory, name=None): + """Barrier to calling decorated function. + + >>> import diskcache, time + >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> @barrier(cache, Lock) + ... def work(num): + ... time.sleep(1) + ... return int(time.time()) + >>> from concurrent.futures import ThreadPoolExecutor + >>> with ThreadPoolExecutor() as executor: + ... times = sorted(executor.map(work, range(4))) + >>> [times[i] - times[i - 1] for i in range(1, 4)] + [1, 1, 1] + + """ + def decorator(func): + if name is None: + try: + key = func.__qualname__ + except AttributeError: + key = func.__name__ + + key = func.__module__ + '.' + key + else: + key = name + + lock = lock_factory(cache, key) + + @functools.wraps(func) + def wrapper(*args, **kwargs): + with lock: + return func(*args, **kwargs) + + return wrapper + + return decorator From 2e91dc6e1d1dbdc0985ece3241df1889bb788954 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 29 Mar 2019 08:57:12 -0700 Subject: [PATCH 312/550] Add doctest for Disk Cache recipes: stampede-lock decorator recipe --- diskcache/recipes.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 25445a2..f3f01fe 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -1,5 +1,22 @@ -"""Cache Recipes - +"""Disk Cache Recipes + +>>> import diskcache as dc, time +>>> cache = dc.Cache('/tmp/diskcache/example') +>>> @dc.memoize(cache) +... @dc.barrier(cache, dc.Lock) +... @dc.memoize(cache) +... def work(num): +... time.sleep(1) +... return -num +>>> from concurrent.futures import ThreadPoolExecutor +>>> with ThreadPoolExecutor() as executor: +... start = time.time() +... times = list(executor.map(work, range(5))) +... end = time.time() +>>> times +[0, -1, -2, -3, -4] +>>> int(end - start) +5 """ From 1277747b0c5ad80d5132b08f4ce2987932cd4ce6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 29 Mar 2019 09:01:46 -0700 Subject: [PATCH 313/550] Add recipes to top-level package exports --- diskcache/__init__.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 26532cc..17cbeb3 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -3,21 +3,30 @@ from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning, Timeout from .core import DEFAULT_SETTINGS, ENOVAL, EVICTION_POLICY, UNKNOWN from .fanout import FanoutCache +from .memo import memoize from .persistent import Deque, Index +from .recipes import Averager, Lock, RLock, BoundedSemaphore, throttle, barrier __all__ = [ + 'Averager', + 'BoundedSemaphore', 'Cache', - 'Disk', - 'UnknownFileWarning', - 'EmptyDirWarning', - 'Timeout', 'DEFAULT_SETTINGS', + 'Deque', + 'Disk', 'ENOVAL', 'EVICTION_POLICY', - 'UNKNOWN', + 'EmptyDirWarning', 'FanoutCache', - 'Deque', 'Index', + 'Lock', + 'RLock', + 'Timeout', + 'UNKNOWN', + 'UnknownFileWarning', + 'barrier', + 'memoize', + 'throttle', ] try: From 15bbb1d4a4de512da760a695db819ab4dd7afe7f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 29 Mar 2019 09:08:06 -0700 Subject: [PATCH 314/550] Improve recipes doctest to show caching --- diskcache/recipes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index f3f01fe..e9f894f 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -17,6 +17,14 @@ [0, -1, -2, -3, -4] >>> int(end - start) 5 +>>> with ThreadPoolExecutor() as executor: +... start = time.time() +... times = list(executor.map(work, range(5))) +... end = time.time() +>>> times +[0, -1, -2, -3, -4] +>>> int(end - start) +0 """ From 8ef124ff6547a6a2d210573d19fb8d18df974ca9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 29 Mar 2019 09:10:15 -0700 Subject: [PATCH 315/550] Add expire and tag parameters to recipes --- diskcache/recipes.py | 60 +++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index e9f894f..83505df 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -52,9 +52,11 @@ class Averager(object): 0.12 """ - def __init__(self, cache, key): + def __init__(self, cache, key, expire=None, tag=None): self._cache = cache self._key = key + self._expire = expire + self._tag = tag def add(self, value): "Add `value` to average." @@ -62,7 +64,9 @@ def add(self, value): total, count = self._cache.get(self._key, default=(0.0, 0)) total += value count += 1 - self._cache.set(self._key, (total, count)) + self._cache.set( + self._key, (total, count), expire=self._expire, tag=self._tag, + ) def get(self): "Get current average." @@ -87,13 +91,20 @@ class Lock(object): ... pass """ - def __init__(self, cache, key): + def __init__(self, cache, key, expire=None, tag=None): self._cache = cache self._key = key + self._expire = expire + self._tag = tag def acquire(self): "Acquire lock using spin-lock algorithm." - while not self._cache.add(self._key, None, retry=True): + while True: + added = self._cache.add( + self._key, None, expire=self._expire, tag=self._tag, retry=True, + ) + if added: + break time.sleep(0.001) def release(self): @@ -125,9 +136,11 @@ class RLock(object): AssertionError: cannot release un-acquired lock """ - def __init__(self, cache, key): + def __init__(self, cache, key, expire=None, tag=None): self._cache = cache self._key = key + self._expire = expire + self._tag = tag pid = os.getpid() tid = threading.get_ident() self._value = '{}-{}'.format(pid, tid) @@ -138,7 +151,10 @@ def acquire(self): with self._cache.transact(): value, count = self._cache.get(self._key, default=(None, 0)) if self._value == value or count == 0: - self._cache.set(self._key, (self._value, count + 1)) + self._cache.set( + self._key, (self._value, count + 1), + expire=self._expire, tag=self._tag, + ) return time.sleep(0.001) @@ -148,7 +164,10 @@ def release(self): value, count = self._cache.get(self._key, default=(None, 0)) is_owned = self._value == value and count > 0 assert is_owned, 'cannot release un-acquired lock' - self._cache.set(self._key, (value, count - 1)) + self._cache.set( + self._key, (value, count - 1), expire=self._expire, + tag=self._tag, + ) def __enter__(self): self.acquire() @@ -175,10 +194,12 @@ class BoundedSemaphore(object): AssertionError: cannot release un-acquired semaphore """ - def __init__(self, cache, key, value=1): + def __init__(self, cache, key, value=1, expire=None, tag=None): self._cache = cache self._key = key self._value = value + self._expire = expire + self._tag = tag def acquire(self): "Acquire semaphore by decrementing value using spin-lock algorithm." @@ -186,7 +207,10 @@ def acquire(self): with self._cache.transact(): value = self._cache.get(self._key, default=self._value) if value > 0: - self._cache.set(self._key, value - 1) + self._cache.set( + self._key, value - 1, expire=self._expire, + tag=self._tag, + ) return time.sleep(0.001) @@ -196,7 +220,9 @@ def release(self): value = self._cache.get(self._key, default=self._value) assert self._value > value, 'cannot release un-acquired semaphore' value += 1 - self._cache.set(self._key, value) + self._cache.set( + self._key, value, expire=self._expire, tag=self._tag, + ) def __enter__(self): self.acquire() @@ -205,8 +231,8 @@ def __exit__(self, *exc_info): self.release() -def throttle(cache, count, seconds, name=None, time=time.time, - sleep=time.sleep): +def throttle(cache, count, seconds, name=None, expire=None, tag=None, + time=time.time, sleep=time.sleep): """Decorator to throttle calls to function. >>> import diskcache, time @@ -232,7 +258,7 @@ def decorator(func): else: key = name - cache.set(key, (time(), count), retry=True) + cache.set(key, (time(), count), expire=expire, tag=tag, retry=True) @functools.wraps(func) def wrapper(*args, **kwargs): @@ -244,9 +270,9 @@ def wrapper(*args, **kwargs): delay = 0 if tally > count: - cache.set(key, (now, count - 1), retry=True) + cache.set(key, (now, count - 1), expire, retry=True) elif tally >= 1: - cache.set(key, (now, tally - 1), retry=True) + cache.set(key, (now, tally - 1), expire, retry=True) else: delay = (1 - tally) / rate @@ -262,7 +288,7 @@ def wrapper(*args, **kwargs): return decorator -def barrier(cache, lock_factory, name=None): +def barrier(cache, lock_factory, name=None, expire=None, tag=None): """Barrier to calling decorated function. >>> import diskcache, time @@ -289,7 +315,7 @@ def decorator(func): else: key = name - lock = lock_factory(cache, key) + lock = lock_factory(cache, key, expire=expire, tag=tag) @functools.wraps(func) def wrapper(*args, **kwargs): From 1d0ee0f80a4da1ce50e0de6ff8545be232e48c86 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 29 Mar 2019 09:46:11 -0700 Subject: [PATCH 316/550] Refactor memoize to use full_name and args_to_key --- diskcache/djangocache.py | 38 ++++----------------- diskcache/memo.py | 73 +++++++++++++++++++++++----------------- diskcache/recipes.py | 13 ++----- 3 files changed, 52 insertions(+), 72 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 4c75d47..16c2414 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -10,8 +10,7 @@ DEFAULT_TIMEOUT = 300 from .fanout import FanoutCache - -MARK = object() +from .memo import MARK, args_to_key, full_name class DjangoCache(BaseCache): @@ -390,43 +389,18 @@ def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, if callable(name): raise TypeError('name cannot be callable') - def decorator(function): + def decorator(func): "Decorator created by memoize call for callable." - if name is None: - try: - reference = function.__qualname__ - except AttributeError: - reference = function.__name__ - - reference = function.__module__ + reference - else: - reference = name - - reference = (reference,) + base = (full_name(func),) if name is None else (name,) - @wraps(function) + @wraps(func) def wrapper(*args, **kwargs): "Wrapper for callable to cache arguments and return values." - - key = reference + args - - if kwargs: - key += (MARK,) - 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) - + key = args_to_key(base, args, kwargs, typed) result = self.get(key, MARK, version, retry=True) if result is MARK: - result = function(*args, **kwargs) + result = func(*args, **kwargs) self.set(key, result, timeout, version, tag=tag, retry=True) return result diff --git a/diskcache/memo.py b/diskcache/memo.py index 20c6404..76d1e5a 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -6,6 +6,44 @@ MARK = object() + +def full_name(func): + "Return full name of `func` by adding the module and function name." + try: + name = func.__qualname__ + except AttributeError: + name = func.__name__ + return func.__module__ + '.' + name + + +def args_to_key(base, args, kwargs, typed): + """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 + :return: cache key tuple + + """ + key = base + args + + if kwargs: + key += (MARK,) + 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) + + return key + + def memoize(cache, name=None, typed=False, expire=None, tag=None): """Memoizing cache decorator. @@ -60,43 +98,18 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): if callable(name): raise TypeError('name cannot be callable') - def decorator(function): + def decorator(func): "Decorator created by memoize call for callable." - if name is None: - try: - reference = function.__qualname__ - except AttributeError: - reference = function.__name__ - - reference = function.__module__ + reference - else: - reference = name + base = (full_name(func),) if name is None else (name,) - reference = (reference,) - - @wraps(function) + @wraps(func) def wrapper(*args, **kwargs): "Wrapper for callable to cache arguments and return values." - - key = reference + args - - if kwargs: - key += (MARK,) - 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) - + key = args_to_key(base, args, kwargs, typed) result = cache.get(key, default=MARK, retry=True) if result is MARK: - result = function(*args, **kwargs) + result = func(*args, **kwargs) cache.set(key, result, expire=expire, tag=tag, retry=True) return result diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 83505df..1dfa965 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -33,6 +33,8 @@ import threading import time +from .memo import full_name + class Averager(object): """Recipe for calculating a running average. @@ -305,16 +307,7 @@ def barrier(cache, lock_factory, name=None, expire=None, tag=None): """ def decorator(func): - if name is None: - try: - key = func.__qualname__ - except AttributeError: - key = func.__name__ - - key = func.__module__ + '.' + key - else: - key = name - + key = full_name(func) if name is None else name lock = lock_factory(cache, key, expire=expire, tag=tag) @functools.wraps(func) From bf7196dbc6d0a5da20f7afc0be74b8950dac8e24 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 29 Mar 2019 09:52:45 -0700 Subject: [PATCH 317/550] Fixes and improvements for pylint --- .pylintrc | 3 +-- diskcache/recipes.py | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pylintrc b/.pylintrc index 3ce5d57..3314897 100644 --- a/.pylintrc +++ b/.pylintrc @@ -145,7 +145,6 @@ disable=print-statement, inconsistent-return-statements, ungrouped-imports, not-callable, - duplicate-code # PyCQA/pylint #1055 # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -361,7 +360,7 @@ ignore-docstrings=yes ignore-imports=no # Minimum lines number of a similarity. -min-similarity-lines=4 +min-similarity-lines=9 [BASIC] diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 1dfa965..d729cf5 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -234,7 +234,7 @@ def __exit__(self, *exc_info): def throttle(cache, count, seconds, name=None, expire=None, tag=None, - time=time.time, sleep=time.sleep): + time_func=time.time, sleep_func=time.sleep): """Decorator to throttle calls to function. >>> import diskcache, time @@ -260,14 +260,15 @@ def decorator(func): else: key = name - cache.set(key, (time(), count), expire=expire, tag=tag, retry=True) + now = time_func() + cache.set(key, (now, count), expire=expire, tag=tag, retry=True) @functools.wraps(func) def wrapper(*args, **kwargs): while True: with cache.transact(): last, tally = cache.get(key, retry=True) - now = time() + now = time_func() tally += (now - last) * rate delay = 0 @@ -279,7 +280,7 @@ def wrapper(*args, **kwargs): delay = (1 - tally) / rate if delay: - sleep(delay) + sleep_func(delay) else: break From a902c663837098cfdbebf52d1625a82d93feb41d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 29 Mar 2019 10:34:07 -0700 Subject: [PATCH 318/550] Add sub-cache method to DjangoCache and FanoutCache --- diskcache/djangocache.py | 16 ++++++++++------ diskcache/fanout.py | 33 +++++++++++++++++++++++++++++++++ tests/test_djangocache.py | 5 +++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 16c2414..eb28e18 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -30,18 +30,22 @@ def __init__(self, directory, params): self._cache = FanoutCache(directory, shards, timeout, **options) - @property - def cache(self): - "FanoutCache used by DjangoCache." - return self._cache - - @property def directory(self): """Cache directory.""" return self._directory + def cache(self, name): + """Return Cache with given `name` in subdirectory. + + :param str name: subdirectory name for Cache + :return: Cache with given name + + """ + return self._cache.cache(name) + + def deque(self, name): """Return Deque with given `name` in subdirectory. diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 7691c8b..1e48947 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -44,6 +44,7 @@ def __init__(self, directory, shards=8, timeout=0.010, disk=Disk, for num in range(shards) ) self._hash = self._shards[0].disk.hash + self._caches = {} self._deques = {} self._indexes = {} @@ -492,6 +493,7 @@ def close(self): "Close database connection." for shard in self._shards: shard.close() + self._caches.clear() self._deques.clear() self._indexes.clear() @@ -559,6 +561,37 @@ def reset(self, key, value=ENOVAL): return result + def cache(self, name): + """Return Cache with given `name` in subdirectory. + + >>> fanout_cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> cache = fanout_cache.cache('test') + >>> _ = cache.clear() + >>> cache.set('abc', 123) + True + >>> cache.get('abc') + 123 + >>> len(cache) + 1 + >>> cache.delete('abc') + True + + :param str name: subdirectory name for Cache + :return: Cache with given name + + """ + _caches = self._caches + + try: + return _caches[name] + except KeyError: + parts = name.split('/') + directory = op.join(self._directory, 'cache', *parts) + temp = Cache(directory=directory) + _caches[name] = temp + return temp + + def deque(self, name): """Return Deque with given `name` in subdirectory. diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 113156c..45f94a7 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -974,6 +974,11 @@ def test_pickle(self): for key in letters: self.assertEqual(other.get(key), cache.get(key)) + def test_cache(self): + subcache = cache.cache('test') + directory = os.path.join(cache.directory, 'cache', 'test') + self.assertEqual(subcache.directory, directory) + def test_deque(self): deque = cache.deque('test') directory = os.path.join(cache.directory, 'deque', 'test') From a1619c0ca322f7d0ca8583d1621d0412aa838e50 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 2 Apr 2019 20:24:36 -0700 Subject: [PATCH 319/550] Add tests for coverage --- diskcache/core.py | 3 - diskcache/memo.py | 2 + diskcache/persistent.py | 4 ++ tests/test_core.py | 147 ++++++++++++++++++++++++++++++++++++++ tests/test_djangocache.py | 5 ++ tests/test_fanout.py | 26 +++++++ 6 files changed, 184 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 7c7a7a7..dab3806 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1706,9 +1706,6 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): continue else: raise - finally: - if name is not None: - self._disk.remove(name) break if expire_time and tag: diff --git a/diskcache/memo.py b/diskcache/memo.py index 76d1e5a..9f85738 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -10,6 +10,8 @@ def full_name(func): "Return full name of `func` by adding the module and function name." try: + # The __qualname__ attribute is only available in Python 3.3 and later. + # GrantJ 2019-03-29 Remove after support for Python 2 is dropped. name = func.__qualname__ except AttributeError: name = func.__name__ diff --git a/diskcache/persistent.py b/diskcache/persistent.py index a8bbffe..81b12f9 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -116,6 +116,8 @@ def fromcache(cls, cache, iterable=()): >>> cache = Cache('/tmp/diskcache/index') >>> _ = cache.clear() >>> deque = Deque.fromcache(cache, [5, 6, 7, 8]) + >>> deque.cache is cache + True >>> len(deque) 4 >>> 7 in deque @@ -736,6 +738,8 @@ def fromcache(cls, cache, *args, **kwargs): >>> cache = Cache('/tmp/diskcache/index') >>> _ = cache.clear() >>> index = Index.fromcache(cache, {'a': 1, 'b': 2, 'c': 3}) + >>> index.cache is cache + True >>> len(index) 3 >>> 'b' in index diff --git a/tests/test_core.py b/tests/test_core.py index 422a065..c440e0f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -935,8 +935,10 @@ def test_push_pull_prefix(cache): cache.push(value, prefix='key') for value in range(10): + key, peek_value = cache.peek(prefix='key') key, pull_value = cache.pull(prefix='key') assert key.startswith('key') + assert peek_value == value assert pull_value == value assert len(cache) == 0 @@ -949,6 +951,11 @@ def test_push_pull_extras(cache): assert len(cache) == 0 cache.push('test', expire=10) + (key, value), expire_time = cache.peek(expire_time=True) + assert key == 500000000000000 + assert value == 'test' + assert expire_time > time.time() + assert len(cache) == 1 (key, value), expire_time = cache.pull(expire_time=True) assert key == 500000000000000 assert value == 'test' @@ -956,6 +963,11 @@ def test_push_pull_extras(cache): assert len(cache) == 0 cache.push('test', tag='foo') + (key, value), tag = cache.peek(tag=True) + assert key == 500000000000000 + assert value == 'test' + assert tag == 'foo' + assert len(cache) == 1 (key, value), tag = cache.pull(tag=True) assert key == 500000000000000 assert value == 'test' @@ -963,6 +975,12 @@ def test_push_pull_extras(cache): assert len(cache) == 0 cache.push('test') + (key, value), expire_time, tag = cache.peek(expire_time=True, tag=True) + assert key == 500000000000000 + assert value == 'test' + assert expire_time is None + assert tag is None + assert len(cache) == 1 (key, value), expire_time, tag = cache.pull(expire_time=True, tag=True) assert key == 500000000000000 assert value == 'test' @@ -986,6 +1004,17 @@ def test_push_pull_expire(cache): assert len(cache.check()) == 0 +def test_push_peek_expire(cache): + cache.push(0, expire=0.1) + cache.push(0, expire=0.1) + cache.push(0, expire=0.1) + cache.push(1) + time.sleep(0.2) + assert cache.peek() == (500000000000003, 1) + assert len(cache) == 1 + assert len(cache.check()) == 0 + + def test_push_pull_large_value(cache): value = b'test' * (2 ** 20) cache.push(value) @@ -994,6 +1023,14 @@ def test_push_pull_large_value(cache): assert len(cache.check()) == 0 +def test_push_peek_large_value(cache): + value = b'test' * (2 ** 20) + cache.push(value) + assert cache.peek() == (500000000000000, value) + assert len(cache) == 1 + assert len(cache.check()) == 0 + + def test_pull_ioerror(cache): assert cache.push(0) == 500000000000000 @@ -1012,6 +1049,25 @@ def test_pull_ioerror(cache): assert cache.pull() == (None, None) +def test_peek_ioerror(cache): + assert cache.push(0) == 500000000000000 + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.ENOENT + fetch.side_effect = [io_error, 0] + + with mock.patch.object(cache, '_disk', disk): + _, value = cache.peek() + assert value == 0 + + def test_pull_ioerror_eacces(cache): assert cache.push(0) == 500000000000000 @@ -1031,6 +1087,97 @@ def test_pull_ioerror_eacces(cache): cache.pull() +def test_peek_ioerror_eacces(cache): + assert cache.push(0) == 500000000000000 + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.EACCES + fetch.side_effect = io_error + + with mock.patch.object(cache, '_disk', disk): + with pytest.raises(IOError): + cache.peek() + + +def test_peekitem_extras(cache): + with pytest.raises(KeyError): + cache.peekitem() + + assert cache.set('a', 0) + assert cache.set('b', 1) + assert cache.set('c', 2, expire=10, tag='foo') + assert cache.set('d', 3, expire=0.1) + assert cache.set('e', 4, expire=0.1) + + time.sleep(0.2) + + (key, value), expire_time, tag = cache.peekitem(expire_time=True, tag=True) + assert key == 'c' + assert value == 2 + assert expire_time > 0 + assert tag == 'foo' + + (key, value), expire_time = cache.peekitem(expire_time=True) + assert key == 'c' + assert value == 2 + assert expire_time > 0 + + (key, value), tag = cache.peekitem(tag=True) + assert key == 'c' + assert value == 2 + assert expire_time > 0 + assert tag == 'foo' + + +def test_peekitem_ioerror(cache): + assert cache.set('a', 0) + assert cache.set('b', 1) + assert cache.set('c', 2) + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.ENOENT + fetch.side_effect = [io_error, 2] + + with mock.patch.object(cache, '_disk', disk): + _, value = cache.peekitem() + assert value == 2 + + +def test_peekitem_ioerror_eacces(cache): + assert cache.set('a', 0) + assert cache.set('b', 1) + assert cache.set('c', 2) + + disk = mock.Mock() + put = mock.Mock() + fetch = mock.Mock() + + disk.put = put + put.side_effect = [(0, True)] + disk.fetch = fetch + io_error = IOError() + io_error.errno = errno.EACCES + fetch.side_effect = io_error + + with mock.patch.object(cache, '_disk', disk): + with pytest.raises(IOError): + cache.peekitem() + + def test_iterkeys(cache): assert list(cache.iterkeys()) == [] diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 45f94a7..ea163bc 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -990,6 +990,11 @@ def test_index(self): self.assertEqual(index.directory, directory) def test_memoize(self): + with self.assertRaises(TypeError): + @cache.memoize # <-- Missing parens! + def test(): + pass + count = 1000 def fibiter(num): diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 65cb1fb..89f3a58 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -117,6 +117,19 @@ def test_touch(cache): assert not cache.touch(0) +def test_touch_timeout(cache): + shards = mock.Mock() + shard = mock.Mock() + touch_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.touch = touch_func + touch_func.side_effect = dc.Timeout + + with mock.patch.object(cache, '_shards', shards): + assert not cache.touch(0) + + def test_add(cache): assert cache.add(0, 0) assert not cache.add(0, 1) @@ -189,6 +202,19 @@ def test_decr(cache): cache.decr('key', delta=2) == -2 +def test_decr_timeout(cache): + shards = mock.Mock() + shard = mock.Mock() + decr_func = mock.Mock() + + shards.__getitem__ = mock.Mock(side_effect=lambda key: shard) + shard.decr = decr_func + decr_func.side_effect = dc.Timeout + + with mock.patch.object(cache, '_shards', shards): + assert cache.decr('key', 1) is None + + def stress_incr(cache, limit): for _ in range(limit): cache.incr(b'key', retry=True) From cfff0ff316d02c1d34fd814d8cc42fc2122741d6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 3 Apr 2019 10:38:07 -0700 Subject: [PATCH 320/550] Add pytest-xdist and setup for parallel testing --- requirements.txt | 1 + tox.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index c982310..00f826c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ pytest pytest-cov pytest-django pytest-env +pytest-xdist sphinx tox twine diff --git a/tox.ini b/tox.ini index 080f92d..3f8de58 100644 --- a/tox.ini +++ b/tox.ini @@ -15,6 +15,7 @@ setenv = [pytest] addopts= + -n auto --ignore tests/benchmark_core.py --ignore tests/benchmark_djangocache.py --ignore tests/benchmark_glob.py From 645ddfc01b44e15004360997464791df93dbd75d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 3 Apr 2019 10:47:40 -0700 Subject: [PATCH 321/550] Update caches and tests for automatic directory creation --- diskcache/core.py | 26 +++---- diskcache/djangocache.py | 3 +- diskcache/fanout.py | 24 ++++--- diskcache/memo.py | 2 +- diskcache/persistent.py | 143 +++++++++++++-------------------------- diskcache/recipes.py | 14 ++-- docs/tutorial.rst | 36 +++++----- tests/test_core.py | 59 ++++++++-------- tests/test_deque.py | 7 +- tests/test_doctest.py | 15 ---- tests/test_fanout.py | 41 ++++++----- tests/test_index.py | 7 +- 12 files changed, 156 insertions(+), 221 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index dab3806..f86cf66 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -13,6 +13,7 @@ import sqlite3 import struct import sys +import tempfile import threading import time import warnings @@ -359,7 +360,7 @@ class EmptyDirWarning(UserWarning): class Cache(object): "Disk and file backed cache." # pylint: disable=bad-continuation - def __init__(self, directory, timeout=60, disk=Disk, **settings): + def __init__(self, directory=None, timeout=60, disk=Disk, **settings): """Initialize cache instance. :param str directory: cache directory @@ -373,6 +374,11 @@ def __init__(self, directory, timeout=60, disk=Disk, **settings): except (TypeError, AssertionError): raise ValueError('disk must subclass diskcache.Disk') + if directory is None: + directory = tempfile.mkdtemp(prefix='diskcache-') + directory = op.expanduser(directory) + directory = op.expandvars(directory) + self._directory = directory self._timeout = 0 # Manually handle retries during initialization. self._local = threading.local() @@ -621,8 +627,7 @@ def transact(self, retry=False): Raises :exc:`Timeout` error when database timeout occurs and `retry` is `False` (default). - >>> cache = Cache('/tmp/diskcache') - >>> _ = cache.clear() + >>> cache = Cache() >>> with cache.transact(): # Atomically increment two keys. ... _ = cache.incr('total', 123.4) ... _ = cache.incr('count', 1) @@ -1344,8 +1349,7 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, See also `Cache.pull`. - >>> cache = Cache('/tmp/diskcache') - >>> _ = cache.clear() + >>> cache = Cache() >>> print(cache.push('first value')) 500000000000000 >>> cache.get(500000000000000) @@ -1438,8 +1442,7 @@ def pull(self, prefix=None, default=(None, None), side='front', See also `Cache.push` and `Cache.get`. - >>> cache = Cache('/tmp/diskcache') - >>> _ = cache.clear() + >>> cache = Cache() >>> cache.pull() (None, None) >>> for letter in 'abc': @@ -1555,8 +1558,7 @@ def peek(self, prefix=None, default=(None, None), side='front', See also `Cache.pull` and `Cache.push`. - >>> cache = Cache('/tmp/diskcache') - >>> _ = cache.clear() + >>> cache = Cache() >>> for letter in 'abc': ... print(cache.push(letter)) 500000000000000 @@ -1654,8 +1656,7 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): Raises :exc:`Timeout` error when database timeout occurs and `retry` is `False` (default). - >>> cache = Cache('/tmp/diskcache') - >>> _ = cache.clear() + >>> cache = Cache() >>> for num, letter in enumerate('abc'): ... cache[letter] = num >>> cache.peekitem() @@ -2043,8 +2044,7 @@ def _select_delete(self, select, args, row_index=0, arg_index=0, def iterkeys(self, reverse=False): """Iterate Cache keys in database sort order. - >>> cache = Cache('/tmp/diskcache') - >>> _ = cache.clear() + >>> cache = Cache() >>> for key in [4, 1, 3, 0, 2]: ... cache[key] = key >>> list(cache.iterkeys()) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index eb28e18..d6022ba 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -26,14 +26,13 @@ def __init__(self, directory, params): shards = params.get('SHARDS', 8) timeout = params.get('DATABASE_TIMEOUT', 0.010) options = params.get('OPTIONS', {}) - self._directory = directory self._cache = FanoutCache(directory, shards, timeout, **options) @property def directory(self): """Cache directory.""" - return self._directory + return self._cache.directory def cache(self, name): diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 1e48947..e89f0f6 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -4,6 +4,7 @@ import operator import os.path as op import sqlite3 +import tempfile import time try: @@ -18,7 +19,7 @@ class FanoutCache(object): "Cache that shards keys and values." - def __init__(self, directory, shards=8, timeout=0.010, disk=Disk, + def __init__(self, directory=None, shards=8, timeout=0.010, disk=Disk, **settings): """Initialize cache instance. @@ -29,13 +30,19 @@ def __init__(self, directory, shards=8, timeout=0.010, disk=Disk, :param settings: any of `DEFAULT_SETTINGS` """ - self._directory = directory - self._count = shards + if directory is None: + directory = tempfile.mkdtemp(prefix='diskcache-') + directory = op.expanduser(directory) + directory = op.expandvars(directory) + default_size_limit = DEFAULT_SETTINGS['size_limit'] size_limit = settings.pop('size_limit', default_size_limit) / shards + + self._count = shards + self._directory = directory self._shards = tuple( Cache( - op.join(directory, '%03d' % num), + directory=op.join(directory, '%03d' % num), timeout=timeout, disk=disk, size_limit=size_limit, @@ -564,9 +571,8 @@ def reset(self, key, value=ENOVAL): def cache(self, name): """Return Cache with given `name` in subdirectory. - >>> fanout_cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> fanout_cache = FanoutCache() >>> cache = fanout_cache.cache('test') - >>> _ = cache.clear() >>> cache.set('abc', 123) True >>> cache.get('abc') @@ -595,9 +601,8 @@ def cache(self, name): def deque(self, name): """Return Deque with given `name` in subdirectory. - >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> cache = FanoutCache() >>> deque = cache.deque('test') - >>> deque.clear() >>> deque.extend('abc') >>> deque.popleft() 'a' @@ -625,9 +630,8 @@ def deque(self, name): def index(self, name): """Return Index with given `name` in subdirectory. - >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> cache = FanoutCache() >>> index = cache.index('test') - >>> index.clear() >>> index['abc'] = 123 >>> index['def'] = 456 >>> index['ghi'] = 789 diff --git a/diskcache/memo.py b/diskcache/memo.py index 9f85738..84cac88 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -65,7 +65,7 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): for rewrapping the function with a different cache. >>> from diskcache import FanoutCache - >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> cache = FanoutCache() >>> @cache.memoize(typed=True, expire=1, tag='fib') ... def fibonacci(number): ... if number == 0: diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 81b12f9..ab22e09 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -70,10 +70,7 @@ class Deque(Sequence): Items are serialized to disk. Deque may be initialized from directory path where items are stored. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque - Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque += range(5) >>> list(deque) [0, 1, 2, 3, 4] @@ -102,8 +99,6 @@ def __init__(self, iterable=(), directory=None): :param directory: deque directory (default None) """ - if directory is None: - directory = mkdtemp() self._cache = Cache(directory, eviction_policy='none') with self.transact(): self.extend(iterable) @@ -113,8 +108,7 @@ def __init__(self, iterable=(), directory=None): def fromcache(cls, cache, iterable=()): """Initialize deque using `cache`. - >>> cache = Cache('/tmp/diskcache/index') - >>> _ = cache.clear() + >>> cache = Cache() >>> deque = Deque.fromcache(cache, [5, 6, 7, 8]) >>> deque.cache is cache True @@ -189,8 +183,7 @@ def __getitem__(self, index): See also `Deque.peekleft` and `Deque.peek` for indexing deque at index ``0`` or ``-1``. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.extend('abcde') >>> deque[1] 'b' @@ -210,8 +203,7 @@ def __setitem__(self, index, value): Store `value` in deque at `index`. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.extend([None] * 3) >>> deque[0] = 'a' >>> deque[1] = 'b' @@ -233,8 +225,7 @@ def __delitem__(self, index): Delete item in deque at `index`. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.extend([None] * 3) >>> del deque[0] >>> del deque[1] @@ -309,8 +300,7 @@ def __reversed__(self): Return iterator of deque from back to front. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.extend('abcd') >>> iterator = reversed(deque) >>> next(iterator) @@ -339,8 +329,7 @@ def __setstate__(self, state): def append(self, value): """Add `value` to back of deque. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.append('a') >>> deque.append('b') >>> deque.append('c') @@ -356,8 +345,7 @@ def append(self, value): def appendleft(self, value): """Add `value` to front of deque. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.appendleft('a') >>> deque.appendleft('b') >>> deque.appendleft('c') @@ -380,8 +368,7 @@ def clear(self): def count(self, value): """Return number of occurrences of `value` in deque. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque += [num for num in range(1, 5) for _ in range(num)] >>> deque.count(0) 0 @@ -410,8 +397,7 @@ def extend(self, iterable): def extendleft(self, iterable): """Extend front side of deque with value from `iterable`. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.extendleft('abc') >>> list(deque) ['c', 'b', 'a'] @@ -430,8 +416,7 @@ def peek(self): If deque is empty then raise IndexError. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.peek() Traceback (most recent call last): ... @@ -458,8 +443,7 @@ def peekleft(self): If deque is empty then raise IndexError. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque.peekleft() Traceback (most recent call last): ... @@ -484,8 +468,7 @@ def pop(self): If deque is empty then raise IndexError. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque += 'ab' >>> deque.pop() 'b' @@ -510,8 +493,7 @@ def pop(self): def popleft(self): """Remove and return value at front of deque. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque += 'ab' >>> deque.popleft() 'a' @@ -536,8 +518,7 @@ def popleft(self): def remove(self, value): """Remove first occurrence of `value` in deque. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque += 'aab' >>> deque.remove('a') >>> list(deque) @@ -575,8 +556,7 @@ def remove(self, value): def reverse(self): """Reverse deque in place. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque += 'abc' >>> deque.reverse() >>> list(deque) @@ -601,8 +581,7 @@ def rotate(self, steps=1): If steps is negative then rotate left. - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque += range(5) >>> deque.rotate(2) >>> list(deque) @@ -661,8 +640,7 @@ def transact(self): Transactions may be nested and may not be shared between threads. >>> from diskcache import Deque - >>> deque = Deque(directory='/tmp/diskcache/deque') - >>> deque.clear() + >>> deque = Deque() >>> deque += range(5) >>> with deque.transact(): # Atomically rotate elements. ... value = deque.pop() @@ -687,10 +665,7 @@ class Index(MutableMapping): Hashing protocol is not used. Keys are looked up by their serialized format. See ``diskcache.Disk`` for details. - >>> index = Index('/tmp/diskcache/index') - >>> index - Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index['a'] 1 @@ -726,7 +701,7 @@ def __init__(self, *args, **kwargs): else: if args and args[0] is None: args = args[1:] - directory = mkdtemp(prefix='diskcache-') + directory = None self._cache = Cache(directory, eviction_policy='none') self.update(*args, **kwargs) @@ -735,8 +710,7 @@ def __init__(self, *args, **kwargs): def fromcache(cls, cache, *args, **kwargs): """Initialize index using `cache` and update items. - >>> cache = Cache('/tmp/diskcache/index') - >>> _ = cache.clear() + >>> cache = Cache() >>> index = Index.fromcache(cache, {'a': 1, 'b': 2, 'c': 3}) >>> index.cache is cache True @@ -777,8 +751,7 @@ def __getitem__(self, key): Return corresponding value for `key` in index. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update({'a': 1, 'b': 2}) >>> index['a'] 1 @@ -802,8 +775,7 @@ def __setitem__(self, key, value): Set `key` and `value` item in index. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index['a'] = 1 >>> index[0] = None >>> len(index) @@ -821,8 +793,7 @@ def __delitem__(self, key): Delete corresponding item for `key` from index. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update({'a': 1, 'b': 2}) >>> del index['a'] >>> del index['b'] @@ -846,8 +817,7 @@ def setdefault(self, key, default=None): If `key` is not in index then set corresponding value to `default`. If `key` is in index then ignore `default` and return existing value. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.setdefault('a', 0) 0 >>> index.setdefault('a', 1) @@ -869,8 +839,7 @@ def setdefault(self, key, default=None): def peekitem(self, last=True): """Peek at key and value item pair in index based on iteration order. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> for num, letter in enumerate('xyz'): ... index[letter] = num >>> index.peekitem() @@ -892,7 +861,7 @@ def pop(self, key, default=ENOVAL): If `key` is missing then return `default`. If `default` is `ENOVAL` then raise KeyError. - >>> index = Index('/tmp/diskcache/index', {'a': 1, 'b': 2}) + >>> index = Index({'a': 1, 'b': 2}) >>> index.pop('a') 1 >>> index.pop('b') @@ -924,8 +893,7 @@ def popitem(self, last=True): True else first-in-first-out (FIFO) order. LIFO order imitates a stack and FIFO order imitates a queue. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index.popitem() ('c', 3) @@ -974,8 +942,7 @@ def push(self, value, prefix=None, side='back'): See also `Index.pull`. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> print(index.push('apples')) 500000000000000 >>> print(index.push('beans')) @@ -1010,8 +977,7 @@ def pull(self, prefix=None, default=(None, None), side='front'): See also `Index.push`. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> for letter in 'abc': ... print(index.push(letter)) 500000000000000 @@ -1059,8 +1025,7 @@ def __reversed__(self): Return iterator of index keys in reversed insertion order. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> iterator = reversed(index) >>> next(iterator) @@ -1085,8 +1050,7 @@ def __len__(self): def keys(self): """List of index keys. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index.keys() ['a', 'b', 'c'] @@ -1100,8 +1064,7 @@ def keys(self): def values(self): """List of index values. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index.values() [1, 2, 3] @@ -1115,8 +1078,7 @@ def values(self): def items(self): """List of index items. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> index.items() [('a', 1), ('b', 2), ('c', 3)] @@ -1130,8 +1092,7 @@ def items(self): def iterkeys(self): """Iterator of index keys. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> list(index.iterkeys()) ['a', 'b', 'c'] @@ -1145,8 +1106,7 @@ def iterkeys(self): def itervalues(self): """Iterator of index values. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> list(index.itervalues()) [1, 2, 3] @@ -1168,8 +1128,7 @@ def itervalues(self): def iteritems(self): """Iterator of index items. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> list(index.iteritems()) [('a', 1), ('b', 2), ('c', 3)] @@ -1191,8 +1150,7 @@ def iteritems(self): def viewkeys(self): """Set-like object providing a view of index keys. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> keys_view = index.viewkeys() >>> 'b' in keys_view @@ -1207,8 +1165,7 @@ def viewkeys(self): def viewvalues(self): """Set-like object providing a view of index values. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> values_view = index.viewvalues() >>> 2 in values_view @@ -1223,8 +1180,7 @@ def viewvalues(self): def viewitems(self): """Set-like object providing a view of index items. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> items_view = index.viewitems() >>> ('b', 2) in items_view @@ -1240,8 +1196,7 @@ def viewitems(self): def keys(self): """Set-like object providing a view of index keys. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> keys_view = index.keys() >>> 'b' in keys_view @@ -1256,8 +1211,7 @@ def keys(self): def values(self): """Set-like object providing a view of index values. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> values_view = index.values() >>> 2 in values_view @@ -1272,8 +1226,7 @@ def values(self): def items(self): """Set-like object providing a view of index items. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update({'a': 1, 'b': 2, 'c': 3}) >>> items_view = index.items() >>> ('b', 2) in items_view @@ -1304,8 +1257,7 @@ def __eq__(self, other): Comparison to another index or ordered dictionary is order-sensitive. Comparison to all other mappings is order-insensitive. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> pairs = [('a', 1), ('b', 2), ('c', 3)] >>> index.update(pairs) >>> from collections import OrderedDict @@ -1339,8 +1291,7 @@ def __ne__(self, other): Comparison to another index or ordered dictionary is order-sensitive. Comparison to all other mappings is order-insensitive. - >>> index = Index('/tmp/diskcache/index') - >>> index.clear() + >>> index = Index() >>> index.update([('a', 1), ('b', 2), ('c', 3)]) >>> from collections import OrderedDict >>> od = OrderedDict([('c', 3), ('b', 2), ('a', 1)]) @@ -1375,7 +1326,7 @@ def memoize(self, name=None, typed=False): or for rewrapping the function with a different cache. >>> from diskcache import Index - >>> mapping = Index('/tmp/diskcache/index') + >>> mapping = Index() >>> @mapping.memoize(typed=True) ... def fibonacci(number): ... if number == 0: @@ -1418,7 +1369,7 @@ def transact(self): Transactions may be nested and may not be shared between threads. >>> from diskcache import Index - >>> mapping = Index('/tmp/diskcache/index') + >>> mapping = Index() >>> with mapping.transact(): # Atomically increment two keys. ... mapping['total'] = mapping.get('total', 0) + 123.4 ... mapping['count'] = mapping.get('count', 0) + 1 diff --git a/diskcache/recipes.py b/diskcache/recipes.py index d729cf5..4525fe8 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -1,7 +1,7 @@ """Disk Cache Recipes >>> import diskcache as dc, time ->>> cache = dc.Cache('/tmp/diskcache/example') +>>> cache = dc.Cache() >>> @dc.memoize(cache) ... @dc.barrier(cache, dc.Lock) ... @dc.memoize(cache) @@ -43,7 +43,7 @@ class Averager(object): total and count. The average can then be calculated at any time. >>> import diskcache - >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> cache = diskcache.Cache() >>> ave = Averager(cache, 'latency') >>> ave.add(0.080) >>> ave.add(0.120) @@ -85,7 +85,7 @@ class Lock(object): """Recipe for cross-process and cross-thread lock. >>> import diskcache - >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> cache = diskcache.Cache() >>> lock = Lock(cache, 'report-123') >>> lock.acquire() >>> lock.release() @@ -124,7 +124,7 @@ class RLock(object): """Recipe for cross-process and cross-thread re-entrant lock. >>> import diskcache - >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> cache = diskcache.Cache() >>> rlock = RLock(cache, 'user-123') >>> rlock.acquire() >>> rlock.acquire() @@ -182,7 +182,7 @@ class BoundedSemaphore(object): """Recipe for cross-process and cross-thread bounded semaphore. >>> import diskcache - >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> cache = diskcache.Cache() >>> semaphore = BoundedSemaphore(cache, 'max-connections', value=2) >>> semaphore.acquire() >>> semaphore.acquire() @@ -238,7 +238,7 @@ def throttle(cache, count, seconds, name=None, expire=None, tag=None, """Decorator to throttle calls to function. >>> import diskcache, time - >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> cache = diskcache.Cache() >>> @throttle(cache, 1, 1) ... def int_time(): ... return int(time.time()) @@ -295,7 +295,7 @@ def barrier(cache, lock_factory, name=None, expire=None, tag=None): """Barrier to calling decorated function. >>> import diskcache, time - >>> cache = diskcache.Cache('/tmp/diskcache/recipes') + >>> cache = diskcache.Cache() >>> @barrier(cache, Lock) ... def work(num): ... time.sleep(1) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index c168cf7..20aaaaa 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -68,7 +68,7 @@ represents a disk and file backed cache. As a Cache, it supports a familiar Python Mapping interface with additional cache and performance parameters. >>> from diskcache import Cache - >>> cache = Cache('/tmp/mycachedir') + >>> cache = Cache() Initialization requires a directory path reference. If the directory path does not exist, it will be created. Additional keyword parameters are discussed @@ -85,7 +85,7 @@ in a `with` statement to safeguard calling :meth:`close `. >>> cache.close() - >>> with Cache('/tmp/mycachedir') as reference: + >>> with Cache(cache.directory) as reference: ... pass Closed Cache objects will automatically re-open when accessed. But opening @@ -100,7 +100,6 @@ safely leave Cache objects open. Set an item, get a value, and delete a key using the usual operators: - >>> cache = Cache('/tmp/mycachedir') >>> cache[b'key'] = b'value' >>> cache[b'key'] b'value' @@ -227,7 +226,8 @@ tag. The default tag is ``None``. Tag values may be any of integer, float, string, bytes and None. To accelerate the eviction of items by tag, an index can be created. To do so, initialize the cache with ``tag_index=True``. - >>> cache = Cache('/tmp/mycachedir', tag_index=True) + >>> cache.clear() + 50 >>> for num in range(100): ... _ = cache.set(num, num, tag=(num % 2)) >>> cache.evict(0) @@ -311,6 +311,9 @@ The third is :meth:`check ` which verifies cache consistency. It can also fix inconsistencies and reclaim unused space. The return value is a list of warnings. + >>> import shutil + >>> shutil.rmtree(cache.directory) + .. _tutorial-fanoutcache: FanoutCache @@ -343,20 +346,19 @@ when :exc:`Timeout ` errors occur. :class:`FanoutCache exception. The default `timeout` is 0.010 (10 milliseconds). >>> from diskcache import FanoutCache - >>> cache = FanoutCache('/tmp/mycachedir', shards=4, timeout=1) + >>> cache = FanoutCache(shards=4, timeout=1) -The example above creates a cache in the local ``/tmp/mycachedir`` directory -with four shards and a one second timeout. Operations will attempt to abort if -they take longer than one second. The remaining API of :class:`FanoutCache -` matches :class:`Cache ` as described -above. +The example above creates a cache in a temporary directory with four shards and +a one second timeout. Operations will attempt to abort if they take longer than +one second. The remaining API of :class:`FanoutCache ` +matches :class:`Cache ` as described above. :class:`FanoutCache ` adds an additional feature: :meth:`memoizing ` cache decorator. The decorator wraps a callable and caches arguments and return values. >>> from diskcache import FanoutCache - >>> cache = FanoutCache('/tmp/diskcache/fanoutcache') + >>> cache = FanoutCache() >>> @cache.memoize(typed=True, expire=1, tag='fib') ... def fibonacci(number): ... if number == 0: @@ -547,9 +549,7 @@ are updated lazily. Prefer idioms like :meth:`len ` rather than using :meth:`reset ` directly. - >>> cache = Cache('/tmp/mycachedir', size_limit=int(4e9)) - >>> cache.clear() - 100 + >>> cache = Cache(size_limit=int(4e9)) >>> cache.size_limit 4000000000 >>> cache.disk_min_file_size @@ -559,7 +559,7 @@ are updated lazily. Prefer idioms like :meth:`len >>> cache.set(b'key', 1.234) True >>> cache.count # Stale attribute. - 100 + 0 >>> cache.reset('count') # Prefer: len(cache) 1 @@ -615,10 +615,10 @@ tradeoffs for accessing and storing items. All clients accessing the cache are expected to use the same eviction policy. The policy can be set during initialization using a keyword argument. - >>> cache = Cache('/tmp/mydir') + >>> cache = Cache() >>> cache.eviction_policy 'least-recently-stored' - >>> cache = Cache('/tmp/mydir', eviction_policy='least-frequently-used') + >>> cache = Cache(eviction_policy='least-frequently-used') >>> cache.eviction_policy 'least-frequently-used' >>> cache.reset('eviction_policy', 'least-recently-used') @@ -673,7 +673,7 @@ example below uses compressed JSON. data = json.loads(zlib.decompress(data).decode('utf-8')) return data - with Cache('/tmp/mydir', disk=JSONDisk, disk_compress_level=6) as cache: + with Cache(disk=JSONDisk, disk_compress_level=6) as cache: pass Four data types can be stored natively in the cache metadata database: diff --git a/tests/test_core.py b/tests/test_core.py index c440e0f..bcaea05 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -17,6 +17,7 @@ import sqlite3 import subprocess as sp import sys +import tempfile import threading import time import unittest @@ -38,10 +39,9 @@ @pytest.fixture def cache(): - shutil.rmtree('tmp', ignore_errors=True) - with dc.Cache('tmp') as cache: + with dc.Cache() as cache: yield cache - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) def test_init(cache): @@ -53,18 +53,17 @@ def test_init(cache): def test_init_disk(): - with dc.Cache('tmp', disk_pickle_protocol=1, disk_min_file_size=2 ** 20) as cache: + with dc.Cache(disk_pickle_protocol=1, disk_min_file_size=2 ** 20) as cache: key = (None, 0, 'abc') cache[key] = 0 cache.check() - assert cache.directory == 'tmp' assert cache.disk_min_file_size == 2 ** 20 assert cache.disk_pickle_protocol == 1 - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) def test_disk_reset(): - with dc.Cache('tmp', disk_min_file_size=0, disk_pickle_protocol=0) as cache: + with dc.Cache(disk_min_file_size=0, disk_pickle_protocol=0) as cache: value = (None, 0, 'abc') cache[0] = value @@ -86,12 +85,12 @@ def test_disk_reset(): assert cache._disk.min_file_size == 2 ** 10 assert cache._disk.pickle_protocol == 2 - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) def test_disk_valueerror(): with pytest.raises(ValueError): - with dc.Cache('tmp', disk=dc.Disk('tmp')) as cache: + with dc.Cache(disk=dc.Disk('test')): pass @@ -123,7 +122,7 @@ def fetch(self, mode, filename, value, read): def test_custom_disk(): - with dc.Cache('tmp', disk=JSONDisk, disk_compress_level=6) as cache: + with dc.Cache(disk=JSONDisk, disk_compress_level=6) as cache: values = [None, True, 0, 1.23, {}, [None] * 10000] for value in values: @@ -132,7 +131,7 @@ def test_custom_disk(): for value in values: assert cache[value] == value - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) class SHA256FilenameDisk(diskcache.Disk): @@ -143,7 +142,7 @@ def filename(self, key=dc.UNKNOWN, value=dc.UNKNOWN): def test_custom_filename_disk(): - with dc.Cache('tmp', disk=SHA256FilenameDisk) as cache: + with dc.Cache(disk=SHA256FilenameDisk) as cache: for count in range(100, 200): key = str(count).encode('ascii') cache[key] = str(count) * int(1e5) @@ -151,25 +150,26 @@ def test_custom_filename_disk(): for count in range(100, 200): key = str(count).encode('ascii') filename = hashlib.sha256(key).hexdigest()[:32] - full_path = op.join('tmp', filename) + full_path = op.join(cache.directory, filename) with open(full_path) as reader: content = reader.read() assert content == str(count) * int(1e5) - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) def test_init_makedirs(): - shutil.rmtree('tmp', ignore_errors=True) + cache_dir = tempfile.mkdtemp() + shutil.rmtree(cache_dir) makedirs = mock.Mock(side_effect=OSError(errno.EACCES)) with pytest.raises(EnvironmentError): try: with mock.patch('os.makedirs', makedirs): - cache = dc.Cache('tmp') + cache = dc.Cache(cache_dir) except EnvironmentError: - shutil.rmtree('tmp') + shutil.rmtree(cache_dir, ignore_errors=True) raise @@ -689,11 +689,11 @@ def test_integrity_check(cache): cache.close() - with io.open('tmp/cache.db', 'r+b') as writer: + with io.open(op.join(cache.directory, 'cache.db'), 'r+b') as writer: writer.seek(52) writer.write(b'\x00\x01') # Should be 0, change it. - cache = dc.Cache('tmp') + cache = dc.Cache(cache.directory) with warnings.catch_warnings(): warnings.filterwarnings('ignore') @@ -724,9 +724,9 @@ def test_expire(cache): def test_tag_index(): - with dc.Cache('tmp', tag_index=True) as cache: + with dc.Cache(tag_index=True) as cache: assert cache.tag_index == 1 - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) def test_evict(cache): @@ -771,7 +771,7 @@ def test_tag(cache): def test_with(cache): - with dc.Cache('tmp') as tmp: + with dc.Cache(cache.directory) as tmp: tmp[u'a'] = 0 tmp[u'b'] = 1 @@ -1323,7 +1323,7 @@ def test_constant(): def test_copy(): - cache_dir1 = op.join('tmp', 'foo') + cache_dir1 = tempfile.mkdtemp() with dc.Cache(cache_dir1) as cache1: for count in range(10): @@ -1332,7 +1332,8 @@ def test_copy(): for count in range(10, 20): cache1[count] = str(count) * int(1e5) - cache_dir2 = op.join('tmp', 'bar') + cache_dir2 = tempfile.mkdtemp() + shutil.rmtree(cache_dir2) shutil.copytree(cache_dir1, cache_dir2) with dc.Cache(cache_dir2) as cache2: @@ -1342,7 +1343,8 @@ def test_copy(): for count in range(10, 20): assert cache2[count] == str(count) * int(1e5) - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache_dir1, ignore_errors=True) + shutil.rmtree(cache_dir2, ignore_errors=True) def run(command): @@ -1362,8 +1364,8 @@ def test_rsync(): return # No rsync installed. Skip test. rsync_args = ['rsync', '-a', '--checksum', '--delete', '--stats'] - cache_dir1 = op.join('tmp', 'foo') + os.sep - cache_dir2 = op.join('tmp', 'bar') + os.sep + cache_dir1 = tempfile.mkdtemp() + os.sep + cache_dir2 = tempfile.mkdtemp() + os.sep # Store some items in cache_dir1. @@ -1415,7 +1417,8 @@ def test_rsync(): for count in range(300, 400): assert cache1[count] == str(count) * int(1e5) - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache_dir1, ignore_errors=True) + shutil.rmtree(cache_dir2, ignore_errors=True) def test_custom_eviction_policy(cache): diff --git a/tests/test_deque.py b/tests/test_deque.py index cdb97ed..a3ac018 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -20,11 +20,8 @@ def rmdir(directory): @pytest.fixture def deque(): deque = dc.Deque() - try: - yield deque - except Exception: - rmdir(deque.directory) - raise + yield deque + rmdir(deque.directory) def test_init(): diff --git a/tests/test_doctest.py b/tests/test_doctest.py index d90d548..d171e48 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -10,39 +10,27 @@ import diskcache.recipes -def rmdir(directory): - try: - shutil.rmtree(directory) - except OSError: - pass - - def test_core(): - rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.core) assert failures == 0 def test_djangocache(): - rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.djangocache) assert failures == 0 def test_fanout(): - rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.fanout) assert failures == 0 def test_memo(): - rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.memo) assert failures == 0 def test_persistent(): - rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.persistent) assert failures == 0 @@ -50,8 +38,6 @@ def test_persistent(): def test_tutorial(): if sys.hexversion < 0x03000000: return - rmdir('/tmp/mycachedir') - rmdir('/tmp/mydir') failures, _ = doctest.testfile('../docs/tutorial.rst') assert failures == 0 @@ -59,6 +45,5 @@ def test_tutorial(): def test_recipes(): if sys.hexversion < 0x03000000: return - rmdir('/tmp/diskcache') failures, _ = doctest.testmod(diskcache.recipes) assert failures == 0 diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 89f3a58..1d96d13 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -16,6 +16,7 @@ import sqlite3 import subprocess as sp import sys +import tempfile import threading import time import warnings @@ -36,15 +37,12 @@ @pytest.fixture def cache(): - shutil.rmtree('tmp', ignore_errors=True) - with dc.FanoutCache('tmp') as cache: + with dc.FanoutCache() as cache: yield cache - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) def test_init(cache): - assert cache.directory == 'tmp' - default_settings = dc.DEFAULT_SETTINGS.copy() del default_settings['size_limit'] for key, value in default_settings.items(): @@ -160,8 +158,7 @@ def stress_add(cache, limit, results): def test_add_concurrent(): - shutil.rmtree('tmp', ignore_errors=True) - with dc.FanoutCache('tmp', shards=1) as cache: + with dc.FanoutCache(shards=1) as cache: results = co.deque() limit = 1000 @@ -178,7 +175,7 @@ def test_add_concurrent(): assert sum(results) == limit cache.check() - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) def test_incr(cache): @@ -222,8 +219,7 @@ def stress_incr(cache, limit): def test_incr_concurrent(): - shutil.rmtree('tmp', ignore_errors=True) - with dc.FanoutCache('tmp', shards=1, timeout=0.001) as cache: + with dc.FanoutCache(shards=1, timeout=0.001) as cache: count = 16 limit = 50 @@ -240,7 +236,7 @@ def test_incr_concurrent(): assert cache.get(b'key') == count * limit cache.check() - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) def test_getsetdel(cache): @@ -568,7 +564,7 @@ def fibrec(num): def test_copy(): - cache_dir1 = op.join('tmp', 'foo') + cache_dir1 = tempfile.mkdtemp() with dc.FanoutCache(cache_dir1) as cache1: for count in range(10): @@ -577,7 +573,8 @@ def test_copy(): for count in range(10, 20): cache1[count] = str(count) * int(1e5) - cache_dir2 = op.join('tmp', 'bar') + cache_dir2 = tempfile.mkdtemp() + shutil.rmtree(cache_dir2) shutil.copytree(cache_dir1, cache_dir2) with dc.FanoutCache(cache_dir2) as cache2: @@ -587,7 +584,8 @@ def test_copy(): for count in range(10, 20): assert cache2[count] == str(count) * int(1e5) - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache_dir1, ignore_errors=True) + shutil.rmtree(cache_dir2, ignore_errors=True) def run(command): @@ -607,8 +605,8 @@ def test_rsync(): return # No rsync installed. Skip test. rsync_args = ['rsync', '-a', '--checksum', '--delete', '--stats'] - cache_dir1 = op.join('tmp', 'foo') + os.sep - cache_dir2 = op.join('tmp', 'bar') + os.sep + cache_dir1 = tempfile.mkdtemp() + os.sep + cache_dir2 = tempfile.mkdtemp() + os.sep # Store some items in cache_dir1. @@ -660,7 +658,8 @@ def test_rsync(): for count in range(300, 400): assert cache1[count] == str(count) * int(1e5) - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache_dir1, ignore_errors=True) + shutil.rmtree(cache_dir2, ignore_errors=True) class SHA256FilenameDisk(dc.Disk): @@ -671,24 +670,24 @@ def filename(self, key=dc.UNKNOWN, value=dc.UNKNOWN): def test_custom_filename_disk(): - with dc.FanoutCache('tmp', disk=SHA256FilenameDisk) as cache: + with dc.FanoutCache(disk=SHA256FilenameDisk) as cache: for count in range(100, 200): key = str(count).encode('ascii') cache[key] = str(count) * int(1e5) - disk = SHA256FilenameDisk('tmp') + disk = SHA256FilenameDisk(cache.directory) for count in range(100, 200): key = str(count).encode('ascii') subdir = '%03d' % (disk.hash(key) % 8) filename = hashlib.sha256(key).hexdigest()[:32] - full_path = op.join('tmp', subdir, filename) + full_path = op.join(cache.directory, subdir, filename) with open(full_path) as reader: content = reader.read() assert content == str(count) * int(1e5) - shutil.rmtree('tmp', ignore_errors=True) + shutil.rmtree(cache.directory, ignore_errors=True) if __name__ == '__main__': diff --git a/tests/test_index.py b/tests/test_index.py index 2b4aa14..83ba5cf 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -20,11 +20,8 @@ def rmdir(directory): @pytest.fixture def index(): index = dc.Index() - try: - yield index - except Exception: - rmdir(index.directory) - raise + yield index + rmdir(index.directory) def test_init(): From 62417f84e3c3b76c92fe7286f9816cf9b6e98842 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 3 Apr 2019 11:08:48 -0700 Subject: [PATCH 322/550] Add make_key attribute to memoized functions --- diskcache/djangocache.py | 8 ++++++-- diskcache/memo.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index d6022ba..bc6a15f 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -10,7 +10,7 @@ DEFAULT_TIMEOUT = 300 from .fanout import FanoutCache -from .memo import MARK, args_to_key, full_name +from .memo import MARK, _args_to_key, full_name class DjangoCache(BaseCache): @@ -399,7 +399,7 @@ def decorator(func): @wraps(func) def wrapper(*args, **kwargs): "Wrapper for callable to cache arguments and return values." - key = args_to_key(base, args, kwargs, typed) + key = wrapper.make_key(args, kwargs) result = self.get(key, MARK, version, retry=True) if result is MARK: @@ -408,6 +408,10 @@ def wrapper(*args, **kwargs): return result + def make_key(args, kwargs): + return _args_to_key(base, args, kwargs, typed) + + wrapper.make_key = make_key return wrapper return decorator diff --git a/diskcache/memo.py b/diskcache/memo.py index 84cac88..f5bfdfc 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -18,7 +18,7 @@ def full_name(func): return func.__module__ + '.' + name -def args_to_key(base, args, kwargs, typed): +def _args_to_key(base, args, kwargs, typed): """Create cache key out of function arguments. :param tuple base: base of key @@ -107,7 +107,7 @@ def decorator(func): @wraps(func) def wrapper(*args, **kwargs): "Wrapper for callable to cache arguments and return values." - key = args_to_key(base, args, kwargs, typed) + key = wrapper.make_key(args, kwargs) result = cache.get(key, default=MARK, retry=True) if result is MARK: @@ -116,6 +116,10 @@ def wrapper(*args, **kwargs): return result + def make_key(args, kwargs): + return _args_to_key(base, args, kwargs, typed) + + wrapper.make_key = make_key return wrapper return decorator From d1591ff5f390377099b028191143a45b6c87d11c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 5 Apr 2019 10:21:05 -0700 Subject: [PATCH 323/550] Small docstring improvements. --- diskcache/recipes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 4525fe8..e5da48c 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -7,14 +7,14 @@ ... @dc.memoize(cache) ... def work(num): ... time.sleep(1) -... return -num +... return num >>> from concurrent.futures import ThreadPoolExecutor >>> with ThreadPoolExecutor() as executor: ... start = time.time() ... times = list(executor.map(work, range(5))) ... end = time.time() >>> times -[0, -1, -2, -3, -4] +[0, 1, 2, 3, 4] >>> int(end - start) 5 >>> with ThreadPoolExecutor() as executor: @@ -22,7 +22,7 @@ ... times = list(executor.map(work, range(5))) ... end = time.time() >>> times -[0, -1, -2, -3, -4] +[0, 1, 2, 3, 4] >>> int(end - start) 0 @@ -294,6 +294,8 @@ def wrapper(*args, **kwargs): def barrier(cache, lock_factory, name=None, expire=None, tag=None): """Barrier to calling decorated function. + Supports different kinds of locks: Lock, RLock, BoundedSemaphore. + >>> import diskcache, time >>> cache = diskcache.Cache() >>> @barrier(cache, Lock) From 6cf31dc840e7c2e6e209fb3b54840cb0b0f02320 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 5 Apr 2019 16:49:22 -0700 Subject: [PATCH 324/550] Add early recomputation option for memoization --- diskcache/djangocache.py | 79 ++++++++++++++++++++++++++++++------- diskcache/memo.py | 84 +++++++++++++++++++++++++++++++++------- diskcache/stampede.py | 78 ------------------------------------- 3 files changed, 135 insertions(+), 106 deletions(-) delete mode 100644 diskcache/stampede.py diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index bc6a15f..fa2e061 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -1,6 +1,7 @@ "Django-compatible disk and file backed cache." from functools import wraps +from time import time from django.core.cache.backends.base import BaseCache try: @@ -358,7 +359,7 @@ def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, - typed=False, tag=None): + typed=False, tag=None, early_recompute=False, time_func=time): """Memoizing cache decorator. Decorator to wrap callable with memoizing function using cache. @@ -372,12 +373,29 @@ def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, cached separately. For example, f(3) and f(3.0) will be treated as distinct calls with distinct results. + Cache stampedes are a type of cascading failure that can occur when + parallel computing systems using memoization come under heavy + load. This behaviour is sometimes also called dog-piling, cache miss + storm, cache choking, or the thundering herd problem. + + The memoization decorator includes cache stampede protection through + the early recomputation parameter. When set to True (default False), + the expire parameter must not be None. Early recomputation of results + will occur probabilistically before expiration. + + Early probabilistic recomputation is based on research by Vattani, A.; + Chierichetti, F.; Lowenstein, K. (2015), Optimal Probabilistic Cache + Stampede Prevention, VLDB, pp. 886?897, ISSN 2150-8097 + The original underlying function is accessible through the __wrapped__ attribute. This is useful for introspection, for bypassing the cache, or for rewrapping the function with a different cache. - Remember to call memoize when decorating a callable. If you forget, then a - TypeError will occur. + An additional `__cache_key__` attribute can be used to generate the + cache key used for the given arguments. + + Remember to call memoize when decorating a callable. If you forget, + then a TypeError will occur. :param str name: name given for callable (default None, automatic) :param float timeout: seconds until the item expires @@ -385,6 +403,9 @@ def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, :param int version: key version number (default None, cache parameter) :param bool typed: cache different types separately (default False) :param str tag: text to associate with arguments (default None) + :param bool early_recompute: probabilistic early recomputation + (default False) + :param time_func: callable for calculating current time :return: callable decorator """ @@ -392,26 +413,56 @@ def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, if callable(name): raise TypeError('name cannot be callable') + if early_recompute and expire is None: + raise ValueError('expire required') + def decorator(func): "Decorator created by memoize call for callable." base = (full_name(func),) if name is None else (name,) - @wraps(func) - def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." - key = wrapper.make_key(args, kwargs) - result = self.get(key, MARK, version, retry=True) + if early_recompute: + @wraps(func) + def wrapper(*args, **kwargs): + "Wrapper for callable to cache arguments and return values." + key = wrapper.__cache_key__(*args, **kwargs) + pair, expire_time = self.get( + key, MARK, version, expire_time=True, retry=True, + ) - if result is MARK: - result = func(*args, **kwargs) - self.set(key, result, timeout, version, tag=tag, retry=True) + if pair is not MARK: + result, delta = pair + now = time_func() + ttl = expire_time - now - return result + if (-delta * log(random())) < ttl: + return result - def make_key(args, kwargs): + start = time_func() + result = func(*args, **kwargs) + delta = time_func() - start + pair = result, delta + self.set(key, pair, timeout, version, tag=tag, retry=True) + return result + else: + @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, MARK, version, retry=True) + + if result is MARK: + result = func(*args, **kwargs) + self.set( + key, result, timeout, version, 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) - wrapper.make_key = make_key + wrapper.__cache_key__ = __cache_key__ return wrapper return decorator diff --git a/diskcache/memo.py b/diskcache/memo.py index f5bfdfc..607c01a 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -3,6 +3,9 @@ """ from functools import wraps +from math import log +from random import random +from time import time MARK = object() @@ -46,7 +49,8 @@ def _args_to_key(base, args, kwargs, typed): return key -def memoize(cache, name=None, typed=False, expire=None, tag=None): +def memoize(cache, name=None, typed=False, expire=None, tag=None, + early_recompute=False, time_func=time): """Memoizing cache decorator. Decorator to wrap callable with memoizing function using cache. Repeated @@ -60,6 +64,20 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): cached separately. For example, f(3) and f(3.0) will be treated as distinct calls with distinct results. + Cache stampedes are a type of cascading failure that can occur when + parallel computing systems using memoization come under heavy load. This + behaviour is sometimes also called dog-piling, cache miss storm, cache + choking, or the thundering herd problem. + + The memoization decorator includes cache stampede protection through the + early recomputation parameter. When set to True (default False), the expire + parameter must not be None. Early recomputation of results will occur + probabilistically before expiration. + + Early probabilistic recomputation is based on research by Vattani, A.; + Chierichetti, F.; Lowenstein, K. (2015), Optimal Probabilistic Cache + Stampede Prevention, VLDB, pp. 886?897, ISSN 2150-8097 + The original underlying function is accessible through the __wrapped__ attribute. This is useful for introspection, for bypassing the cache, or for rewrapping the function with a different cache. @@ -74,8 +92,15 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): ... return 1 ... else: ... return fibonacci(number - 1) + fibonacci(number - 2) - >>> print(sum(fibonacci(number=value) for value in range(100))) - 573147844013817084100 + >>> fibonacci(100) + 354224848179261915075 + + An additional `__cache_key__` attribute can be used to generate the cache key + used for the given arguments. + + >>> key = fibonacci.__cache_key__(100) + >>> cache[key] + 354224848179261915075 Remember to call memoize when decorating a callable. If you forget, then a TypeError will occur. Note the lack of parenthenses after memoize below: @@ -93,6 +118,9 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): :param float expire: seconds until arguments expire (default None, no expiry) :param str tag: text to associate with arguments (default None) + :param bool early_recompute: probabilistic early recomputation + (default False) + :param time_func: callable for calculating current time :return: callable decorator """ @@ -100,26 +128,54 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None): if callable(name): raise TypeError('name cannot be callable') + if early_recompute and expire is None: + raise ValueError('expire required') + def decorator(func): "Decorator created by memoize call for callable." base = (full_name(func),) if name is None else (name,) - @wraps(func) - def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." - key = wrapper.make_key(args, kwargs) - result = cache.get(key, default=MARK, retry=True) + if early_recompute: + @wraps(func) + def wrapper(*args, **kwargs): + "Wrapper for callable to cache arguments and return values." + key = wrapper.__cache_key__(*args, **kwargs) + pair, expire_time = cache.get( + key, default=MARK, expire_time=True, retry=True, + ) - if result is MARK: - result = func(*args, **kwargs) - cache.set(key, result, expire=expire, tag=tag, retry=True) + if pair is not MARK: + result, delta = pair + now = time_func() + ttl = expire_time - now - return result + if (-delta * log(random())) < ttl: + return result - def make_key(args, kwargs): + start = time_func() + result = func(*args, **kwargs) + delta = time_func() - start + pair = result, delta + cache.set(key, pair, expire=expire, tag=tag, retry=True) + return result + else: + @wraps(func) + def wrapper(*args, **kwargs): + "Wrapper for callable to cache arguments and return values." + key = wrapper.__cache_key__(*args, **kwargs) + result = cache.get(key, default=MARK, retry=True) + + if result is MARK: + result = func(*args, **kwargs) + cache.set(key, result, expire=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) - wrapper.make_key = make_key + wrapper.__cache_key__ = __cache_key__ return wrapper return decorator diff --git a/diskcache/stampede.py b/diskcache/stampede.py deleted file mode 100644 index b103338..0000000 --- a/diskcache/stampede.py +++ /dev/null @@ -1,78 +0,0 @@ -"Stampede barrier implementation." - -import functools as ft -import math -import random -import tempfile -import time - -from .core import Cache, ENOVAL - - -class StampedeBarrier(object): - """Stampede barrier mitigates cache stampedes. - - Cache stampedes are also known as dog-piling, cache miss storm, cache - choking, or the thundering herd problem. - - Based on research by Vattani, A.; Chierichetti, F.; Lowenstein, K. (2015), - Optimal Probabilistic Cache Stampede Prevention, - VLDB, pp. 886?897, ISSN 2150-8097 - - Example: - - ```python - stampede_barrier = StampedeBarrier('/tmp/user_data', expire=3) - - @stampede_barrier - def load_user_info(user_id): - return database.lookup_user_info_by_id(user_id) - ``` - - """ - # pylint: disable=too-few-public-methods - def __init__(self, cache=None, expire=None): - if isinstance(cache, Cache): - pass - elif cache is None: - cache = Cache(tempfile.mkdtemp()) - else: - cache = Cache(cache) - - self._cache = cache - self._expire = expire - - def __call__(self, func): - cache = self._cache - expire = self._expire - - @ft.wraps(func) - def wrapper(*args, **kwargs): - "Wrapper function to cache function result." - key = (args, kwargs) - - try: - result, expire_time, delta = cache.get( - key, default=ENOVAL, expire_time=True, tag=True - ) - - if result is ENOVAL: - raise KeyError - - now = time.time() - ttl = expire_time - now - - if (-delta * math.log(random.random())) < ttl: - return result - - except KeyError: - pass - - now = time.time() - result = func(*args, **kwargs) - delta = time.time() - now - cache.set(key, result, expire=expire, tag=delta) - - return result - - return wrapper From ce44c40a548abc524dad540e41887e15991e2e44 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 9 Apr 2019 12:16:21 -0700 Subject: [PATCH 325/550] Add script for testing early recomputation --- diskcache/core.py | 3 + diskcache/memo.py | 2 +- tests/test_early_recompute.py | 141 ++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 tests/test_early_recompute.py diff --git a/diskcache/core.py b/diskcache/core.py index f86cf66..deef26c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -707,6 +707,9 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): :raises Timeout: if database timeout occurs """ + if expire is not None and expire <= 0: + return False + now = time.time() db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire diff --git a/diskcache/memo.py b/diskcache/memo.py index 607c01a..9d4fc24 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -149,7 +149,7 @@ def wrapper(*args, **kwargs): now = time_func() ttl = expire_time - now - if (-delta * log(random())) < ttl: + if (-delta * early_recompute * log(random())) < ttl: return result start = time_func() diff --git a/tests/test_early_recompute.py b/tests/test_early_recompute.py new file mode 100644 index 0000000..2ae8ffe --- /dev/null +++ b/tests/test_early_recompute.py @@ -0,0 +1,141 @@ +"""Early Recomputation Measurements + +TODO + +* Publish graphs: + 1. Cache stampede (single memo decorator). + 2. Double-checked locking (memo, barrier, memo). + 3. Early recomputation (memo with early recomputation). + 4. Advanced usage: adjust "Beta" parameter. + +""" + +import concurrent.futures +import diskcache as dc +import functools +import matplotlib.pyplot as plt +import shutil +import threading +import time + + +def make_timer(times): + """Make a decorator which accumulates (start, end) in `times` for function + calls. + + """ + lock = threading.Lock() + def timer(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + start = time.time() + result = func(*args, **kwargs) + pair = start, time.time() + with lock: + times.append(pair) + return wrapper + return timer + + +def make_worker(times, delay=1): + """Make a worker which accumulates (start, end) in `times` and sleeps for + `delay` seconds. + + """ + @make_timer(times) + def worker(): + time.sleep(delay) + return worker + + +def make_repeater(func, total=60, delay=0.01): + """Make a repeater which calls `func` and sleeps for `delay` seconds + repeatedly until `total` seconds have elapsed. + + """ + def repeat(num): + start = time.time() + while time.time() - start < total: + func() + time.sleep(delay) + return repeat + + +def frange(start, stop, step=1e-3): + "Generator for floating point values from `start` to `stop` by `step`." + while start < stop: + yield start + start += step + + +def plot(cache_times, worker_times): + "Plot concurrent workers and latency." + fig, (workers, latency) = plt.subplots(2, sharex=True) + + changes = [(start, 1) for start, _ in worker_times] + changes.extend((stop, -1) for _, stop in worker_times) + changes.sort() + start = (changes[0][0] - 1e-6, 0) + counts = [start] + + for mark, diff in changes: + # Re-sample between previous and current data point for a nicer-looking + # line plot. + + for step in frange(counts[-1][0], mark): + pair = (step, counts[-1][1]) + counts.append(pair) + + pair = (mark, counts[-1][1] + diff) + counts.append(pair) + + max_x = max(start for start, _ in cache_times) + for step in frange(counts[-1][0], max_x): + pair = (step, counts[-1][1]) + counts.append(pair) + + x_counts = [x for x, y in counts] + y_counts = [y for x, y in counts] + + workers.set_title('Concurrent Workers') + workers.set_ylabel('Workers') + workers.plot(x_counts, y_counts) + + latency.set_title('Latency') + latency.set_ylabel('Seconds') + latency.set_xlabel('Time') + x_latency = [start for start, _ in cache_times] + y_latency = [stop - start for start, stop in cache_times] + latency.scatter(x_latency, y_latency) + + plt.show() + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + + shutil.rmtree('/tmp/cache', ignore_errors=True) + cache = dc.Cache('/tmp/cache') + + count = 16 + + cache_times = [] + worker_times = [] + + worker = make_worker(worker_times) + decorators = [ + make_timer(cache_times), + cache.memoize(expire=10, early_recompute=1.5), + # dc.barrier(cache, dc.Lock), + # cache.memoize(expire=10), + ] + for decorator in reversed(decorators): + worker = decorator(worker) + + repeater = make_repeater(worker) + + with concurrent.futures.ThreadPoolExecutor(count) as executor: + executor.map(repeater, [worker] * count) + + plot(cache_times, worker_times) From 78977bf2ab2d9de84ccc09d32ed735da0b061ffb Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 19 Apr 2019 10:06:01 -0700 Subject: [PATCH 326/550] Add benchmarking script from issue 109 --- tests/issue_109.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/issue_109.py diff --git a/tests/issue_109.py b/tests/issue_109.py new file mode 100644 index 0000000..de03cf9 --- /dev/null +++ b/tests/issue_109.py @@ -0,0 +1,51 @@ +from time import time, sleep +from diskcache import FanoutCache + +# Initialise the maximum delay reference +max_delay = 0 + +# Initialise the reference to current time +# Add a significant amount of time to ignore the first measurement due to other initialisations +current_time = time() + 100 + +# Initialise the Fanout Cache instance +data = FanoutCache('test', shards=8) + + +# Declare the function used to modify the data +def set_data(**kwargs): + """ + + Function used to modify the cache. + + :param kwargs: Key, value pairs of data to modify. + + """ + + # Fetch the global variables + global max_delay + global current_time + global data + + # Update the data with the given keyword arguments + for key, value in kwargs.items(): + + # Measure the time taken to update it for each item + temp = time() + dif = temp - current_time + if dif > max_delay: + max_delay = dif + print("New max delay encountered: ", max_delay) + current_time = temp + + # Update the data + data[key] = value + + +# Initialise some test values +test_values = {str(key): value for key in range(25) for value in range(25)} + +# Keep writing the data in 0.1 sec intervals +while True: + sleep(0.1) + set_data(**test_values) From 65b648e4a3174a2178533a4b0bcd9bf52cdc0a96 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 19 Apr 2019 21:36:14 -0700 Subject: [PATCH 327/550] Measure FanoutCache performance for Issue #109 --- tests/issue_109.py | 101 ++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/tests/issue_109.py b/tests/issue_109.py index de03cf9..a650b4c 100644 --- a/tests/issue_109.py +++ b/tests/issue_109.py @@ -1,51 +1,50 @@ -from time import time, sleep -from diskcache import FanoutCache - -# Initialise the maximum delay reference -max_delay = 0 - -# Initialise the reference to current time -# Add a significant amount of time to ignore the first measurement due to other initialisations -current_time = time() + 100 - -# Initialise the Fanout Cache instance -data = FanoutCache('test', shards=8) - - -# Declare the function used to modify the data -def set_data(**kwargs): - """ - - Function used to modify the cache. - - :param kwargs: Key, value pairs of data to modify. - - """ - - # Fetch the global variables - global max_delay - global current_time - global data - - # Update the data with the given keyword arguments - for key, value in kwargs.items(): - - # Measure the time taken to update it for each item - temp = time() - dif = temp - current_time - if dif > max_delay: - max_delay = dif - print("New max delay encountered: ", max_delay) - current_time = temp - - # Update the data - data[key] = value - - -# Initialise some test values -test_values = {str(key): value for key in range(25) for value in range(25)} - -# Keep writing the data in 0.1 sec intervals -while True: - sleep(0.1) - set_data(**test_values) +"""Benchmark for Issue #109 + +""" + +import time +import diskcache as dc + + +def main(): + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--cache-dir', default='/tmp/test') + parser.add_argument('--iterations', type=int, default=100) + parser.add_argument('--sleep', type=float, default=0.1) + parser.add_argument('--size', type=int, default=25) + args = parser.parse_args() + + data = dc.FanoutCache(args.cache_dir) + delays = [] + values = {str(num): num for num in range(args.size)} + iterations = args.iterations + + for i in range(args.iterations): + print(f'Iteration {i + 1}/{iterations}', end='\r') + time.sleep(args.sleep) + for key, value in values.items(): + start = time.monotonic() + data[key] = value + stop = time.monotonic() + diff = stop - start + delays.append(diff) + + # Discard warmup delays, first two iterations. + del delays[:(len(values) * 2)] + + # Convert seconds to microseconds. + delays = sorted(delay * 1e6 for delay in delays) + + # Display performance. + print() + print(f'Total #: {len(delays)}') + print(f'Min delay (us): {delays[0]:>8.3f}') + print(f'50th %ile (us): {delays[int(len(delays) * 0.50)]:>8.3f}') + print(f'90th %ile (us): {delays[int(len(delays) * 0.90)]:>8.3f}') + print(f'99th %ile (us): {delays[int(len(delays) * 0.99)]:>8.3f}') + print(f'Max delay (us): {delays[-1]:>8.3f}') + + +if __name__ == '__main__': + main() From 1e56fd5152aac53d41738d872bc33ecf0c2f357c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 13:56:51 -0700 Subject: [PATCH 328/550] Add pytest-xdist to tox.ini for testenv --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 3f8de58..59c2da7 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ deps= mock pytest pytest-django + pytest-xdist commands=python -m pytest setenv = DJANGO_SETTINGS_MODULE=tests.settings From b482ea1a19f2ad8169962232d59254790acf2ed7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 15:25:52 -0700 Subject: [PATCH 329/550] Change expirations in tests from 0 to 1e-9 --- diskcache/core.py | 3 +++ docs/tutorial.rst | 2 +- tests/test_core.py | 10 +++++----- tests/test_fanout.py | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index deef26c..5fb1f00 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -693,6 +693,9 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. + If `expire` is less than or equal to zero then immediately returns + `False`. + Raises :exc:`Timeout` error when database timeout occurs and `retry` is `False` (default). diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 20aaaaa..8a67ab8 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -195,7 +195,7 @@ Another four methods remove items from the cache. >>> cache.reset('cull_limit', 0) # Disable automatic evictions. 0 >>> for num in range(10): - ... _ = cache.set(num, num, expire=0) # Expire immediately. + ... _ = cache.set(num, num, expire=1e-9) # Expire immediately. >>> len(cache) 10 >>> list(cache) diff --git a/tests/test_core.py b/tests/test_core.py index bcaea05..acf6b3f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -402,7 +402,7 @@ def test_pop(cache): assert cache.set('alpha', 123, expire=1, tag='blue') assert cache.pop('alpha', tag=True) == (123, 'blue') - assert cache.set('beta', 456, expire=0, tag='green') + assert cache.set('beta', 456, expire=1e-9, tag='green') time.sleep(0.01) assert cache.pop('beta', 'dne') == 'dne' @@ -522,7 +522,7 @@ def test_expire_rows(cache): cache.reset('cull_limit', 0) for value in range(10): - assert cache.set(value, value, expire=0) + assert cache.set(value, value, expire=1e-9) for value in range(10, 15): assert cache.set(value, value) @@ -709,12 +709,12 @@ def test_expire(cache): time_time = mock.Mock(return_value=now) with mock.patch('time.time', time_time): - for value in range(100): + for value in range(1, 101): assert cache.set(value, value, expire=value) assert len(cache) == 100 - time_time = mock.Mock(return_value=now + 10) + time_time = mock.Mock(return_value=now + 11) cache.reset('cull_limit', 10) with mock.patch('time.time', time_time): assert cache.expire() == 10 @@ -885,7 +885,7 @@ def test_iter(cache): def test_iter_expire(cache): cache.reset('cull_limit', 0) for num in range(100): - cache.set(num, num, expire=0) + cache.set(num, num, expire=1e-9) assert len(cache) == 100 assert list(cache) == list(range(100)) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 1d96d13..a62f8a2 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -374,7 +374,7 @@ def test_expire(cache): cache.reset('cull_limit', 0) for value in range(100): - cache.set(value, value, expire=0) + cache.set(value, value, expire=1e-9) assert len(cache) == 100 @@ -502,7 +502,7 @@ def test_iter_expire(cache): """ cache.reset('cull_limit', 0) for num in range(100): - cache.set(num, num, expire=0) + cache.set(num, num, expire=1e-9) time.sleep(0.1) assert set(cache) == set(range(100)) cache.expire() From 8051922d19cd80be0f89133efd036bf28071484e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 15:26:21 -0700 Subject: [PATCH 330/550] Addm missing imports for DjangoCache memoize --- diskcache/djangocache.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index fa2e061..6b16a87 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -1,6 +1,8 @@ "Django-compatible disk and file backed cache." from functools import wraps +from math import log +from random import random from time import time from django.core.cache.backends.base import BaseCache @@ -413,8 +415,8 @@ def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, if callable(name): raise TypeError('name cannot be callable') - if early_recompute and expire is None: - raise ValueError('expire required') + if early_recompute and timeout is None: + raise ValueError('timeout required') def decorator(func): "Decorator created by memoize call for callable." From bec534c9ad54fe48bd11e0702c7d04f70a06e985 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 15:26:39 -0700 Subject: [PATCH 331/550] Remove unused import --- diskcache/persistent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index ab22e09..623c56c 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -8,7 +8,6 @@ from collections import OrderedDict from contextlib import contextmanager from shutil import rmtree -from tempfile import mkdtemp from .core import BytesType, Cache, ENOVAL, TextType From 9f5232c3faea20d9379eae761fa4450035d7a5d0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 15:59:52 -0700 Subject: [PATCH 332/550] Test fixes for Python 2.7 --- diskcache/memo.py | 4 ++-- tests/test_early_recompute.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/diskcache/memo.py b/diskcache/memo.py index 9d4fc24..2669657 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -92,14 +92,14 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None, ... return 1 ... else: ... return fibonacci(number - 1) + fibonacci(number - 2) - >>> fibonacci(100) + >>> print(fibonacci(100)) 354224848179261915075 An additional `__cache_key__` attribute can be used to generate the cache key used for the given arguments. >>> key = fibonacci.__cache_key__(100) - >>> cache[key] + >>> print(cache[key]) 354224848179261915075 Remember to call memoize when decorating a callable. If you forget, then a diff --git a/tests/test_early_recompute.py b/tests/test_early_recompute.py index 2ae8ffe..6ffa4e9 100644 --- a/tests/test_early_recompute.py +++ b/tests/test_early_recompute.py @@ -10,10 +10,9 @@ """ -import concurrent.futures import diskcache as dc import functools -import matplotlib.pyplot as plt +import multiprocessing.pool import shutil import threading import time @@ -70,6 +69,8 @@ def frange(start, stop, step=1e-3): def plot(cache_times, worker_times): "Plot concurrent workers and latency." + import matplotlib.pyplot as plt + fig, (workers, latency) = plt.subplots(2, sharex=True) changes = [(start, 1) for start, _ in worker_times] @@ -135,7 +136,7 @@ def plot(cache_times, worker_times): repeater = make_repeater(worker) - with concurrent.futures.ThreadPoolExecutor(count) as executor: - executor.map(repeater, [worker] * count) + with multiprocessing.pool.ThreadPool() as pool: + pool.map(repeater, [worker] * count) plot(cache_times, worker_times) From b40c0c7af5c426ed316a5cb1b0ba47e97376f46a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 16:27:06 -0700 Subject: [PATCH 333/550] Specify ThreadPoolExecutor max workers for Python 3.4 --- diskcache/recipes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index e5da48c..96a9f58 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -9,7 +9,7 @@ ... time.sleep(1) ... return num >>> from concurrent.futures import ThreadPoolExecutor ->>> with ThreadPoolExecutor() as executor: +>>> with ThreadPoolExecutor(5) as executor: ... start = time.time() ... times = list(executor.map(work, range(5))) ... end = time.time() @@ -17,7 +17,7 @@ [0, 1, 2, 3, 4] >>> int(end - start) 5 ->>> with ThreadPoolExecutor() as executor: +>>> with ThreadPoolExecutor(5) as executor: ... start = time.time() ... times = list(executor.map(work, range(5))) ... end = time.time() @@ -303,7 +303,7 @@ def barrier(cache, lock_factory, name=None, expire=None, tag=None): ... time.sleep(1) ... return int(time.time()) >>> from concurrent.futures import ThreadPoolExecutor - >>> with ThreadPoolExecutor() as executor: + >>> with ThreadPoolExecutor(4) as executor: ... times = sorted(executor.map(work, range(4))) >>> [times[i] - times[i - 1] for i in range(1, 4)] [1, 1, 1] From c250a0243800ca03ad59c4f5610654bb63d02157 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 16:48:13 -0700 Subject: [PATCH 334/550] Change PYTHONPATH for Windows testing --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 59c2da7..0566db9 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ deps= commands=python -m pytest setenv = DJANGO_SETTINGS_MODULE=tests.settings - PYTHONPATH={toxinidir}:{toxinidir}/tests + PYTHONPATH={toxinidir} [pytest] addopts= From 8c77394aa4417cd45aee2dbf5c596938b36f481c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 11:18:20 -0700 Subject: [PATCH 335/550] Add changes for threaded recomputation --- diskcache/memo.py | 24 ++++++++++++++++++------ tests/test_early_recompute.py | 34 +++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/diskcache/memo.py b/diskcache/memo.py index 2669657..67f0274 100644 --- a/diskcache/memo.py +++ b/diskcache/memo.py @@ -5,6 +5,7 @@ from functools import wraps from math import log from random import random +from threading import Thread from time import time MARK = object() @@ -144,6 +145,14 @@ def wrapper(*args, **kwargs): key, default=MARK, expire_time=True, retry=True, ) + def recompute(): + start = time_func() + result = func(*args, **kwargs) + delta = time_func() - start + pair = result, delta + cache.set(key, pair, expire=expire, tag=tag, retry=True) + return result + if pair is not MARK: result, delta = pair now = time_func() @@ -151,13 +160,16 @@ def wrapper(*args, **kwargs): if (-delta * early_recompute * log(random())) < ttl: return result + elif True: # Background + # How to support asyncio? + thread_key = key + (MARK,) + if cache.add(thread_key, None, expire=delta): + thread = Thread(target=recompute) + thread.daemon = True + thread.start() + return result - start = time_func() - result = func(*args, **kwargs) - delta = time_func() - start - pair = result, delta - cache.set(key, pair, expire=expire, tag=tag, retry=True) - return result + return recompute() else: @wraps(func) def wrapper(*args, **kwargs): diff --git a/tests/test_early_recompute.py b/tests/test_early_recompute.py index 6ffa4e9..36f31d5 100644 --- a/tests/test_early_recompute.py +++ b/tests/test_early_recompute.py @@ -69,8 +69,8 @@ def frange(start, stop, step=1e-3): def plot(cache_times, worker_times): "Plot concurrent workers and latency." + # TODO: Update x-axis to normalize to 0 import matplotlib.pyplot as plt - fig, (workers, latency) = plt.subplots(2, sharex=True) changes = [(start, 1) for start, _ in worker_times] @@ -90,12 +90,13 @@ def plot(cache_times, worker_times): pair = (mark, counts[-1][1] + diff) counts.append(pair) + min_x = min(start for start, _ in cache_times) max_x = max(start for start, _ in cache_times) for step in frange(counts[-1][0], max_x): pair = (step, counts[-1][1]) counts.append(pair) - x_counts = [x for x, y in counts] + x_counts = [x - min_x for x, y in counts] y_counts = [y for x, y in counts] workers.set_title('Concurrent Workers') @@ -105,7 +106,7 @@ def plot(cache_times, worker_times): latency.set_title('Latency') latency.set_ylabel('Seconds') latency.set_xlabel('Time') - x_latency = [start for start, _ in cache_times] + x_latency = [start - min_x for start, _ in cache_times] y_latency = [stop - start for start, stop in cache_times] latency.scatter(x_latency, y_latency) @@ -122,15 +123,34 @@ def plot(cache_times, worker_times): count = 16 cache_times = [] - worker_times = [] + timer = make_timer(cache_times) - worker = make_worker(worker_times) decorators = [ - make_timer(cache_times), - cache.memoize(expire=10, early_recompute=1.5), + timer, + + # Option 0: No Caching + + # Option 1: Traditional Caching + # cache.memoize(expire=10), + + # Option 2: Synchronized Locking + # cache.memoize(expire=0), # dc.barrier(cache, dc.Lock), # cache.memoize(expire=10), + + # Option 3: Early Recomputation + # cache.memoize(expire=10, early_recompute=True), + + # Option 4: Early Recomputation Tuning + # cache.memoize(expire=10, early_recompute=1.5), # =0.5), + + # Option 5: Background Early Recomputation + # cache.memoize(expire=10, early_recompute=True, background='threading'), + # TODO: background parameter? or early_recompute='background' ] + + worker_times = [] + worker = make_worker(worker_times) for decorator in reversed(decorators): worker = decorator(worker) From c6703b6d4c4f46aaf67e58b9fb60513232b30034 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 7 Jun 2019 11:18:20 -0700 Subject: [PATCH 336/550] Add changes for threaded recomputation --- tests/test_early_recompute.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_early_recompute.py b/tests/test_early_recompute.py index 36f31d5..5f68c73 100644 --- a/tests/test_early_recompute.py +++ b/tests/test_early_recompute.py @@ -156,7 +156,16 @@ def plot(cache_times, worker_times): repeater = make_repeater(worker) +<<<<<<< HEAD with multiprocessing.pool.ThreadPool() as pool: pool.map(repeater, [worker] * count) +======= + import multiprocessing.pool as mp + with mp.ThreadPool(count) as pool: + list(pool.map(repeater, [worker] * count)) + + # with concurrent.futures.ThreadPoolExecutor(count) as executor: + # executor.map(repeater, [worker] * count) +>>>>>>> Add changes for threaded recomputation plot(cache_times, worker_times) From 5caa8fdb94da3b767d230781ae3f625f4cdbe897 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 11 Jun 2019 09:07:53 -0700 Subject: [PATCH 337/550] Remove early recomputation and move into core.Cache --- diskcache/core.py | 127 +++++++++++++++++++++- diskcache/djangocache.py | 74 +++---------- diskcache/fanout.py | 3 +- diskcache/memo.py | 193 ---------------------------------- diskcache/persistent.py | 13 ++- diskcache/recipes.py | 143 +++++++++++++++++++++++++ tests/test_early_recompute.py | 9 -- 7 files changed, 293 insertions(+), 269 deletions(-) delete mode 100644 diskcache/memo.py diff --git a/diskcache/core.py b/diskcache/core.py index 5fb1f00..5047ef8 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -19,8 +19,6 @@ import warnings import zlib -from .memo import memoize - if sys.hexversion < 0x03000000: import cPickle as pickle # pylint: disable=import-error # ISSUE #25 Fix for http://bugs.python.org/issue10211 @@ -40,6 +38,16 @@ INT_TYPES = (int,) io_open = open # pylint: disable=invalid-name +def full_name(func): + "Return full name of `func` by adding the module and function name." + try: + # The __qualname__ attribute is only available in Python 3.3 and later. + # GrantJ 2019-03-29 Remove after support for Python 2 is dropped. + name = func.__qualname__ + except AttributeError: + name = func.__name__ + return func.__module__ + '.' + name + try: WindowsError except NameError: @@ -357,6 +365,34 @@ class EmptyDirWarning(UserWarning): "Warning used by Cache.check for empty directories." +def args_to_key(base, args, kwargs, typed): + """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 + :return: cache key tuple + + """ + key = base + args + + if kwargs: + key += (ENOVAL,) + 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) + + return key + + class Cache(object): "Disk and file backed cache." # pylint: disable=bad-continuation @@ -1725,7 +1761,92 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): return key, value - memoize = memoize + def memoize(self, name=None, typed=False, expire=None, tag=None): + """Memoizing cache decorator. + + 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. + + 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, + or for rewrapping the function with a different cache. + + >>> from diskcache import Cache + >>> cache = Cache() + >>> @cache.memoize(expire=1, tag='fib') + ... def fibonacci(number): + ... if number == 0: + ... return 0 + ... elif number == 1: + ... return 1 + ... else: + ... return fibonacci(number - 1) + fibonacci(number - 2) + >>> print(fibonacci(100)) + 354224848179261915075 + + An additional `__cache_key__` attribute can be used to generate the + cache key used for the given arguments. + + >>> key = fibonacci.__cache_key__(100) + >>> print(cache[key]) + 354224848179261915075 + + 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 and return 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) + :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 is None else (name,) + + @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) + self.set(key, result, expire=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) + + wrapper.__cache_key__ = __cache_key__ + return wrapper + + return decorator def check(self, fix=False, retry=False): diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 6b16a87..995fb95 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -12,8 +12,8 @@ # For older versions of Django simply use 300 seconds. DEFAULT_TIMEOUT = 300 +from .core import ENOVAL, args_to_key, full_name from .fanout import FanoutCache -from .memo import MARK, _args_to_key, full_name class DjangoCache(BaseCache): @@ -361,7 +361,7 @@ def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, - typed=False, tag=None, early_recompute=False, time_func=time): + typed=False, tag=None): """Memoizing cache decorator. Decorator to wrap callable with memoizing function using cache. @@ -375,20 +375,6 @@ def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, cached separately. For example, f(3) and f(3.0) will be treated as distinct calls with distinct results. - Cache stampedes are a type of cascading failure that can occur when - parallel computing systems using memoization come under heavy - load. This behaviour is sometimes also called dog-piling, cache miss - storm, cache choking, or the thundering herd problem. - - The memoization decorator includes cache stampede protection through - the early recomputation parameter. When set to True (default False), - the expire parameter must not be None. Early recomputation of results - will occur probabilistically before expiration. - - Early probabilistic recomputation is based on research by Vattani, A.; - Chierichetti, F.; Lowenstein, K. (2015), Optimal Probabilistic Cache - Stampede Prevention, VLDB, pp. 886?897, ISSN 2150-8097 - The original underlying function is accessible through the __wrapped__ attribute. This is useful for introspection, for bypassing the cache, or for rewrapping the function with a different cache. @@ -405,60 +391,30 @@ def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, :param int version: key version number (default None, cache parameter) :param bool typed: cache different types separately (default False) :param str tag: text to associate with arguments (default None) - :param bool early_recompute: probabilistic early recomputation - (default False) - :param time_func: callable for calculating current time :return: callable decorator """ - # Caution: Nearly identical code exists in memo.memoize + # Caution: Nearly identical code exists in Cache.memoize if callable(name): raise TypeError('name cannot be callable') - if early_recompute and timeout is None: - raise ValueError('timeout required') - def decorator(func): - "Decorator created by memoize call for callable." + "Decorator created by memoize() for callable `func`." base = (full_name(func),) if name is None else (name,) - if early_recompute: - @wraps(func) - def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." - key = wrapper.__cache_key__(*args, **kwargs) - pair, expire_time = self.get( - key, MARK, version, expire_time=True, retry=True, - ) - - if pair is not MARK: - result, delta = pair - now = time_func() - ttl = expire_time - now - - if (-delta * log(random())) < ttl: - return result + @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, ENOVAL, version, retry=True) - start = time_func() + if result is ENOVAL: result = func(*args, **kwargs) - delta = time_func() - start - pair = result, delta - self.set(key, pair, timeout, version, tag=tag, retry=True) - return result - else: - @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, MARK, version, retry=True) - - if result is MARK: - result = func(*args, **kwargs) - self.set( - key, result, timeout, version, tag=tag, retry=True, - ) - - return result + self.set( + key, result, timeout, version, tag=tag, retry=True, + ) + + return result def __cache_key__(*args, **kwargs): "Make key for cache given function arguments." diff --git a/diskcache/fanout.py b/diskcache/fanout.py index e89f0f6..dc31593 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -13,7 +13,6 @@ reduce # pylint: disable=pointless-statement from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout -from .memo import memoize from .persistent import Deque, Index @@ -356,7 +355,7 @@ def __delitem__(self, key): del shard[key] - memoize = memoize + memoize = Cache.memoize def check(self, fix=False, retry=False): diff --git a/diskcache/memo.py b/diskcache/memo.py deleted file mode 100644 index 67f0274..0000000 --- a/diskcache/memo.py +++ /dev/null @@ -1,193 +0,0 @@ -"""Memoization utilities. - -""" - -from functools import wraps -from math import log -from random import random -from threading import Thread -from time import time - -MARK = object() - - -def full_name(func): - "Return full name of `func` by adding the module and function name." - try: - # The __qualname__ attribute is only available in Python 3.3 and later. - # GrantJ 2019-03-29 Remove after support for Python 2 is dropped. - name = func.__qualname__ - except AttributeError: - name = func.__name__ - return func.__module__ + '.' + name - - -def _args_to_key(base, args, kwargs, typed): - """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 - :return: cache key tuple - - """ - key = base + args - - if kwargs: - key += (MARK,) - 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) - - return key - - -def memoize(cache, name=None, typed=False, expire=None, tag=None, - early_recompute=False, time_func=time): - """Memoizing cache decorator. - - 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. - - 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. - - Cache stampedes are a type of cascading failure that can occur when - parallel computing systems using memoization come under heavy load. This - behaviour is sometimes also called dog-piling, cache miss storm, cache - choking, or the thundering herd problem. - - The memoization decorator includes cache stampede protection through the - early recomputation parameter. When set to True (default False), the expire - parameter must not be None. Early recomputation of results will occur - probabilistically before expiration. - - Early probabilistic recomputation is based on research by Vattani, A.; - Chierichetti, F.; Lowenstein, K. (2015), Optimal Probabilistic Cache - Stampede Prevention, VLDB, pp. 886?897, ISSN 2150-8097 - - The original underlying function is accessible through the __wrapped__ - attribute. This is useful for introspection, for bypassing the cache, or - for rewrapping the function with a different cache. - - >>> from diskcache import FanoutCache - >>> cache = FanoutCache() - >>> @cache.memoize(typed=True, expire=1, tag='fib') - ... def fibonacci(number): - ... if number == 0: - ... return 0 - ... elif number == 1: - ... return 1 - ... else: - ... return fibonacci(number - 1) + fibonacci(number - 2) - >>> print(fibonacci(100)) - 354224848179261915075 - - An additional `__cache_key__` attribute can be used to generate the cache key - used for the given arguments. - - >>> key = fibonacci.__cache_key__(100) - >>> print(cache[key]) - 354224848179261915075 - - 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 and return 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 bool early_recompute: probabilistic early recomputation - (default False) - :param time_func: callable for calculating current time - :return: callable decorator - - """ - # Caution: Nearly identical code exists in DjangoCache.memoize - if callable(name): - raise TypeError('name cannot be callable') - - if early_recompute and expire is None: - raise ValueError('expire required') - - def decorator(func): - "Decorator created by memoize call for callable." - base = (full_name(func),) if name is None else (name,) - - if early_recompute: - @wraps(func) - def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." - key = wrapper.__cache_key__(*args, **kwargs) - pair, expire_time = cache.get( - key, default=MARK, expire_time=True, retry=True, - ) - - def recompute(): - start = time_func() - result = func(*args, **kwargs) - delta = time_func() - start - pair = result, delta - cache.set(key, pair, expire=expire, tag=tag, retry=True) - return result - - if pair is not MARK: - result, delta = pair - now = time_func() - ttl = expire_time - now - - if (-delta * early_recompute * log(random())) < ttl: - return result - elif True: # Background - # How to support asyncio? - thread_key = key + (MARK,) - if cache.add(thread_key, None, expire=delta): - thread = Thread(target=recompute) - thread.daemon = True - thread.start() - return result - - return recompute() - else: - @wraps(func) - def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." - key = wrapper.__cache_key__(*args, **kwargs) - result = cache.get(key, default=MARK, retry=True) - - if result is MARK: - result = func(*args, **kwargs) - cache.set(key, result, expire=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) - - wrapper.__cache_key__ = __cache_key__ - return wrapper - - return decorator diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 623c56c..a8b5946 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -1326,7 +1326,7 @@ def memoize(self, name=None, typed=False): >>> from diskcache import Index >>> mapping = Index() - >>> @mapping.memoize(typed=True) + >>> @mapping.memoize() ... def fibonacci(number): ... if number == 0: ... return 0 @@ -1334,8 +1334,15 @@ def memoize(self, name=None, typed=False): ... return 1 ... else: ... return fibonacci(number - 1) + fibonacci(number - 2) - >>> print(sum(fibonacci(number=value) for value in range(100))) - 573147844013817084100 + >>> print(fibonacci(100)) + 354224848179261915075 + + An additional `__cache_key__` attribute can be used to generate the + cache key used for the given arguments. + + >>> key = fibonacci.__cache_key__(100) + >>> print(cache[key]) + 354224848179261915075 Remember to call memoize when decorating a callable. If you forget, then a TypeError will occur. Note the lack of parenthenses after diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 96a9f58..6ce2d1b 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -321,3 +321,146 @@ def wrapper(*args, **kwargs): return wrapper return decorator + + +def memoize(cache, name=None, typed=False, expire=None, tag=None, + early_recompute=False, time_func=time): + """Memoizing cache decorator. + + 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. + + 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. + + Cache stampedes are a type of cascading failure that can occur when + parallel computing systems using memoization come under heavy load. This + behaviour is sometimes also called dog-piling, cache miss storm, cache + choking, or the thundering herd problem. + + The memoization decorator includes cache stampede protection through the + early recomputation parameter. When set to True (default False), the expire + parameter must not be None. Early recomputation of results will occur + probabilistically before expiration. + + Early probabilistic recomputation is based on research by Vattani, A.; + Chierichetti, F.; Lowenstein, K. (2015), Optimal Probabilistic Cache + Stampede Prevention, VLDB, pp. 886?897, ISSN 2150-8097 + + The original underlying function is accessible through the __wrapped__ + attribute. This is useful for introspection, for bypassing the cache, or + for rewrapping the function with a different cache. + + >>> from diskcache import FanoutCache + >>> cache = FanoutCache() + >>> @cache.memoize(typed=True, expire=1, tag='fib') + ... def fibonacci(number): + ... if number == 0: + ... return 0 + ... elif number == 1: + ... return 1 + ... else: + ... return fibonacci(number - 1) + fibonacci(number - 2) + >>> print(fibonacci(100)) + 354224848179261915075 + + An additional `__cache_key__` attribute can be used to generate the cache key + used for the given arguments. + + >>> key = fibonacci.__cache_key__(100) + >>> print(cache[key]) + 354224848179261915075 + + 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 and return 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 bool early_recompute: probabilistic early recomputation + (default False) + :param time_func: callable for calculating current time + :return: callable decorator + + """ + # Caution: Nearly identical code exists in DjangoCache.memoize + if callable(name): + raise TypeError('name cannot be callable') + + if early_recompute and expire is None: + raise ValueError('expire required') + + def decorator(func): + "Decorator created by memoize call for callable." + base = (full_name(func),) if name is None else (name,) + + if early_recompute: + @wraps(func) + def wrapper(*args, **kwargs): + "Wrapper for callable to cache arguments and return values." + key = wrapper.__cache_key__(*args, **kwargs) + pair, expire_time = cache.get( + key, default=MARK, expire_time=True, retry=True, + ) + + def recompute(): + start = time_func() + result = func(*args, **kwargs) + delta = time_func() - start + pair = result, delta + cache.set(key, pair, expire=expire, tag=tag, retry=True) + return result + + if pair is not MARK: + result, delta = pair + now = time_func() + ttl = expire_time - now + + if (-delta * early_recompute * log(random())) < ttl: + return result + elif True: # Background + # How to support asyncio? + thread_key = key + (MARK,) + if cache.add(thread_key, None, expire=delta): + thread = Thread(target=recompute) + thread.daemon = True + thread.start() + return result + + return recompute() + else: + @wraps(func) + def wrapper(*args, **kwargs): + "Wrapper for callable to cache arguments and return values." + key = wrapper.__cache_key__(*args, **kwargs) + result = cache.get(key, default=MARK, retry=True) + + if result is MARK: + result = func(*args, **kwargs) + cache.set(key, result, expire=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) + + wrapper.__cache_key__ = __cache_key__ + return wrapper + + return decorator diff --git a/tests/test_early_recompute.py b/tests/test_early_recompute.py index 5f68c73..36f31d5 100644 --- a/tests/test_early_recompute.py +++ b/tests/test_early_recompute.py @@ -156,16 +156,7 @@ def plot(cache_times, worker_times): repeater = make_repeater(worker) -<<<<<<< HEAD with multiprocessing.pool.ThreadPool() as pool: pool.map(repeater, [worker] * count) -======= - import multiprocessing.pool as mp - with mp.ThreadPool(count) as pool: - list(pool.map(repeater, [worker] * count)) - - # with concurrent.futures.ThreadPoolExecutor(count) as executor: - # executor.map(repeater, [worker] * count) ->>>>>>> Add changes for threaded recomputation plot(cache_times, worker_times) From 1f9731c4bbe1bf05e522dfb27923fdf9d5136542 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 11 Jun 2019 10:33:43 -0700 Subject: [PATCH 338/550] Fixes for memoizing --- diskcache/__init__.py | 9 +-- diskcache/core.py | 2 +- diskcache/djangocache.py | 2 +- diskcache/persistent.py | 2 +- diskcache/recipes.py | 168 ++++++++++++++++----------------------- tests/test_doctest.py | 6 -- 6 files changed, 76 insertions(+), 113 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 17cbeb3..dba3cde 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -1,11 +1,11 @@ "DiskCache: disk and file backed cache." -from .core import Cache, Disk, UnknownFileWarning, EmptyDirWarning, Timeout +from .core import Cache, Disk, EmptyDirWarning, UnknownFileWarning, Timeout from .core import DEFAULT_SETTINGS, ENOVAL, EVICTION_POLICY, UNKNOWN from .fanout import FanoutCache -from .memo import memoize from .persistent import Deque, Index -from .recipes import Averager, Lock, RLock, BoundedSemaphore, throttle, barrier +from .recipes import Averager, BoundedSemaphore, Lock, RLock +from .recipes import barrier, memoize_stampede, throttle __all__ = [ 'Averager', @@ -25,7 +25,7 @@ 'UNKNOWN', 'UnknownFileWarning', 'barrier', - 'memoize', + 'memoize_stampede', 'throttle', ] @@ -36,7 +36,6 @@ # Django not installed or not setup so ignore. pass - __title__ = 'diskcache' __version__ = '3.1.1' __build__ = 0x030101 diff --git a/diskcache/core.py b/diskcache/core.py index 5047ef8..10b8e0b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1827,7 +1827,7 @@ def decorator(func): "Decorator created by memoize() for callable `func`." base = (full_name(func),) if name is None else (name,) - @wraps(func) + @ft.wraps(func) def wrapper(*args, **kwargs): "Wrapper for callable to cache arguments and return values." key = wrapper.__cache_key__(*args, **kwargs) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 995fb95..fbafeaa 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -418,7 +418,7 @@ def wrapper(*args, **kwargs): def __cache_key__(*args, **kwargs): "Make key for cache given function arguments." - return _args_to_key(base, args, kwargs, typed) + return args_to_key(base, args, kwargs, typed) wrapper.__cache_key__ = __cache_key__ return wrapper diff --git a/diskcache/persistent.py b/diskcache/persistent.py index a8b5946..5f2dbde 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -1341,7 +1341,7 @@ def memoize(self, name=None, typed=False): cache key used for the given arguments. >>> key = fibonacci.__cache_key__(100) - >>> print(cache[key]) + >>> print(mapping[key]) 354224848179261915075 Remember to call memoize when decorating a callable. If you forget, diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 6ce2d1b..04bff6a 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -2,26 +2,26 @@ >>> import diskcache as dc, time >>> cache = dc.Cache() ->>> @dc.memoize(cache) -... @dc.barrier(cache, dc.Lock) -... @dc.memoize(cache) +>>> @cache.memoize(expire=0) +... @barrier(cache, dc.Lock) +... @cache.memoize() ... def work(num): ... time.sleep(1) ... return num >>> from concurrent.futures import ThreadPoolExecutor >>> with ThreadPoolExecutor(5) as executor: ... start = time.time() -... times = list(executor.map(work, range(5))) +... nums = list(executor.map(work, range(5))) ... end = time.time() ->>> times +>>> nums [0, 1, 2, 3, 4] >>> int(end - start) 5 >>> with ThreadPoolExecutor(5) as executor: ... start = time.time() -... times = list(executor.map(work, range(5))) +... nums = list(executor.map(work, range(5))) ... end = time.time() ->>> times +>>> nums [0, 1, 2, 3, 4] >>> int(end - start) 0 @@ -29,11 +29,13 @@ """ import functools +import math import os +import random import threading import time -from .memo import full_name +from .core import ENOVAL, args_to_key, full_name class Averager(object): @@ -234,7 +236,7 @@ def __exit__(self, *exc_info): def throttle(cache, count, seconds, name=None, expire=None, tag=None, - time_func=time.time, sleep_func=time.sleep): + time_func=time.monotonic, sleep_func=time.sleep): """Decorator to throttle calls to function. >>> import diskcache, time @@ -323,13 +325,21 @@ def wrapper(*args, **kwargs): return decorator -def memoize(cache, name=None, typed=False, expire=None, tag=None, - early_recompute=False, time_func=time): - """Memoizing cache decorator. +def memoize_stampede(cache, expire, name=None, typed=False, tag=None, + time_func=time.monotonic): + """Memoizing cache decorator with cache stampede protection. - Decorator to wrap callable with memoizing function using cache. Repeated - calls with the same arguments will lookup result in cache and avoid - function evaluation. + Cache stampedes are a type of cascading failure that can occur when + parallel computing systems using memoization come under heavy load. This + behaviour is sometimes also called dog-piling, cache miss storm, cache + choking, or the thundering herd problem. + + The memoization decorator implements cache stampede protection through + early recomputation. Early recomputation of function results will occur + probabilistically before expiration in a background thread of + execution. Early probabilistic recomputation is based on research by + Vattani, A.; Chierichetti, F.; Lowenstein, K. (2015), Optimal Probabilistic + Cache Stampede Prevention, VLDB, pp. 886?897, ISSN 2150-8097 If name is set to None (default), the callable name will be determined automatically. @@ -338,27 +348,13 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None, cached separately. For example, f(3) and f(3.0) will be treated as distinct calls with distinct results. - Cache stampedes are a type of cascading failure that can occur when - parallel computing systems using memoization come under heavy load. This - behaviour is sometimes also called dog-piling, cache miss storm, cache - choking, or the thundering herd problem. - - The memoization decorator includes cache stampede protection through the - early recomputation parameter. When set to True (default False), the expire - parameter must not be None. Early recomputation of results will occur - probabilistically before expiration. - - Early probabilistic recomputation is based on research by Vattani, A.; - Chierichetti, F.; Lowenstein, K. (2015), Optimal Probabilistic Cache - Stampede Prevention, VLDB, pp. 886?897, ISSN 2150-8097 - The original underlying function is accessible through the __wrapped__ attribute. This is useful for introspection, for bypassing the cache, or for rewrapping the function with a different cache. - >>> from diskcache import FanoutCache - >>> cache = FanoutCache() - >>> @cache.memoize(typed=True, expire=1, tag='fib') + >>> from diskcache import Cache + >>> cache = Cache() + >>> @memoize_stampede(cache, expire=1) ... def fibonacci(number): ... if number == 0: ... return 0 @@ -369,96 +365,70 @@ def memoize(cache, name=None, typed=False, expire=None, tag=None, >>> print(fibonacci(100)) 354224848179261915075 - An additional `__cache_key__` attribute can be used to generate the cache key - used for the given arguments. + An additional `__cache_key__` attribute can be used to generate the cache + key used for the given arguments. >>> key = fibonacci.__cache_key__(100) - >>> print(cache[key]) - 354224848179261915075 + >>> del cache[key] 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 + TypeError will occur. :param cache: cache to store callable arguments and return values + :param float expire: seconds until arguments expire :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 bool early_recompute: probabilistic early recomputation - (default False) :param time_func: callable for calculating current time :return: callable decorator """ - # Caution: Nearly identical code exists in DjangoCache.memoize - if callable(name): - raise TypeError('name cannot be callable') - - if early_recompute and expire is None: - raise ValueError('expire required') - + # Caution: Nearly identical code exists in Cache.memoize def decorator(func): "Decorator created by memoize call for callable." base = (full_name(func),) if name is None else (name,) - if early_recompute: - @wraps(func) - def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." - key = wrapper.__cache_key__(*args, **kwargs) - pair, expire_time = cache.get( - key, default=MARK, expire_time=True, retry=True, - ) - - def recompute(): - start = time_func() - result = func(*args, **kwargs) - delta = time_func() - start - pair = result, delta - cache.set(key, pair, expire=expire, tag=tag, retry=True) - return result - - if pair is not MARK: - result, delta = pair - now = time_func() - ttl = expire_time - now - - if (-delta * early_recompute * log(random())) < ttl: - return result - elif True: # Background - # How to support asyncio? - thread_key = key + (MARK,) - if cache.add(thread_key, None, expire=delta): - thread = Thread(target=recompute) - thread.daemon = True - thread.start() - return result - - return recompute() - else: - @wraps(func) - def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." - key = wrapper.__cache_key__(*args, **kwargs) - result = cache.get(key, default=MARK, retry=True) + @functools.wraps(func) + def wrapper(*args, **kwargs): + "Wrapper for callable to cache arguments and return values." + key = wrapper.__cache_key__(*args, **kwargs) + pair, expire_time = cache.get( + key, default=ENOVAL, expire_time=True, retry=True, + ) + + def recompute(): + start = time_func() + result = func(*args, **kwargs) + delta = time_func() - start + pair = result, delta + cache.set(key, pair, expire=expire, tag=tag, retry=True) + return result + + if pair is not ENOVAL: + result, delta = pair + now = time_func() + ttl = expire_time - now - if result is MARK: - result = func(*args, **kwargs) - cache.set(key, result, expire=expire, tag=tag, retry=True) + if (-delta * math.log(random.random())) < ttl: + return result # Cache hit. + + # Check whether a thread has started for early recomputation. + + thread_key = key + (ENOVAL,) + + if cache.add(thread_key, None, expire=delta): + # Start thread for early recomputation. + thread = threading.Thread(target=recompute) + thread.daemon = True + thread.start() return result + return recompute() # Cache miss. + def __cache_key__(*args, **kwargs): "Make key for cache given function arguments." - return _args_to_key(base, args, kwargs, typed) + return args_to_key(base, args, kwargs, typed) wrapper.__cache_key__ = __cache_key__ return wrapper diff --git a/tests/test_doctest.py b/tests/test_doctest.py index d171e48..e398391 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -5,7 +5,6 @@ import diskcache.core import diskcache.djangocache import diskcache.fanout -import diskcache.memo import diskcache.persistent import diskcache.recipes @@ -25,11 +24,6 @@ def test_fanout(): assert failures == 0 -def test_memo(): - failures, _ = doctest.testmod(diskcache.memo) - assert failures == 0 - - def test_persistent(): failures, _ = doctest.testmod(diskcache.persistent) assert failures == 0 From 0e63f94ff7f6f322eac7eca331605d7966ae6f01 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 11 Jun 2019 12:32:41 -0700 Subject: [PATCH 339/550] Add fixes and graphs for landing page caching case study --- README.rst | 2 +- diskcache/recipes.py | 15 ++- docs/_static/early-recomputation-03.png | Bin 0 -> 28276 bytes docs/_static/early-recomputation-05.png | Bin 0 -> 25410 bytes docs/_static/early-recomputation.png | Bin 0 -> 25889 bytes docs/_static/no-caching.png | Bin 0 -> 29528 bytes docs/_static/synchronized-locking.png | Bin 0 -> 26431 bytes docs/_static/traditional-caching.png | Bin 0 -> 28854 bytes docs/case-study-landing-page-caching.rst | 17 ++++ docs/conf.py | 2 +- docs/index.rst | 1 + tests/test_early_recompute.py | 116 ++++++++++++----------- 12 files changed, 88 insertions(+), 65 deletions(-) create mode 100644 docs/_static/early-recomputation-03.png create mode 100644 docs/_static/early-recomputation-05.png create mode 100644 docs/_static/early-recomputation.png create mode 100644 docs/_static/no-caching.png create mode 100644 docs/_static/synchronized-locking.png create mode 100644 docs/_static/traditional-caching.png create mode 100644 docs/case-study-landing-page-caching.rst diff --git a/README.rst b/README.rst index eae044c..ae0b086 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ DiskCache: Disk Backed Cache `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. -The cloud-based computing of 2018 puts a premium on memory. Gigabytes of empty +The cloud-based computing of 2019 puts a premium on memory. Gigabytes of empty space is left on disks as processes vie for memory. Among these processes is Memcached (and sometimes Redis) which is used as a cache. Wouldn't it be nice to leverage empty disk space for caching? diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 04bff6a..8ca2c09 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -325,8 +325,7 @@ def wrapper(*args, **kwargs): return decorator -def memoize_stampede(cache, expire, name=None, typed=False, tag=None, - time_func=time.monotonic): +def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1): """Memoizing cache decorator with cache stampede protection. Cache stampedes are a type of cascading failure that can occur when @@ -379,7 +378,6 @@ def memoize_stampede(cache, expire, name=None, typed=False, tag=None, :param str name: name given for callable (default None, automatic) :param bool typed: cache different types separately (default False) :param str tag: text to associate with arguments (default None) - :param time_func: callable for calculating current time :return: callable decorator """ @@ -397,26 +395,27 @@ def wrapper(*args, **kwargs): ) def recompute(): - start = time_func() + start = time.time() result = func(*args, **kwargs) - delta = time_func() - start + delta = time.time() - start pair = result, delta cache.set(key, pair, expire=expire, tag=tag, retry=True) return result if pair is not ENOVAL: result, delta = pair - now = time_func() + now = time.time() ttl = expire_time - now - if (-delta * math.log(random.random())) < ttl: + if (-delta * beta * math.log(random.random())) < ttl: return result # Cache hit. # Check whether a thread has started for early recomputation. thread_key = key + (ENOVAL,) + thread_added = cache.add(thread_key, None, expire=delta) - if cache.add(thread_key, None, expire=delta): + if thread_added: # Start thread for early recomputation. thread = threading.Thread(target=recompute) thread.daemon = True diff --git a/docs/_static/early-recomputation-03.png b/docs/_static/early-recomputation-03.png new file mode 100644 index 0000000000000000000000000000000000000000..2c75aff77f49e5404c19f2fa21265faf1b582112 GIT binary patch literal 28276 zcmbrmbyQVt_cpp{K_n%l8|jpm5@{r)kp^i20YRixQUnA2fYpy7r8!8tFXb2DlxuB-1q=z7wAqavo za}F1Nb7f%S7yJ*~OHob#9Q*{Fvx|g(<9n!@cp(U}HToY$o?NaI{8G~Uy0Q07_uJmS z)_3g@H*0SXS9fn$CmW`F_IJIU-0v`n@C)*b@G?1idwWO;2>h=%@Vno25O{OG;4OkM zA!eXGD9@S!W`rkbT$1TOYkUy%k=`J2`Q2Xs10Lc&pMfIa%L4>~-?jNAs01 zj!G#O+ zh%(!DXLkR|k3QGnzxd>2V^YS49X685(nwa9sL!%~XlSUTR*Bh%;!9av=|Z+`83qN0 zHNNW%vGmg6hugndB_%I$-bj^~mk+6JsZjl%qjjw@=%oLX?|QpGy6X66&q82(u<`I* zx{r28EVW2}&W)AZpYKhTeEhQ|!f4UI*s?J;G!z@&@%H}q0>#H-mHFSlS*4_CYHMo` zW*!sIEG|ZxRNQ737AAw+dl(lN_j#XD%KKbTinxlVCZUXsjK}gI7q4mMIk~fAkytuO zOmT7XvOkfetb#$uer#-PO)V{Nv*c*vuUlcNie*38Uvq>7gz=hX>t)u|Q1|xsQj{MP z(Mx4mHU`bE;9j7aDf%$gygwZrw?FluL~&T{ucV~pE4N9F7rY8bdw+D_DqpB*)fs3w zT%b*r_9Z&nUsowGEJ7yEjxG=rw?DlkA_$xOczwFm`tfjOwcB_WTz3@i*v7`@%TnOc zUhCq(+p;^8Qgxecv^ghCN|aAthD0%Jwo+MUn18yrvAOv$I{G{d3rlIxiGR_(~OW5p*z2O-+=v%RyrjP*Cp{7fhAe3Pu0)IR$Pl9LPE|{@jLd6e{g5~ z=(B<=ARtil@goatUGV8)uHh$_kz(t`{>*cPgrR!*N~)^oruclNyYXApMV_7< zwTzF`78e)${awo4-0qWe;Z+HIc5>$P*GKHuG9U^Yk<$oSfru;iVURW;p{3-?BAeQx3q=OFe!nhK7eL=IdvHsY-av zgbe@oADsN;Tjn+{=Djvy;!bNjPEvFBDR?eMd}e)}I9o5OEr!;1bGCK%_}4Exp-mcL zyU2sXm;B}g$Y_NF@!$RRIMXU;g?xRM%a_m1wZ*i&k@kHWgv&KtVi{*xWEv%e$hU-F z7|6fLS!n)guk1zsl>6PgcSUoK4i7V(zUPeA-QTut*RXRl#$ZcHNJ)wA?$(TPA_!BQ zyZh~>Wl)qwy}$KL)5FXB{B7Wn190=lJKwdzN+Q}G1fQMw!bK_HD#s{RIS+YraOmpl zo+BVo)YBvTyR-CGNPvqAKQlAaalG2CRxVn~B;T~^y`d)Dt%{NoHpB=*X=!O*lX7f& z>3d(&u0H7h=;MOy3#?b&nWO=;rj`wmpb>M8MC}3s&t+cT(8NUYZ{NQ47n@68q@=Xz zP8I@-{9JDTwZFhfpwP0>Vr83+gQK~%6^ESj#uo?xLJqxj5>isly}d+ZV`C5%2~JOs z+KV-kY6lhOz zZ-^Q$GK+qxe(l;d3}I6c1ql;#Qntikxh9eP}1hNr2ibamUk(lmdJ zif=`MgV=tFBt=bTwW4q1n})zo2#_A$-h)TA5DP+QXKiG52X9LK)))(Sa&kibq4nMP z$Ox6$hdZtPnb+c9zC=H?!s z9PQgeysPow<%V12Y`dwi9|{YgJqg5+heJgsN^%&b?`b+*DfU?zK7b(Ud9>&D_N5Ds zLQ|312OT}VM_#{st!KYPm45gza)0}VlF}FQT`Kl+Da`l_#^rWVi2Uz3z5GE(Gh^et z&5t!T#^;EMsif|$<_Xb@AoG?XZI4Vom0=@iD|J`ML2vQXK+(~T?1o-%* zX%E&UJxyI*3(@bPlkkvtd9%C^slgCoS{q&;r<#C|RAwMBr%vOR;oCgP2OqUJ(;sg=pn|}$@x#nM; ziD4>@-zFv|LKPn4I#mVk`0}26RO-8Kff2HCvOgXD9o=ArR@GQEo_u(g;dl-1=`B$%A(X7WsMUmFk)qVNnHPeKF5mIW`Nmk^u z^dmefD(aD9CJ96}-Wwu2ANCjHOkqb6c`%dr!%_>qZ+H-r8|kucRy>owb8Q!5=0^%m zZW>qIj_Iw2Y19dn5&A8jy^bBU3Go_;zbq& zd*dqSq~R?VR#s9<%G=)cWo2cmnwnq!?yd-VFX|voudleEwliY!Kud2m&hT15U|?Cm z;oTSwJIJG-YrTGNOrM=neYi9E&2buH7q4Xl9d{A|ve7~;Hxou>(Lcti7s#~>5lUfY zxM;Imq%U%NVscU_;K1Wcd;1(51x-@z=%{hbvH<{@f=vc zz`k(s;XD;KHug-Ckp*e4q?8mIhy3P~j7nWb_;*&vZl;P`H6by}MIW1Quksu1uI~uoar15#b&YjC!5{nA&Gzk!5<&o-34RI$4fIZ5Z|>4g-`eX zFrC9ZJ^al$_})O{)`ENE$!!mlI6X^cL+&Kg?ZH6T>|4%zdwXs0LO8&g+4SJk&yXjs zg!gc1)L#`55&8N1w^6ZQR3Rz3d^s05s+d1{hql~FXq?}4_8hI0QvY4&(J`lcb60)W=n$*^ z3GWOM<>O!B#rdxE4V+VDrPoVey@MLRpYbIeZ3DS$q(P{;n9dzpbeITXSbSAJH*WS%Ge87drTm_bf zhHo^8K6&zF1Mu10N=f63<5P&ek$4qT^$*^bmXbgoZV~B|VdXGhUY`1$9$f4@O62S7 z3$CqZ;eLOypnoZ)SRhJBX&U2WWu^X>uyPzj47P~!z7}(H_PCUto$a?)`}7IPg%;5(1kgU`O#99P%!)>v{q*x|n+|EU>wQJZoEKn=uJYe=rMm0>?nGJaIK%#D`W z>g4Ow7|7prF9^Rti;3vz>Gi);BQHACtImvwz=QaHVU0E9v@aL}X6n@klwV))U4zsh zVLp;Qh35Z`XUw=GZ*y)0{aIZ_KS(Fzr&G|=5O}1Md*e+zL&-Yi0x9xu5Rml?47Dbd zmF~7%TSaz3){9Wg?9bychw6vT7tgT3@B#pw_9qlP&z?UIy)*gos%{mtsOUu$TmugD z++NT^_SfwQFI)gn9gta3lK7YF+`W7E7AHT6qmg}^sX&$liiPdb#^A;_!7Dg8IAGe$ zj~+i}-wo!CtADURyYaJSc4cr--*u|)5_~Nw&usu%9X&m*ko3RY6TdUjFWDWNl9JME zHvo~j;F}nrpd#)p|57XSuYQAooa0i&-EDeaUzx2;NFeKeC4vVvAS5HvkAz%+?Pq@d zdbhogOFbP95oU4xg8-a*#x{1wYNGH?V9@S*)~eV=Yz)hw!|dU$!9XPY`1w$Yr5u`H z=e*J+y>J`PPCrS=#Qj+Mp6fZ^MX~9lpnySe{(P0qSgAEuc}0cKJuFG@#eUn#7;U>E z6n2*TtQeND+%$x!2b)d5vp9eS7NG>ee`C3zgyrg0dVm<$A>Cggi*2W%rR5=7I37@8 zyt=u?p_v>4@xlgNI9WDOYISvW3=Fa0OwNSXmTzD){1!Md~4qs8yJ#3=BCz}B52PQ1G^GNKv~a@6^4@6Wi9 z*McTCE-nij8@AhzN(IRKXu|Wmf1jC`mk?Im3h${(;~%KI?=?C;J_kYg`kg!c;G8H} z+U9r&5YG1VBOGcOKS2}(&dgW=;$?u+iSu?}np-)iri8TgTPRCL8v>@T&x z1~rP!@xf-zy+1UtZLPnSb>qI{kswn+M{Z~_d)0rZ>0op2Cse<$L>$n1XT9O5Sb1DI zh6YMuJ-WA>5DFG2YDI7fsYAdWQ2>rd%e=XGtRdD)MNnffJ*AV>`~7#LLr~BeDm+CvA{;0p zfA+qSPOvc|E-Qz^_09I%tSoGZE-}ov(fs){`=tdLlx^7AdR&9W=4O6}^Do1o-f*nj z-&YF#(nS5?-y^b&u;U~U%!$){YSSio2=rz=*{N8_l7)U_$)A@AIt;pJzlFyWFF#o0s z3aF{c76z=X00~0b^JQL2QW9V5wdeqi;`OV9-Dn2ct=;(}9`~ch^DPQjd5nr3>n)eX zG&MBHm1F6+uyJtkAs#CN|IrU1${Nxn%^xuUk)dH>_(ViRG{HQa+G!W1eAjquK7Be5 z`$-Om#X)z~Uw^$+D30~Y6@$f*lJuFSB`);!s}J@r!d~YN5rwp^1G2jY6w{`@(Updg7r-3DgE(VDbZ zgstvH0vuSXNinUhs{t@a&CSglTVDlI-X*xMm{vMI1bU|X^_8y^nIr}@dl+o*mE?{$ zu!Mz$MWuSllU~->Ujn#u%Hy~nU z0?aWD9EzlnXUXdNLcpZUo-FRpfYJ;Hhlf9*5E1v40Mcy^J_m8IT&VDWmwfv_sXL0< z=CJeBLN?gTgZWsHG5!4gvmgT;z^!@y?xpvNP*Jk`PS;J3kaqU<$(=m~cH_En0fSGy{pt!_!mfn!b|Ke2YetlAT_9;NGRJ?QPW0oY;dA zoey`+g${YRxIU{e28Qwr4|<2Cn~SXN$FH@@uwB%gr_2kqm{c1Fj^k5|AUbRkZ^ z=`-l%rqXBNJtKXNJf?1<5{+7+LQii+2`R6bxG4HcxM81Qeu2M<^|IB@gx^J!M}1=1X z+Y6q+h*p$<_OXDwX@yE~bG`BGi5u+XK;|_(G><|y{*1CFXUD(gz@tTrE$aUor~iiN zf##Ce;_k6y6%Ifjnx^_LbYH65>f%9B+6I&aSwJr$A=^H?oEvXv$biD zrT5z#bBIq&d|P%5<(2o!@C8WSEx_fWu7_rw(T_d_qdAq87r{Z56csVSb_H$Q2vJ0e zq5{Zf5F9psxRh8!t<=%g_09Mcc&TT=5fF+_o_8=0% z-+RQT2a{!ise}p^+#~uspfx_IVArSkZfIz0Q$c{XCkjkHc6fR z+#@F7b(9dMckkXgc2C!OQ9 zKY#vgBhA91K!R#82EQ*z%*Sk^VtEHC|8m?8mfQSJ$pui#%bFhP-bQ7<)cB0 zRYc_GG5)!8^7a0^rCz^p#Owi%q8XCp@P;!X~uRMUXtkMD#Cfr;FiZg8vo1I3dy6pO%-I%^Wc<&(G#JfIGWSpq)h zo2fE{>3GN>4GkVYF`HXj7&*P`!4ojxTiED$ESsv0cA|kA7N*r@b zOHoZt@_^J8w;sGR%vt^O$L9uBm*lgD4ZBG%UM#2HSuD0N-`3}*fG}QtXY#$sZ767B zMsoh%LZDE2>^axQ3>+ZJff*JWKLo4G0{A{}K}p(4`_BQPxUzw)H2TR`;77Gv!3T5T zSzvcP)+QuHod;D@(_Xv?2W|{9_os;!q6-(Uybb_TlLD!{)8dr$)ahWQ*O+_g?) zP+%gT*K|W5%Ky{R)9+SqL%$=A;VKHlA7bI5_xS~AA1yL0($4b&807J4^}wM6=4nCc zy~;|_qC+bFTTQ6@LS|(^D#@M$w!uXFcNTTmeD?p0Q>nx;v_d6ZxscD}42T;E0iYkf z4V8tKnBcC4Kt1+@2M>VKcQn|<{zr~=)ptYvz#<|-4s2o7aGlD?=HHbPl$=Nww8ljD za!OZA3Gxu>D}6`D%Z5PxVPN5A0BuL3jd=)Iq7(oPA%hI{dfhe=aQ~qY&saD)@#5p- zp*WC(-3*7y(uME?%TOpjIZDaztWPt#*KOiL1VBqaNFt=$zxz}mn)TOs+M>;s$hbHa zfPi)N^<_}dLq1Mk-3Hj(4!>`Qm<_!VcRxQrCU$llK!VLsX$S=!AKc{=fw&H-=qg(1 z1)l{0e8+&o1cjO?B#hZSgI&U4WMo89H0(-5dV0E=EEQ}tddjt6i9ex0J2>9%E4#ma z8->Q|*<@NNVy$RL!gW*-X#$cxTFPhHYUx`xS~Ox}Vv;a2#zBupZrFcvaAN-c!>Jy%-=JVg-bB_$P=x!T`pBAvg}B|KSB-$Q#e z%*+@F$;FGI{N^7sj7lu#Ez$r!1Ob^7u+_;4wLCjo-NHS{YALNEr>Cyw9baPs<zYs_mSvHWXdwU*;|_xZ-_Hnf!ViR@K03_KV06QYGb2}fX8 zU)4mSBD)?&s3n}w?4IgA?%NXt*d?5`S{6?spAcRCsl4eXZAuI5RUTdPS^O=>)X}_d zO59iYU{WlsU3+mhmRt#}hShGw@QbXwGu8>e6t~F)$7s-2sj{O5_grSeFNJLmlC_a) zM>9daC_I!reN)$V5MV*HAA z^Ri1pC@)^j024}<_ATx`MA1{j-(?=OY65Wi7>JdR_x?NqI>PYoJ0-g%zYI3X*~vfE zTGiT^OOWc#ilv z8?F7G{hn*-)p9LphpVZ5hEC!@t(Sx6YXC1lfWZU;Kp2B9OGQRT>h=VG^H}A$l$n8- zQS*3De)%I7e-?YMhT|-<`XiqhZhnuOKkPm-xriOo*5BXeK$@nfEp0zGHa1FVzl16W zn!`~g+gn@Q9P%vX#^>)@et9%W<pONt=Do@qX4YjX@c8t`r;nq8i+_1YTqvhm4y;o7zMOotd{b&XG-%_z`j~>w3%dtY zrQqevpc80<;_{0VkdLF#Y(z85ejr4gw6ruDIVHB>dCB8V9kpS_`V#JL8k|?rt~?=! zhldiur*#KX5cyOL`PsPtM+*R5UEs)^fg%PpHaiz5rwrMigpBMF9308RhYx|b`Ecni z2aCq-MfuLhmrZDW%1ko_8}_%pC7xD;kvy>~( z`bIru&3?{J5YHa}%`)jhRlt}XBltl(^B*!njSxHYcd4IB%lYI`CwqE%M2ksACn3o? zsZUG@oGhWE-`>IkgTsY&9(9kcR&cV8b6f^jTOUgioDZ?%3&nd-@SIM2CXG)Ty#QN+ zcg8Qzgu>9K@entffBwg2Am>NI&a*9eCgOpgEM4vTpX=%bU*6@l;AVB%EG5p^sb*vR z_x(TR``NC|FJY-}q3*28^7v(!-$iyFFQKsP06G>6+puHhp!$xbq) zzk>d=em0w=xL1-BHg*yeVEm1|Jf&P zy>@42lk-VW=8zx4XdEHCB&AcXr#P%EWAHOk-j(b>vBzt5?qNb<7}Flcud!&pe_tH> z5lbfhmHJ(ld0mh2@JQyBwtshe4L>6@c1P)jDjnXlPsLj0W7wsD(;0&fm;tlQ%+5Z7 zYAax8K&5WzO=P9&gNvy<)k-hq3tcO^91@{C45fD0D9 z-A#?wk(7~m>IsPvElrE=Ez21JW3HG7BbLD^j8_8mgCKy5p%+83w?6H@&=J`&yP@mB zc8yp7gSKz0#_Vfcj|Ju(;Ct__jy|k#=)*)}8LmE&J3YXHEIx9S24Mo&eWzoR_&OnXo<_4*+?p&}ZmM;_*D#utvEx%YaQBxW%0PtDBpfP_$44>)ixPw_y<$ zZ^2S1D8VpNzpSLKS%=*%+Q)%jqB#^l8FD-577zp|kBTYSH+R7+LRpDYSr132?tG1nEweC1 z(m^OK$NlP#+vH7>UJEnOr}{GP-WUVBs=GtqlEa&YH1n&p)Z_AZa&P$s7 zjEXrPb;Vo+?E;jBwm@Vq5QRnZ(!~X`sVtxT=#|HLR;6%QJ|+vsIL|&GgW?HfG#Gipcw9(EO99aS_*XV_wl(``z%j@^)zEb}^Ca+9&LF58}D;$kMTVpJBI3x$Q~O1Vkz; zKR*#robAA?l-=&3L7HH5bt`UbiKwV3Rcl($JW2+nb&1IY-;+-MF{hX&(R|k|cL>nT zVQ=5EqS6T{$1{P=L_z?yWkFy4->OKxq!L)m%H5^}J2ZLUV;VyP6EqnpA&P;4L9-2L z)IKK<;MTK#v$PibXH_KbnDILEb9eaUC=D>YJv^F%Pq!H%?Y@$}$ifm|X>G+&S=6r@ zshKMiXr>m#%Z-5@0#v~qH1mWYqbiyD@v=$krWaB#kE?Il>%I)Zm;rXX?bCWgbb(O` z06)w-cka+^115IP0nawXCtLPk{_0q#uRN^yts;c6y0}B9gOHaW<7reB1_Dr9IWH*Y3afFH8i3W49W= z?TK#%j@v_zNg96xw;kh`d6S)e=-sDSEt94}ylWgJh;c)JBy=+4pkln$5O4_*e6Z&V zJ&LjJc+z;j*|NA_i`F&|V$T9uoJr*lu?xKYVXc@YbM) zTZ~O$xs{FsiY^~PfTW=Cuyc5ODK{Mz)<9#DG&d|XG}-k~iA|38`7=5&gB0FkOlm*^ ztSYgNTGPNiLbJiHd>3syf{KeBq&dWLa&mA%OrVE_$F`z6s$~Mm&bhGL(KAe8HH-F9 zn%u$>1WSbre1Uiil`KT*IA9H!p%*$GbX3$i?6o<2y{z#k9%DcV79J6*QURj1IYZ&G zcS9~{n&yOj!2LL)BO-azKVxP4QF+XNQQUdAXW50xv`Hqo?=n_agS`PXPAI6CQ1F=r zeMpd{aARU&Z36A|M42!59j({#?q!c#9Q{BV3cUUmw=`)@L*_n>sGx#1+j~#L`m17$*>C zHA7E!r-4&emH}9%S#79C)NEN;zTU1W8GM`~&&@@d#kn`r&C#UcRL8aNimuuNc#hdK zb8~ZO%5pZO30gnu9R3amnkwm~Z2JTJ`2vwrkv{j&rKSAMYf4+3e@vQ)e{7*CojsqJ zNh9R0J;XCJ_j;6Tan@plq75hr>+;>FwBkgdToQ*$J{*c;+xe~}w7~`>v{ShcNxY0; zKC7f&`Y7NRxAGcnL7*={4MK2wFh}h9169Y}`(wH~QTyF)#cAk01#mi{=y}P=&@l$? zkb?cjKu!10!UV$8o-}<5>HLwWzkhZ`ltlCh>3Z2ZiT&-Z3UR=8kNDzH0aeh^AMt?w zNncQ5iJ*NNkVm9=o1-)yw1>1lYO*9Sat#qPT<-kw+=a<8ZKNX>Y##a76$9i50WPAX zMBpAMn1H@{EzQeP?x}ZI27NnqA!2Alr(esmeYA7y+P^H3Hr5eyoh<|+a9r;aXnn3*Sfoiz>zvWDWIbssVM-w9^2P8< ze<={HifG;U!Xu5($0sXZk%feiL{?6;95@skh`1$OpTr3O9tngyGhd>}(Ojk){dwS< z1QCJ8i*};v#HoH%8RrsELiK}Ga<** zXD5uZ2QygEAO9CHFN7a4Z|i#4nGYVl|GdffioWZ`^&Je}6(CC5K77CswPV0VIulw2I$UK%GXSTyp7|4jzuPwkAWxUo)703Q z8YDZ>kPnlEgjvcB64G$GdF<`%yS4{u(ndStS`82bZVKSSs&b^Iq!a<=Fte~^9kH?B zuQOCORPL$>IgGq^2(DIY+aBxp`;9Lut@4CD91*-TA>q9|*!dJ9cTvLK)dzl5g7E=R zPoR-I=wx>U7I@9WLlEdUQW}~kz>NY*v<4a30ugZQ&-~93w)9~x#3%fU<`mnq6$(Ud z>gl016VL~AvH{efFKdVHFa`pFK@pI~znzhi6?*tfNpISa2#3#%G7$+656=SA2CiQj zIB-%>*Zod=&^uvGN}A+OO+BpM(Y5iVP0U0S^8lD>ROt)5jsjf}aG~7^@6}OK*n6~9 z02Oa-3mXekGIIna|FKwjPG;whPd`U-8ZLUbH}z6OEW z+0N4UOr6Vu0FMdG@JLN^w+OLX)bTsXeO+tIk_oI#KnAurTE?1of8i3aPPUL~j7zN^ zj&k-7646P7;GCz-0Ffm{c>qfS-s8A#8=q%(kx;9dXyT9{u+Xvx`*(mbWCB4kbQ(XS z@W9T&0o9HutE)fh9C?+V9tZKn223sww3D4LwUvaMFuc^l40-B8}L7s8(b{uLFp zY!c0Mbf_L29Oy4f0W}tbik+GsVkP{bqM*oxG!yXV9IihGXr^w@-4T_%{Rv! z(ow<>^n>DKf6*2=U~5zd>EYoK^7t_p8U|6pCG<5f^YIP&YX=0#-1YKmO$Vh!V(T@f z7aHH*&K&SclnVpwwuT^7maNef2k&?c^*z8z-704TG;DzhaF_*w=mVA3#W-CHCsY98 z&gSXSdJCv2R;~h%pq~BBPKYEvNeDn>6Mae?l;m@V38bV=@AhThSvvrdM?Py%5oHmlEv?mgM*FLY#m>C=2M!CU8zs!0dAfw&nV zxWR2RaZ(e0H7@$N=MW9{pSmQ_MynwzPpPffNFpN%6TbDu;m9YD@3|!jMAx2Aj_lA= z`*{990sq+{qY15A3$17S3;O?5602Apiuj!*zAlIVs$8Y}PZ-exY4jZC5{MR-^w=2;cCYy$n%{g_;v3>{y}*FGPI? zPa<`{lOXVqvMYVBa#z3jVaB02(|=iqd-%!dWeT^(IR3puwYKbWyH0}0)CAiY7zPccKkF7FIU%p+Jv}Uldm54D-}ekw;bkU1R^72Eeg&o{ca5^IaS9CTtFR z;exXk|D_m&558c(P*)8H0Q{dTz1s=aGtwP3dQy1(unl-XKJowDh92QhU>ZXytG&8bdQ@Y(eX?hRdJ5l9A1xO?c(gAJb$sR3`mJujlosrQ5XCIFv42kqS zI~=M|gJkEk$F*33xu8yUs`7>#f|<8q z>lnz?)Rfe3aBF4(0qwbn$Y1MC*}O;_+@YW#o7}Q_(0Z8J?U=`!ariS>Ah?9Ya2+^K zQU-?N3N0HO7F1N44y#d*O^EFNJYo(^P1=Kz?syV|;=xsEB!rBj&-GV0d=`bYKo6j* z!FspZxw-h16rR&}IXQ+lp-OiA%hxPF=`iBE!OpEP*?as>Hstm0NYdKJ(5JoPd@<hf5*}FMtsYynQ z)r-pn8H&xx)Bx{OWeSnIeJ^NJ*03bpr({65$O4^f&=FDv4;Px)uJuQDJj(k*kZ~$T zClFH@14i(5;6-7ymJxDa`I?fR%qo`SBH2dZu_?33&kf~r`|lt*VBx~X7k?OuvQ`DG zKfi?k*Eoe0v;8+1ul+YJn>YQJjq}-Uv0{|>DGJ3?FDKJWklGFEfja9Srk!JBGe3?&W{B`c%gVVUNM;(8KRM#}o43%W}0e8aS z2+Nv7Ods#meLDE{@@5BDokOX4NUliEjx!+@=aKpo%@0NG?p8gY^&eYOe(JrAAS^3K zG`EIDd>ViW63seYnX~?dGO*WuxF-p6DB`PcbHqZ?0Q==6}A& zJT$Rk-6uGzap}{SP;3SH+2QXJUfb25@7+;<@O1qf;uA2*!zFyun>*e1)NBdezLxM7_}yF1gca+FDS`InB}An{T@gg$Etl+#Nh+uUhJv`LN{mt$AJYt9Nsg zkT$f?lMY;Qn&7@H_Je=D_i6=u2+zLx=V5a3nCfvKF)V?r23T4yhWVj+3?GwCLc%@? zs5_gDw_jNIbus=-dg;Sm%lGfxmRB-Gf8><&u+tJ*w)VAONUzNM)A-!JQp?=Z_vwS; z)5P%I(I*=E$$MY9Hp@RTUpl;=^a45c3X zj!W4))}xpeXK0+xxp{SPZ&+5N!RUSSmzZ|ubMjHo--h7l&&91zkqk{#5%1;R`So*L zPVdWfv}E4MWL25m$55O_*TkA#QQ__A?sDzCk)=%UrsozhKGlbZ2N#Zmq$XMROpwd` z^_GVZ_`4Y=X;!9k8W_|oiHmZ4FD-n;Q>wr4mgZnKEa#!rc-^P9yo0k#Cn-y=6^DyH ztFkH9!-2Gjy1}F5WA=tY>>GPOt|s+5{Ia#!3R9m99vsguPM4l~LO3+G5WzZ9`ukSS z{OSntY~G)_Cvay=t~dLp>8I}ClF1Wcv6^Wje5rq<1xMR21hP6WyH+g7bL$D8=ba_i zpETnBn8|25`2PKsv$c9XA&gbBg}4nG3#oCA`}zbxMW|k%wBciLvfys-)wu z9{Z<+;`uL4axSdN#C0FDs2Nmsn|8Y3M11dl(X={F8 z8>-6tq+;F(nJLbzcbS~DNbtz@N5pA~T%0}$=Ce-6dBGnBiGn z#E{$e=?(a%zh^}2XD+b>8JSAsshk!cliy)>aME>Af~V)63RjD4q?CugQ|cL=mkU!i zoBVqDXsYX!h2m+dkXiGkp!@He7bHk7N#i5DoAUXm^(h(pdY$Q=7xC^l)g5V?yrz(! zbnDhtc=4_NI=y>-t@td~@?3i&ArAW#whcZWwJ;Y)2xYORJ9WJqLt5smGh%%i>ZP#? zQ2~sVeB?zDnc-h8Cg1}MF+|t3m|mAH+Vo1W?{f;dpsb9r#4S8md~uGvC<@W5_yx~tWiH+>L}=Er z98aAKo@2#J@+9ez>1+IOz*t%1JrmsHH0y0q?<2?E34LeoTz}?n&ta-pz1`Af$BxbR zF#ml^$l?BZ;j6Ip8_%|~w`VLh7PWUb_)RCbvn^~yj10aTZ`t4nj(pK5#QhN&<=h(a zcK)YYNYD!pu6o|hHkzBf1?AeNU+%EvgcNkh)M$d5F?%-{86tVN$qIb-FhmtBxjap@8MN;w zFAJx$ua57J1fN-Ie2QAMzD0JUjnA&}1zCmAD%NF7u{TJuNd+#DmdP^rg;5bLR3n)% zou;a-E$-`>wY9Tf9=e{orLRXA<`SDi-BtWGE8^k(XM<{zB(ExnGv4)XC6PYE&XLf0 zZxHk$4A0kJcFDYna;u{xf-}BtKfUXzp{f11WAUh@?AUAV-*?!^<%<6KN6HNZ5Mz{e zOSmw<>e=>(NrnYqPmB;Lq(w>PcQe;M?q@r@fgnc+|NvFBn-oWUH$84 zr-y4$U5S+~^@LL`vpbM#Be8f|@4ZpN->cD_5)_Paub-#L^Zp?#b!x-VPp3BSh+s)9 z8*@OFcXKdQ1i}!iTquznU}jhoJWjK4*6jBn}T>n`O6S$%vCn~X=Z z>tI@cMo@Q)PK)%lF1vk0)oxhHDNB9e2khy!T4Q`&r%&Dm@~o8RgB8{3>McX7&0Xg- zo+miT$nLK1-!g3&tF|?v`nHWBbNHqb@!+Z`)sy=!DYVB~hkZQLlK#73d*wiWnR zw{LR*>-}?my+mjd6w9E>9X>=GT>tvuUMMyODwTrr_nYQ%9SVx9#TyM%yO-A{AHL5c zk)yAROeZolzC5oNz{z<3{>MaJgMpLKFSkfCh>!X{q$O3A2jl){=;_BaE`Vwq0 zUv!ZRJ)N4w%zHu*Z-%Wo>6iD-mPf-tKg0KEENLPPF6we7W!9;i6!TfvJwQ>HS^s*N zgl9epC;w#Oua2FY6cn<1(-nV3-h9-H%s2X`N%zY0z0FBk#32xCyD~CnjjvCq)izHK zh|>kW{M;Vl5!P3uqf6as^Iz{hF+Sh3;hR_EV8vMLD_6`)Md10XIYvF^E7LV0+D!ja z%k;Wik@+uq+#7?Wxi9X%cMir&dE);d`O~c14T8_gawkwoqLakOFz@6zUR@+{0!pbC zkYJU%PslCeYUIBQT5On}zS1sHTV*ub%Z7 zH{{hAYXJEw&|N?*h?)^D+^)dSfC*4&d;noZgcedwC!u$M9HPGB{JeJ`RIBhO#tE2| z{0-wF(5)Ya=_}N@@eqA*YyJ73OMfM+E_wO#rOAl<9kCPyQ~uU1I#gJN3e$k{L=_qY z1O$S19VBRfPf1DN+BN#`@A&`G0!U%d30fhbK1Nj|Apao;aYj3|%qs5?0<*@FKz;<2 zlBSR^$-*FvvZ6#Y+UW&J_3g4Z3V;6m0TK%b4=<&6j0gLFEeY<{#`DM&H>DmbanSx4 zj_rWV7k*q}b_=e`LW?%dQBe?DT%xjMv=D`t3E|~(M^OBSDP}eS>yYj!33Hw3kSmNM zYmrlv##7irPh2b$D1SIF^iyc_9Imv0Ua0`a4WGh{7pxr9!omU-|3?tfqdLa29#DdT zEdD$K+Kv##O!y-|hR6IyHjoAcwkTZ!3gCfqdkwVHL9?P~8G8zCKR97LOH0d|+S*L* zbXj<$K>rgrpu=FUBoyXv;4}u_8}Jm~swG1Nfoz4g%yPgq=*!UggNgbFBNtO5BUMqk zSh+K};lbXxJ4*S%$l66z*sH9Ji)w73fI|?VQiY%O(b`H=xCAgU1pG!S~ng4x6Ea@6hAR9=Hvv450S$%FD}9 z)frHe(S5XdA<#yrhhaf<{;}-{W$RFd9q0fd({M~%C@CSxLns6J{s~>t@t5DnFiwNg zWwxf!GKJ}F0d$Ohw({vyLW~gfri<)>R8>Q@Wgh!$=AN&CN&>c(6CH5lc>&U+^V~`5 zsj|Smeg>wgqqCC~g#R$CjSW(-Oi;@;1E(Z9Y_JyyB|imdf!+!Q104;Mu|T2vSDzyt zNVLUy7FqZjdhw~LsR#n~yNQX(WiBoSXz!q%_P6VOU0ux}tBnPf<0Vuw1~E}sM8tDt zm=DCQ6mXpuV1GrYL|~2*#^E=8z#?=^O`pKPD;9z_D<&EOX<@c{@cnF3prxxTFF0oe z4E0BTPe@2WwRLdpTkeFw?x331AsDE<1oLPFAZ-C|u?cz*=u;4YhKOm{hZIJFje+_H zoadDwbQ&CiL^|T94c&H83nNtJ`RvV`3y6&(6$8T^k4Y?C!VqZ7!E8Cg1S9@1jX{n} zM3)zTnT+(+YhXf{Sp3Og7^4YVg#&Qnn$VdJ3JQXzpo)!6ma@GTW0E=!LM<001BUdz zLJJ)yvVi9QiYwk1;N?2ju2;@#S(vaa{5X2hP$U^V< zx&qTdFc%d7?E*N3b{=S>9neOB>HxSrJBq=%gR9Fx#-QV+k8sX!G@hLhTU%Qjm)YPz z4?K4lgjl zg)hP{&}lH)zl*F8X0qTGsKxK38jizppo>`?%qNG|^jW&<36nj#raG4#gJ@fG_IX18NhmV;)F=k}n$a?UR!6DwhZd-PSM=cd+<2&ih|y z8Ibwx%EZJin3+Bg(;^!?JB1s`kSZ5D=jZ2{FJJD+ac@Ytj%i$EdIcuzSo!$0Z2jSs zKx@p#*8=TGKGVu3@Q>YUUG5}!7KG{dt5>hSLPvm1E}5-0ut%StzH#FQav}OSsd%OW)X^GRiT~OL^zLQ$>MPw}u%0479|B zK!^^r9@wyP$#TK65RA+GHf`$Hx%l{6p&5ogEC^z~zD*^%rwgpn*roq<8c3V1PUr|P zblUdTCP8Zo03<9uoe@S%OfOlZt1`jH^TM+tfYB)EO@%>2vdud;f#_n;2EmH`+iX;P-76n<9Yxh-pH+R8d>s&M`_#{I77AmAe=#od6$$i zS6l>T+hL+8^2rnD%{pjvJBre*^x-~!$c&obtynh9jNDvKm@UW{axT)SP42nvSY--w z|Jv(k=;#1sbTKz{q)OM)GS0~2es+&eu0D6BKKJ<91(*>dm5gV@WJ2JYF#XR83KIak zXn*?JHFQuAK>)8PL8}~=KQlic1``=SWTv71Lrg2l;lpY%c9>QW1t7_Vfg=qL4l=>4 z0|4paC}sl#gJ)kSw-QwT!EoW|RvS|D&;ns9URX@V3M{A#m(OD8?1_oi!$8jXEYj{it?puQf z=1~DFtTTb(CkCoeg{~(Cf(lc03Wg+tjwR3*E}GOmd|;VM-&kc3F)^_$s6GA(GDNInORD(u! z<%cpfX+>!+fPe|rkk-}7K%;6F>=mA6rJ@J;F&QZCI%;Lq_~nZd zA&uxcK-72$7&_EObFUoI6mH+nF(0Rv@L+_3DU&Z4LhrN&W(CJr9YkrY1*u&bl-SI}=7;>8 z&GWSmutI18$whKv-OyPTYf-0p6|B9;j`J`R9l0SW1}LmwUD z=F1I_auC(zARlzD()wgtqztp#DSh8pc7eHiJm3h?qRFMCJ{)9>pL$LK?Iw#j#F}3r zjdu}w#g|-hNMb5|-3EgROVDAPd4it4`}=35Uw1>bF=77V0+`923&9ACGiD4pmV&S| zqTwFEB^^VeV8D@AeO{SuY3EM&Pg<;ATX(@DI@`(3Te z#f4rLx=Koaln&UQffZ+FWnqHqs?w<>FE3=wwc3Ril>PAhrT!&vu|HC8id%mr@N~}K zMW0N7kkHabgFXV4?)y&q7!{nryd%s({=0*MB6sQINmV(PtXL&Kq`RY|2?hpYp^0kZ za0;zlD$u?`g;eT73#bk7++)LnR*01tqS}}P&Q-uFmQPlJO;E?Qtpt-Ru}LbJ*Zrts zB-K@6kwM@YXw;~byW9@{eL;#q9uYqNXH;y9ielFO{NDt>$5)hYq)L!NHiJqMqd(xP z<^NIJm4{=w_S?shlzB)cLqwAZJIW9;CbS#LJgZ)_(5o_3$}1sMK2b?q*~yd$2}vkJ zR8o|AiVUd`;jFv8_xbj9&iT%Du5+F9&u(Ad>6z~P{;l;}Yuyk;;I`Xs5++iLPNLRD z{oOim!XHy($l%msJm*lFzGa%la*>JMRB7WP9o-&U=t8YRtEi?mJ9ifQeD{o3SKhR#1G9f6)DN79PWU?5!~q^a$HKy5 zrdUDJ=kVe}&Rl(i?%(}VFEA-fjE!H6HotjOg(;L_6??&tN)`PQkvl z0s#~x5nx~sgK;oC|0NRMF8yy;?CrV5DXk@Y=;c~S))z{k_E_afV zw&3RfGCh4Fd{ z;cehmr0*4{ciHmlzBD~x6M6g1rThZ6v2nxpOVOM?Ygfi_bbWrvQpw{PDL!4mcJwZt)s*kig7Fr$W2iX-AF zbHvkAID-!H`Kg77)>u`fF6pz^iv7xOwNClT181wvwaD}M-3K!`DK_b)RqP7Dji<; zWW5;})xBW{<46NmX~_*a+3vz5PgFBAar<<_X-P{9uA>0T{8UEQPw z#xI||*ko-;r`%i%<59glwv%Va7cg=|43AAE+>%+u69TqQ2R^EZ2@tx(bxuC0^1aGl%Xkt(&57i89|`nO~TC9L4F(+#fo zidXGq^1{4HOr3o%u17~}OtV9RGSFVCS@L7th*MN`pKpM+cgAW-?-KQWj%J!R8}%|B zn(D=e2anbtD!(TFVr8L0@@W~#n9%FYY$1T!okSgn+73Bokkae!6D} zkEgj|W@pd4FaDFVs{WNHFYPjDkYMhB(ikkBX9qFVSX+Pj`sL-zJs4ImN8AMk1+{KA z+JOc^QLl#>BzRVKTUxf#=7EB{Z-j}J1FHflhoWNU6xaAsWbl+!Vmu2&43`bw)rmU0RlwUJb zwA?B^eEZ!Ym5iBQ`>QN65;V=-CpG*hO^Pnv4o=-kr(Pf0ME{giMc9Y3nxO~3_v6vK ze_XT-EMM|(zp>&%zEQ@K0Hr+oChzhhD=YP_is^hzOz!Q?uMTZ0x08RXE^^XUi7))A zv;6jRg! z4Ws-QUSVOHfMoDSEVAn_J)ef=?}2M;a|HR?OT5cN>aR`Ig%qo)zWsU8)INzc%~Un4 zt*881%2|L>-29}5AquS$pBr0v`}GBo3>W~8;7XNmUMPkAe!(Lf&p;ACCGky4mu;nP z@0t9$FL5^ll)bc_T5n#`_3ak)SuS)wA}8Hb-{-f}=@K;Y(>IGAx8HuXe`boleyldT zHEADTxRg2-wfq;h`RV3TFUa(%5aZQ4`}Z#)HUpI3t$C3!LZC1j@Xc@T8t_$RU1&R- z^#wh!~uwup8hc{1W zw)8(!Th;kd;)mwtI)Y4-3Acy za9h8~bf+IXCMhRZ@4{TwyGRtiQg;nc`0in7l|wWWpSXDNtI^c0A6{){l@VXgu)8$O zaLRtpf&Z`eqr#KyzEy0aJ8ZAv(^!7xiS3*1Z5a%!nyrC47v`(ASZCI`x1656SlyrV zG^2WhkWtt_hA?+}fDorJyX&CC0xHVi`o%^F8j<>A6FlojB(Ly|kywEB>(?inrYm@N z>4J(x?&Cr#+wL(l5y66oGeBgBpIS*t$=J8NQEl_nk124tk8DP>8@1XNg>EnAh_9tT z7+52c zgLW{UDYqslxe7t+|3Sm^p6J>h#Wt67+vR`dQ4tgONgq&Tv~WesO1ZIm!Sq!yl=+Zk zaZsc|gAPd;G^?a>1)D`VoMrEBl)oMQypbJnsrgCwgECjD=J|8ConPSZCMcE&$AL?T z_m4+oN^4&L0Wh)B8z`hSWGN3#48P4`S*3qIdrtjO!^$w(Ty4DEQy;oOE6*bG`STrA z-O+K(OeCo!qckfk%i#7-GutdI5e!D=^=xVj>H{Yi7Y7vce{|(|5SZmKI}5=vxM%4* z>IB6-<5%XOV+8_(DgruVF&?Pm*@;$gYxn1Zz)F$`ptN!Fl~P7isv*Z7iQ#ska#kMp z^YcUeMBbt^qFe_7(D(|)#7Y-LQ8u^s{BGgS!`>0-LC6=8lSc0d4FA7H!v8&t*5AYA zg=8lDdI(I_pwo9-S-p2@M>q&KXgrcl>!i3G`5{RM*5Sxvw!|(9LD&J1#XoS{(LzQf z%xTi~eDfy1r8Fmp??Kx!88kKcL_|LI(8x(u#28aZ!(Mld-n9tC9g%l~NF`adAUG|C zHF39yX$<#tC6Gg0x->2{zX3nf7zzQ>u7UPD&qOYM6}-rJfSF|oG9RR?>n?2&!>aeU z$r5?HjUHK!fNh(EGc;Flp7n24CWoB6-GQ~i{BML!qcr}^XT8~wwd}7+Nj1^`>dxj& z0d;h}{z6LkK`Y|;ll1tR8eY8;0HNAI%X_1o`;IfiGy+*J{>MdA_bdX+Ok7BQCOxJ? zDTD$PBPAC?m;M~rTwGj0ITU65H~69M-GeFV&3y29kKx5jmmZ@Rgb*>_2@U)lL&QXp zNnTc#3!-ankO4Frjesol5r!%*+_|&9y0?^zPEuSDEEw{wA2TS)LtdupyB2o9kx%14 z4_V(O=fH-P2(SHYr`L zsAq>4*Q6@|f$kehyz4Hpeq*h$Xyozfv+Q>f9bNrsOi*4$4`2hhm?khsCF{3`qKZz* zF1Fa^j4?P8#&kiX-YMU%zE9_nd(rpae3e9=$4-5rzPOPG?I@`*nasPc3gbtmIYMZG z0_S9glFS5gJy$s5rGLkel9EL#oFP44f1*GFz1g&U3PP)?(6mgk5$%0-M&+QtV!9Qm z*eC0H6wikjPAhJ7bkHsk1N9UWCyzfT)C!##A8v{d_}t4AV*qz^y&64yFrwk@RJ zT%AB)!@HN+xp6l)2l=86leG4u%?|*4VSAqmy2EpLsf`PYw2d+MzbwJeZ}AU zal{UDc}pJneyNP=pO?(O*X|`G530JVd~ccDr^(LI9aS%qsv7icRSHwyNb9nd%?q!J zwGLGEChsDLRkSv+`>FDic4xdVtmJb_-1;Jwq`7UFyVN;D?sFVZ1mx0C!Ze^)>`U>3cLHG{=?}xpU8I)>dOpQ_S`Xc;lPF5W!WB@ELRtC z>ByrUIa8iKj+yU0PhPzwZ@J;EWdK_Ur{V4o@>6d--baIpYgjP+{qy+`fjU{<3%L(= zNb&mUT^CKO)YOzSuzs?@)iV3~QkUrP%B_J}CD|$}`t|`f@u|Jor67as5uoG}JT6M( z)F*G_&#n5}S^8|h;AJyM+;Vrhs=O1eyQgM+wMPnq@Kd4Wr=~kEeh6$(bB#n&QzDj$H9i_%%@ETLKdy_*TlNA#tVvVgEL*r z`rDh5E!tTVjL&L*5MoF?n7#4jyt>u*QCoVp@cit~=9FI}Z!DTWV0P|@0h@mTi?ii* zTZ0psS3kEuzm(P4{EWq@bXsiTufi5z(-oGfRg@YU%*+*7y?8TvCubtKd5&A7m7b&X z$-qY};i*ICx5>@be-Thf{(o^e>-gwB>$UT#y-OwdtP?r4%shF@9LnFV=BVAA%~i-b zW#3k9$}@&)T8*c$Y>ur|k5&p|v^YhJ8_ZJr}zvi=$^`WhAP?7IW( zC(oB2&}{9$B4Gr}P3Cj+hA%#5+$gk7$r0tLj4JQwpO*W_N!!8Jd7kq%N1b<^8$7E} zbF{PUg#+7)D{|&u{ax;37g!AXqxtuoejTo<`8tZE5`lLlDU1pVd{3X6U1yr<3=>a* z35GC=*ezfTT#3r>FB1g>Mm83)NeAY(pfpHCEM^lYl zlB*)FpOe#xl@g#h7B9xkC=c@n(6i(p0cL3#8SZ)W^K+A$>R%gl=*Zpq3~we0cqMW5 z&ueO~T7CaE)1Ri1Vzg3{+B>)R++40^*o}{U`hwxA{I)!LVeG;2)`~3q{WDK&n~1Ab z3PAwJvQ3A0FK@KvrsNarP$*X+V)k@xZLQEO$<=6R2tml%fmRYSj{av)e}C(@E0S3f z^scLcQ(e1;IxjPlo)~d&@_rpHb?aBJgD1>htt{eP(I%!S5)|O{&^@}D+R-EYSM?7$ z!QZO?yy+y!r$9X==3_a8#Nzk5W(DC$dEsEoAg!!sND)`6w8rLaZJbQJo-|ja`*lT> z;RQia^|sF%@3<-_w!X1xyR2JT=E0C+?ttdPUNHfspnFRbO$k+@Mc_^o6Mz|48m;BT zRb)VcySc4*oKBzO+Gm0}c4AL&-n{bA&=BIHT7sX*X<^AS$5Q5|&sFF0%i!Q(1GtB3 zrzp$OXLj-{(KyCN5MHe}Xa=1nej+25Re%07xOS%5At$+{L=1^eZRf494hBi6uuo=md$p@_|M8Eh!Q0Xi z)$yYVQZ7u#f7aaG78!3^T$B@UG|^%%UVJ@x1X2LEX_hjizc#uAV?mJo{Xzx%|EPa~ zj2>iwU>(gtpf^d7C$U1r;*RXy7*CS)Q{5?u6o+fFMU07t2cEW5Gc%4LMGq9y6{rq~ zxO8AEWul0m6+2VF)OLWX6WEO2S)^Ws5ZN~Yr%U=m&<~w?6qdewtstpiJ31v2YTNAp zd5$<$NAG!m&RFw5+J~Q3g}eT2dQ=Dft1d?wx5t01xHc3?#mC3RoknEC4S-n?>gC`< zy?d(p8l4UIpiPM)3?ALpMS{;rqg^bZhmU065%K^@e2{r=EZPu%P%%LsxvIMQ`sLUh z0_6~i=&@{vi6r;F{Af7v+7cc;t z(~Fl}j@GLTt{>TO;m2g@{8KnbN&Gs=j`KY%=ZS_jiJHQAjRJ{G0$qvu*e3AX{>Ie8 zq_bz)Dk>`MaH%rpf`HEsKtO}8L=H0Ku{p$YG_?=WXg)rvBat;_zIJ~b`^n!&R)gN7 z_az(zC%LXb_2U+#zCm3_lydI_`md(c#DoGKlD!=0!+Fg=4=77E-T;Lwa3H}o?wQkDq>cJ<+2CR#yhMHS{ShLz zpfKQdGEBS4_9RGjTzk*zK{;O;_u#*%Xye2l+3-*Z>fyBt_a%ZGr10MR{^MocOi$de zM1uwRE)MDn6(41qWg}a(!LXn^N)7!%qR(KVNccVJVgmi}S|!}yXKI>q>sDpo!)>X1 zFp?iW$b9Atvb(9oZKgs#|LdXyJsl-!PCl3X>d7Owxlus~DG4ef()Fe1%^A8Up zh5F8&JFNSqL_y!HLmT!PyWEgnV(4fFe#Ukb2%t0YJsj)ved?)WBAqit^x{8Tx9+&K z78ydI7no&^1PVngAO{2e(r2WIkX&G2!&uT}N3=Gtv$OLX12iPWf~M?&2e#eV#gJ=E zm0q*v!GU`Ngu9V_@Ln=a^5Y>lw@@pObb@vM{-^17-w$rQ} zw}oW)j*)_HZj$fCqdxZ2r;7>pu0`GsGJ-*7l0Y7$dPQjjPIn+Y@Gq!{@GP!@sMV8s;IdlO>qYG6&(it(V19c<<+t8OGYl zFWZ3iA6~0$uN$PiO`~tYK%kMGg9on$OR6j_uZ4UM zc*R`w2z1>VJs3lO_NHyntoUCwrF|^n@D4Lx{RG^pUc9z$&pxaRQih5NpYBBCPZ-D} zT7G^?*r;Q`C!CRV?kKZ_q+!vC=z|n-y#fJZqa+9SFW3hPtOesGgAv-zzLL*ykC#{W zH!Sxnni=?pqegX*+m5@GQww zBn$zj4&*UY6nVVK{0?0x3Csq;Sb%n%*I384_x`Q!r`iFEjL3>;RzUwON#Y%$$D_({ zfFv|k`}Qk>g5e0B0>geDiXI6-V6<2cJoJ~v7z72nm?Bdv2s#ovK_WSNS|ucDOQ0-h zT!L_vyPZ7LYnl=&2)E3o`_h|GT`r)=;94EG2&ezB&i}mrDkMYnC8dNjG6jBq3a?;in{rUYSWadQ=7fYZ5@t|d7j~!5ZyUfkQ$tDmzJss%&F0mHl+nrAM7mt_Fwi05GED;0ma@P>P4U6Rs xJm)D!t8gAmPXUiY8Xscm=a-vEbWNcsQ( literal 0 HcmV?d00001 diff --git a/docs/_static/early-recomputation-05.png b/docs/_static/early-recomputation-05.png new file mode 100644 index 0000000000000000000000000000000000000000..17d822289cb8353ccb304699db4d19309f8392ef GIT binary patch literal 25410 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 CH0?$J literal 0 HcmV?d00001 diff --git a/docs/_static/early-recomputation.png b/docs/_static/early-recomputation.png new file mode 100644 index 0000000000000000000000000000000000000000..6e69b2842fd1aacaa20fccefb69a95cec99e4c61 GIT binary patch literal 25889 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 NRFyOi=PH=`{udhvI%5C; literal 0 HcmV?d00001 diff --git a/docs/case-study-landing-page-caching.rst b/docs/case-study-landing-page-caching.rst new file mode 100644 index 0000000..eb5dabe --- /dev/null +++ b/docs/case-study-landing-page-caching.rst @@ -0,0 +1,17 @@ +Case Study: Landing Page Caching +================================ + +:doc:`DiskCache ` version 4 added recipes for cache stampede mitigation. +Let's look at how that applies to landing page caching. + +.. image:: _static/no-caching.png + +.. image:: _static/traditional-caching.png + +.. image:: _static/synchronized-locking.png + +.. image:: _static/early-recomputation.png + +.. image:: _static/early-recomputation-05.png + +.. image:: _static/early-recomputation-03.png diff --git a/docs/conf.py b/docs/conf.py index 5f04890..683dd3e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,7 +51,7 @@ # General information about the project. project = u'DiskCache' -copyright = u'2016, Grant Jenks' +copyright = u'2019, Grant Jenks' author = u'Grant Jenks' # The version info for the project you're documenting, acts as replacement for diff --git a/docs/index.rst b/docs/index.rst index 9fe19ac..cea1122 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,7 @@ cache-benchmarks djangocache-benchmarks case-study-web-crawler + case-study-landing-page-caching sf-python-2017-meetup-talk api development diff --git a/tests/test_early_recompute.py b/tests/test_early_recompute.py index 36f31d5..bbebc1a 100644 --- a/tests/test_early_recompute.py +++ b/tests/test_early_recompute.py @@ -1,17 +1,9 @@ """Early Recomputation Measurements -TODO - -* Publish graphs: - 1. Cache stampede (single memo decorator). - 2. Double-checked locking (memo, barrier, memo). - 3. Early recomputation (memo with early recomputation). - 4. Advanced usage: adjust "Beta" parameter. - """ import diskcache as dc -import functools +import functools as ft import multiprocessing.pool import shutil import threading @@ -25,7 +17,7 @@ def make_timer(times): """ lock = threading.Lock() def timer(func): - @functools.wraps(func) + @ft.wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) @@ -36,7 +28,7 @@ def wrapper(*args, **kwargs): return timer -def make_worker(times, delay=1): +def make_worker(times, delay=0.2): """Make a worker which accumulates (start, end) in `times` and sleeps for `delay` seconds. @@ -47,7 +39,7 @@ def worker(): return worker -def make_repeater(func, total=60, delay=0.01): +def make_repeater(func, total=10, delay=0.01): """Make a repeater which calls `func` and sleeps for `delay` seconds repeatedly until `total` seconds have elapsed. @@ -67,12 +59,13 @@ def frange(start, stop, step=1e-3): start += step -def plot(cache_times, worker_times): +def plot(option, filename, cache_times, worker_times): "Plot concurrent workers and latency." - # TODO: Update x-axis to normalize to 0 import matplotlib.pyplot as plt fig, (workers, latency) = plt.subplots(2, sharex=True) + fig.suptitle(option) + changes = [(start, 1) for start, _ in worker_times] changes.extend((stop, -1) for _, stop in worker_times) changes.sort() @@ -99,64 +92,77 @@ def plot(cache_times, worker_times): x_counts = [x - min_x for x, y in counts] y_counts = [y for x, y in counts] - workers.set_title('Concurrent Workers') + workers.set_title('Concurrency') workers.set_ylabel('Workers') + workers.set_ylim(0, 11) workers.plot(x_counts, y_counts) latency.set_title('Latency') latency.set_ylabel('Seconds') + latency.set_ylim(0, 0.5) latency.set_xlabel('Time') x_latency = [start - min_x for start, _ in cache_times] y_latency = [stop - start for start, stop in cache_times] latency.scatter(x_latency, y_latency) - plt.show() + plt.savefig(filename) -if __name__ == '__main__': - import argparse - parser = argparse.ArgumentParser() - - shutil.rmtree('/tmp/cache', ignore_errors=True) +def main(): + shutil.rmtree('/tmp/cache') cache = dc.Cache('/tmp/cache') - count = 16 + count = 10 cache_times = [] timer = make_timer(cache_times) - decorators = [ - timer, - - # Option 0: No Caching - - # Option 1: Traditional Caching - # cache.memoize(expire=10), + options = { + ('No Caching', 'no-caching.png'): [ + timer, + ], + ('Traditional Caching', 'traditional-caching.png'): [ + timer, + cache.memoize(expire=1), + ], + ('Synchronized Locking', 'synchronized-locking.png'): [ + timer, + cache.memoize(expire=0), + dc.barrier(cache, dc.Lock), + cache.memoize(expire=1), + ], + ('Early Recomputation', 'early-recomputation.png'): [ + timer, + dc.memoize_stampede(cache, expire=1), + ], + ('Early Recomputation (beta=0.5)', 'early-recomputation-05.png'): [ + timer, + dc.memoize_stampede(cache, expire=1, beta=0.5), + ], + ('Early Recomputation (beta=0.3)', 'early-recomputation-03.png'): [ + timer, + dc.memoize_stampede(cache, expire=1, beta=0.3), + ], + } + + for (option, filename), decorators in options.items(): + print('Simulating:', option) + worker_times = [] + worker = make_worker(worker_times) + for decorator in reversed(decorators): + worker = decorator(worker) + + worker() + repeater = make_repeater(worker) + + with multiprocessing.pool.ThreadPool(count) as pool: + pool.map(repeater, [worker] * count) + + plot(option, filename, cache_times, worker_times) + + cache.clear() + cache_times.clear() - # Option 2: Synchronized Locking - # cache.memoize(expire=0), - # dc.barrier(cache, dc.Lock), - # cache.memoize(expire=10), - # Option 3: Early Recomputation - # cache.memoize(expire=10, early_recompute=True), - - # Option 4: Early Recomputation Tuning - # cache.memoize(expire=10, early_recompute=1.5), # =0.5), - - # Option 5: Background Early Recomputation - # cache.memoize(expire=10, early_recompute=True, background='threading'), - # TODO: background parameter? or early_recompute='background' - ] - - worker_times = [] - worker = make_worker(worker_times) - for decorator in reversed(decorators): - worker = decorator(worker) - - repeater = make_repeater(worker) - - with multiprocessing.pool.ThreadPool() as pool: - pool.map(repeater, [worker] * count) - - plot(cache_times, worker_times) +if __name__ == '__main__': + main() From 024c4d9573313869c5dd713f0914eeb71977b53a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 9 Nov 2018 14:58:38 -0800 Subject: [PATCH 340/550] Add Comparison with related projects --- README.rst | 215 +++++++++++++++++++++++++++++++++++++++- docs/_static/custom.css | 5 + tests/timings.py | 48 +++++++++ 3 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 tests/timings.py diff --git a/README.rst b/README.rst index ae0b086..6aad03c 100644 --- a/README.rst +++ b/README.rst @@ -52,6 +52,7 @@ Does your company or website use `DiskCache`_? Send us a `message Features -------- +- TODO: update with Comparison below - Pure-Python - Fully Documented - Benchmark comparisons (alternatives, Django cache backends) @@ -88,12 +89,15 @@ function:: >>> help(Cache) >>> help(FanoutCache) >>> help(DjangoCache) + >>> from diskcache import Deque, Index + >>> help(Deque) + >>> help(Index) User Guide ---------- For those wanting more details, this part of the documentation describes -introduction, benchmarks, development, and API. +tutorial, benchmarks, API, and development. * `DiskCache Tutorial`_ * `DiskCache Cache Benchmarks`_ @@ -111,6 +115,215 @@ introduction, benchmarks, development, and API. .. _`DiskCache API Reference`: http://www.grantjenks.com/docs/diskcache/api.html .. _`DiskCache Development`: http://www.grantjenks.com/docs/diskcache/development.html +Comparisons +----------- + +Comparisons to popular projects related to `DiskCache`_. + +Key-Value Stores +................ + +`DiskCache`_ is mostly a simple key-value store. Feature comparisons with four +other projects are shown in the tables below. + +* `dbm`_ is part of Python's standard library and implements a generic + interface to variants of the DBM database — dbm.gnu or dbm.ndbm. If none of + these modules is installed, the slow-but-simple dbm.dumb is used. +* `shelve`_ is part of Python's standard library and implements a “shelf” as a + persistent, dictionary-like object. The difference with “dbm” databases is + that the values can be anything that the pickle module can handle. +* `sqlitedict`_ is a lightweight wrapper around Python's sqlite3 database with + a simple, Pythonic dict-like interface and support for multi-thread + access. Keys are arbitrary strings, values arbitrary pickle-able objects. +* `pickleDB`_ is a lightweight and simple key-value store. It is built upon + Python's simplejson module and was inspired by Redis. It is licensed with the + BSD three-caluse license. + +.. _`dbm`: https://docs.python.org/3/library/dbm.html +.. _`shelve`: https://docs.python.org/3/library/shelve.html +.. _`sqlitedict`: https://github.com/RaRe-Technologies/sqlitedict +.. _`pickleDB`: https://pythonhosted.org/pickleDB/ + +**Features** + +================ ================ ======= ======= ============ ============ +Feature diskcache dbm shelve sqlitedict pickleDB +================ ================ ======= ======= ============ ============ +Atomic? Always Maybe Maybe Maybe No +Persistent? Yes Yes Yes Yes Yes +Thread-safe? Yes No No Yes No +Process-safe? Yes No No Maybe No +Backend? SQLite DBM DBM SQLite File +Serialization? Customizable None Pickle Customizable JSON +Data Types? Mapping/Deque Mapping Mapping Mapping Mapping +Ordering? Insertion/Sorted None None None None +Eviction? None/LRS/LRU/LFU None None None None +Vacuum? Automatic Maybe Maybe Manual Automatic +Transactions? Yes No No Maybe No +Multiprocessing? Yes No No No No +Forkable? Yes No No No No +Metadata? Yes No No No No +================ ================ ======= ======= ============ ============ + +**Quality** + +================ ================ ======= ======= ============ ============ +Project diskcache dbm shelve sqlitedict pickleDB +================ ================ ======= ======= ============ ============ +Tests? Yes Yes Yes Yes Yes +Coverage? Yes Yes Yes Yes No +Stress? Yes No No No No +CI Tests? Travis/AppVeyor Yes Yes Travis No +Python? 2/3/PyPy All All 2/3 2/3 +License? Apache2 Python Python Apache2 3-Clause BSD +Docs? Extensive Summary Summary Readme Summary +Benchmarks? Yes No No No No +Sources? GitHub GitHub GitHub GitHub GitHub +Pure-Python? Yes Yes Yes Yes Yes +Server? No No No No No +Integrations? Django None None None None +================ ================ ======= ======= ============ ============ + +**Timings** + +These are very rough measurements. See `DiskCache Cache Benchmarks`_ for more +rigorous data. + +================ ================ ======= ======= ============ ============ +Project diskcache dbm shelve sqlitedict pickleDB +================ ================ ======= ======= ============ ============ +get 25 µs 36 µs 41 µs 513 µs 92 µs +set 198 µs 900 µs 928 µs 697 µs 1,020 µs +delete 248 µs 740 µs 702 µs 1,717 µs 1,020 µs +================ ================ ======= ======= ============ ============ + +Caching Libraries +................. + +* `joblib.Memory`_ provides caching functions and works by explicitly saving + the inputs and outputs to files. It is designed to work with non-hashable and + potentially large input and output data types such as numpy arrays. +* `klepto`_ extends Python’s `lru_cache` to utilize different keymaps and + alternate caching algorithms, such as `lfu_cache` and `mru_cache`. Klepto + uses a simple dictionary-sytle interface for all caches and archives. + +.. _`klepto`: https://pypi.org/project/klepto/ +.. _`joblib.Memory`: https://joblib.readthedocs.io/en/latest/memory.html + +Data Structures +............... + +* `dict`_ is a mapping object that maps hashable keys to arbitrary + values. Mappings are mutable objects. There is currently only one standard + Python mapping type, the dictionary. +* `pandas`_ is a Python package providing fast, flexible, and expressive data + structures designed to make working with “relational” or “labeled” data both + easy and intuitive. +* `Sorted Containers`_ is an Apache2 licensed sorted collections library, + written in pure-Python, and fast as C-extensions. Sorted Containers + implements sorted list, sorted dictionary, and sorted set data types. + +.. _`dict`: https://docs.python.org/3/library/stdtypes.html#typesmapping +.. _`pandas`: https://pandas.pydata.org/ +.. _`Sorted Containers`: http://www.grantjenks.com/docs/sortedcontainers/ + +Pure-Python Databases +..................... + +* `ZODB`_ supports an isomorphic interface for database operations which means + there's very little impact on your code to make objects persistent and + there's no database mapper that partially hides the datbase. +* `CodernityDB`_ is an open source, pure-Python, multi-platform, schema-less, + NoSQL database and includes an HTTP server version, and a Python client + library that aims to be 100% compatible with the embedded version. +* `TinyDB`_ is a tiny, document oriented database optimized for your + happiness. If you need a simple database with a clean API that just works + without lots of configuration, TinyDB might be the right choice for you. + +.. _`ZODB`: http://www.zodb.org/ +.. _`CodernityDB`: https://pypi.org/project/CodernityDB/ +.. _`TinyDB`: https://tinydb.readthedocs.io/ + +Object Relational Mappings (ORM) +................................ + +* `Django ORM`_ provides models that are the single, definitive source of + information about data and contains the essential fields and behaviors of the + stored data. Generally, each model maps to a single SQL database table. +* `SQLAlchemy`_ is the Python SQL toolkit and Object Relational Mapper that + gives application developers the full power and flexibility of SQL. It + provides a full suite of well known enterprise-level persistence patterns. +* `Peewee`_ is a simple and small ORM. It has few (but expressive) concepts, + making it easy to learn and intuitive to use. Peewee supports Sqlite, MySQL, + and PostgreSQL with tons of extensions. +* `SQLObject`_ is a popular Object Relational Manager for providing an object + interface to your database, with tables as classes, rows as instances, and + columns as attributes. +* `Pony ORM`_ is a Python ORM with beautiful query syntax. Use Python syntax + for interacting with the database. Pony translates such queries into SQL and + executes them in the database in the most efficient way. + +.. _`Django ORM`: https://docs.djangoproject.com/en/dev/topics/db/ +.. _`SQLAlchemy`: https://www.sqlalchemy.org/ +.. _`Peewee`: http://docs.peewee-orm.com/ +.. _`dataset`: https://dataset.readthedocs.io/ +.. _`SQLObject`: http://sqlobject.org/ +.. _`Pony ORM`: https://ponyorm.com/ + +SQL Databases +............. + +* `SQLite`_ is part of Python's standard library and provides a lightweight + disk-based database that doesn’t require a separate server process and allows + accessing the database using a nonstandard variant of the SQL query language. +* `MySQL`_ is one of the world’s most popular open source databases and has + become a leading database choice for web-based applications. MySQL includes a + standardized database driver for Python platforms and development. +* `PostgreSQL`_ is a powerful, open source object-relational database system + with over 30 years of active development. Psycopg is the most popular + PostgreSQL adapter for the Python programming language. +* `Oracle DB`_ is a relational database management system (RDBMS) from the + Oracle Corporation. Originally developed in 1977, Oracle DB is one of the + most trusted and widely-used enterprise relational database engines. +* `Microsoft SQL Server`_ is a relational database management system developed + by Microsoft. As a database server, it stores and retrieves data as requested + by other software applications. + +.. _`SQLite`: https://docs.python.org/3/library/sqlite3.html +.. _`MySQL`: https://dev.mysql.com/downloads/connector/python/ +.. _`PostgreSQL`: http://initd.org/psycopg/ +.. _`Oracle DB`: https://pypi.org/project/cx_Oracle/ +.. _`Microsoft SQL Server`: https://pypi.org/project/pyodbc/ + +Other Databases +............... + +* `Memcached`_ is free and open source, high-performance, distributed memory + object caching system, generic in nature, but intended for use in speeding up + dynamic web applications by alleviating database load. +* `Redis`_ is an open source, in-memory data structure store, used as a + database, cache and message broker. It supports data structures such as + strings, hashes, lists, sets, sorted sets with range queries, and more. +* `MongoDB`_ is a cross-platform document-oriented database program. Classified + as a NoSQL database program, MongoDB uses JSON-like documents with + schema. PyMongo is the recommended way to work with MongoDB from Python. +* `LMDB`_ is a lightning-fast, memory-mapped database. With memory-mapped + files, it has the read performance of a pure in-memory database while + retaining the persistence of standard disk-based databases. +* `BerkeleyDB`_ is a software library intended to provide a high-performance + embedded database for key/value data. Berkeley DB is a programmatic toolkit + that provides built-in database support for desktop and server applications. +* `LevelDB`_ is a fast key-value storage library written at Google that + provides an ordered mapping from string keys to string values. Data is stored + sorted by key and users can provide a custom comparison function. + +.. _`Memcached`: https://pypi.org/project/python-memcached/ +.. _`MongoDB`: https://api.mongodb.com/python/current/ +.. _`Redis`: https://redis.io/clients#python +.. _`LMDB`: https://lmdb.readthedocs.io/ +.. _`BerkeleyDB`: https://pypi.org/project/bsddb3/ +.. _`LevelDB`: https://plyvel.readthedocs.io/ + Reference --------- diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 998ebea..1a8e2a0 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -3,6 +3,11 @@ table { width: 100%; } +#comparison table { + display: block; + overflow: scroll; +} + th.head { text-align: center; } diff --git a/tests/timings.py b/tests/timings.py new file mode 100644 index 0000000..d1459e7 --- /dev/null +++ b/tests/timings.py @@ -0,0 +1,48 @@ +import dbm +import diskcache +import pickledb +import shelve +import sqlitedict +import timeit + +value = 'value' + +print('diskcache set') +dc = diskcache.FanoutCache('/tmp/diskcache') +%timeit -n 100 -r 7 dc['key'] = value +print('diskcache get') +%timeit -n 100 -r 7 dc['key'] +print('diskcache set/delete') +%timeit -n 100 -r 7 dc['key'] = value; del dc['key'] + +print('dbm set') +d = dbm.open('/tmp/dbm', 'c') +%timeit -n 100 -r 7 d['key'] = value; d.sync() +print('dbm get') +%timeit -n 100 -r 7 d['key'] +print('dbm set/delete') +%timeit -n 100 -r 7 d['key'] = value; del d['key']; d.sync() + +print('shelve set') +s = shelve.open('/tmp/shelve') +%timeit -n 100 -r 7 s['key'] = value; s.sync() +print('shelve get') +%timeit -n 100 -r 7 s['key'] +print('shelve set/delete') +%timeit -n 100 -r 7 s['key'] = value; del s['key']; s.sync() + +print('sqlitedict set') +sd = sqlitedict.SqliteDict('/tmp/sqlitedict', autocommit=True) +%timeit -n 100 -r 7 sd['key'] = value +print('sqlitedict get') +%timeit -n 100 -r 7 sd['key'] +print('sqlitedict set/delete') +%timeit -n 100 -r 7 sd['key'] = value; del sd['key'] + +print('pickledb set') +p = pickledb.load('/tmp/pickledb', True) +%timeit -n 100 -r 7 p['key'] = value +print('pickledb get') +%timeit -n 100 -r 7 p = pickledb.load('/tmp/pickledb', True); p['key'] +print('pickledb set/delete') +%timeit -n 100 -r 7 p['key'] = value; del p['key'] From 05aa1000f7103705ef4ec1c7dd3d1b670628a39f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 12 Jun 2019 13:50:41 -0700 Subject: [PATCH 341/550] Use time.time for Python 2 compatibility --- diskcache/recipes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 8ca2c09..52f606a 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -236,7 +236,7 @@ def __exit__(self, *exc_info): def throttle(cache, count, seconds, name=None, expire=None, tag=None, - time_func=time.monotonic, sleep_func=time.sleep): + time_func=time.time, sleep_func=time.sleep): """Decorator to throttle calls to function. >>> import diskcache, time From 509b0986c3bae7a584a1517c3498819813ca6cfc Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 12 Jun 2019 13:50:57 -0700 Subject: [PATCH 342/550] Open README in utf-8 for Windows --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 64da622..90dc280 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +from io import open from setuptools import setup from setuptools.command.test import test as TestCommand @@ -15,7 +16,7 @@ def run_tests(self): exit(errno) -with open('README.rst') as reader: +with open('README.rst', encoding='utf-8') as reader: readme = reader.read() setup( From 48f2c5a088b445ec885a0937a9483947933774cd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 12 Jun 2019 13:51:13 -0700 Subject: [PATCH 343/550] Remove unused imports for pylint --- diskcache/djangocache.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index fbafeaa..d171408 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -1,9 +1,6 @@ "Django-compatible disk and file backed cache." from functools import wraps -from math import log -from random import random -from time import time from django.core.cache.backends.base import BaseCache try: From 012f311653c107d76c44ab60cb4bac7debc49034 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 12 Jun 2019 14:02:08 -0700 Subject: [PATCH 344/550] Add testimonials for #110 --- README.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.rst b/README.rst index 6aad03c..f1cac23 100644 --- a/README.rst +++ b/README.rst @@ -46,9 +46,24 @@ testing has 100% coverage with unit tests and hours of stress. Testimonials ------------ +`Daren Hasenkamp`_, Founder -- + + "It's a useful, simple API, just like I love about Redis. It has reduced + the amount of queries hitting my Elasticsearch cluster by over 25% for a + website that gets over a million users/day (100+ hits/second)." + +`Mathias Petermann`_, Senior Linux System Engineer -- + + "I implemented it into a wrapper for our Ansible lookup modules and we were + able to speed up some Ansible runs by almost 3 times. DiskCache is saving + us a ton of time." + Does your company or website use `DiskCache`_? Send us a `message `_ and let us know. +.. _`Daren Hasenkamp`: https://www.linkedin.com/in/daren-hasenkamp-93006438/ +.. _`Mathias Petermann`: https://www.linkedin.com/in/mathias-petermann-a8aa273b/ + Features -------- From 5e69d5b66e52373e9b5fbe4a51eb8811bb766df7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 12 Jun 2019 15:23:19 -0700 Subject: [PATCH 345/550] Add comment blocks for Python 2/3 shims --- diskcache/core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index 10b8e0b..89c83a0 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -19,6 +19,10 @@ import warnings import zlib +############################################################################ +# BEGIN Python 2/3 Shims +############################################################################ + if sys.hexversion < 0x03000000: import cPickle as pickle # pylint: disable=import-error # ISSUE #25 Fix for http://bugs.python.org/issue10211 @@ -48,6 +52,10 @@ def full_name(func): name = func.__name__ return func.__module__ + '.' + name +############################################################################ +# END Python 2/3 Shims +############################################################################ + try: WindowsError except NameError: From c8f125d93af209743a39bf71ad15b6e471bfdcb8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 12 Jun 2019 15:23:34 -0700 Subject: [PATCH 346/550] Add FanoutCache.memoize using monkey-patching for Python 2 --- diskcache/fanout.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index dc31593..a520f25 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -355,9 +355,6 @@ def __delitem__(self, key): del shard[key] - memoize = Cache.memoize - - def check(self, fix=False, retry=False): """Check database and file system consistency. @@ -656,3 +653,21 @@ def index(self, name): temp = Index(directory) _indexes[name] = temp return temp + + +############################################################################ +# BEGIN Python 2/3 Shims +############################################################################ + +import sys # pylint: disable=wrong-import-position,wrong-import-order + +if sys.hexversion < 0x03000000: + import types + memoize_func = Cache.__dict__['memoize'] # pylint: disable=invalid-name + FanoutCache.memoize = types.MethodType(memoize_func, None, FanoutCache) +else: + FanoutCache.memoize = Cache.memoize + +############################################################################ +# END Python 2/3 Shims +############################################################################ From 54744b946263d410372020bfe8c86b6c26e701aa Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 12 Jun 2019 15:41:15 -0700 Subject: [PATCH 347/550] Simplify recipes and make more robust --- diskcache/recipes.py | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 52f606a..53cc589 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -1,31 +1,5 @@ """Disk Cache Recipes ->>> import diskcache as dc, time ->>> cache = dc.Cache() ->>> @cache.memoize(expire=0) -... @barrier(cache, dc.Lock) -... @cache.memoize() -... def work(num): -... time.sleep(1) -... return num ->>> from concurrent.futures import ThreadPoolExecutor ->>> with ThreadPoolExecutor(5) as executor: -... start = time.time() -... nums = list(executor.map(work, range(5))) -... end = time.time() ->>> nums -[0, 1, 2, 3, 4] ->>> int(end - start) -5 ->>> with ThreadPoolExecutor(5) as executor: -... start = time.time() -... nums = list(executor.map(work, range(5))) -... end = time.time() ->>> nums -[0, 1, 2, 3, 4] ->>> int(end - start) -0 - """ import functools @@ -241,12 +215,16 @@ def throttle(cache, count, seconds, name=None, expire=None, tag=None, >>> import diskcache, time >>> cache = diskcache.Cache() - >>> @throttle(cache, 1, 1) - ... def int_time(): - ... return int(time.time()) - >>> times = [int_time() for _ in range(4)] - >>> [times[i] - times[i - 1] for i in range(1, 4)] - [1, 1, 1] + >>> count = 0 + >>> @throttle(cache, 5, 1) + ... def increment(): + ... global count + ... count += 1 + >>> start = time.time() + >>> while (time.time() - start) <= 4: + ... increment() + >>> count + 25 """ def decorator(func): From 912f96c6895e48f78e6e4d9020aeedac02fd4d35 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 12 Jun 2019 15:45:52 -0700 Subject: [PATCH 348/550] Close Cache before removing directory --- docs/tutorial.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 8a67ab8..f2db508 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -311,6 +311,8 @@ The third is :meth:`check ` which verifies cache consistency. It can also fix inconsistencies and reclaim unused space. The return value is a list of warnings. + >>> _ = cache.check(fix=True) + >>> cache.close() >>> import shutil >>> shutil.rmtree(cache.directory) From a60b54108ddd6c71f3cbc0fa83bc03c7429a35bf Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 09:59:26 -0700 Subject: [PATCH 349/550] Suppress PermissionError for Windows wonkiness --- docs/tutorial.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f2db508..5ce890d 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -311,10 +311,11 @@ The third is :meth:`check ` which verifies cache consistency. It can also fix inconsistencies and reclaim unused space. The return value is a list of warnings. - >>> _ = cache.check(fix=True) + >>> warnings = cache.check() >>> cache.close() - >>> import shutil - >>> shutil.rmtree(cache.directory) + >>> import contextlib, shutil + >>> with contextlib.suppress(PermissionError): # Windows wonkiness + ... shutil.rmtree(cache.directory) .. _tutorial-fanoutcache: From 56927b51098395cb1ff9c45a4fbfb5d109bb8806 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 10:13:13 -0700 Subject: [PATCH 350/550] Update DjangoCache tests with touch() test cases --- tests/test_djangocache.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index ea163bc..f70b87c 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -430,6 +430,11 @@ def test_forever_timeout(self): self.assertEqual(cache.get('key3'), 'sausage') self.assertEqual(cache.get('key4'), 'lobster bisque') + cache.set('key5', 'belgian fries', timeout=1) + cache.touch('key5', timeout=None) + time.sleep(2) + self.assertEqual(cache.get('key5'), 'belgian fries') + def test_zero_timeout(self): """ Passing in zero into timeout results in a value that is not cached @@ -444,6 +449,10 @@ def test_zero_timeout(self): self.assertIsNone(cache.get('key3')) self.assertIsNone(cache.get('key4')) + cache.set('key5', 'belgian fries', timeout=5) + cache.touch('key5', timeout=0) + self.assertIsNone(cache.get('key5')) + def test_float_timeout(self): # Make sure a timeout given as a float doesn't crash anything. cache.set("key1", "spam", 100.2) From bce5a77b607781b30702a22b31730604e08fcda4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 10:38:50 -0700 Subject: [PATCH 351/550] Fix recipes doctest for Python 2 support: get_ident and ThreadPool --- diskcache/recipes.py | 36 +++++++++++++++++++++++++++--------- tests/test_doctest.py | 10 ++++------ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 53cc589..fbd5fa8 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -6,11 +6,25 @@ import math import os import random +import sys import threading import time from .core import ENOVAL, args_to_key, full_name +############################################################################ +# BEGIN Python 2/3 Shims +############################################################################ + +if sys.hexversion < 0x03000000: + from thread import get_ident +else: + from threading import get_ident + +############################################################################ +# END Python 2/3 Shims +############################################################################ + class Averager(object): """Recipe for calculating a running average. @@ -120,7 +134,7 @@ def __init__(self, cache, key, expire=None, tag=None): self._expire = expire self._tag = tag pid = os.getpid() - tid = threading.get_ident() + tid = get_ident() self._value = '{}-{}'.format(pid, tid) def acquire(self): @@ -221,10 +235,10 @@ def throttle(cache, count, seconds, name=None, expire=None, tag=None, ... global count ... count += 1 >>> start = time.time() - >>> while (time.time() - start) <= 4: + >>> while (time.time() - start) <= 2: ... increment() >>> count - 25 + 15 """ def decorator(func): @@ -280,13 +294,17 @@ def barrier(cache, lock_factory, name=None, expire=None, tag=None): >>> cache = diskcache.Cache() >>> @barrier(cache, Lock) ... def work(num): + ... print('worker started') ... time.sleep(1) - ... return int(time.time()) - >>> from concurrent.futures import ThreadPoolExecutor - >>> with ThreadPoolExecutor(4) as executor: - ... times = sorted(executor.map(work, range(4))) - >>> [times[i] - times[i - 1] for i in range(1, 4)] - [1, 1, 1] + ... print('worker finished') + >>> import multiprocessing.pool + >>> pool = multiprocessing.pool.ThreadPool(2) + >>> _ = pool.map(work, range(2)) + worker started + worker finished + worker started + worker finished + >>> pool.terminate() """ def decorator(func): diff --git a/tests/test_doctest.py b/tests/test_doctest.py index e398391..981d00c 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -29,15 +29,13 @@ def test_persistent(): assert failures == 0 -def test_tutorial(): - if sys.hexversion < 0x03000000: - return - failures, _ = doctest.testfile('../docs/tutorial.rst') +def test_recipes(): + failures, _ = doctest.testmod(diskcache.recipes) assert failures == 0 -def test_recipes(): +def test_tutorial(): if sys.hexversion < 0x03000000: return - failures, _ = doctest.testmod(diskcache.recipes) + failures, _ = doctest.testfile('../docs/tutorial.rst') assert failures == 0 From 8701cf901e07f145d5307eb8c9a9dd0cb6fb8e65 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 10:54:32 -0700 Subject: [PATCH 352/550] Update tutorial doctests to work on Python 2/3 --- docs/tutorial.rst | 64 ++++++++++++++++++++++++------------------- tests/test_doctest.py | 2 -- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 5ce890d..deea9cc 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -92,26 +92,26 @@ Closed Cache objects will automatically re-open when accessed. But opening Cache objects is relatively slow, and since all operations are atomic, you can safely leave Cache objects open. - >>> cache.set(b'key', b'value') + >>> cache.set('key', 'value') True >>> cache.close() - >>> cache.get(b'key') # Automatically opens, but slower. - b'value' + >>> cache.get('key') # Automatically opens, but slower. + 'value' Set an item, get a value, and delete a key using the usual operators: - >>> cache[b'key'] = b'value' - >>> cache[b'key'] - b'value' - >>> b'key' in cache + >>> cache['key'] = 'value' + >>> cache['key'] + 'value' + >>> 'key' in cache True - >>> del cache[b'key'] + >>> del cache['key'] There's also a :meth:`set ` method with additional keyword parameters: `expire`, `read`, and `tag`. >>> from io import BytesIO - >>> cache.set(b'key', BytesIO(b'value'), expire=5, read=True, tag='data') + >>> cache.set('key', BytesIO(b'value'), expire=5, read=True, tag='data') True In the example above: the key expires in 5 seconds, the value is read as a @@ -119,14 +119,14 @@ file-like object, and tag metadata is stored with the key. Another method, :meth:`get ` supports querying extra information with `default`, `read`, `expire_time`, and `tag` keyword parameters. - >>> result = cache.get(b'key', default=b'', read=True, expire_time=True, tag=True) + >>> result = cache.get('key', read=True, expire_time=True, tag=True) >>> reader, timestamp, tag = result - >>> type(reader) - - >>> type(timestamp) - - >>> tag - 'data' + >>> print(reader.read().decode()) + value + >>> type(timestamp).__name__ + 'float' + >>> print(tag) + data The return value is a tuple containing the value, expire time (seconds from epoch), and tag. Because we passed ``read=True`` the value is returned as a @@ -180,8 +180,14 @@ used to delete an item in the cache and return its value. 'does not exist' >>> cache.set('dave', 0, expire=None, tag='admin') True - >>> cache.pop('dave', expire_time=True, tag=True) - (0, None, 'admin') + >>> result = cache.pop('dave', expire_time=True, tag=True) + >>> value, timestamp, tag = result + >>> value + 0 + >>> print(timestamp) + None + >>> print(tag) + admin The :meth:`pop ` operation is atomic and using :meth:`incr ` together is an accurate method for counting and dumping @@ -313,9 +319,11 @@ return value is a list of warnings. >>> warnings = cache.check() >>> cache.close() - >>> import contextlib, shutil - >>> with contextlib.suppress(PermissionError): # Windows wonkiness + >>> import shutil + >>> try: ... shutil.rmtree(cache.directory) + ... except PermissionError: # Windows wonkiness + ... pass .. _tutorial-fanoutcache: @@ -475,8 +483,8 @@ access and editing at both front and back sides. :class:`Deque >>> deque.appendleft('foo') >>> len(deque) 4 - >>> type(deque.directory) - + >>> type(deque.directory).__name__ + 'str' >>> other = Deque(directory=deque.directory) >>> len(other) 4 @@ -619,13 +627,13 @@ All clients accessing the cache are expected to use the same eviction policy. The policy can be set during initialization using a keyword argument. >>> cache = Cache() - >>> cache.eviction_policy - 'least-recently-stored' + >>> print(cache.eviction_policy) + least-recently-stored >>> cache = Cache(eviction_policy='least-frequently-used') - >>> cache.eviction_policy - 'least-frequently-used' - >>> cache.reset('eviction_policy', 'least-recently-used') - 'least-recently-used' + >>> print(cache.eviction_policy) + least-frequently-used + >>> print(cache.reset('eviction_policy', 'least-recently-used')) + least-recently-used Though the eviction policy is changed, the previously created indexes will not be dropped. Prefer to always specify the eviction policy as a keyword argument diff --git a/tests/test_doctest.py b/tests/test_doctest.py index 981d00c..70fa61c 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -35,7 +35,5 @@ def test_recipes(): def test_tutorial(): - if sys.hexversion < 0x03000000: - return failures, _ = doctest.testfile('../docs/tutorial.rst') assert failures == 0 From f5422b4f774bfe98510b120c007b72816064b407 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 11:05:01 -0700 Subject: [PATCH 353/550] Ignore old long-integer "L"-suffix --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index deea9cc..aabf9ff 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -561,7 +561,7 @@ are updated lazily. Prefer idioms like :meth:`len ` directly. >>> cache = Cache(size_limit=int(4e9)) - >>> cache.size_limit + >>> print(cache.size_limit) 4000000000 >>> cache.disk_min_file_size 32768 From 00dced81389745759b455e813ff7f41a6c6e1f54 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 11:05:36 -0700 Subject: [PATCH 354/550] Change PermissionError to OSError for Python 2 --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index aabf9ff..d09fa3b 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -322,7 +322,7 @@ return value is a list of warnings. >>> import shutil >>> try: ... shutil.rmtree(cache.directory) - ... except PermissionError: # Windows wonkiness + ... except OSError: # Windows wonkiness ... pass .. _tutorial-fanoutcache: From 03a5f67de75d925f6c2bf4ac3f8c78fad94549e6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 11:05:54 -0700 Subject: [PATCH 355/550] Decrease throttle rate for clock skew on AppVeyor --- diskcache/recipes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index fbd5fa8..90ca376 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -230,7 +230,7 @@ def throttle(cache, count, seconds, name=None, expire=None, tag=None, >>> import diskcache, time >>> cache = diskcache.Cache() >>> count = 0 - >>> @throttle(cache, 5, 1) + >>> @throttle(cache, 2, 1) ... def increment(): ... global count ... count += 1 @@ -238,7 +238,7 @@ def throttle(cache, count, seconds, name=None, expire=None, tag=None, >>> while (time.time() - start) <= 2: ... increment() >>> count - 15 + 6 """ def decorator(func): From 665683c2f35021ee4b03146cc75112012634c1e2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 11:35:14 -0700 Subject: [PATCH 356/550] Disable pylint import-error for Python 2 import --- diskcache/recipes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 90ca376..43edf4b 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -17,7 +17,7 @@ ############################################################################ if sys.hexversion < 0x03000000: - from thread import get_ident + from thread import get_ident # pylint: disable=import-error else: from threading import get_ident From 325a525145fb511aad8b6c570c44a51af2c2774d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 11:51:39 -0700 Subject: [PATCH 357/550] Use full_name() for throttle key and accept 6 or 7 calls --- diskcache/recipes.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 43edf4b..3223096 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -230,30 +230,20 @@ def throttle(cache, count, seconds, name=None, expire=None, tag=None, >>> import diskcache, time >>> cache = diskcache.Cache() >>> count = 0 - >>> @throttle(cache, 2, 1) + >>> @throttle(cache, 2, 1) # 2 calls per 1 second ... def increment(): ... global count ... count += 1 >>> start = time.time() >>> while (time.time() - start) <= 2: ... increment() - >>> count - 6 + >>> count in (6, 7) # 6 or 7 calls depending on processor load + True """ def decorator(func): rate = count / float(seconds) - - if name is None: - try: - key = func.__qualname__ - except AttributeError: - key = func.__name__ - - key = func.__module__ + '.' + key - else: - key = name - + key = full_name(func) if name is None else name now = time_func() cache.set(key, (now, count), expire=expire, tag=tag, retry=True) From 2be5bac6cf9a640829bd8e75ace8f5ccbc24c012 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 12:00:45 -0700 Subject: [PATCH 358/550] Update docstrings regarding Timeout errors --- diskcache/djangocache.py | 2 +- diskcache/fanout.py | 3 +-- diskcache/persistent.py | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index d171408..a2d863c 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -295,7 +295,7 @@ def stats(self, enable=True, reset=False): def create_tag_index(self): """Create tag index on cache database. - It is better to initialize cache with `tag_index=True` than use this. + Better to initialize cache with `tag_index=True` than use this. :raises Timeout: if database timeout occurs diff --git a/diskcache/fanout.py b/diskcache/fanout.py index a520f25..d3cd413 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -395,7 +395,7 @@ def expire(self, retry=False): def create_tag_index(self): """Create tag index on cache database. - It is better to initialize cache with `tag_index=True` than use this. + Better to initialize cache with `tag_index=True` than use this. :raises Timeout: if database timeout occurs @@ -550,7 +550,6 @@ def reset(self, key, value=ENOVAL): :param str key: Settings key for item :param value: value for item (optional) :return: updated value for item - :raises Timeout: if database timeout occurs """ for shard in self._shards: diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 5f2dbde..5350fbf 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -648,7 +648,6 @@ def transact(self): [4, 0, 1, 2, 3] :return: context manager for use in `with` statement - :raises Timeout: if database timeout occurs """ with self._cache.transact(retry=True): @@ -1385,7 +1384,6 @@ def transact(self): 123.4 :return: context manager for use in `with` statement - :raises Timeout: if database timeout occurs """ with self._cache.transact(retry=True): From a1feac48c11bb9f199324298a7b91c1f2a44a6ec Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 13 Jun 2019 16:09:00 -0700 Subject: [PATCH 359/550] Add caveat about full disk --- docs/tutorial.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index d09fa3b..1d70e55 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -711,6 +711,12 @@ this reason, :doc:`DiskCache ` currently `performs poorly`_ on `Python Anywhere`_. Users have also reported issues running inside of `Parallels`_ shared folders. +:doc:`DiskCache ` uses transactions when writing data to disk using +SQLite. When the disk or database is full, a :exc:`sqlite3.OperationalError` +will be raised from any method that attempts to write data. Read operations +will still succeed so long as they do not cause any write (as might occur if +cache statistics are being recorded). + .. _`hash protocol`: https://docs.python.org/library/functions.html#hash .. _`not recommended`: https://www.sqlite.org/faq.html#q5 .. _`performs poorly`: https://www.pythonanywhere.com/forums/topic/1847/ From f3866d21b74d506bf86bf83d480c4e826a820162 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 11:39:45 -0700 Subject: [PATCH 360/550] Audit and add retry=True as needed to recipes --- diskcache/recipes.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 3223096..b6ac6ac 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -52,7 +52,7 @@ def __init__(self, cache, key, expire=None, tag=None): def add(self, value): "Add `value` to average." - with self._cache.transact(): + with self._cache.transact(retry=True): total, count = self._cache.get(self._key, default=(0.0, 0)) total += value count += 1 @@ -140,7 +140,7 @@ def __init__(self, cache, key, expire=None, tag=None): def acquire(self): "Acquire lock by incrementing count using spin-lock algorithm." while True: - with self._cache.transact(): + with self._cache.transact(retry=True): value, count = self._cache.get(self._key, default=(None, 0)) if self._value == value or count == 0: self._cache.set( @@ -152,7 +152,7 @@ def acquire(self): def release(self): "Release lock by decrementing count." - with self._cache.transact(): + with self._cache.transact(retry=True): value, count = self._cache.get(self._key, default=(None, 0)) is_owned = self._value == value and count > 0 assert is_owned, 'cannot release un-acquired lock' @@ -196,7 +196,7 @@ def __init__(self, cache, key, value=1, expire=None, tag=None): def acquire(self): "Acquire semaphore by decrementing value using spin-lock algorithm." while True: - with self._cache.transact(): + with self._cache.transact(retry=True): value = self._cache.get(self._key, default=self._value) if value > 0: self._cache.set( @@ -208,7 +208,7 @@ def acquire(self): def release(self): "Release semaphore by incrementing value." - with self._cache.transact(): + with self._cache.transact(retry=True): value = self._cache.get(self._key, default=self._value) assert self._value > value, 'cannot release un-acquired semaphore' value += 1 @@ -250,16 +250,16 @@ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): while True: - with cache.transact(): - last, tally = cache.get(key, retry=True) + with cache.transact(retry=True): + last, tally = cache.get(key) now = time_func() tally += (now - last) * rate delay = 0 if tally > count: - cache.set(key, (now, count - 1), expire, retry=True) + cache.set(key, (now, count - 1), expire) elif tally >= 1: - cache.set(key, (now, tally - 1), expire, retry=True) + cache.set(key, (now, tally - 1), expire) else: delay = (1 - tally) / rate @@ -399,7 +399,9 @@ def recompute(): # Check whether a thread has started for early recomputation. thread_key = key + (ENOVAL,) - thread_added = cache.add(thread_key, None, expire=delta) + thread_added = cache.add( + thread_key, None, expire=delta, retry=True, + ) if thread_added: # Start thread for early recomputation. From c61a7c6fa32a0010385a13767d1bbfe6c4c345fe Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 11:41:05 -0700 Subject: [PATCH 361/550] Fix formatting for keyword arguments --- diskcache/recipes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index b6ac6ac..b23be1b 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -157,8 +157,8 @@ def release(self): is_owned = self._value == value and count > 0 assert is_owned, 'cannot release un-acquired lock' self._cache.set( - self._key, (value, count - 1), expire=self._expire, - tag=self._tag, + self._key, (value, count - 1), + expire=self._expire, tag=self._tag, ) def __enter__(self): @@ -200,8 +200,8 @@ def acquire(self): value = self._cache.get(self._key, default=self._value) if value > 0: self._cache.set( - self._key, value - 1, expire=self._expire, - tag=self._tag, + self._key, value - 1, + expire=self._expire, tag=self._tag, ) return time.sleep(0.001) From 24a6299b4a8bd03d84cc0ff4d85d23c4440e9d09 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 11:41:26 -0700 Subject: [PATCH 362/550] Group Python 2/3 shims together --- diskcache/fanout.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index d3cd413..085345f 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -4,17 +4,24 @@ import operator import os.path as op import sqlite3 +import sys import tempfile import time -try: - from functools import reduce -except ImportError: - reduce # pylint: disable=pointless-statement - from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout from .persistent import Deque, Index +############################################################################ +# BEGIN Python 2/3 Shims +############################################################################ + +if sys.hexversion >= 0x03000000: + from functools import reduce + +############################################################################ +# END Python 2/3 Shims +############################################################################ + class FanoutCache(object): "Cache that shards keys and values." From 7c9b6f73e67d4a288d6ca8d6c92e7c6a4db7163b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 11:42:07 -0700 Subject: [PATCH 363/550] Doctest and formatting fixes --- diskcache/persistent.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 5350fbf..bff2df0 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -173,7 +173,6 @@ def _index(self, index, func): raise IndexError('deque index out of range') - def __getitem__(self, index): """deque.__getitem__(index) <==> deque[index] @@ -360,6 +359,13 @@ def appendleft(self, value): def clear(self): """Remove all elements from deque. + >>> deque = Deque('abc') + >>> len(deque) + 3 + >>> deque.clear() + >>> list(deque) + [] + """ self._cache.clear(retry=True) @@ -902,7 +908,7 @@ def popitem(self, last=True): >>> index.popitem() Traceback (most recent call last): ... - KeyError + KeyError: 'dictionary is empty' :param bool last: pop last item pair (default True) :return: key and value item pair @@ -1005,6 +1011,13 @@ def pull(self, prefix=None, default=(None, None), side='front'): def clear(self): """Remove all items from index. + >>> index = Index({'a': 0, 'b': 1, 'c': 2}) + >>> len(index) + 3 + >>> index.clear() + >>> dict(index) + {} + """ self._cache.clear(retry=True) From c66f0b21214cda18bf6d4b62e85351a3f5cc984c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 11:42:55 -0700 Subject: [PATCH 364/550] Change Index.popitem to use Cache.peekitem and transaction --- diskcache/persistent.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index bff2df0..961f773 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -918,21 +918,11 @@ def popitem(self, last=True): # pylint: disable=arguments-differ _cache = self._cache - while True: - try: - if last: - key = next(reversed(_cache)) - else: - key = next(iter(_cache)) - except StopIteration: - raise KeyError + with _cache.transact(retry=True): + key, value = _cache.peekitem(last=last) + del _cache[key] - try: - value = _cache.pop(key, retry=True) - except KeyError: - continue - else: - return key, value + return key, value def push(self, value, prefix=None, side='back'): From 892b53e186abe479fa36446f065fc2ec1014d988 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 11:43:34 -0700 Subject: [PATCH 365/550] Add tests for Index and Deque coverage --- tests/test_deque.py | 57 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_index.py | 4 ++++ 2 files changed, 61 insertions(+) diff --git a/tests/test_deque.py b/tests/test_deque.py index a3ac018..640cee2 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -76,12 +76,56 @@ def test_getsetdel(deque): assert len(deque) == 0 +def test_index_positive(deque): + cache = mock.MagicMock() + cache.__len__.return_value = 3 + cache.iterkeys.return_value = ['a', 'b', 'c'] + cache.__getitem__.side_effect = [KeyError, 101, 102] + with mock.patch.object(deque, '_cache', cache): + assert deque[0] == 101 + + +def test_index_negative(deque): + cache = mock.MagicMock() + cache.__len__.return_value = 3 + cache.iterkeys.return_value = ['c', 'b', 'a'] + cache.__getitem__.side_effect = [KeyError, 101, 100] + with mock.patch.object(deque, '_cache', cache): + assert deque[-1] == 101 + + +def test_index_out_of_range(deque): + cache = mock.MagicMock() + cache.__len__.return_value = 3 + cache.iterkeys.return_value = ['a', 'b', 'c'] + cache.__getitem__.side_effect = [KeyError] * 3 + with mock.patch.object(deque, '_cache', cache): + with pytest.raises(IndexError): + deque[0] + + +def test_iter_keyerror(deque): + cache = mock.MagicMock() + cache.iterkeys.return_value = ['a', 'b', 'c'] + cache.__getitem__.side_effect = [KeyError, 101, 102] + with mock.patch.object(deque, '_cache', cache): + assert list(iter(deque)) == [101, 102] + + def test_reversed(deque): sequence = list('abcde') deque += sequence assert list(reversed(deque)) == list(reversed(sequence)) +def test_reversed_keyerror(deque): + cache = mock.MagicMock() + cache.iterkeys.return_value = ['c', 'b', 'a'] + cache.__getitem__.side_effect = [KeyError, 101, 100] + with mock.patch.object(deque, '_cache', cache): + assert list(reversed(deque)) == [101, 100] + + def test_state(deque): sequence = list('abcde') deque.extend(sequence) @@ -178,6 +222,15 @@ def test_remove_valueerror(deque): deque.remove(0) +def test_remove_keyerror(deque): + cache = mock.MagicMock() + cache.iterkeys.return_value = ['a', 'b', 'c'] + cache.__getitem__.side_effect = [KeyError, 100, 100] + cache.__delitem__.side_effect = [KeyError, None] + with mock.patch.object(deque, '_cache', cache): + deque.remove(100) + + def test_reverse(deque): deque += 'abcde' deque.reverse() @@ -223,3 +276,7 @@ def test_rotate_indexerror_negative(deque): with mock.patch.object(deque, '_cache', cache): deque.rotate(-1) + + +def test_repr(deque): + assert repr(deque).startswith('Deque(') diff --git a/tests/test_index.py b/tests/test_index.py index 83ba5cf..f4232c8 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -179,3 +179,7 @@ def fibrec(num): assert hits2 == (hits1 + count) assert misses2 == misses1 + + +def test_repr(index): + assert repr(index).startswith('Index(') From 0bf521c6a6cf2268fa4856b454b95e48ea10ec82 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 13:08:49 -0700 Subject: [PATCH 366/550] Rename test_early_recompute.py to plot_early_recompute.py --- tests/{test_early_recompute.py => plot_early_recompute.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_early_recompute.py => plot_early_recompute.py} (100%) diff --git a/tests/test_early_recompute.py b/tests/plot_early_recompute.py similarity index 100% rename from tests/test_early_recompute.py rename to tests/plot_early_recompute.py From 13a59228f2c918d200acfeafa703b7805dcd9d0a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 13:09:10 -0700 Subject: [PATCH 367/550] Add test cases for recipes --- tests/test_recipes.py | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/test_recipes.py diff --git a/tests/test_recipes.py b/tests/test_recipes.py new file mode 100644 index 0000000..35de332 --- /dev/null +++ b/tests/test_recipes.py @@ -0,0 +1,78 @@ +"Test diskcache.recipes." + +import diskcache as dc +import mock +import pytest +import shutil +import threading +import time + + +@pytest.fixture +def cache(): + with dc.Cache() as cache: + yield cache + shutil.rmtree(cache.directory, ignore_errors=True) + + +def test_averager(cache): + nums = dc.Averager(cache, 'nums') + for i in range(10): + nums.add(i) + assert nums.get() == 4.5 + assert nums.pop() == 4.5 + for i in range(20): + nums.add(i) + assert nums.get() == 9.5 + assert nums.pop() == 9.5 + + +def test_rlock(cache): + state = {'num': 0} + rlock = dc.RLock(cache, 'demo') + def worker(): + state['num'] += 1 + with rlock: + state['num'] += 1 + time.sleep(0.1) + with rlock: + thread = threading.Thread(target=worker) + thread.start() + time.sleep(0.1) + assert state['num'] == 1 + thread.join() + assert state['num'] == 2 + + +def test_semaphore(cache): + state = {'num': 0} + semaphore = dc.BoundedSemaphore(cache, 'demo', value=3) + def worker(): + state['num'] += 1 + with semaphore: + state['num'] += 1 + time.sleep(0.1) + semaphore.acquire() + semaphore.acquire() + with semaphore: + thread = threading.Thread(target=worker) + thread.start() + time.sleep(0.1) + assert state['num'] == 1 + thread.join() + assert state['num'] == 2 + semaphore.release() + semaphore.release() + + +def test_memoize_stampede(cache): + state = {'num': 0} + @dc.memoize_stampede(cache, 0.1) + def worker(num): + time.sleep(0.01) + state['num'] += 1 + return num + start = time.time() + while (time.time() - start) < 1: + worker(100) + assert state['num'] > 0 From bb750fbf629c1d93f0f0ac7bab931669ec45a98c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 13:09:24 -0700 Subject: [PATCH 368/550] Create connection in __enter__ --- diskcache/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/core.py b/diskcache/core.py index 89c83a0..3c0b023 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -2332,6 +2332,7 @@ def close(self): def __enter__(self): + connection = self._con # Create connection in thread. return self From fb1bc6738a9cf1510e08bdc3db092730c2d9df44 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 13:10:03 -0700 Subject: [PATCH 369/550] Fix rlock value calculation (must be function local) --- diskcache/recipes.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index b23be1b..069269e 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -133,18 +133,19 @@ def __init__(self, cache, key, expire=None, tag=None): self._key = key self._expire = expire self._tag = tag - pid = os.getpid() - tid = get_ident() - self._value = '{}-{}'.format(pid, tid) def acquire(self): "Acquire lock by incrementing count using spin-lock algorithm." + pid = os.getpid() + tid = get_ident() + pid_tid = '{}-{}'.format(pid, tid) + while True: with self._cache.transact(retry=True): value, count = self._cache.get(self._key, default=(None, 0)) - if self._value == value or count == 0: + if pid_tid == value or count == 0: self._cache.set( - self._key, (self._value, count + 1), + self._key, (pid_tid, count + 1), expire=self._expire, tag=self._tag, ) return @@ -152,9 +153,13 @@ def acquire(self): def release(self): "Release lock by decrementing count." + pid = os.getpid() + tid = get_ident() + pid_tid = '{}-{}'.format(pid, tid) + with self._cache.transact(retry=True): value, count = self._cache.get(self._key, default=(None, 0)) - is_owned = self._value == value and count > 0 + is_owned = pid_tid == value and count > 0 assert is_owned, 'cannot release un-acquired lock' self._cache.set( self._key, (value, count - 1), From ac3a3b619c0e5b6eaca429e93cd0d3678fb1ec0c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 13:10:37 -0700 Subject: [PATCH 370/550] Refactor early recomputation to close cache in thread --- diskcache/recipes.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 069269e..48b8241 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -377,6 +377,13 @@ def decorator(func): "Decorator created by memoize call for callable." base = (full_name(func),) if name is None else (name,) + def timer(*args, **kwargs): + "Time execution of `func` and return result and time delta." + start = time.time() + result = func(*args, **kwargs) + delta = time.time() - start + return result, delta + @functools.wraps(func) def wrapper(*args, **kwargs): "Wrapper for callable to cache arguments and return values." @@ -385,14 +392,6 @@ def wrapper(*args, **kwargs): key, default=ENOVAL, expire_time=True, retry=True, ) - def recompute(): - start = time.time() - result = func(*args, **kwargs) - delta = time.time() - start - pair = result, delta - cache.set(key, pair, expire=expire, tag=tag, retry=True) - return result - if pair is not ENOVAL: result, delta = pair now = time.time() @@ -410,13 +409,21 @@ def recompute(): if thread_added: # Start thread for early recomputation. + def recompute(): + with cache: + pair = timer(*args, **kwargs) + cache.set( + key, pair, expire=expire, tag=tag, retry=True, + ) thread = threading.Thread(target=recompute) thread.daemon = True thread.start() return result - return recompute() # Cache miss. + pair = timer(*args, **kwargs) + cache.set(key, pair, expire=expire, tag=tag, retry=True) + return pair[0] def __cache_key__(*args, **kwargs): "Make key for cache given function arguments." From 713600acd72d86cabb18d16361ebef27bb72a55d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Jun 2019 13:18:57 -0700 Subject: [PATCH 371/550] Pylint fixes --- diskcache/core.py | 3 ++- diskcache/fanout.py | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 3c0b023..c74241e 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -2332,7 +2332,8 @@ def close(self): def __enter__(self): - connection = self._con # Create connection in thread. + # Create connection in thread. + connection = self._con # pylint: disable=unused-variable return self diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 085345f..8a0a722 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -665,8 +665,6 @@ def index(self, name): # BEGIN Python 2/3 Shims ############################################################################ -import sys # pylint: disable=wrong-import-position,wrong-import-order - if sys.hexversion < 0x03000000: import types memoize_func = Cache.__dict__['memoize'] # pylint: disable=invalid-name From 291a1cfb8ed552cc93a4982f926d581449ef5149 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 19 Jun 2019 17:39:11 -0700 Subject: [PATCH 372/550] Update benchmarks for v4 --- docs/_static/core-p1-delete.png | Bin 45852 -> 44624 bytes docs/_static/core-p1-get.png | Bin 45803 -> 46320 bytes docs/_static/core-p1-set.png | Bin 46824 -> 45292 bytes docs/_static/core-p8-delete.png | Bin 47424 -> 48531 bytes docs/_static/core-p8-get.png | Bin 46972 -> 46379 bytes docs/_static/core-p8-set.png | Bin 47227 -> 48240 bytes docs/_static/djangocache-delete.png | Bin 30412 -> 33750 bytes docs/_static/djangocache-get.png | Bin 33536 -> 29965 bytes docs/_static/djangocache-set.png | Bin 33357 -> 33364 bytes tests/{timings.py => benchmark_kv_store.py} | 0 tests/timings_core_p1.txt | 40 ++++++++++---------- tests/timings_core_p8.txt | 40 ++++++++++---------- tests/timings_djangocache.txt | 40 ++++++++++---------- 13 files changed, 60 insertions(+), 60 deletions(-) rename tests/{timings.py => benchmark_kv_store.py} (100%) diff --git a/docs/_static/core-p1-delete.png b/docs/_static/core-p1-delete.png index 6cf196f17d253df0244b374281281f2a1fb667fb..25907e5c3c59521586661bf0a5ed3cef4937458c 100644 GIT binary patch literal 44624 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 JZDQ;5{{VSWOq~D# literal 45803 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 J($!3S{|n=Q5_bRq diff --git a/docs/_static/core-p8-delete.png b/docs/_static/core-p8-delete.png index f717e0eeef7388d6285ee795a1d092c09205a9e1..c6cb29968237ca40fad85bd4ccab59ecbd961dde 100644 GIT binary patch literal 48531 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 literal 47424 zcmeFa2UL{lwk=v}+h%FAD1xmZVgNIewQYqGMahy?l4K+Y3FbBdmIwxdO3spV=tc!W zM2V77P=q3=B*}NKa_@cidH0=n-n-|$H^w>lIQAZ`Q1#azzHhC$=A3KQwc`r13+Aqx z%V01TFy($%VlZaV|DVnN89xc-o~wcX{ABwZQ)M>(oSSX%2mU?BT28~3!B}iZ|DV=u z#OH(`irF1JVW(_mWaoIw#*krg%Ff!{%Ff*6^jZf)8(R}A%e6a%w+nCIvi6Lfowc}# z$iKWm*viIOgz@RvLk43lgZcY@6{mpiCPzC})#evpJHltJ3vy*%~#9{I@@QP3zv0(KMY0F4FI8+vvq-gU%97CeU?FP$ZkzN0h9!4` zgbstzI=N`&%E-qDt{Rzm-@0`-)O>75VSUNn%~mEB!`Dwgzb-bTr!Ltz)qTRHELcWw z-tsMP#z*?Aqg7)hv>vT?X2+%PI`d}jGGXoS3jWhuh3Q5$?m2({{A;U`cwg?&BzCDv zlEF(YmFir9b?erZj_tMkd^~sL)5EToe3|`Sb5ET*HFw^;>`FsMhWuF*KE}I&I^*Cd zn~x>mhKJSV5< z-g87N&0I0rsB9^ozjme#orYO$ygfJHqDAlLe;#DlI*v3Nm-O;RnQf0+QIP9x70M{M z^0lofMlaue?)>>GR~N3Kw}#34N2|qYmOK=-N-KKy;sveK5`U54Ag2#=E?>HI1}ksX zPNSQCTQr{7vulKo1xeNhX3P5vKiFgO;QCUbCxXG}l(1kl^WAfGJtpkqv@?}T{YCuo zXWjbsyRlrJzj%?QJtXk*V^GvFnF*Ec+qZuk98~T9RJM$lH@+#`k=^DkQ}g8DjTEfW zWm`3?dbJ$n&*3g><~SwNNep(^DdWPb>EU`zx#tenXwd_}1`CPT$u=Du>d{O&b9<<# zA;O{|?PX!x)YQWd=V#1PNzj$*Y01~K@2XO?vrBm$Bo*`g#){42;+o3gM-+#9n-VnS z-~V;FI#O}{R;{!n4e3_uBi}yijNW0fQm}?p6~B6|3y{nKO}-lf(I& z_wGGWqNChdN;Wo0rd5%TJv|xNv6{TRyqiuu zeZ(&$AaD@Rq_!qbOEt^R#J;oQptQ7feYRsl_|b>q_^CuukhGiTf>pbY|MuI3O{&qM z!a7+S*RMZ-TV0)8E*%r>F{!cs(9Pl`zwh6ja>shJqcxJlv+UX7E$(ASr$)*>s-B)Y zU{RNJ5D(Okf3UMscHRo%Dg>Bmads@X~9gI9Q8<^n&|V`%2X?RushDzcd37#NoBYOD^}WVG$UojX0=zF)M8Jh z>1(B$8U{)@z7Dhc{N}MAKRb80+1AA0;NZc7hS`349uw8yhKAA(`)x5ze4KiX^~e1DYuB#f zB=w8D>b!E_x=d>vo2|sKzH{j2%2UjwW~a7WTijfSds8JPC6g{)L_5G_Z$8$Ov^umt-745{{3iF#EljmQar>l!=g*(xp`I$dBG!;*UU!%CKOA8ANHJ_e5Z#!P*ekFeScds=P zM$S%7Cy+MOOijCXn@Kr;3;o#HX`&jXB+xMVs^xjX`5DQveQ#y?%63eCcz(kVVMI04 z#?XH#V6Kv=vWTZEpa0Obf}^9I5yAXSEDVj&!@e6IfB9m)X0LTYm|IhpJUg*t@KUU#(lZNx@r2zMya^e)w=SZYTm`D zC)8j+CJpe(xNCQIcGjnw`EOE>KUdh1s}rpf#R`i%{`fbRuSM?t>=atsb$%nfkCo8nOh28C5i%m|kM@dO3UaYSy ziEiJD{c{pBW~a2E3B8-J$_)=R7qJmVO1#JVxZxt5U}@b zQ-5P5&<&Gy$eS0pw^7kyIK;5I!8R`Mm5gWDln0leh+h21Qhz=B_|`sIWS74;;*RNL~IVLaQQeU!cnSE2ohPsjY zm#AF^JuJR$+qQ;8U0q#VX?|~`XrTfEnof89EQY91 zKi5Eh@9%z{I9eU=lBG-e+2!&v38TufClcAOwGvN1X9pG8ZOxyW9NS&)-;{1OeErpM z>%?$N{wLRM7WFBs#r1HD2i_du>SQP6*8rp;J_{h(s{i@tpRZ5ahb>$qer95HkZx4o z$fwozJ@u+B9#griL@fghBXR>Y*=6Uh#5cx%IDCIv$CvkoqLLmhd2R_}!@yaV4%fuG zEMry_^z8SU?)gv!E1l_~yKqRjB_J=l9?-WM>?o_ORA!lb zDBEXu&9rv>RSvt%v^G94CpUNZ*$=<_cf2t$FgVj%aK5CtxY*_hE>D1o=Go^}|8Bc@4(L!a_s$_PsChmGPJ~FAU_*{r=$wHm4finu>hBWdeIM zr(^1mSf#M*l+0(&TmHH7m`s_&E97$f@$X+(OB(~Dw7-A<`2J2~Qv@?sd(y_(F=L@KB|sU zk8$X!AMMTVix+eB7uHE}?l(I6U>8$B-cP0N#Vys8Gl~eFo40RQq=x{M8jH-4{b~YF zu(Qu&DjNWR-^$7guu&bTtGbX&CYHbxQj}s{lHsy7YvPo`4&$WcM|;v+QjE%0E!%i} z-_%&kl;um;PGQ`uke?T)jEudzEgMe&(i}c=qzdOXI9RJE5v()x=PbT609!6?w?ucD)oiJb z*U`d*uDZQmHpR9>0eg>$;H&!e!*eT_Tfj&DfC)cy@ly69w9XnR_n55wuALsD%W%9O z(7^bG(RkPg(FDqMkF)Nq+`PoHipZD?pf*h%Ac zZeJ8*)sjcCUHgSmdXHoH!5hmp@XBPw-ILC)u5?l@uL-l4g0N*+8+zOD{ zy}NqV_S2VC^UgV!wzRa&ojv<$iLa1;?)bM-jkKf3*qe{F%fWIQnn;TmoY4T)sVaK)_kg< zTX}yJ)Cd2c6x8kZ5%V(i0)TFjUe&PTkaGL%#BjI*x2;>Z9-YO-tD>Ufi}LT5x1e0p z26|Pg-_|}mZu-dhr`GR(OFi@UukU*=R9P(Mh7CGB)IhU)!ZE7zfJMZ7-H zBjVvYy#2m^zts2Ei+C*EYvPIa@C<#J*K?(810Aj47bHOZnZGFXe~*9q)DjtTvT|Ed zVEo#cQ#FC{xFK5CQ4HeGzjrsXho&)l=asG4lU*rb@YTN5s@HA~!}B4r1nn8C4)`~B zE>4;qItiNN&j$Lk} zcDt>bwco#gPlYGt1&Yfi0m;E zb{7DYa;jd;5zm;uvo12&dw+#1^+~(yOLm;d@mlcbxdF^HBHS2 z;DwR`M3Z}pZ?E5kY@KFHm4u)|fVD|OvaxsEAf1*q->`uXB~sMVkC}HiU?se%bDtb{ z2B~G>dK0DX>tLBa9>ED`lT!|D@jQ0|=|y|?42832;q9%>y_svVukCCp2SmyKV#J#R*hD9i@HZxBq5f2`;Hw-EI_cn3SQu_n>Xhh=i}BB z$z-VgNmAgj@0%H1lGgLoD4e%iS|9r<3fuw~uo0m#TvCEdozBId7x;?kNxOZ2e^t1L z&;9#jtf4TYvIk-fL%s&vLlrzKon6+g`}~dfGnHPl1XMw?F+G}Vcb)rgo%`jld8Q3%aX|U8gn)*+>sE=` zgns*2+94UQo}fGWImEjvR%7RamD~2A)S_Zw?(!{>fQ~KU)tu!cYsnY-;wE>WQ3qx)1!T4}Ypvi!RIVOY44S#iRWflXzNyA_Qhkf z>N#rPUUzWd`3k+2fvfSdg518K3|USNv=qFs6hC3CTqj3&-t*W0{^x(+jsKL3!G^u| z?t=%>P(e0<-=!c``2G#};^Z~iC^8Pp%5Gk}b{~FR37RhgN!WnfAM}Z1i<>%XHh;uh zs_VeW*NY3%Mi{#R(A27_5F{Ce%32v=qk6r!x^v zZ+m^7TlOBy#*HWmI^Nalx*oO4pGeEX4n$7rib=?;0E4^vYT@IEVE57R*PQJ-`Jrb2 zrY&2JFqzD`3l^xJJC}n5`=BR7G2~EZcU^LQj&t($8HG|-P?}_OY49;XN98F zF+=inK^7c9btT85tRES)7Zq+0Q5S_1Tiw z1?!o&R_#`bQVJVDs+4HR8|~VRRK+AIDou=Mq14oPzqs9JACS_<=M-=t#xP&{LM71; zU^c|v$1^xYSb}D%>0voJ>pTTHx#JLUprYL0(zavlX!(z;Ypd}eYWgEIlpkcr&)?Mv zR{Thi0RL2^Uzj}$tGce+>0P~Z9o>9Na>j-GZE`jX2nY!2<(+$N<%I;EVp_dP z!|$Vwv)-q~%X63Qz}X?qEnU3W=FVmk5?gzFN6R+MT_GGhedgSa00HtXFK(?~#?9S% zZVfkg6bf3Cj@N<0ZF|h$wRVe2{a91wI((u0oi7E0tB}#`5&exD0HDBx2>Z)-L&5kJ zDq#pcc{e}8nyBUsbo)~%lXgLqCUC;yzzR03UfSJXHc73LJ%~guM(`#A1N)J*C37=MZBjKRR9F%8Lxbw{3K39o^_9!L_Gt1EvoX-HW-!4w`6B= zF)?u&tX5ZzJpL;xr5KmNF3r4iBNiI-=!1uiHp_*Dg}nqnH{t@e)wCk?T29VnW8nuY zkFkfSlnfI7T5XL8j>|gAu1%=DA(9sfMdKJEVHN6&!B;!W_ZE>9yI}R6D!__emW_LX z8IFR9S4AR94oJq|s9b=&6aijd4+5xZeM*!&iW{vwUOI=~vNN@L`I~feGN?ubTPq|j zt&7_2Y^HA?6is6IblL}BJ=iS|ZL)?-X8aNA{==-(r%#hG1ipL|^ydBh_tWFOaNzTHj#|D|^9&vc%q9N(pwHRJaL_1pyq3CbMNYcIPr3|T5pN-@ zP<>3rVpmgkx=qLHID9-Cm0L8#hEl5A!KA8y1L05tkcw^QXtPw}Gx7<&55K{(dppIMLpOo`Temve6U=S`EF}(Lt^c0Kc91VI>zCy~R?=Ikz z9#^aGwd;Pj9qfg%Nj`*7{!Tn|2)F?dRFfLh?IBLlzJ#gfbsFUKp8C|%%+)8uu}}Ao zaUgTPc~8CQ$pQvIlZH;VgT3@6br#-Bdxm}l4KNw|&UcgQ;W=~WkUA_s25RmjG^)AF zHf@D7o>(8ZPj}qjAao0FQMG%bAHCGF5oIv==K8uc$+*X$H^I!H!-Azm{R5})C{T# zk!UN-7xZ8unN=V{41RC(hS{awIS&?)7}N!%Q~_hhQ;vW<`vx?{Akb*zw;z<0Ra)L{ zR6#zU${t2Q?KkeshQk2=aMbD*jMhqzM=XfJ3Not*Wp-6Yc$16<4JYfTOcVte)X_g5ka>~ALppe*Wy)UdMstE%GzxE`8Jf@=mhb2@$-E(O70KEoV^KqE%GjSbajoYha8c9_Mpa27{)D3E%t=F{~yJn7FYX%g$vbCbJ|AJ)?j38eutFo zI#h-I4x37lGUp+BHYD4d=ppO-1_i~-@;d)cav}5c1 zMaj>^Q?G2997+!m-%Y>HSk|zgYF#C$zyxFbhfwI4HD;V8zg-h2hT@a!C(nUqbNTjC zP=Fb7=vD=e7DY)Ne`gtLApWQ3!P~?mw3H6ncsb50l$c~}t}{^Uh+t37g(<}act$nb z!2;egRQ&Z0kLlH|!sc%4Q?ZHcyWhPP_}!3RO@;(4{<`}d18AdH+*-02wzs#(3<(Mf zk_7jf^GU4^cxotUKEo7Hi-J07z~bH0{e-q~V%~^TD8ee0itnpYGG=&u6H_Lj=?qbsy~%B#RtM*TjFky&;&3 zPg=XhZ8(rPbwrUE^SUHfC`8>e5KiaLne!Hb-Zu0&U4iwAvCiT`;~5g7mp3r>!Yq`C~6GAp3FS3fy8500q1z#og`dfeL7zoABu zsA^RB-oE|5dmj8NFwVpaDTlukUQ8d=cr9)%?bW+N%7+N*AG)>bZWvboXgz$Yfj?G! z5BA!_Jr+s;qaWGqhdpkJm*=ghfg`l%?~6*!hfGzO0euMz`J`ikm?f0y;GPZGizLAV z1{gOuLhrz1JPM8bpPoD|f8avwj7m5eHZ$w9>|>-Szv~l*hvx7GW|pA!m+7iJv!4#~ zL?IQJ(e9fZ>8J+IV=93z79dYT6-z)K4`Jq*D(A(k6DRgH({7-J4aOwJb=cC-z#tOQ z7ao8eT%Ty2SRYGT6T0Kzp+lRXg8icc!WJO}hNP%d?JAnQzi8l ztNFNmysJ)op@WUOhU5bH<5;1%b0!d^hBgu8WQbyETQp^7C5E^p|?*F1d3#FD%0g!9>+( z+D7nW3o_w(t%0)D+kNcl(Ysf##{K<~)u$$a^n0-P>=R+_4En(0+FCMU*$i3XgdkWv z{ihNIguM9K50{`tgu!G%G#wDIFMptz?IC2r+Ib>5KL2UEOmRKkw2Wn1Epi}qrj&tg>_-Po=niaB4!@b~22%(bWl?0zx+|%jVFbd!N;Wxsf)Hnk@O!Mkc{3-E&_L z($4vpfJ5FN0*$FM>hap-Mf<0Huxzth73`F;u)Ijgw*VXr0DwfINd3o>X7fgY)dOM6 zCNU3t1~QTmvmzmXG8$&G&p=P`CQjb{r~3(b`v?n|oJlTYJ?T25QhA!Pk?L6qBJ?56_4AQJ2}cGXh`Pwi7$m*;^)3U zg_SFLoL*6o`>e)j2{?ee{DdJMAe^ff0k1aP|CxT>h~P8$VzrgEH>D-dF(j|SWz~BUP9#1#t*`%2A%%3<2fIvcswYv$BLO@A;}&n0 zgKf!blSWdz>KMxRcxY4a{^ju@Vp^9y^1XL&ql}EqKLsL0jtB(4&xqR^qYHT@BVj9x z#(w?BVa>++Q3G*OBK$B}4n1Ax_VvMOGytb&NzxX8bIE_C#m#b9fSP7Cv2TgJge5(D zfQ(L*Z6TJMFC#UP(fdNphZRbRAEli*e4=oL2{Jb+KDl(^;>Eu|xnc*O64)w_IMGe9 z*4s8$3HQ^NpHMXXg|BOCD-q`UOXzq7M^UptLxy($8Rz8Z?d=V4VArJoPvC5$9K8F7 zStLK}(hV<|c(zdvtG4?=0>zJ3_mk}NIdm{z4ii(N>Y@st$d@-4dVDNXH zem0u|VY(~zKQS6_{(V;!=3yuPFx;udMk3qkpg)96`Gn_rk_}6jWA`1$NfI|lg!?V- zoreGlYLIT(HhEZ1CIAvt5RnOxVm~qyb=Rn%zHZ2sq))R%MPjo8Hg!creA0{2h%*(P zJ$oVdj^Dx1T)%NcIY`o(pNWA^!e#CYarwbAzCO&A8cZ=6A`qL+FQ-#k%CaaXixX&CvvSK*nhTeDFVb`f4NNdIEo!XK|jK; z1q)W{)TaaLm)+kftN-HWTP#?>s~DI9qMW{dm^*haiV1r7r%ykjY=#IO1>eqS>qV|A z&`esXKle1}YWa)kslk0imr7kFcoLQ9+o7yU)?Wy7tk9=VpKd7vGm$CDwOL50GrEnx z6XkYotTF0f*l1FdIaHiO^lBROt9>* z++w^+yYv0<2>z9$qii`PkbAi2ro`s5b7{QBk)XVi_V_=d1fWh4sSdaK%J2fyd^kN+2Dry~wQy%eqdli7D!vipokNAdIzO#6jK*M+Dj|DLKA8fijAx zM7AJrG0foPPd!FeC z^eP$m9Jt2Xc$DD*k2{h0TSDGwH1XcEc%2F|bw>5d0>qR7b+QObqW4BM@{31zle6|R z!>OsS!;1_S{Uo|gMf8J9BlE*lgB) zKeO})4-A+4$zSuCxA>#iW6#Ce=+-^xTa|gRQ4(F!6 z28GB!?LT-`WQ`77ftnf2{qJ4`7u|TgoX~#SZ#is6ySh%lI`9 zJtoxEm2v8GTwGmaK&Wei5AeG|rGsYxzx@6#6vIjhy1A5Mj>u?5!Si0jy@@{5Ny$J+ z4y98xXmv;J-uLfM4L?q!Uwp|ZRq~-p)oSO=udQbz}V zS`CISIYy0+Tf8V~X((LwRH>VQLPiw2^YGzAxW~i5!tTuE$ggQ!DGs`Z@eQLrHG|gK$ zJX__5<q(aWA&|cWnWGdEBQQ-a!n(H88Eqlx)lA`r_HdF zHwp&#GsxUs^{FxFdQ!pq07R%8NIbEH`XG{3IB~U|A7_A?I}g>!22oMbnLqtxFj~b4 zHBJ?Hh!o8bWoh-!QKVzr+}hGandA|iPfMQAPue%(5Fqa=U=B%&;gDxeTkux0g%L;( zKNcJhbYY;*0Y5$=hC-YGM%DAZ?fPi(+|0$rh3Hdv?(7fbBzV-1ch6Tau_dPy4gTpu zHNki9xyxB9-rieu(3XNfnYW|3N$_K`5aESxJU{K`CV~aALyu!^Za*G=LFL4Wds~*@ z0ASvzM z$>iz}Oxb=SoNv$DQ05YFUOlI(I5^RJX662pZ8xtfa?VI{kJC6=uNn6S_U)%jaOB8` zU*0)AT3a0=%Y~=VRO_Fd9M!>rO8dNq_X$P7inj^C)uHys0WZ&*s|1Eq4QoFs`){}O z%To^Y+;$D^&iJ$8Z>l|i<**#88=0Fw7{tUsZ*u6idw=W5zJoS6{5iS!hb#NY-<)X7 zzcp(A9}o7!p!VNy{{QBNbzYmV1n05e^V1aao07mcsG%i89m?nc^0CSHFYi(Rrm+1X zN2898K}xf6bW{i80qL41t4RCOlc)FEJSBkLw@_u!x^pVM8)=oqO~9^jjRJ=?*k?by z0Lex|XyAq;@;E?;P76$@;IZW46RriwL@^Tz`zT4C&Pxkvjrd#?lgRSy-ec1)CpFfc zjP~$KFk=>ew~(DdxR6)+z?+$r6qYVs8VW|B z6T*sH%0*h$J|U8dJC$0__1{9-5CW{+ur=G}<8`$8hNFx+alqGZ=URxB$ResWX$WS; zFJCfo~ z;Mq@6xNtV&I~9ZBrEP6(0!(*zcZX8Ic4F!t<~eo=GCd%W=$JW!cSABd+mcE#-zi6O z(f%$tiwKG!XbST21s@sX7Z6~wv~_fP{BSKA{>B5j%K2L9_JR=h(p)_cqInu_Fzey- zH~_iziOamk_m zUBFA!4GMY8!uF*O`lNx}?7y`$5neS!N=q}I^{D|P5;-%N3V9P4Vn>hQ%Wx8GJR3U_ zJxukqwF3y_K??-u`eJktY&|tlQh|onLOfM|V`F1#1vQ$0Fwe%)qkEhR>dus!4Iyls zk0K)u*LMuMuy-8X{82knbqMcCBFQFIRVo&Z8HykhGJl zqBcpzyaCtvzE7XRL9d+wi%_RcnaQ(AC;q_?qN#h|k5-@mj?|3*&Q6T~DZT$E%g9I} zwDB2S$F$FS$oij*aMF$qkuOdXJbkF^DxdosYVldQaw8z$-7OkQj~_oq)JS5FK}R7E z)3<>E3VS3{)9{0MJr|cO!jBl-QCwDblyE)p^tX*yDAumM>blM-l&VlbB~tb1*BIgu z1zJA}iU{?FQov%0+kU#aYLEFw@_z!)LJ4^Qu>%AYWv)^br&(p9%}xi%QA~Lap^R7s zm_#1_z>Qszk16F8PYe2E?QRfV=tHd7Fq>*z>cJpOCTa(H7W}zSz}|NujHy5a5@4cr zl9X8*?17jh&!UnI6G_g%N%}z4Du|!5GXZC_gxqj+y8swUeNg0ll2Fhe@?$swvn5aX zqtJ$nX(CmqTTLyrvK``39GfjrWD(JYf`<6?R14hM4m#hvY1`6I0>ba%X6A|XO{{Cb(8!Rm76*5P~$H&9JqYgdN zF!4n9D6;}V^K7jfV*MD}PKv!w(K;zUA5^?{5ckjocO_am!V5NrtsGkqe`Z!z{h*_* zZSv4rd;8SMVathLqc5$0B8(@R-0<>J1KIbAizu0WUj5HzMxmcu8Qre8)6>%dF!bFv zUdqnSrl}y$Ed$%z4IWDO_Vx}}_x&wxjsZ4^1Ui zKoQGUuQsz7gY#+GvSo(0$10(CJ%S~<3&}Y>jr$4-m@y#b$o5gVnq%$e>ARL8+vPDe z>@kRAM1lj#0baOTF@ZOe+EhDY`n#^XPz9DofVp~)`5P3F-G0VD389Y`_d*_n zR7e~ZM#+_GQhBH^r{Akdd9>Mm%wpM=6UV8&h_(f_sX-9KN^dCS`vnK}yP zH>WQTZ~^9&QmHYB;~WYS$vf#dCwmIMU2>MA-!LQ(|A#7>*Iww|WVG{~eLk^9=R)9z zvx#29(-_^`*8l4o>;L~C%!#Ak2^k+Iy@dZ%_?mG-)*mNoGtDRz|6vsH|KB?!$w?l( zx_AR$-qXgn0`E-NpQi^MfAymo>3^*R{4m3L8pHY*>5L@|UcLX&k^WwZ(jzFoR`1dL z0%th#q|l$90E02b_k(NsN9FpVLb`SP*Z1>E=|QD&o?F5GdmIeK__wf=r1|uP!`+v* z;vRbS$?)jVfo*pDJNFRrz+!_YkIw!LdwwLdf~Eh?JN~oR)=dW?u3`L090oGHY--X0 z&0d4Lj7NtP?Psl*YdQ!Lg-BDdK4x&qfM2XYBus!oYH!vq+*HQG_U_NqZKD63QqbP| zGQF7RGIx~333NsT1O$-L+6;8uLtU3n0eDD{!LPY6b|GHnxqeY-?^<*=BD>O`qprwO z{GBK{yf%`Jc;7atG7& z5P2ubpJs|>O_%$SlP42UJAAEwmD9KONB!b^_NbvvSTvUxhihzV+IckM<;#~fA02l% zeL0DFVDD0NGHnjRh5{$;;qYk-j)lVwWdHfien6Yd62GnN&o&H$8L!P+UluwCN?-)Y zMjQ|@!sXH693{tAdv>>LfB?j^<}>R4 zptG{p?U3f$vE=Ugt4AC6qh4CNY+2Z5_4s!%nf;@ZgJQ784hBtFE2+dx-HHs4M6ZTm z|FN&Hub_(_?B2!bN!pJekx^^A{tIHKX?g?sR6u-~0t@#_4^YJj+ z*AKvP;}JPl4zpQfVz$vE&QJJ3! z7_JO%oHRXx)hU+7knOTu2BTq4m8AlL1#dzu>~Cbc!o5I!CsdBY;-HSoHDQ;h<@ zqnT!Y-@XAcq!#i^3N=rg)%L#SS|~Q%f9O~B(cq~OKAQ4FZ3AgK`R+P!O(=qQ?H+}} z)eH@7z}~(HPo%wK?#*Xd4E9XSFMOkAl^eEL;P6JUw$#3W4)xRNxvzR}la>I14?`W9 z0I<|EMX+d)_(w^t-?$oOQPC3J7o0 zC3oxQJ^@?@}Uru(UfG)T@d4>CI%|8nV-Aq$bxJ)1-v|p_VEgM(lCKep0r*3IEnqx z&o3Nmd$jfYzo_COat7{ASum1L1?#7`s&Tq4L6)I8@F38+gB$R-AhX6XzU{*Ys!iAu zt?6)?sTmlwABbI9kSkg29LQp=V>-B5^<8beCO_t5265sjDJa+yh^48gpU;JtSPp9@ zxicNBiTo81&!66pqlbjbE{p~TX{B3|CQOy(9*g=j@IWM;*un~>A`rVE(^v|lc#=o< zHz738h(3==J(i2YM3=d?Iz3$cuvT}v{f@j4i{fiadw(b$fZj^k!XW}P96vN zs`(uq9YNlz>A8=DIrKHhq59DRTu+a`^R?gs0yU_}X)Jz9U_E0aBb_j_LR5GDJbZF` zhLFT-_D${>ayny^*SG_Hr*-18xrN#_Q8+^#u0U-ti1rqm`*2^kCLNCglXhZI|B8D| z<_yNbE{TddMf$xl*#X=qvar>SP&y8jY))uYmrB19N%bnml@fI}zp!{9up7SP`W(wQ zkIoZ`H&(xbp#|QHVluitt0ChPLklvG`etBX426IegQ|skJy8AVI)7XSZg3wf*1yGQ zTr~NS@7ZJRJ^o$Oot%=|a^PvmBK->U4#}iWjw9UtP;4r)^#@dD^6XS9PDRl~l`T!V z#5z~j)Fd*{ytWV2ChTQCQ0l`Q#88m(cY>c~g`$F}>xqDNJ{;SDeq^Fu^S*zWm+hLc zc=2L$-R#WFmqkTI;c<9{1AAJA42|AHq;?jQkca{EN3Boa+A~LV^^tmZJN9amwJaJZ z${z(oEjb`@4+Y@bU{1O1ku$020Sk`? zi9}hMZS5<65^ATLr3}aF0J!#OELPG0b%F;5>@bSM6wCC!W!uoc{otKdDSQ4k@r z-fH*-bv$5wK(7*#l4=UizjH^~Z+K2?KD=2wbJK+iL8Mei9OEM!5VR{rWbG7KoS34) z%>om{th}4@e#U8@>xm zZBk(8&m5=v_=FFb`20Q|+db!zo54FiIC~Ewa+QH1mkOq3ilu5!zZ-fbg z%N{QOjKQc23?8yq!h4^X*Nnz(>`c5}3Zv29ef#<~QFoPH41gk8*}P-b7{eX99fNVG znvHY2+L3Fh;68EUUC5?oV!sR{vGflNRH3Kh5t>q(zuK;ab?`3Y3Yx8h9ZD@5Gah3N z%4a!mb$|zto*?&?Q$0JOYPiURpF@R!w;+t3Th&p=`eWC)rAiK(tOwQef0Clq0ZqKOws3GE+0h9NWAlrQm_ z5g=}V7&{zIKe_aL(985YjIQcXMtXvZ#r`jAcb;9-8RWsSrG9X%3)q&T!OPR+ z3eu~~me}7O`z6$KY#VfT%Fk4czwlbK8N@!h2GNK3^!}s={7&s)tr4rt(CiqEhLAJ} z@z}79_G1Vz)d(dt!h@XY04vnwab@9}XyYK~Pp7!J3Ur*DozdQRjArLy)v4wk;n{Nfqd#% z_B8OUL2}`m%sbCB1~u0a^ed418x8RPs$$7X4w=WWxdS1IvOPIcPp$M4p2hIgettT! zxVl;uBNE~F@_ByxtR2(orpElq2Qzr*6mFv)DYUs$BzXNg$9o#1AcXovkU*I<+yzp% z{Jp23|gpwc8e=3E8hyd-~L=msbD># z+%o2eCg?Og#kf}>qF9q-+1yUQ{3O)6hzV}j;t8JVsI zb{6~gHHBMW0;wfL-uVpLD`U}$F6%!H1YBWTp=ZF!(+e{dT-S* zKvEp~0muctFt^scEHXt#`+b|rO&=@xITjECn zlg|8kVWyYx5&krYCqU+IIOM|E zU!ZMWO}{+N;33M@NHpKr*xD+?$4LEO+0yAP>uItD#_&;7CYJIh?Tn{1$_dE7$zBic zJYQr#x>%58W;7TS7LxTCb*hI|0NCZH7O!ohlLu80wHx{Oab9=x^Yf|Q4~wC$rwvX< zWi-Yjv9#5(J?T&D!j&hD(i{KrFOM?wA zm%UfO1lA^M{ljDe^WRjifEw$9?G9yPRTdiO*shI}uOXA~>}uwc}EKwbf|yNt8lg{hQH8D|fW zYZ!%FUsf}`d*uFt+)6{5;nt=$C5%{=A$HI0g=5vLx5IA%=;-BY*zer?MC@ z=rJ%W!JztS1iBi zZTB9t0gqafr@j>h01PoRaeBZ`1LHE*pJtwF8I z&qOj%zP4BZ9@ZBUmJ%3qLUjx3H|iUtRD+g-pL~C={<|084XW%o|G69Z`Khp_H9~Se zkr*x8=Ckk2z)@P-l<-;5%gaI%Z(oUU`aTDZb^B8aA1JD1|B)=LT*WS|;w*o!od4+OD`sTQU)G3Ma zWy(b{xB~JCku4YCS>z*o9=?C=oY`7HyG<2O#k`jzXHd{4Gb_{1QpKcr-xQm=~j9Vzc%K@&4)cJ z#kuLx{uxw5Vn^KOCr2?FQPr{K(K~E4LBonw;nN=(g% z-IT`M$e}LC24dxJn)=G(2i`={T{v2gSPv1YM&`HBhdbS9Vga(D#f$Haw&lzUoub){ zf;Q+x41+r0FzWIpFuXMekQQ1_j6}Qyx`JuM7C9f#qFxoPx|w(g$YiOV{s958Nam4v z#(-gW0|G5P&KbT+X=Ly8RtT1kL%qi)$0kOdoBJ7Fx_Q&nIzKqr&h;IQI}Uy5{|t_T zPC)V<^Ve6lt!HBU=eDKg=Hy@)4eAEB>UB&!o$5<-mSFInbhc|ME)6Tdt))nwg%;*h zE8?lK1~t+1(NP?Q`rt4#^G@d;8kXBKID_$J?^?NO){)qPXkI@=BLF}dq+(7b&1I+7 zi@ypB<0fFKpvi1zpb03ZkTB+=2ZD-JH?qN!hX#};a`kce8ZdVT>3DLVzBSyDDN=1V zF95?ewk!wo5UR{`9D907L_`Ew_y`t!UV!g z+ndeKRg`U4i&8p>UzemL;02u%(-}T@V9YYmc%j_0BU_Rt2@GrJqq&Y8?1SO^{Ia%in+if8guv#3QKT%gE6_a$@zW9S2Fvdw_*`i?#h#sp>3W)uGDW2~ zJv|u{xNYzZnS}exOlN!veUGW58lT_@!Y1qjv0ICBH?D`G4>prct-XkL#>xJA%rC}n zDjT?6hkQ;m4OGw+4a};5X*21G;t`O4&)6gk^;VJ5% z`cX$CjvcHygi$b%;ZcijuImg?c@1|u zo+Xva)Q5~A3so`dBCy+Zf=e4kC?`6OZEOpR%WrsMG;A7e4v(+|-Ov$3HeAfaN(~v01~*}T)zZs{`~MK=dqvD#FKlXRibOxvcI0qu zeRhpB!uuZZyyipX#t%UhdjWI0A&-oEWBnw})kEF98#Uwt+|BiS_rCiM(|T;FH?Nc_ z1|GlqGBB_U1=Z;Rc5V50RJ9A3FTkQ$44s8Rp1Ly*ou58)KCG=FKvOT!WD;At^9AfV zVh;zGi|Crm?STFBRAnC8QA5!Ea|J67b4E|0Hha4glReI2)Ev3bqesrk)o1)kMsvzog2AF>%EVPws=pF(zyH$G4R`c%?J!H0lTp;t@iXGIND&HfU~RjjX3WWke8+K zB$Nk9c?k>D#sD3n++=^=;W_IN2DbB90_uQ&yYEiiijo|aB(yS{*j`w=sgo0rZ~zl#&H!RLOpq)_g9O0dm&R9| zmo2G#JCorP6!`S{TLC#%-io2AN3~$^ycq4gjnF*q2E1aH z>089bHNmFgdxr>??#56|uuyF3_NSnYFK1aJU4-~x0?@tym!KdpvKaue2%ch#M5MK{)Ch`A>Ay`3F5*Fo3- z@I?cWaB_dfDJddyoBQFbYBcDJ2#(u-LgTj?ihb&T4UvDT0vrq&CyZ?;>wmfpc(g{A z&y5-pWm05f`_W6(_9C#dyD3{8L5sy-S=0sYj*RTO?jwdYg^5KyP`FZRt-@2eGg!6@ z(*$V1J<<7~J&hfO_WD$QP21*ao(P^7k4hf38%18YjP9A_H}u`cTo6^ne-KN8I1iE; z=<8unlJ;ih!+A;MJJPKhFmtLKb}P*lLHV`M1dY?xC;ntkBRHOu8YD zF60VoiF-FlQ$2t*R1oNhiv^JmBL0C+J{C3cvGiKx8tRBbgG94^fYBE4$K_JP0c7U{ z;ev2Lw#RrqfP0b&Af}ama!muVot*~<4$UYg>;yuJdMyd|91TW$A&lmqVS3!tliqRz z^C8?QWTObLkzazr-J6+&@d%UyYu8=g$V9=37PeeNw_MB~V28u5|F2%e2CYT9xJerQb(j6K*FCrdv=c-WlYp6STnD} zrFQRDrP2}Ogy_0xJRaf%Nl!gPOE9?#oMI^u1@7M}^xwgBYB72Q{r8CV7=Lt`%3t(B z5`DVI4J_ZU*r()3!PO|^TZ2A!bwwkrfcF#9d65A@Arjx8^BIrYVGyodzAg-dfUYL2 zuC5Mxh4d_O45-yqi4AI+*{qId;N8Dspl36Opv6phL{itEmcv}vhfW7w+d)r*LVrk?o<{dm(jO^ z;2H?sK(#prZ0sD)9!J--XQRaG$dZn-cDZ*l$3za;Cp3ud^r0)oe z0~cXJ?XF-*hG1zT5fE#Rs(5=o8qYwj;_xL!qx2<{I1n`HZD`c03l9QNqbUxk6st+d zq9u!#e-c`T?t^B>mX5$nKrOIQW1%G}9*(_8d=ti)OU6F5`!!wMN=bfV3UY@YZmBX9 zjgQ^ku{e1%u&_JkA3%J2sv!cT{I{7bG-eV71Be89tVb2n6Pp55qtp|EXiNxPMjoN+PKwr8FR-LKD#{p;@Js5E>B@k+4cFLL`KWiZoC9 zf6jjQv)!-$&x?C|w&!`VwyjoN*LOJ2tFEgdtUMj z#*wFdGzHEHzagtn=xT(xxf?fbJohOggZL#vl&}m^IEqmn!xmjR|Ia5=>T1!QO0N!h zcN5Z=P*K0q1jWot9U{^n4`GppLvHlab*Nr3P>hdTQ5+Q`ND*{Y(RVMJcrovBOh)Nr zUA7*wlr?C@$g^ptnDHP+La`HR+MnolQ;^fV2+<2BifvX0&bvPaGfkXux!eliZHCpk zI^Z%?!!R?mB|?70elJe46ZH`5U;%v9tNq?=2|Q`Bp!zt@U12BPyu!p4p;ptfbx0i4 zR#Wcc4fuTI98t%^c_1!RuE5#Q z@~ZY%2(Rw(`osH0@&lZM^DibIw>HgCS>cJm3aS?Q1HZBK?>P7%up zX**-qVw5@%H6T9GoyRkU=n={tEUx0guAXK<5MSv|PqCgjicD5k);27UM8ABR0mBm9 zKZ?UVEdNS@8+BVu;0~o&xiKz&e%vz8;y1rtP7%m$G&pfR1MJ)u6X}(p!*4uZ`(r}Z z+kB1SA!mpq#(r=^D^)qQ?ki57#|5AkeNKftx{U}u8vD?iUro1Xu=j5^l9Q8D*Uf0l zS#swQ)5I+Q(swCTJ^GQ5{n-88$7kUjY(^v3U3Oj2)sY%0$BcCr%a2NA;hxFwn0(~(W#O)i1&((8{;);#JuRj@hOvFPM{u}FDm^nHIZvE81SJ~T-Qr~Zk@N=@WR^GWIbz^cTl}=Xk zl{?MJR`2y{*(Cig*R-V%tNj~Np)xqX$IgIds@4Zj>FcKqIqeu@x4VC*TceUWNwo6Y zv-BBull8q%-XFWQ8?Kw`G?&QZriS;G6croZOqU7B%rpb@95`{p;rZ7ddt`Y4mXjt) zB_t-c6d#R<2x74S3OnfO>()klvW6%-b(wwAADA!t`OCdM^oMMOrPxpb)qEdTDkdvkz9sW+ZWN@xg5O-s`$ zZKTGtU*zL6j$*-UOIY4AeTt{ubk(Bc-MV-0VvUc9)#S;Y%1z6Z3-6sl@KAFuxOXo& zI=U~}G>Edaji=`Xh4^??;O+zmw2VASoS~caaL6V&yiGYtF)j`HL4iICQJ^pBS#L zuF~3=e>f&aGoVlI>f6io8D)^I7JoJ7d`!%PQ?HR2V(&Tjq?V;EYqTJ#^`^#WWE?9G z_es0g?o&Q~D0CAd^)8DETeohNWW$tbBx?Rf5W|BZ_x$FDMg?Su-GU_d8KB`atDtPt z0E8;~{Up&d&t2IrYaXLnpf<1Ive(;XvYk5Jdi+=&gl~yMKpD%N^;9!6Gc{6v>GMWE zma8tQXfJX|ElQ|;`X#sNcJH%cxN?e%71PtxGu}sP*b@~$1MBqZ*p7;dI)%061)cKL zm)@3M{rmT?d-39Y^6_*WeRbHva)4RhXwA1H&1+e)q^R%pTB(3963@cIy#zbyjfUv;o`;M zu(0lQ8j8!WsH~hcq(>dRh#JSC=2&$E_fQC${%_;Nvn~dGWu>JUf_13LJ!F>>qsnuu zsxX1_{3wBqDK;T`qALZO-fn>8@S( znRHm*xaM3F-Xbl5g$RWm()p)~k}#tAq1s8E+Pu(~>UWVu&ii~{Mw6#b3x=Bhp76onxre5KID{I?FC0(9PnKDJqbn^%k{i=We?Ma!K z5q!veP&Ub2pG~IN*Fd&!-@a|_?e&L?wst`+b?bi)ns(>+@4lE~f+RSIpoVhdjb{XTGrFpf>SFC6c zPNR5Cx#ePOYg@~CQB_b1gVg{3+~^4?)S9+#BIkGJIq_ZUY}r{<}lWx zW0v;!v6>B6<%29ffBh;JO@>|ppa3rR!^Bmext5Gi1mNULm+F<~{q=8YsnzP$!_Xp> zm6fx*o~gnF^rEH38E~)QDc!JTf8&W0+5!AKABa<#%nC|*w(}8Jc+ny;)5NYH(ckayn+G>tBXj|4T;`^=>m;55`F5kUx z-?K#L)vFcYUl`w@9k{|j;@udEi1TrAMlZ%g8z>=>E0o_aD!-AHHR0^sxQ8v76D(qs zSFc{3F(4IkVZDm7M$Y~F-8otGx!J{2Y+%A)Q{&d}pHp(47Zw&O@L)31anp9E8|cEP z1@EFA0h+mQp4{+GqCAjfv(dqyZ)}=p#lK$Uwx+qcSzbZG0=MA9&Ql@}9rEzUbyo+( z%ovbcSU8;nWfg!2MBdJA_4+E}GVZHNVl6vQ)>7eZd53KeuS)Y=x%1vscQe-y6lQl& zBE0L`Wn^S{sNS1gLa2n4^h`{iH$~lLNiCIRAHDoX!Ocz|rm0vsEMBu_gn^;q1HkFb zgrzo`uAUt&2(J(CJ&DQLdvn@$DNih52PT|96L6;YoG51gz;BZ*Smu# zEyxod4FxIy*GVIKJEI0h={ZTW7W8OKQ&U&3ryLhJ0@X&^)YO#CX*Fd^JrPxTU*0r^ z^^B9{IN)wfqVqzf(?=e(U9_m4z*BM4f8wRfmluqiFgH$>n)nt~ya%2Wf|$k9r30xu z@_D4<7x+AB30y!ZPj|VC%Uhr7W=@Uhs-&bu5_V=-mcVGZSe#a`E>t==w~P-8KXgcz z%9R5TWPo|>gU?VM$rJ@|{`BIl)z;VVAibSAdsbH9jU5?7F(REkdlu(^4{AEDyzSGc zT|hwWlS*(MsW)yobAqW086k_7KQKxSMo^#|H| z`6&Z5*AjR>@d`kT$ycw6HjA9Fc6jtNoJS@QQonxv2B|6>`0KA70Mupl|J!HTL=XOL z-T2ZWt$sL3CObL~igZ}8pc4<+A#9;Li{n6ecmP6^#juDRoJFzs4-K2}^Sfuiu^VJ5 znQw7rPV3f%J$yL#JIim(h6Z_)sjKlq_>Vem^}oCT6{+h7V^Q{~zukqfI7mmQ3qzjo zP^zTXZ6Y(sck7l@a|*!=lVLEfv+aFO^!Iw; zR&&jLv4Y>99mi|_^f0$5@HZ=NW^QT}mL_7|ax_gB^xx+>JImj1`1}r9A(?@UdV8#L z;8dcHAMd}IL#4NNd>gd!Z{@&eY8%xE(XZm!7cWrHsS_gLTvC>+xa^)S|LHT49)xzS!!nxW>E96(Wu(flk zQ=<+a?tS^nmE~yOzwN`|C_iiMiWTQZ%U*|IwMc!jV~2F_e*KCS6&0hdT{HIFw8@3D zkri5ctEwi$Qs{%7#2xh>VVg|=>18p-X8!zSEdGXEphP>+LU-gy7jlgwRsBpGn@CqT zx64$-DYGYh34D^}ceE(-=+PqB^gJsq6+>g=6?nN`(YdlvUg;k-XCRIXWK_cvXXxeX z4c?#MUAkmY<8w7=)6=VNeLS~pd4Bv33Q5hT0ySsSLUDO{WaKrhrcL$rIc0e@H7BSu zLl!JtxWRkM{(g741J(U+p&xqX*kimk96o%wKsv8pzpezMsJ7qDJvo&_mJi*n*mwUS zB55>n|KPMCYiLV+1%r{{?EU;+e`8EWE8N_Q#O3PgIkKW$*vHe0e3xA16$~HlEUCb? z)I1W%tpU2+Cf;J74Vl;4p2KEXYg<;XjJ@{EyXDbHV;F!(;KOqFccd#vm-lEOtUGDc$`w z%$5O&kb@6~glb!;%aD2650@O*wUF0rcrxi{)vP9vGPR5Jb_v|g z;(0bf8B#Qh=hGPeu(WK@&)L@E>TA}MXq4q;r!R%Uac^#pTMozskHl}nxiXe%9}jr# zFJ8W!Ga<8=^rY`H(~9yRZ71K$3Q6*xl;)8=&IlNx!Sy+;a2t<-n<~ZRrR>d0^bOnyszk zJ6Y3tG}D|8*EKZk$vWP7=FH&I(o#nkmj!Oo2GNS3n4M%YqR#^mKLbQ`8jc#S?1oX; zdhXnZwO@O*Uno7AE*+@sm_6Xg$91(GCRw2w8B7j+V0O$Vg)K^UZ-2;oJaXPR zX$AMMJz~UdUV!RhES))2P_RpPpjCtre?C6`8O>U|!@{O6TBMF~>8b41ZAMFd-*%$7 z?{%;59C6;dCW#JTa+I;VI@l{2#nQ{iN7NkwmoHx~biv@Sjw`}q)hcb>Ob%e7-L1=& zD_7Lxzs?fp7ociq)9j&jxH5KfEn-iRaJC|XSVDL3>-w6NDc;e zPF}W53t3_x22fGl+Gnu_G>wdU5x*t~Wp`yXrxo9VQrp9Pn=z)QgZVcw*v=zI)H&w3 zxRIgKLS|2fc9~)}G*H`86hn(YEdBEKp(?;=7kl1hlTpoX&>!`99b51-iPL-HHHjls z0EE5nk@K4`-0*%kl*pSxfpG5P-nqOW5b3UK*H|E(_|qLQupZKqD34~q4hM&ZDlrxD z?R2HM$6(#ep5K04WP-j52Z9P3Qo?I=U=G%6ULl71TEgcb{O5$$;sBP(XbJpF{CN0sBA|R2#!Ap)r7-Q zg=F&NR8NHdp#W9z?%K7hKK@N$v4xFIALwPv6)T47&bF|SCe&cp$)DlxJ!ba+p>&u8r`Nw=4|IDoo z6Mud;B>3o&oE^Thv5`wP{~ zV;3%5Q05$(w%loCcC`_UuPLH#n^QSh&zZBGYORil1e+E1a1+NGcA?;*TedGuSatVA zZgOqL4^qsa1JgVhehM+MjG^X|0J193QL|o=~gEWqwgiX7%$neZ(GTK2;qbAhDo3*&&I}Tju@dBq8}I(^!5=4 zINYIcQan|Wx;c?RUP);(T)mTRPc{U~Gghz>8F+%Snu zg8WBB+)jYV`rbaH-*_)&w&K`?gakdr^-)hLn~Aa^1e|)DVCb2hp;mCWi`d+L&5zTo zUR9?ZG+lutA+!cPM+q!{cd_a=Z{AEXev4n}wcp#?TA9Hq`V?1!qJn}G%qh`H4mc^v z<|xVt;}uHKAC282W7Lsgp`0RDQZX3(fRY|vss+;AK%CPSx<@@ZjNi z1M4celC<&6YgzgA>(}D4vIsNp_Q2~|GlxFw*aqih@daGwEWJIz#-FWpE-C5$jIC)2 zL~gkNtVH`Gf2Vo?+=OiPhdddos!&PY@BM{o2~p7X7GH~SQxr?I{mRa+d5Z4)&zd!B zyj;;r6eZsC1uvTss9p(jxuh0=n5EK=9h^k|nL3kM%$#t$Udz9KcaL0XXLnbk#}g;n7Zja5cC1*Ej23q!^65YSgi1Mr z-&{iU7tWoVBT2?+J^K8c51HaQEXLsw#+fn)BJUIy?q!gBd0J+A#pB1r*6h-6ERQ9` z$Yj$&oq7cIEw3C+(9bUO=RRLzL z*p1Q0sQp{N9yIlAQk2Oiz#h72Q?+HMZ)MR_=a9_L6Qr|6O+F*??(&)1s+R~d<#y%i z9lAXqDwhtPEbl!MIaVkD^uMItcnC&c*PX<0_V2&T)3U6zZegI|Y$A!9Tg$k2D_mSO z2Mv-Cac*(VF>Lse7<`lmjQ0xQ$A%UApC3CVt4S{gWvUK$Fu7mGS&{t|4A!C*xDD$C zHtHSMXX{4(W&)tE`9=_S21c+s18heK+6C2)j8WJ!$H2%a=;~FYOP4OGt6toKB~f9^ za<>a<>lp8NJ|RKq8UTd07Y_!%c{5gmn|y$<)@@4L__9ajVe~}!l$zrXDYhY`PQjj0 zQ6*o#9Dlt=i6)m@AO187y!7tHn>Y8Kyfrp7%mp%e;E(jhmxZIU*Sa6UCy;8stAq`l z7<}>KD3fA0Qh3I@RV!8mqbX<&AHHv22}ASDz@Z4*de!N%a zIioOco?SY1(uJ>yQ;%*gIH;VGjr6eAw5X{GSu4DdJA0$ zPvxiXXkZ-<`Q!k0eeghqr85<4o5!%L@NV!H>6@l`4tNt1 zgo4?gBxmxCQnO})_s9vvmrqX?9lUn!+QYhwMrFSwQTwM~L+6|`9~5Av6*F~SxXFI? zxq;#3*p_x6c5A!KRz80Gs7iRhROGt#lZVEH4~~IRl<(4I642NC3n^8>-``)9LzqOJ z2AI#8qZ07-Oa+#7XAY&UKv@Jsc@=>Iq`tRyo@+U{KC`+4_$1r5%r=zOf)KCfeAQT0 z!(gh!OFty`;{RJoG$=o}=y_)^{UIBu$;Pl3fgJg#Xh&z>$g=Yvs;29F+n$Rx0wca3VOP!%`B#pO2$u}Q05H8HW6F{3MV+q4-oMA`E7Q)^QDH8N@4vu8UYcSRXMl2qPfMc3#!aX5wE zftxXb7BLSue(CNSJyQ;{BqT&J)?4G5Q!W zAIj`{UHxI}T2QTsAK8@sR?8mwaPK4q>JH>t7KuK6)k2=7j-udHo4D<}hFKWYI5OdM z#+zHx@wY-Dbb#aq=LfX<0D|vuKr22=S%=wwTHc$zaA9vEU|nNl5YDb!R1>%lU<1rC z(dDEkwaZ*P_rDiR+vPrtp^jOvRF;r1!k$K}y0MeF`{b0ATY&D4MIY9lf4tw@@?uws znwigN5x7UMLs^)AVTQBsxqv<<>FSsE!!vF<6N?P%1y>Ak@(%q%^sHxXc%hdzkBH}= zrl0&Wbawo@ACC*aLwl>+4|P@~BxZ~~8<^4MmoK5Wx77`(+8`G~p3<;f5Gz$izK~w%o0089Zq4U>W=)kgB?q2%(9V=YRspg<(*pZ6`*)+Yp#d z$fF5GE^fX;zS@Y9BU}0%`819ezJ2i1&Tu9f1ABd#JTYM(3H+faBi<%U0sz4QKR;Ag z-}FCe68LB3lMj!#4U&_UbtGL^P&5Mu7p%-g%z-l74JG9{$x5Pa)id6E)@Z(EFSCmX ztZ&rgLZQw-w%T;qU*k8NnC+ovvbG=DM#5-DT02Isnr1%>hj~d^*<{w=I_*aQ4-NZZ zz8xn=q0-Vr@vkqtxwr)J<`6ivbalH^1K!3o`OoCP!Q)3Bz6jH80Y~>SY6H|t4w>{A zHhlP;1q;e*f5g3X|1c%$$Po+T^3a1iR<6F$d@Lxqm(PsmuQSWe7IK7|=q^;AGdCI- z<4ohPBmmJsT7u;{2qD75o30J1e6{Nt#^PIns%>p`mNm0ZV}O^j&YYv8YpAcU!n)6B zwf2tIEsw(tLMpbOJy3I$`+D-4Yn-&?hHXG)PSvL;bsLur<2Ao_V z|N8qdGh@6n$_hcdLqiwXp487bl_Rg^aYGTovy=8+@`D?HW)?Yh!T+z=YIL&uyafwl zN54;>He~MS_3{!yZ0+1BL*q(flu3|^>Kg~#APnNE32J%X*s_EQY;N3@M=|p^sVD?l&ztw`LpIXUo_zro%3Mmd z=dlKA-DG-)9I^x#<9m^s2m0Yo{5QdpVxaU{YIL&8(4S5jxW^s7kdv&OoHTi1rM%m? zYi@}taq;nkwYB9j!& zxY!Diqz|1if58GLs&6#J+b|z3_T?C#*u7|0!LTf19#tOINHY_!w0B_T7V=1!*>zp*Js;6PWQK z+#lfoJ79906np4AaAZ~kKw{y`&umr*C|S6rTx`0^YHIRSf%aLxeWA|t`2|977m01A zj}eB;0}&BTgDk1JA$5^J6oFSmh7PS4 z6ccDM&9n~vBw*jZt_+Y4Wx4Rr4)X0sgy@Ju$xa7Q2)gg-(}83VYD^Q_#B1NY*^lad z>&cV;RE~^k&10tu9s*4yn}mD#@Zrz6&F9$LD`IXM^84?eo@ZA7wJWN%G_Gf#{0>I` z2!#B}IX;GRNSSgL+QOdYxHbP@3e5vYj_idTej_DjweGUkzD;fq#PRXA5Zz=u8lj?+ z(t3nUv5^p+$IlKw1W?hkCLZ|v_wQj{Z#5>8 zC4+}^%L+rup+kWLGG`WJb=x@TPub3$h1TyF5(D`u5_g2$?C@P*L3!iY z9TecVI$idP7&{kR3gw)Im6fdUWT2Jzo8YSk338ixsbSc}>50#USqMQKZICI8~ebGmbTw-Qia&hs@Amw{l=+~hA&(u1gCsC-|52aU$ z6*%hP!Jhw?mAO2A{1`{x=2zI~ov6B-+G(e`Bvj!8({6`^hT2iYeuFIQWnsyKCUS{d z-Mict`{H%`0Ua3$2_e@V+?}4lpyUx9y`SqZkJ!psP&KDNtR~HeW!Tc+AV2El$$@YP zjj^;1>`qHf_1Khh{rXBlc0@!(*q;z}zu*s?ad-I!>6POIN8#QJyQz;0@*2c;kZ*h2 zb6OY_Tns+eUlzvz2zF#hk1iS@GOQP>t+q93p1bHtiQokoaOo(;wqO=JK;T1z1@yZQ z?%lIzE{2FG5H#;<#GpX|LXwY6;tr(Fo5-3thcJ#}CmkPQh~sZ9E|&N1$UvM>aMAvj zP`Y_MCr9QngEm}R+I-M?Bh~Z^%(J*pe2T({!fIGXx08Z*!^z{3xmN(g zQKFx=xU}?;5Z>A6$y}9Ag6_`{ovon*C!UDBe&NF2k;n+qM}H~KG+N*Owl*pH{_Wd= zoCa2UBTTj9SjH$R=On$X?aHSOyoaQIZCa>o(V;+-;<9im{Sth4?>(5~Q4uEwxH>@k*e+*KV508o}h~Ii*GwAPR9Zg)xoi9j~p3#j!PQ}MZEFjBoqdVfB&;byQ83VZ8#&}NKvMQNEe%iVxc zQGsM-!e;-Hl9JlP5HMX4q0C)eToPMg+$@(I&pOvdGcbIM$}iv8QQuMbL5=#H`Uh%) zxKL!Hd3}AaBRNGV2aoN1_yHlWuBBzM+n2(oX?Od>RfCHIg+A7=UpV7!Ha>Z}?VA!- z?0}RMeRd_yQElJ9cv3_RBz@p$3d9gi)>q0`OHz!U8O-xOP0T?B4CWVv{))m;=55bH zbHtp6u5!41_g_SeM9&j8?w)5UW8|=H38jak2vYYBoN{i_v&P0wgiE^m2M{>}p?j#* z+i24E^hWg+E#s1GUXiASeZXY6u^VNr%-(AM)qen*89YTBKy;24Ureqyo*G zLmf@{@qV5FUrf5l1}b>B;P)N?WV2!zT#E0P4Rj~+$pFUFM~ z9O}e338xfI)*6XfV&nSL->expTAd_-5+fx6c*p@Qk>G312+3yLSk4k1C6^-U5g{mBJ}kMXRE4TT=pgZ>3ZpfUPd|6UqP!cZ}cH(Fpr_MW)-n zd)4TTrQ=R7UynbJnx(CJ8r&zKUL@TF((obdL~Z3+-wlti`T*3WhI>eIaDj30x=i;0gBl+6RQ&3833V&_t{toJ;M&mLea8fK*> z%-Q@Mnd@ka{v48KgoQKSTRDkp(>%idyBZBOHHqYx+ui|>Z6=M1IowTW&|O6HP?RZ2 z_W|RrT{F{}G7+FJ=jP^y+WyW#*nly&czxmg@mLREz^?bVsRXNV8%xV?@|x?4ize1n z5U`xOuBp1F#6k6f1Arl~d;R)He#_j&r4wCVZc?gr(}vlRnE(0bJ-5i;em{6u1k(ct zVy@H(|Lmu$uRWb#4J4el|LiXuL80ePosxjttH0&sSQIQb5W-{jzJVW^Z)naEp|5{G z+gYI5q<)Vxl9QD~^c{ANoH%iyB-K4@6f6UNvQt;C%#U@@ft-ZM*TT+0Su=lCL&oYQ zYR<`5t~B`6IM^)7?MawZo@=W9c&fZBfi0mfb19Zs$tf0%{UTaCU@4V*_YM{lSr!KD zL>6?USgZcFcdvr)rzcy4YRSDWO{+e_gb&$#HHx5Khs$17a&%@s+ts^>ihMm>G6K95 zy01dZKq4ayshh`=|Mo$b#NSrSCAX^EA3o;0_HmKQ&tI9vq#2vl=p*Dy=j;F}Q6dQa zPCR8EL#^3F(K5*-nH&Tbc>OT?Qh!EVU+L_;lX~wNJx5~EXJJqh$|u6k+K?ww5|K&3 z`JzODVI{0~CDyWdgpEtSd-u+5%R0)zT3$-WsHik)^kMns!6b@ehJ+-|%?mbgTgR7` zDY-UL9Q$J>9dOpKIY05)!^XZ z7mu$F$3Lp)m@SEDHW2J$MTH|Jj=ioO5%B?@_J|^B30fGAnogql<$*#T1!0Yt_sg6X zw=3X5g0n@9f_K47l8^EFS?Ws>=~)1bg5AOMC_jn@7a=Oo^=NTHt9-;Bu4-G_adT!( zwIqU(O_)asmOkJt&!gIp{4|l-UG*5DYkkY7*k}JexfN@-##UXI%S2 zW-U3Q--k~dB1VxoxK8NK!LVnO{ML`xA-QNj%;-nc6a z1)UnJFO4%Q#SluU_n?wEcd>BzoP|Fr@lWweW-a_(T+k8=VSW0!7|Z?c-Ma(e{~$6% zp)M*@l9SixHqFR&fD&OSs+oSA%)y+OGaG@xwV9Y_c~ILa!f!^Ti%i|%{~!NZZMt2H z@SkPB`Z{sek{}x;ACOf4Bo81MBOTeMdG%GJ7^M{``5b#@E7Y27>|jwG$7Q^ZbZA zglcBA)Yo)ZAC3RHn{C>LMvZmTTvx5ic+f7On14?ZqrBZ|mcdmx#=m1cr)Wx{G=d)_ z65(wE(FsBryzrtr%}|VCv(87kD`6xLu;>*2BzEeahc(?@JkJU*2G*_@&CNNOCWLzt zB-xzciA)n{p~=9A3(1I;o}q&fMqfR>Igqs57+iYyd6&D-RbE%8^~CENKr12bf;{0l zE&bx43vy|bU}qr-ta_SdhKr(0ZC<~#sL_(z?$6@)A<^Pr-H%irJPExJBp|vlpn4F zZAp`|M9->YNF6;F!ky0r(yZC_z_dW7yVFUa_1kZqn2#V49{Cso9`ZtcJ99%Ur%wI5 z#+^1D74T^`3Iy5%MkP&Fcxp13O`tm}!RyU;&l(fT7e@R#w?44KCpyhYD-TK+7{kO5}tH8qzpd_035iyG@WG)bk?shYa2 z^uAx|0(RqPClSMh4bK@D|K}HH!cxbF=!@1AqELwbn+nFm^vTM~5)Fl9f2G>Ily#Mt zeoN9k7azY5a9_5#&mOBCLa?WtQMx!$1gWK>8E@K|6cA9ys1YM%ofX-@ILw+d@gz%0 zMAN0jKGSD`YwN+pMEA_9W0-zn8X3gb2juC@j0UliH0D3WO}~s6m5MQyjsvo*a0iME z4&SkwQ*!)ox(z;=i_|b{XoLEr{})|CT>$Sy86@aI1Ny>2w>2|He)_g zj&{nP_z~elAv7(x-#4s0IYdPmZcEyf^%!}MZ7u6jhY0>Zdyl>n#=c+uv9Lg9GWyRW z^;!1{3!ATAcLfT;nTdWS+B}8L30XCTIUID_7@t?9(N`cs+IZoRv6%|745wDfsSg?k zk?|>4R+lNzl_8NZ5FKw?KYE#kqzG0+rmhH6H@fiX%Th!w|JUhQ>j#;dYKXdd$`mQ2-__nIN9^C?T>ogs!T(HZq`(=H(4)YOd1Gb0sjQq}@PTd{TrVhQ#)pnqvqHo>e#f%R9lwkdXhqo8sYU?P zj%(H^g0I*gCNCd?GbH9w`)TiO?2Oz20iENsAh( zfyN9l^Eqo>1MTkt>a8g;Cit>Cd|Kv4f=>qu$VP4#M0U-kBkxig?$`K?o$XF*_%Amr zVG6lfMgj+}NE$@_gUw%H`@GTFA7_MM?p-l$Od%&{R&D!I5l;nVwxlrU?_Uw^w_lQq zGw4qS1@Veoc{i@ziif!V+eddXlSf2qLYj;OU%fH2r1uhrg=3|w|5{!OE}}QvnN;@=tROwSN8pgTsADnx#W@gYWw!=nNv~2YptZ_v?ZlR zBdr)38M#oOt$=mT*hDE&h*LEi>?8PDJH(En;^O(*>*ye=&N9rmg$i-P0xA&eC zK2yNsC+0s7(rZHmTDON@1gsk`_Z=nIxnejZOjtr;()n^zje6UA2BtG>x=fI2ter*c zjjI;bstE1;S`2Qn(lX7T%ifziSk&|&j(}$wjv$Nw0*&)cpa{t8x;QJsW$;@P32b&$ zbo8VpOEUMkP3cI4#VuGo4(wbnTcJyjk4fi_(ARBd>yM_f9xU7lRhAhEZrT*Nz>*-&hmFHP0mYG j?xMpPJk$RBN7ZZU+uV|qBh$pF_CF^7X?4-UamW7$`L}Ig diff --git a/docs/_static/core-p8-get.png b/docs/_static/core-p8-get.png index 0c4451b5f5318c5edfe3ca0a793c2a0fd546229e..889c032d38df1bd4bb53f3c0fbdce413759b43cd 100644 GIT binary patch literal 46379 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

gCDD=n$A2lKusJMq^ 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 diff --git a/docs/_static/core-p8-set.png b/docs/_static/core-p8-set.png index 5be3b6cbd0d46c359469ba49baf43466971a30f0..f653fe1bd23e834bef7c62fee5260d8c70861fd8 100644 GIT binary patch literal 48240 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 literal 47227 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 diff --git a/docs/_static/djangocache-delete.png b/docs/_static/djangocache-delete.png index 59d60579d1ed7d7c4c4e05c1d86fbfd4031fc47d..7d4225c1a2b22953d2abc6450e1bb7a4b2f06e76 100644 GIT binary patch literal 33750 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�bmcN7 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 VrFrt#)8s0uISW6?n7v~AzX6S<>stT- literal 30412 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 HQ$iB}T{U8w diff --git a/docs/_static/djangocache-get.png b/docs/_static/djangocache-get.png index e92d4a7b9d6dc2b01636f2d492c94ecdf1470ce5..4805d2558ef3741b17b713fe247057b37e75d201 100644 GIT binary patch literal 29965 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 literal 33536 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 C+|f<| diff --git a/docs/_static/djangocache-set.png b/docs/_static/djangocache-set.png index bd51a62ded3ce07f59076702d91ca83253856536..a2a241a169bdd9b8dc63c32629f3f3d3e5de4166 100644 GIT binary patch literal 33364 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 literal 33357 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 i_WD4B{}n*o?ord-xLHfj;8Ki0H+oK Date: Fri, 21 Jun 2019 16:33:18 -0700 Subject: [PATCH 373/550] Fix tables for v4 docs --- README.rst | 83 +++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/README.rst b/README.rst index f1cac23..b58e208 100644 --- a/README.rst +++ b/README.rst @@ -67,7 +67,6 @@ Does your company or website use `DiskCache`_? Send us a `message Features -------- -- TODO: update with Comparison below - Pure-Python - Fully Documented - Benchmark comparisons (alternatives, Django cache backends) @@ -161,56 +160,56 @@ other projects are shown in the tables below. **Features** -================ ================ ======= ======= ============ ============ -Feature diskcache dbm shelve sqlitedict pickleDB -================ ================ ======= ======= ============ ============ -Atomic? Always Maybe Maybe Maybe No -Persistent? Yes Yes Yes Yes Yes -Thread-safe? Yes No No Yes No -Process-safe? Yes No No Maybe No -Backend? SQLite DBM DBM SQLite File -Serialization? Customizable None Pickle Customizable JSON -Data Types? Mapping/Deque Mapping Mapping Mapping Mapping -Ordering? Insertion/Sorted None None None None -Eviction? None/LRS/LRU/LFU None None None None -Vacuum? Automatic Maybe Maybe Manual Automatic -Transactions? Yes No No Maybe No -Multiprocessing? Yes No No No No -Forkable? Yes No No No No -Metadata? Yes No No No No -================ ================ ======= ======= ============ ============ +================ ============= ========= ========= ============ ============ +Feature diskcache dbm shelve sqlitedict pickleDB +================ ============= ========= ========= ============ ============ +Atomic? Always Maybe Maybe Maybe No +Persistent? Yes Yes Yes Yes Yes +Thread-safe? Yes No No Yes No +Process-safe? Yes No No Maybe No +Backend? SQLite DBM DBM SQLite File +Serialization? Customizable None Pickle Customizable JSON +Data Types? Mapping/Deque Mapping Mapping Mapping Mapping +Ordering? Insert/Sorted None None None None +Eviction? LRU/LFU/more None None None None +Vacuum? Automatic Maybe Maybe Manual Automatic +Transactions? Yes No No Maybe No +Multiprocessing? Yes No No No No +Forkable? Yes No No No No +Metadata? Yes No No No No +================ ============= ========= ========= ============ ============ **Quality** -================ ================ ======= ======= ============ ============ -Project diskcache dbm shelve sqlitedict pickleDB -================ ================ ======= ======= ============ ============ -Tests? Yes Yes Yes Yes Yes -Coverage? Yes Yes Yes Yes No -Stress? Yes No No No No -CI Tests? Travis/AppVeyor Yes Yes Travis No -Python? 2/3/PyPy All All 2/3 2/3 -License? Apache2 Python Python Apache2 3-Clause BSD -Docs? Extensive Summary Summary Readme Summary -Benchmarks? Yes No No No No -Sources? GitHub GitHub GitHub GitHub GitHub -Pure-Python? Yes Yes Yes Yes Yes -Server? No No No No No -Integrations? Django None None None None -================ ================ ======= ======= ============ ============ +================ ============= ========= ========= ============ ============ +Project diskcache dbm shelve sqlitedict pickleDB +================ ============= ========= ========= ============ ============ +Tests? Yes Yes Yes Yes Yes +Coverage? Yes Yes Yes Yes No +Stress? Yes No No No No +CI Tests? Linux/Windows Yes Yes Linux No +Python? 2/3/PyPy All All 2/3 2/3 +License? Apache2 Python Python Apache2 3-Clause BSD +Docs? Extensive Summary Summary Readme Summary +Benchmarks? Yes No No No No +Sources? GitHub GitHub GitHub GitHub GitHub +Pure-Python? Yes Yes Yes Yes Yes +Server? No No No No No +Integrations? Django None None None None +================ ============= ========= ========= ============ ============ **Timings** These are very rough measurements. See `DiskCache Cache Benchmarks`_ for more rigorous data. -================ ================ ======= ======= ============ ============ -Project diskcache dbm shelve sqlitedict pickleDB -================ ================ ======= ======= ============ ============ -get 25 µs 36 µs 41 µs 513 µs 92 µs -set 198 µs 900 µs 928 µs 697 µs 1,020 µs -delete 248 µs 740 µs 702 µs 1,717 µs 1,020 µs -================ ================ ======= ======= ============ ============ +================ ============= ========= ========= ============ ============ +Project diskcache dbm shelve sqlitedict pickleDB +================ ============= ========= ========= ============ ============ +get 25 µs 36 µs 41 µs 513 µs 92 µs +set 198 µs 900 µs 928 µs 697 µs 1,020 µs +delete 248 µs 740 µs 702 µs 1,717 µs 1,020 µs +================ ============= ========= ========= ============ ============ Caching Libraries ................. From 4be3e397af2bdc9f7e11e359d583afb833f70ca7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 21 Jun 2019 17:36:23 -0700 Subject: [PATCH 374/550] Update quickstart with module, caching, persistence, and recipes sections --- README.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.rst b/README.rst index b58e208..6299ce6 100644 --- a/README.rst +++ b/README.rst @@ -99,14 +99,31 @@ Installing DiskCache is simple with You can access documentation in the interpreter with Python's built-in help function:: + >>> import diskcache + >>> help(diskcache) + +// caching + >>> from diskcache import Cache, FanoutCache, DjangoCache >>> help(Cache) >>> help(FanoutCache) >>> help(DjangoCache) + +// persistence + >>> from diskcache import Deque, Index >>> help(Deque) >>> help(Index) +// recipes + + >>> from diskcache import memoize_stampede, Lock, throttle + >>> help(memoize_stampede) + >>> help(Lock) + >>> help(throttle) + +// tutorial and api are required reading + User Guide ---------- From 99454781f23af5a4cabf03a18ae00b29451b8b89 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 21 Jun 2019 17:36:55 -0700 Subject: [PATCH 375/550] Reformat api page and improve docstrings --- diskcache/__init__.py | 8 ++++++- docs/api.rst | 51 ++++++++++++++++++++----------------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index dba3cde..96a5732 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -1,4 +1,10 @@ -"DiskCache: disk and file backed cache." +""" +DiskCache API Reference +======================= + +The :doc:`tutorial` provides a helpful walkthrough of most methods. + +""" from .core import Cache, Disk, EmptyDirWarning, UnknownFileWarning, Timeout from .core import DEFAULT_SETTINGS, ENOVAL, EVICTION_POLICY, UNKNOWN diff --git a/docs/api.rst b/docs/api.rst index 660d094..7a6d91e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,19 +1,17 @@ -DiskCache API Reference -======================= - -The :doc:`tutorial` provides a helpful walkthrough of most methods. +.. automodule:: diskcache .. contents:: :local: -DjangoCache ------------ +Cache +----- -Read the :ref:`DjangoCache tutorial ` for example usage. +Read the :ref:`Cache tutorial ` for example usage. -.. autoclass:: diskcache.DjangoCache +.. autoclass:: diskcache.Cache :members: :special-members: + :exclude-members: __weakref__ FanoutCache ----------- @@ -25,12 +23,27 @@ Read the :ref:`FanoutCache tutorial ` for example usage. :special-members: :exclude-members: __weakref__ -Cache +DjangoCache +----------- + +Read the :ref:`DjangoCache tutorial ` for example usage. + +.. autoclass:: diskcache.DjangoCache + :members: + :special-members: + +Deque ----- -Read the :ref:`Cache tutorial ` for example usage. +.. autoclass:: diskcache.Deque + :members: + :special-members: + :exclude-members: __weakref__ -.. autoclass:: diskcache.Cache +Index +----- + +.. autoclass:: diskcache.Index :members: :special-members: :exclude-members: __weakref__ @@ -84,19 +97,3 @@ Timeout ------- .. autoexception:: diskcache.Timeout - -Deque ------ - -.. autoclass:: diskcache.Deque - :members: - :special-members: - :exclude-members: __weakref__ - -Index ------ - -.. autoclass:: diskcache.Index - :members: - :special-members: - :exclude-members: __weakref__ From 379ef3cfb1e152ec2bc061cc812ccb6802ca5ed0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 24 Jun 2019 11:13:00 -0700 Subject: [PATCH 376/550] Update Quickstart with caching, persistence, and recipes sections --- README.rst | 27 +++++++++++++++++++++------ docs/tutorial.rst | 13 +++++++++++-- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 6299ce6..7435706 100644 --- a/README.rst +++ b/README.rst @@ -91,8 +91,7 @@ Features Quickstart ---------- -Installing DiskCache is simple with -`pip `_:: +Installing `DiskCache`_ is simple with `pip `_:: $ pip install diskcache @@ -102,27 +101,43 @@ function:: >>> import diskcache >>> help(diskcache) -// caching +The core of `DiskCache`_ is three data types intended for caching. `Cache`_ +objects manage a SQLite database and filesystem directory to store key and +value pairs. `FanoutCache`_ provides a sharding layer to utilize multiple +caches and `DjangoCache`_ integrates that with `Django`_:: >>> from diskcache import Cache, FanoutCache, DjangoCache >>> help(Cache) >>> help(FanoutCache) >>> help(DjangoCache) -// persistence +Built atop the caching data types, are `Deque`_ and `Index`_ which work as a +cross-process, persistent replacement for Python's ``collections.deque`` and +``dict``. These implement the sequence and mapping container base classes:: >>> from diskcache import Deque, Index >>> help(Deque) >>> help(Index) -// recipes +Finally, a number of `recipes`_ for cross-process synchronization are provided +using an underlying cache. Features like memoization with cache stampede +prevention, cross-process locking, and cross-process throttling are available:: >>> from diskcache import memoize_stampede, Lock, throttle >>> help(memoize_stampede) >>> help(Lock) >>> help(throttle) -// tutorial and api are required reading +Python's docstrings are a quick way to get started but not intended as a +replacement for the `DiskCache Tutorial`_ and `DiskCache API Reference`_. + +.. _`Cache`: http://www.grantjenks.com/docs/diskcache/tutorial.html#cache +.. _`FanoutCache`: http://www.grantjenks.com/docs/diskcache/tutorial.html#fanoutcache +.. _`DjangoCache`: http://www.grantjenks.com/docs/diskcache/tutorial.html#djangocache +.. _`Django`: https://www.djangoproject.com/ +.. _`Deque`: http://www.grantjenks.com/docs/diskcache/tutorial.html#deque +.. _`Index`: http://www.grantjenks.com/docs/diskcache/tutorial.html#index +.. _`recipes`: http://www.grantjenks.com/docs/diskcache/tutorial.html#recipes User Guide ---------- diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 1d70e55..eef8e31 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -527,6 +527,15 @@ cross-thread and cross-process communication. :class:`Index ` objects are also useful in scenarios where contents should remain persistent or limitations prohibit holding all items in memory at the same time. +.. _tutorial-recipes: + +Recipes +------- + +.. todo:: + + Synchronization recipes. + .. _tutorial-settings: Settings @@ -723,8 +732,8 @@ cache statistics are being recorded). .. _`Python Anywhere`: https://www.pythonanywhere.com/ .. _`Parallels`: https://www.parallels.com/ -Implementation Notes --------------------- +Implementation +-------------- :doc:`DiskCache ` is mostly built on SQLite and the filesystem. Some techniques used to improve performance: From d4a0afd23e26bded1ce557e89069b67a572557c1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 09:59:09 -0700 Subject: [PATCH 377/550] Fix typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7435706..7c953f6 100644 --- a/README.rst +++ b/README.rst @@ -112,7 +112,7 @@ caches and `DjangoCache`_ integrates that with `Django`_:: >>> help(DjangoCache) Built atop the caching data types, are `Deque`_ and `Index`_ which work as a -cross-process, persistent replacement for Python's ``collections.deque`` and +cross-process, persistent replacements for Python's ``collections.deque`` and ``dict``. These implement the sequence and mapping container base classes:: >>> from diskcache import Deque, Index From 1266d7ebe957d531e77b509b46400e9b5c762931 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 09:59:43 -0700 Subject: [PATCH 378/550] Add note about versioning --- docs/tutorial.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index eef8e31..ce33cd9 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -18,14 +18,14 @@ Pip & PyPI Installing :doc:`DiskCache ` is simple with `pip `_:: - $ pip install diskcache - -or, with `easy_install `_:: - - $ easy_install diskcache - -But `prefer pip `_ if at all -possible. + $ pip install --upgrade diskcache + +The versioning scheme uses `major.minor.micro` with `micro` intended for bug +fixes, `minor` intended for small features or improvements, and `major` +intended for significant new features and breaking changes. While it is +intended that only `major` version changes are backwards incompatible, it is +not always guaranteed. When running in production, it is recommended to pin at +least the `major` version. Get the Code ............ From 28409c65d71f03015e2bd92777860a5e887f1755 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 09:59:59 -0700 Subject: [PATCH 379/550] Refactor link to issues --- docs/tutorial.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index ce33cd9..5475246 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -51,12 +51,12 @@ or install it into your site-packages easily:: $ python setup.py install :doc:`DiskCache ` is looking for a Debian package maintainer. If you can -help, please open an issue in the `DiskCache Issue Tracker -`_. +help, please open an issue in the `DiskCache Issue Tracker`_. :doc:`DiskCache ` is looking for a CentOS/RPM package maintainer. If -you can help, please open an issue in the `DiskCache Issue Tracker -`_. +you can help, please open an issue in the `DiskCache Issue Tracker`_. + +.. _`DiskCache Issue Tracker`: https://github.com/grantjenks/python-diskcache/issues/ .. _tutorial-cache: From e572d198ee3ca72093224a3d4c894ecbca7a24dd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:00:34 -0700 Subject: [PATCH 380/550] Improve Cache tutorial --- docs/tutorial.rst | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 5475246..f18e888 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -65,22 +65,22 @@ Cache The core of :doc:`DiskCache ` is :class:`diskcache.Cache` which represents a disk and file backed cache. As a Cache, it supports a familiar -Python Mapping interface with additional cache and performance parameters. +Python mapping interface with additional cache and performance parameters. >>> from diskcache import Cache >>> cache = Cache() -Initialization requires a directory path reference. If the directory path does -not exist, it will be created. Additional keyword parameters are discussed -below. Cache objects are thread-safe and may be shared between threads. Two -Cache objects may also reference the same directory from separate threads or -processes. In this way, they are also process-safe and support cross-process -communication. +Initialization expects a directory path reference. If the directory path does +not exist, it will be created. When not specified, a temporary directory is +automatically created. Additional keyword parameters are discussed below. Cache +objects are thread-safe and may be shared between threads. Two Cache objects +may also reference the same directory from separate threads or processes. In +this way, they are also process-safe and support cross-process communication. Cache objects open and maintain one or more file handles. But unlike files, all Cache operations are atomic and Cache objects support process-forking and may be serialized using Pickle. Each thread that accesses a cache should also call -:meth:`close ` on the cache. Cache objects can be used +:meth:`close <.Cache.close>` on the cache. Cache objects can be used in a `with` statement to safeguard calling :meth:`close `. @@ -89,8 +89,8 @@ in a `with` statement to safeguard calling :meth:`close ... pass Closed Cache objects will automatically re-open when accessed. But opening -Cache objects is relatively slow, and since all operations are atomic, you can -safely leave Cache objects open. +Cache objects is relatively slow, and since all operations are atomic, may be +safely left open. >>> cache.set('key', 'value') True @@ -167,8 +167,7 @@ decrementing a missing key will raise a :exc:`KeyError`. KeyError: 'carol' Increment and decrement operations are atomic and assume the value may be -stored in a SQLite column. Most builds that target machines with 64-bit pointer -widths will support 64-bit signed integers. +stored in a SQLite integer column. SQLite supports 64-bit signed integers. Like :meth:`delete ` and :meth:`get `, the method :meth:`pop ` can be @@ -194,7 +193,7 @@ The :meth:`pop ` operation is atomic and using :meth:`incr statistics in long-running systems. Unlike :meth:`get ` the `read` argument is not supported. -Another four methods remove items from the cache. +Another four methods remove items from the cache:: >>> cache.clear() 3 @@ -318,6 +317,11 @@ consistency. It can also fix inconsistencies and reclaim unused space. The return value is a list of warnings. >>> warnings = cache.check() + +Caches do not automatically remove the underlying directory where keys and +values are stored. The cache is intended to be persistent and so must be +deleted manually. + >>> cache.close() >>> import shutil >>> try: @@ -325,6 +329,8 @@ return value is a list of warnings. ... except OSError: # Windows wonkiness ... pass +To permanently delete the cache, recursively remove the cache's directory. + .. _tutorial-fanoutcache: FanoutCache From 47fc277de355024bafd54511f21c6a19e331dee3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:00:46 -0700 Subject: [PATCH 381/550] Improve Timeout and retry tutorial --- docs/tutorial.rst | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f18e888..a9744fb 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -348,17 +348,19 @@ transactions. Transactions are used for every operation that writes to the database. When the timeout expires, a :exc:`diskcache.Timeout` error is raised internally. This `timeout` parameter is also present on :class:`diskcache.Cache`. When a :exc:`Timeout ` error -occurs in :class:`Cache ` methods, the exception is raised to -the caller. In contrast, :class:`FanoutCache ` catches -timeout errors and aborts the operation. As a result, :meth:`set +occurs in :class:`Cache ` methods, the exception may be raised +to the caller. In contrast, :class:`FanoutCache ` +catches all timeout errors and aborts the operation. As a result, :meth:`set ` and :meth:`delete ` -methods may silently fail. Most methods that handle :exc:`Timeout -` exceptions also include a `retry` keyword parameter -(default ``False``) to automatically repeat attempts that timeout. The Mapping -interface operators: :meth:`cache[key] `, -:meth:`cache[key] = value `, and :meth:`del -cache[key] ` automatically retry operations -when :exc:`Timeout ` errors occur. :class:`FanoutCache +methods may silently fail. + +Most methods that handle :exc:`Timeout ` exceptions also +include a `retry` keyword parameter (default ``False``) to automatically repeat +attempts that timeout. The mapping interface operators: :meth:`cache[key] +`, :meth:`cache[key] = value +`, and :meth:`del cache[key] +` automatically retry operations when +:exc:`Timeout ` errors occur. :class:`FanoutCache ` will never raise a :exc:`Timeout ` exception. The default `timeout` is 0.010 (10 milliseconds). From ef087b93e98f5c9692df04db3a9f44e443ae24b6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:01:09 -0700 Subject: [PATCH 382/550] Improve memoize docs --- docs/tutorial.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index a9744fb..c51430e 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -372,9 +372,9 @@ a one second timeout. Operations will attempt to abort if they take longer than one second. The remaining API of :class:`FanoutCache ` matches :class:`Cache ` as described above. -:class:`FanoutCache ` adds an additional feature: -:meth:`memoizing ` cache decorator. The -decorator wraps a callable and caches arguments and return values. +Caches have an additional feature: :meth:`memoizing +` decorator. The decorator wraps a callable and +caches arguments and return values. >>> from diskcache import FanoutCache >>> cache = FanoutCache() @@ -386,14 +386,14 @@ decorator wraps a callable and caches arguments and return values. ... return 1 ... else: ... return fibonacci(number - 1) + fibonacci(number - 2) - >>> print(sum(fibonacci(number=value) for value in range(100))) + >>> print(sum(fibonacci(value) for value in range(100))) 573147844013817084100 The arguments to memoize are like those for `functools.lru_cache `_ and -:meth:`FanoutCache.set `. Remember to call -:meth:`memoize ` when decorating a callable. If -you forget, then a TypeError will occur. +:meth:`Cache.set <.Cache.set>`. Remember to call :meth:`memoize +<.FanoutCache.memoize>` when decorating a callable. If you forget, then a +TypeError will occur:: >>> @cache.memoize ... def test(): @@ -403,7 +403,7 @@ you forget, then a TypeError will occur. TypeError: name cannot be callable Observe the lack of parenthenses after :meth:`memoize -` above. +` above. .. _`Sharding`: https://en.wikipedia.org/wiki/Shard_(database_architecture) From 40612613ff92beec9613fcd4e85dfdcf8f8b3f2f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:01:30 -0700 Subject: [PATCH 383/550] Improve Deque and Index tutorial --- docs/tutorial.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index c51430e..63c3cb3 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -479,7 +479,7 @@ Deque `_-compatible double-ended queue. Deques are a generalization of stacks and queues with fast access and editing at both front and back sides. :class:`Deque -` objects inherit the benefits of the :class:`Cache +` objects inherit the benefits of :class:`Cache ` objects but never evict items. >>> from diskcache import Deque @@ -512,7 +512,7 @@ Index `_ and `ordered dictionary `_ -interface. :class:`Index ` objects inherit the benefits of +interface. :class:`Index ` objects inherit all the benefits of :class:`Cache ` objects but never evict items. >>> from diskcache import Index @@ -533,7 +533,9 @@ interface. :class:`Index ` objects inherit the benefits of :class:`Index ` objects provide an efficient and safe means of cross-thread and cross-process communication. :class:`Index ` objects are also useful in scenarios where contents should remain persistent or -limitations prohibit holding all items in memory at the same time. +limitations prohibit holding all items in memory at the same time. The index +uses a fixed amout of memory regardless of the size or number of items stored +inside it. .. _tutorial-recipes: From 8e44b090639563fe05638bc8963953c8febc332d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:01:39 -0700 Subject: [PATCH 384/550] Add transactions section to tutorial --- docs/tutorial.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 63c3cb3..3cac7b3 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -546,6 +546,23 @@ Recipes Synchronization recipes. + Update docs for recipes. + +.. _tutorial-transactions: + +Transactions +------------ + +.. todo:: + + Demonstrate use of transactions on Cache, Index, and Deque objects. + + Example, consistency: Averager + + Example, performance: get_many, set_many, delete_many + + Update docs for Cache.transact, Deque.transact, Index.transact + .. _tutorial-settings: Settings From cdc59c1b06ee0b34fd69b0b57e642bbe42b26643 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:03:59 -0700 Subject: [PATCH 385/550] Add note about FanoutCache and DjangoCache missing transactions --- docs/tutorial.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 3cac7b3..0640f07 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -557,6 +557,9 @@ Transactions Demonstrate use of transactions on Cache, Index, and Deque objects. + Missing from FanoutCache and DjangoCache due to sharding. Request Cache, + Index, or Deque object. + Example, consistency: Averager Example, performance: get_many, set_many, delete_many From 71d1b47c00602e50c3be46b5534257debdbb6c80 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:17:49 -0700 Subject: [PATCH 386/550] Document the "none" eviction policy in tutorial --- docs/api.rst | 2 ++ docs/tutorial.rst | 31 +++++++++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 7a6d91e..1c2ed63 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -48,6 +48,8 @@ Index :special-members: :exclude-members: __weakref__ +.. _constants: + Constants --------- diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 0640f07..509d63c 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -193,6 +193,8 @@ The :meth:`pop ` operation is atomic and using :meth:`incr statistics in long-running systems. Unlike :meth:`get ` the `read` argument is not supported. +.. _tutorial-culling: + Another four methods remove items from the cache:: >>> cache.clear() @@ -645,22 +647,27 @@ accessible at :data:`diskcache.DEFAULT_SETTINGS`. Eviction Policies ----------------- -:doc:`DiskCache ` supports three eviction policies each with different +:doc:`DiskCache ` supports four eviction policies each with different tradeoffs for accessing and storing items. -* `Least Recently Stored` is the default. Every cache item records the time it - was stored in the cache. This policy adds an index to that field. On access, - no update is required. Keys are evicted starting with the oldest stored - keys. As :doc:`DiskCache ` was intended for large caches (gigabytes) - this policy usually works well enough in practice. -* `Least Recently Used` is the most commonly used policy. An index is added to - the access time field stored in the cache database. On every access, the +* ``"least-recently-stored"`` is the default. Every cache item records the time + it was stored in the cache. This policy adds an index to that field. On + access, no update is required. Keys are evicted starting with the oldest + stored keys. As :doc:`DiskCache ` was intended for large caches + (gigabytes) this policy usually works well enough in practice. +* ``"least-recently-used"`` is the most commonly used policy. An index is added + to the access time field stored in the cache database. On every access, the field is updated. This makes every access into a read and write which slows accesses. -* `Least Frequently Used` works well in some cases. An index is added to the - access count field stored in the cache database. On every access, the field - is incremented. Every access therefore requires writing the database which - slows accesses. +* ``"least-frequently-used"`` works well in some cases. An index is added to + the access count field stored in the cache database. On every access, the + field is incremented. Every access therefore requires writing the database + which slows accesses. +* ``"none"`` disables cache evictions. Caches will grow in size without + bound. Cache items will still be lazily removed if they expire. The + persistent data types, :class:`.Deque` and :class:`.Index`, use the + ``"none"`` eviction policy. For :ref:`lazy culling ` use + the :ref:`cull_limit ` setting instead. All clients accessing the cache are expected to use the same eviction policy. The policy can be set during initialization using a keyword argument. From 07f6e5ff25f22a4485c5390d6e66f8a00bb1edb0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:25:56 -0700 Subject: [PATCH 387/550] Add note about fanoutcache and shard size limit --- docs/tutorial.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 509d63c..1e43a20 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -374,6 +374,13 @@ a one second timeout. Operations will attempt to abort if they take longer than one second. The remaining API of :class:`FanoutCache ` matches :class:`Cache ` as described above. +The :class:`.FanoutCache` :ref:`size_limit ` is used as the total +size of the cache. The size limit of individual cache shards is the total size +divided by the number of shards. In the example above, the default total size +is one gigabyte and there are four shards so each cache shard has a size limit +of 256 megabytes. Items that are larger than the size limit are immediately +culled. + Caches have an additional feature: :meth:`memoizing ` decorator. The decorator wraps a callable and caches arguments and return values. From df66e3eae295133c0c785194a2045ecf22d9ec79 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:34:22 -0700 Subject: [PATCH 388/550] Remove delay fuzzer --- docs/case-study-delay-fuzzer.rst | 138 ------------------------------- 1 file changed, 138 deletions(-) delete mode 100644 docs/case-study-delay-fuzzer.rst diff --git a/docs/case-study-delay-fuzzer.rst b/docs/case-study-delay-fuzzer.rst deleted file mode 100644 index e71bdae..0000000 --- a/docs/case-study-delay-fuzzer.rst +++ /dev/null @@ -1,138 +0,0 @@ -Case Study: Delay Fuzzer -======================== - -Raymond keynote: -https://dl.dropboxusercontent.com/u/3967849/pybay2017_keynote/_build/html/index.html - -Fuzzing technique: -https://dl.dropboxusercontent.com/u/3967849/pybay2017_keynote/_build/html/threading.html#fuzzing - -Code below is simple on purpose. Not something to use in production. Ok for -testing. - -// discuss sys.settrace - - >>> def delayfuzzer(function): - ... """Insert random delays into function. - ... - ... WARNING: Not to be used in production scenarios. - ... The use of `sys.settrace` may affect other Python - ... tools like `pdb` and `coverage`. - ... - ... Decorator to insert random delays into a function to - ... encourage race conditions in multi-threaded code. - ... - ... """ - ... from functools import wraps - ... from sys import settrace - ... - ... try: - ... code = function.__code__ - ... except AttributeError: # Python 2 compatibility. - ... code = function.co_code - ... - ... def tracer(frame, event, arg): - ... "Activate sleeper in calls to function." - ... if event == 'call' and frame.f_code is code: - ... return sleeper - ... - ... @wraps(function) - ... def wrapper(*args, **kwargs): - ... """Set tracer before calling function. - ... - ... Tracing is thread-local so set the tracer before - ... every function call. - ... - ... """ - ... settrace(tracer) - ... return function(*args, **kwargs) - ... - ... return wrapper - -Sleeper function that prints location: - - >>> from time import sleep - >>> from random import expovariate - >>> def sleeper(frame, event, arg): - ... "Sleep for random period." - ... lineno = frame.f_lineno - ... print('Tracing line %s in diskcache/core.py' % lineno) - ... sleep(expovariate(100)) - -Check that it's working: - - >>> import diskcache - >>> diskcache.Cache.incr = delayfuzzer(diskcache.Cache.incr) - >>> cache = diskcache.FanoutCache('tmp') - >>> cache.incr(0) - Tracing line 797 in diskcache/core.py - Tracing line 798 in diskcache/core.py - Tracing line 800 in diskcache/core.py - Tracing line 804 in diskcache/core.py - Tracing line 805 in diskcache/core.py - Tracing line 807 in diskcache/core.py - Tracing line 808 in diskcache/core.py - Tracing line 811 in diskcache/core.py - Tracing line 812 in diskcache/core.py - Tracing line 813 in diskcache/core.py - Tracing line 814 in diskcache/core.py - Tracing line 815 in diskcache/core.py - Tracing line 815 in diskcache/core.py - 1 - >>> cache.clear() - 1 - -Simple sleeper function: - - >>> def sleeper(frame, event, arg): - ... "Sleep for random period." - ... sleep(expovariate(100)) - -Increment all numbers in a range: - - >>> def task(cache): - ... for num in range(100): - ... cache.incr(num, retry=True) - -Process worker to start many tasks in separate threads. - - >>> import threading - >>> def worker(): - ... cache = diskcache.FanoutCache('tmp') - ... threads = [] - ... - ... for num in range(8): - ... thread = threading.Thread(target=task, args=(cache,)) - ... threads.append(thread) - ... - ... for thread in threads: - ... thread.start() - ... - ... for thread in threads: - ... thread.join() - -Start many worker processes: - - >>> import multiprocessing - >>> def main(): - ... processes = [] - ... - ... for _ in range(8): - ... process = multiprocessing.Process(target=worker) - ... processes.append(process) - ... - ... for process in processes: - ... process.start() - ... - ... for process in processes: - ... process.join() - -Ok, here goes: - - >>> main() - >>> sorted(cache) == list(range(100)) - True - >>> all(cache[key] == 64 for key in cache) - True - -Yaay! It worked. From 935cc3933bed14c57ec8785ee4f87aa877439491 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Jun 2019 10:34:36 -0700 Subject: [PATCH 389/550] Add link to landing page caching --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 7c953f6..d0b74d0 100644 --- a/README.rst +++ b/README.rst @@ -149,6 +149,7 @@ tutorial, benchmarks, API, and development. * `DiskCache Cache Benchmarks`_ * `DiskCache DjangoCache Benchmarks`_ * `Case Study: Web Crawler`_ +* `Case Study: Landing Page Caching`_ * `Talk: All Things Cached - SF Python 2017 Meetup`_ * `DiskCache API Reference`_ * `DiskCache Development`_ @@ -158,6 +159,7 @@ tutorial, benchmarks, API, and development. .. _`DiskCache DjangoCache Benchmarks`: http://www.grantjenks.com/docs/diskcache/djangocache-benchmarks.html .. _`Talk: All Things Cached - SF Python 2017 Meetup`: http://www.grantjenks.com/docs/diskcache/sf-python-2017-meetup-talk.html .. _`Case Study: Web Crawler`: http://www.grantjenks.com/docs/diskcache/case-study-web-crawler.html +.. _`Case Study: Landing Page Caching`: http://www.grantjenks.com/docs/diskcache/case-study-landing-page-caching.html .. _`DiskCache API Reference`: http://www.grantjenks.com/docs/diskcache/api.html .. _`DiskCache Development`: http://www.grantjenks.com/docs/diskcache/development.html From 8d950e5e4762c576ca381ed554a378f24670a287 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 09:42:47 -0700 Subject: [PATCH 390/550] Use reference in with-statement --- docs/tutorial.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 1e43a20..f726642 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -86,14 +86,13 @@ in a `with` statement to safeguard calling :meth:`close >>> cache.close() >>> with Cache(cache.directory) as reference: - ... pass + ... reference.set('key', 'value') + True Closed Cache objects will automatically re-open when accessed. But opening Cache objects is relatively slow, and since all operations are atomic, may be safely left open. - >>> cache.set('key', 'value') - True >>> cache.close() >>> cache.get('key') # Automatically opens, but slower. 'value' From 6ed398b4d615c46f47b63c1a0a889eb5ff14c66e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 09:43:09 -0700 Subject: [PATCH 391/550] Add quick note on touch() api --- docs/tutorial.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f726642..1bc6b6e 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -131,6 +131,14 @@ The return value is a tuple containing the value, expire time (seconds from epoch), and tag. Because we passed ``read=True`` the value is returned as a file-like object. +Use :meth:`touch <.Cache.touch>` to update the expiration time of an item in +the cache. + + >>> cache.touch('key', expire=None) + True + >>> cache.touch('does-not-exist', expire=1) + False + Like :meth:`set `, the method :meth:`add ` can be used to insert an item in the cache. The item is inserted only if the key is not already present. From 1506b722dd6ed6107f2b0640eeb24df22d853bae Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 09:43:24 -0700 Subject: [PATCH 392/550] Improve wording --- docs/tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 1bc6b6e..8cc3035 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -280,8 +280,8 @@ remove items until the cache volume is less than the size limit. True Some users may defer all culling to a cron-like process by setting the -:ref:`cull_limit ` to zero and calling :meth:`cull -` to manually remove items. Like :meth:`evict +:ref:`cull_limit ` to zero and manually calling :meth:`cull +` to remove items. Like :meth:`evict ` and :meth:`expire `, calls to :meth:`cull ` will work regardless of the :ref:`cull_limit `. From 2e344cdd1c754ae0ac3c7210fc89a364dcef88ca Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 09:44:12 -0700 Subject: [PATCH 393/550] Add section on insertion order and sort order --- docs/tutorial.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 8cc3035..c79401b 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -294,6 +294,26 @@ Some users may defer all culling to a cron-like process by setting the Each of these methods is designed to work concurrent to others. None of them block readers or writers in other threads or processes. +Caches may be iterated by either insertion order or sorted order. The default +ordering uses insertion order. To iterate by sorted order, use :meth:`iterkeys +<.Cache.iterkeys>`. The sort order is determined by the database which makes it +valid only for `str`, `bytes`, `int`, and `float` data types. Other types of +keys will be serialized which is likely to have a meaningless sorted order. + + >>> for key in 'cab': + ... cache[key] = None + >>> list(cache) + ['c', 'a', 'b'] + >>> list(cache.iterkeys()) + ['a', 'b', 'c'] + >>> cache.peekitem() + ('b', None) + >>> cache.peekitem(last=False) + ('c', None) + +If only the first or last item in insertion order is desired then +:meth:`peekitem <.Cache.peekitem>` is more efficient than using iteration. + Lastly, three methods support metadata about the cache. The first is :meth:`volume ` which returns the estimated total size in bytes of the cache directory on disk. From 55ce695ce0b80363ddce5e09bc5ce1f9c6c4658a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 09:44:31 -0700 Subject: [PATCH 394/550] Add section on push, pull, and peek --- docs/tutorial.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index c79401b..8fe4c46 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -314,6 +314,36 @@ keys will be serialized which is likely to have a meaningless sorted order. If only the first or last item in insertion order is desired then :meth:`peekitem <.Cache.peekitem>` is more efficient than using iteration. +Three additional methods use the sorted ordering of keys to maintain a +queue-like data structure within the cache. The :meth:`push <.Cache.push>`, +:meth:`pull <.Cache.pull>`, and :meth:`peek <.Cache.peek>` methods +automatically assign the key within the cache. + + >>> key = cache.push('first') + >>> print(key) + 500000000000000 + >>> cache[key] + 'first' + >>> _ = cache.push('second') + >>> _ = cache.push('zeroth', side='front') + >>> _, value = cache.peek() + >>> value + 'zeroth' + >>> key, value = cache.pull() + >>> print(key) + 499999999999999 + >>> value + 'zeroth' + +The `side` parameter supports access to either the ``'front'`` or ``'back'`` of +the cache. In addition, the `prefix` parameter can be used to maintain multiple +queue-like data structures within a single cache. When prefix is ``None``, +integer keys are used. Otherwise, string keys are used in the format +“prefix-integer”. Integer starts at 500 trillion. Like :meth:`set <.Cache.set>` +and :meth:`get <.Cache.get>`, methods :meth:`push <.Cache.push>`, :meth:`pull +<.Cache.pull>`, and :meth:`peek <.Cache.peek>` support cache metadata like the +expiration time and tag. + Lastly, three methods support metadata about the cache. The first is :meth:`volume ` which returns the estimated total size in bytes of the cache directory on disk. From 616f848dcb61f6820714606a377fdffccec5cdc5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 09:48:27 -0700 Subject: [PATCH 395/550] Add reference to push, pull, and peek in deque --- docs/tutorial.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 8fe4c46..ce9155f 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -545,8 +545,9 @@ Deque `_-compatible double-ended queue. Deques are a generalization of stacks and queues with fast access and editing at both front and back sides. :class:`Deque -` objects inherit the benefits of :class:`Cache -` objects but never evict items. +` objects use the :meth:`push <.Cache.push>`, :meth:`pull +<.Cache.pull>`, and :meth:`peek <.Cache.peek>` methods of :class:`Cache +<.Cache>` objects but never evict or expire items. >>> from diskcache import Deque >>> deque = Deque(range(5, 10)) @@ -579,7 +580,7 @@ Index and `ordered dictionary `_ interface. :class:`Index ` objects inherit all the benefits of -:class:`Cache ` objects but never evict items. +:class:`Cache ` objects but never evict or expire items. >>> from diskcache import Index >>> index = Index([('a', 1), ('b', 2), ('c', 3)]) From 4b7e395a0cdf99033c4cbd5dd36b30c9a66a8166 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 11:06:46 -0700 Subject: [PATCH 396/550] Add recipes in tutorial and api docs --- docs/api.rst | 21 +++++++++++++++++++++ docs/tutorial.rst | 22 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 1c2ed63..99686bb 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -48,6 +48,27 @@ Index :special-members: :exclude-members: __weakref__ +Recipes +------- + +.. autoclass:: diskcache.Averager + :members: + +.. autoclass:: diskcache.Lock + :members: + +.. autoclass:: diskcache.RLock + :members: + +.. autoclass:: diskcache.BoundedSemaphore + :members: + +.. autodecorator:: diskcache.throttle + +.. autodecorator:: diskcache.barrier + +.. autodecorator:: diskcache.memoize_stampede + .. _constants: Constants diff --git a/docs/tutorial.rst b/docs/tutorial.rst index ce9155f..c17b6a6 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -630,8 +630,28 @@ Transactions Example, consistency: Averager Example, performance: get_many, set_many, delete_many +.. _tutorial-recipes: + +Recipes +------- - Update docs for Cache.transact, Deque.transact, Index.transact +:doc:`DiskCache ` includes a few synchronization recipes for +cross-thread and cross-process communication: + +* :class:`.Averager` -- maintains a running average like that shown above. +* :class:`.Lock`, :class:`.RLock`, and :class:`.BoundedSemaphore` -- recipes + for synchronization around critical sections like those found in Python's + `threading`_ and `multiprocessing`_ modules. +* :func:`throttle <.throttle>` -- function decorator to rate-limit calls to a + function. +* :func:`barrier <.barrier>` -- function decorator to synchronize calls to a + function. +* :func:`memoize_stampede <.memoize_stampede>` -- memoizing function decorator + with cache stampede protection. Read :doc:`case-study-landing-page-caching` + for a comparison of memoization strategies. + +.. _threading: https://docs.python.org/3/library/threading.html +.. _multiprocessing: https://docs.python.org/3/library/multiprocessing.html .. _tutorial-settings: From e4cd4dc0daf7ff9e56fb3d564fcc060147dc48ad Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 11:07:12 -0700 Subject: [PATCH 397/550] Add note about memory usage and deques --- docs/tutorial.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index c17b6a6..017a8df 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -569,7 +569,9 @@ access and editing at both front and back sides. :class:`Deque :class:`Deque ` objects provide an efficient and safe means of cross-thread and cross-process communication. :class:`Deque ` objects are also useful in scenarios where contents should remain persistent or -limitations prohibit holding all items in memory at the same time. +limitations prohibit holding all items in memory at the same time. The deque +uses a fixed amout of memory regardless of the size or number of items stored +inside it. Index ----- From 126b033c5715fd058a258fc3c104b7c8d0858fbb Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 11:07:44 -0700 Subject: [PATCH 398/550] Add tutorial section on Transactions --- docs/tutorial.rst | 58 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 017a8df..e028e54 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -606,32 +606,60 @@ limitations prohibit holding all items in memory at the same time. The index uses a fixed amout of memory regardless of the size or number of items stored inside it. -.. _tutorial-recipes: +.. _tutorial-transactions: -Recipes -------- +Transactions +------------ -.. todo:: +Transactions are implemented by the :class:`.Cache`, :class:`.Deque`, and +:class:`.Index` data types and support consistency and improved +performance. Use transactions to guarantee a group of operations occur +atomically. For example, to calculate a running average, the total and count +could be incremented together:: + + >>> with cache.transact(): + ... total = cache.incr('total', 123.45) + ... count = cache.incr('count') + >>> total + 123.45 + >>> count + 1 - Synchronization recipes. +And to calculate the average, the values could be retrieved together: - Update docs for recipes. + >>> with cache.transact(): + ... total = cache.get('total') + ... count = cache.get('count') + >>> average = None if count == 0 else total / count + >>> average + 123.45 -.. _tutorial-transactions: +Keep transactions as short as possible because within a transaction, no other +writes may occur to the cache. Every write operation uses a transaction and +transactions may be nested to improve performance. For example, a possible +implementation to set many items within the cache:: -Transactions ------------- + >>> def set_many(cache, mapping): + ... with cache.transact(): + ... for key, value in mapping.items(): + ... cache[key] = value -.. todo:: +By grouping all operations in a single transaction, performance may improve two +to five times. But be careful, a large mapping will block other concurrent +writers. - Demonstrate use of transactions on Cache, Index, and Deque objects. +Transactions are not implemented by :class:`.FanoutCache` and +:class:`.DjangoCache` due to key sharding. Instead, a cache shard with +transaction support may be requested. - Missing from FanoutCache and DjangoCache due to sharding. Request Cache, - Index, or Deque object. + >>> fanout_cache = FanoutCache() + >>> tutorial_cache = fanout_cache.cache('tutorial') + >>> username_queue = fanout_cache.deque('usernames') + >>> url_to_response = fanout_cache.index('responses') - Example, consistency: Averager +The cache shard exists in a subdirectory of the fanout-cache with the given +name. - Example, performance: get_many, set_many, delete_many .. _tutorial-recipes: Recipes From 5eff80e019de2158d126997655481030223a744b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 11:08:05 -0700 Subject: [PATCH 399/550] Improve formatting for autodoc --- diskcache/recipes.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 48b8241..08b873b 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -33,15 +33,17 @@ class Averager(object): total and count. The average can then be calculated at any time. >>> import diskcache - >>> cache = diskcache.Cache() + >>> cache = diskcache.FanoutCache() >>> ave = Averager(cache, 'latency') >>> ave.add(0.080) >>> ave.add(0.120) >>> ave.get() 0.1 >>> ave.add(0.160) - >>> ave.get() + >>> ave.pop() 0.12 + >>> print(ave.get()) + None """ def __init__(self, cache, key, expire=None, tag=None): @@ -61,14 +63,14 @@ def add(self, value): ) def get(self): - "Get current average." + "Get current average or return `None` if count equals zero." total, count = self._cache.get(self._key, default=(0.0, 0), retry=True) - return 0.0 if count == 0 else total / count + return None if count == 0 else total / count def pop(self): - "Return current average and reset average to 0.0." + "Return current average and delete key." total, count = self._cache.pop(self._key, default=(0.0, 0), retry=True) - return 0.0 if count == 0 else total / count + return None if count == 0 else total / count class Lock(object): @@ -178,7 +180,7 @@ class BoundedSemaphore(object): >>> import diskcache >>> cache = diskcache.Cache() - >>> semaphore = BoundedSemaphore(cache, 'max-connections', value=2) + >>> semaphore = BoundedSemaphore(cache, 'max-cons', value=2) >>> semaphore.acquire() >>> semaphore.acquire() >>> semaphore.release() @@ -242,7 +244,7 @@ def throttle(cache, count, seconds, name=None, expire=None, tag=None, >>> start = time.time() >>> while (time.time() - start) <= 2: ... increment() - >>> count in (6, 7) # 6 or 7 calls depending on processor load + >>> count in (6, 7) # 6 or 7 calls depending on CPU load True """ @@ -329,7 +331,7 @@ def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1): probabilistically before expiration in a background thread of execution. Early probabilistic recomputation is based on research by Vattani, A.; Chierichetti, F.; Lowenstein, K. (2015), Optimal Probabilistic - Cache Stampede Prevention, VLDB, pp. 886?897, ISSN 2150-8097 + Cache Stampede Prevention, VLDB, pp. 886-897, ISSN 2150-8097 If name is set to None (default), the callable name will be determined automatically. @@ -338,27 +340,27 @@ def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1): 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__ + The original underlying function is accessible through the `__wrapped__` attribute. This is useful for introspection, for bypassing the cache, or for rewrapping the function with a different cache. >>> from diskcache import Cache >>> cache = Cache() >>> @memoize_stampede(cache, expire=1) - ... def fibonacci(number): + ... def fib(number): ... if number == 0: ... return 0 ... elif number == 1: ... return 1 ... else: - ... return fibonacci(number - 1) + fibonacci(number - 2) - >>> print(fibonacci(100)) + ... return fib(number - 1) + fib(number - 2) + >>> print(fib(100)) 354224848179261915075 An additional `__cache_key__` attribute can be used to generate the cache key used for the given arguments. - >>> key = fibonacci.__cache_key__(100) + >>> key = fib.__cache_key__(100) >>> del cache[key] Remember to call memoize when decorating a callable. If you forget, then a From dfad0aa27362354901d90457e465b8b246570c3e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 11:23:41 -0700 Subject: [PATCH 400/550] Update caveats regarding asyncio support --- docs/tutorial.rst | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index e028e54..8b20df9 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -865,24 +865,37 @@ floats, equality matches Python's definition. But large integers and all other types will be converted to bytes using pickling and the bytes representation will define equality. -:doc:`DiskCache ` uses SQLite to synchronize database access between -threads and processes and as such inherits all SQLite caveats. Most notably -SQLite is `not recommended`_ for use with Network File System (NFS) mounts. For -this reason, :doc:`DiskCache ` currently `performs poorly`_ on `Python -Anywhere`_. Users have also reported issues running inside of `Parallels`_ -shared folders. - -:doc:`DiskCache ` uses transactions when writing data to disk using -SQLite. When the disk or database is full, a :exc:`sqlite3.OperationalError` -will be raised from any method that attempts to write data. Read operations -will still succeed so long as they do not cause any write (as might occur if -cache statistics are being recorded). +SQLite is used to synchronize database access between threads and processes and +as such inherits all SQLite caveats. Most notably SQLite is `not recommended`_ +for use with Network File System (NFS) mounts. For this reason, :doc:`DiskCache +` currently `performs poorly`_ on `Python Anywhere`_. Users have also +reported issues running inside of `Parallels`_ shared folders. + +When the disk or database is full, a :exc:`sqlite3.OperationalError` will be +raised from any method that attempts to write data. Read operations will still +succeed so long as they do not cause any write (as might occur if cache +statistics are being recorded). + +Asynchronous support using Python's ``async`` and ``await`` keywords and +`asyncio`_ module is blocked by a lack of support in the underlying SQLite +module. But it is possible to run :doc:`DiskCache ` methods in a +thread-pool executor asynchronously. For example:: + + >>> import asyncio + >>> async def set_async(key, val): + ... loop = asyncio.get_running_loop() + ... future = loop.run_in_executor(None, cache.set, key, val) + ... result = await future + ... return result + >>> asyncio.run(set_async('test-key', 'test-value')) + True .. _`hash protocol`: https://docs.python.org/library/functions.html#hash .. _`not recommended`: https://www.sqlite.org/faq.html#q5 .. _`performs poorly`: https://www.pythonanywhere.com/forums/topic/1847/ .. _`Python Anywhere`: https://www.pythonanywhere.com/ .. _`Parallels`: https://www.parallels.com/ +.. _`asyncio`: https://docs.python.org/3/library/asyncio.html Implementation -------------- From 5682d1dbf3fa1a1c91aa95b917c541c386ebc754 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 12:05:32 -0700 Subject: [PATCH 401/550] Half-draft of landing page caching case study --- docs/case-study-landing-page-caching.rst | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/docs/case-study-landing-page-caching.rst b/docs/case-study-landing-page-caching.rst index eb5dabe..97010aa 100644 --- a/docs/case-study-landing-page-caching.rst +++ b/docs/case-study-landing-page-caching.rst @@ -4,10 +4,52 @@ Case Study: Landing Page Caching :doc:`DiskCache ` version 4 added recipes for cache stampede mitigation. Let's look at how that applies to landing page caching. + >>> import time + >>> def generate_landing_page(): + ... time.sleep(0.2) # Work really hard. + ... # Return HTML response. + +Imagine a website under heavy load with a function used to generate the landing +page. There are two processes each with five threads for a total of ten +concurrent workers. Also assume that generating the landing page takes about +two hundred milliseconds. + .. image:: _static/no-caching.png +When we look at the number of concurrent workers and the latency with no +caching at all, the graph looks as above. Notice each worker constantly +regenerates the page with a consistently slow latency. + + >>> import diskcache as dc + >>> cache = dc.Cache() + >>> @cache.memoize(expire=1) + ... def generate_landing_page(): + ... time.sleep(0.2) + +With traditional caching, the result of generating the landing page can be +memoized for one second. After each second, the cached HTML expires and all ten +workers rush to regenerate the result. + .. image:: _static/traditional-caching.png +There is a huge improvement in average latency now but some requests experience +worse latency than before due to the added overhead of caching. The cache +stampede is visible now as the spikes in the concurrency graph. If generating +the landing page requires significant resources then the spikes may be +prohibitive. + +To reduce the number of concurrent workers, a barrier can be used to +synchronize generating the landing page:: + + >>> @cache.memoize(expire=0) + ... @dc.barrier(cache, dc.Lock) + ... @cache.memoize(expire=1) + ... def generate_landing_page(): + ... time.sleep(0.2) + +The double-checked locking uses two memoization decorators to optimistically +look up the cache result before locking. + .. image:: _static/synchronized-locking.png .. image:: _static/early-recomputation.png From 4b721b3bad706e049548bb17a435e26e93e28891 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 1 Jul 2019 17:42:39 -0700 Subject: [PATCH 402/550] Show asyncio without running through doctests --- docs/tutorial.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 8b20df9..a4c3317 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -881,14 +881,15 @@ Asynchronous support using Python's ``async`` and ``await`` keywords and module. But it is possible to run :doc:`DiskCache ` methods in a thread-pool executor asynchronously. For example:: - >>> import asyncio - >>> async def set_async(key, val): - ... loop = asyncio.get_running_loop() - ... future = loop.run_in_executor(None, cache.set, key, val) - ... result = await future - ... return result - >>> asyncio.run(set_async('test-key', 'test-value')) - True + import asyncio + + async def set_async(key, val): + loop = asyncio.get_running_loop() + future = loop.run_in_executor(None, cache.set, key, val) + result = await future + return result + + asyncio.run(set_async('test-key', 'test-value')) .. _`hash protocol`: https://docs.python.org/library/functions.html#hash .. _`not recommended`: https://www.sqlite.org/faq.html#q5 From 2d1f43ea2be4c82a430d245de6260c3e18059ba1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 2 Jul 2019 10:13:55 -0700 Subject: [PATCH 403/550] Final draft of case study for landing page caching --- diskcache/recipes.py | 8 +-- docs/case-study-landing-page-caching.rst | 81 +++++++++++++++++++++--- 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 08b873b..fb64250 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -321,10 +321,10 @@ def wrapper(*args, **kwargs): def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1): """Memoizing cache decorator with cache stampede protection. - Cache stampedes are a type of cascading failure that can occur when - parallel computing systems using memoization come under heavy load. This - behaviour is sometimes also called dog-piling, cache miss storm, cache - choking, or the thundering herd problem. + Cache stampedes are a type of system overload that can occur when parallel + computing systems using memoization come under heavy load. This behaviour + is sometimes also called dog-piling, cache miss storm, cache choking, or + the thundering herd problem. The memoization decorator implements cache stampede protection through early recomputation. Early recomputation of function results will occur diff --git a/docs/case-study-landing-page-caching.rst b/docs/case-study-landing-page-caching.rst index 97010aa..a85d6fc 100644 --- a/docs/case-study-landing-page-caching.rst +++ b/docs/case-study-landing-page-caching.rst @@ -2,7 +2,11 @@ Case Study: Landing Page Caching ================================ :doc:`DiskCache ` version 4 added recipes for cache stampede mitigation. -Let's look at how that applies to landing page caching. +Cache stampedes are a type of system overload that can occur when parallel +computing systems using memoization come under heavy load. This behaviour is +sometimes also called dog-piling, cache miss storm, cache choking, or the +thundering herd problem. Let's look at how that applies to landing page +caching. >>> import time >>> def generate_landing_page(): @@ -10,9 +14,9 @@ Let's look at how that applies to landing page caching. ... # Return HTML response. Imagine a website under heavy load with a function used to generate the landing -page. There are two processes each with five threads for a total of ten -concurrent workers. Also assume that generating the landing page takes about -two hundred milliseconds. +page. There are five processes each with two threads for a total of ten +concurrent workers. The landing page is loaded constantly and takes about two +hundred milliseconds to generate. .. image:: _static/no-caching.png @@ -26,15 +30,15 @@ regenerates the page with a consistently slow latency. ... def generate_landing_page(): ... time.sleep(0.2) -With traditional caching, the result of generating the landing page can be -memoized for one second. After each second, the cached HTML expires and all ten -workers rush to regenerate the result. +Assume the result of generating the landing page can be memoized for one +second. Memoization supports a traditional caching strategy. After each second, +the cached HTML expires and all ten workers rush to regenerate the result. .. image:: _static/traditional-caching.png There is a huge improvement in average latency now but some requests experience worse latency than before due to the added overhead of caching. The cache -stampede is visible now as the spikes in the concurrency graph. If generating +stampede is visible too as the spikes in the concurrency graph. If generating the landing page requires significant resources then the spikes may be prohibitive. @@ -48,12 +52,71 @@ synchronize generating the landing page:: ... time.sleep(0.2) The double-checked locking uses two memoization decorators to optimistically -look up the cache result before locking. +look up the cached result before locking. With `expire` set to zero, the +cache's get-operation is performed but the set-operation is skipped. Only the +inner-nested memoize decorator will update the cache. .. image:: _static/synchronized-locking.png +The number of concurrent workers is now greatly improved. Rather than having +ten workers all attempt to generate the same result, a single worker generates +the result and the other ten benefit. The maximum latency has increased however +as three layers of caching and locking wrap the function. + +Ideally, the system would anticipate the pending expiration of the cached item +and would recompute the result in a separate thread of execution. Coordinating +recomputation would be a function of the number of workers, the expiration +time, and the duration of computation. Fortunately, Vattani, et al. published +the solution in "Optimal Probabilistic Cache Stampede Prevention" in 2015. + + >>> @dc.memoize_stampede(cache, expire=1) + ... def generate_landing_page(): + ... time.sleep(0.2) + +Early probabilistic recomputation uses a random number generator to simulate a +cache miss prior to expiration. The new result is then computed in a separate +thread while the cached result is returned to the caller. When the cache item +is missing, the result is computed and cached synchronously. + .. image:: _static/early-recomputation.png +The latency is now its theoretical best. An initial warmup execution takes two +hundred milliseconds and the remaining calls all return immediately from the +cache. Behind the scenes, separate threads of execution are recomputing the +result of workers and updating the cache. The concurrency graph shows a nearly +constant stream of workers recomputing the function's result. + + >>> @dc.memoize_stampede(cache, expire=1, beta=0.5) + ... def generate_landing_page(): + ... time.sleep(0.2) + +Vattani described an additional parameter, :math:`\beta`, which could be used +to tune the eagerness of recomputation. As the number and frequency of +concurrent worker calls increases, eagerness can be lessened by decreasing the +:math:`\beta` parameter. The default value of :math:`\beta` is one, and above +it is set to half. + .. image:: _static/early-recomputation-05.png +Latency is now still its theoretical best while the worker load has decreased +significantly. The likelihood of simulated cache misses is now half what it was +before. The value was determined through experimentation. + + >>> @dc.memoize_stampede(cache, expire=1, beta=0.3) + ... def generate_landing_page(): + ... time.sleep(0.2) + +Lets see what happens when :math:`\beta` is set too low. + .. image:: _static/early-recomputation-03.png + +When set too low, the cache item expires before a new value is recomputed. The +real cache miss then causes the workers to synchronously recompute the landing +page and cache the result. With no barrier in place, eleven workers cause a +cache stampede. The eleven workers are composed of ten synchronous workers and +one in a background thread. The best way to customize :math:`\beta` is through +experimentation, otherwise the default is very reasonable. + +:doc:`DiskCache ` provides data types and recipes for memoization and +mitigation of cache stampedes. The decorators provided are composable for a +variety of scenarios. The best way to get started is with the :doc:`tutorial`. From 1149e822c4a9b8bb8eaf27874c228100ddc380b1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 2 Jul 2019 10:39:00 -0700 Subject: [PATCH 404/550] Change doctest-like snippets to code-blocks with emphasis --- docs/case-study-landing-page-caching.rst | 68 +++++++++++++++--------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/docs/case-study-landing-page-caching.rst b/docs/case-study-landing-page-caching.rst index a85d6fc..14b2de0 100644 --- a/docs/case-study-landing-page-caching.rst +++ b/docs/case-study-landing-page-caching.rst @@ -8,10 +8,13 @@ sometimes also called dog-piling, cache miss storm, cache choking, or the thundering herd problem. Let's look at how that applies to landing page caching. - >>> import time - >>> def generate_landing_page(): - ... time.sleep(0.2) # Work really hard. - ... # Return HTML response. +.. code-block:: python + + import time + + def generate_landing_page(): + time.sleep(0.2) # Work really hard. + # Return HTML response. Imagine a website under heavy load with a function used to generate the landing page. There are five processes each with two threads for a total of ten @@ -24,11 +27,16 @@ When we look at the number of concurrent workers and the latency with no caching at all, the graph looks as above. Notice each worker constantly regenerates the page with a consistently slow latency. - >>> import diskcache as dc - >>> cache = dc.Cache() - >>> @cache.memoize(expire=1) - ... def generate_landing_page(): - ... time.sleep(0.2) +.. code-block:: python + :emphasize-lines: 5 + + import diskcache as dc + + cache = dc.Cache() + + @cache.memoize(expire=1) + def generate_landing_page(): + time.sleep(0.2) Assume the result of generating the landing page can be memoized for one second. Memoization supports a traditional caching strategy. After each second, @@ -43,13 +51,16 @@ the landing page requires significant resources then the spikes may be prohibitive. To reduce the number of concurrent workers, a barrier can be used to -synchronize generating the landing page:: +synchronize generating the landing page. - >>> @cache.memoize(expire=0) - ... @dc.barrier(cache, dc.Lock) - ... @cache.memoize(expire=1) - ... def generate_landing_page(): - ... time.sleep(0.2) +.. code-block:: python + :emphasize-lines: 1,2,3 + + @cache.memoize(expire=0) + @dc.barrier(cache, dc.Lock) + @cache.memoize(expire=1) + def generate_landing_page(): + time.sleep(0.2) The double-checked locking uses two memoization decorators to optimistically look up the cached result before locking. With `expire` set to zero, the @@ -69,9 +80,12 @@ recomputation would be a function of the number of workers, the expiration time, and the duration of computation. Fortunately, Vattani, et al. published the solution in "Optimal Probabilistic Cache Stampede Prevention" in 2015. - >>> @dc.memoize_stampede(cache, expire=1) - ... def generate_landing_page(): - ... time.sleep(0.2) +.. code-block:: python + :emphasize-lines: 1 + + @dc.memoize_stampede(cache, expire=1) + def generate_landing_page(): + time.sleep(0.2) Early probabilistic recomputation uses a random number generator to simulate a cache miss prior to expiration. The new result is then computed in a separate @@ -86,9 +100,12 @@ cache. Behind the scenes, separate threads of execution are recomputing the result of workers and updating the cache. The concurrency graph shows a nearly constant stream of workers recomputing the function's result. - >>> @dc.memoize_stampede(cache, expire=1, beta=0.5) - ... def generate_landing_page(): - ... time.sleep(0.2) +.. code-block:: python + :emphasize-lines: 1 + + @dc.memoize_stampede(cache, expire=1, beta=0.5) + def generate_landing_page(): + time.sleep(0.2) Vattani described an additional parameter, :math:`\beta`, which could be used to tune the eagerness of recomputation. As the number and frequency of @@ -102,9 +119,12 @@ Latency is now still its theoretical best while the worker load has decreased significantly. The likelihood of simulated cache misses is now half what it was before. The value was determined through experimentation. - >>> @dc.memoize_stampede(cache, expire=1, beta=0.3) - ... def generate_landing_page(): - ... time.sleep(0.2) +.. code-block:: python + :emphasize-lines: 1 + + @dc.memoize_stampede(cache, expire=1, beta=0.3) + def generate_landing_page(): + time.sleep(0.2) Lets see what happens when :math:`\beta` is set too low. From c7f826a22f27e7e70ac431b37d5689e7da705e60 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 2 Jul 2019 11:22:18 -0700 Subject: [PATCH 405/550] Invoke scripts with "python -m" and use "tox -e py" --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c15404..c67140c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ sudo: false language: python -install: pip install tox -script: tox +install: python -m pip install tox +script: python -m tox -e py matrix: include: - python: 2.7 From 845ae3ed8e28c51eee3403adc2c927a72d37daa6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 2 Jul 2019 11:24:35 -0700 Subject: [PATCH 406/550] Pin pytest to 4.6.* for AppVeyor support on Python 2.7 and 3.4 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0566db9..e17bdf4 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ skip_missing_interpreters=True deps= django==1.11.* mock - pytest + pytest==4.6.* pytest-django pytest-xdist commands=python -m pytest From 2c79bb981ea812f775331f758b07ce30fb0f864c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 2 Jul 2019 16:55:00 -0700 Subject: [PATCH 407/550] Bump version to 4.0.0 --- diskcache/__init__.py | 4 ++-- tox.ini | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 96a5732..46536bb 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -43,8 +43,8 @@ pass __title__ = 'diskcache' -__version__ = '3.1.1' -__build__ = 0x030101 +__version__ = '4.0.0' +__build__ = 0x040000 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2018 Grant Jenks' diff --git a/tox.ini b/tox.ini index e17bdf4..43839bf 100644 --- a/tox.ini +++ b/tox.ini @@ -33,3 +33,6 @@ deps= django==1.11.* pylint commands=pylint diskcache + +[doc8] +ignore=D000 From ab8713e271ec8e7fdef9513c31a3a38544ecb5dd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 27 Sep 2019 22:33:28 -0700 Subject: [PATCH 408/550] Add deepsource setup file --- .deepsource.toml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .deepsource.toml diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..154e132 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,14 @@ +version = 1 + +test_patterns = [ + '*/tests/*' +] + +exclude_patterns = [ + +] + +[[analyzers]] +name = 'python' +enabled = true +runtime_version = '3.x.x' From ee47430ce388e4373caa2033307c8b2f321c4638 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 2 Jul 2019 21:20:45 -0700 Subject: [PATCH 409/550] Add rstcheck to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 00f826c..659180d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,7 @@ pytest-cov pytest-django pytest-env pytest-xdist +rstcheck sphinx tox twine From 798c3ea4052a78f02bdaa6c52dc73646cd155746 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 28 Sep 2019 11:39:23 -0700 Subject: [PATCH 410/550] Remove deepsource setup --- .deepsource.toml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .deepsource.toml diff --git a/.deepsource.toml b/.deepsource.toml deleted file mode 100644 index 154e132..0000000 --- a/.deepsource.toml +++ /dev/null @@ -1,14 +0,0 @@ -version = 1 - -test_patterns = [ - '*/tests/*' -] - -exclude_patterns = [ - -] - -[[analyzers]] -name = 'python' -enabled = true -runtime_version = '3.x.x' From 8ba1d34972835a143bedda5927b4cdca338c2ebf Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 28 Sep 2019 22:03:14 -0700 Subject: [PATCH 411/550] Proselint fixes --- README.rst | 10 +++++----- docs/cache-benchmarks.rst | 6 +++--- docs/case-study-landing-page-caching.rst | 4 ++-- docs/case-study-web-crawler.rst | 2 +- docs/djangocache-benchmarks.rst | 6 +++--- docs/sf-python-2017-meetup-talk.rst | 4 ++-- docs/tutorial.rst | 14 +++++++------- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.rst b/README.rst index d0b74d0..57b7a2e 100644 --- a/README.rst +++ b/README.rst @@ -234,7 +234,7 @@ Integrations? Django None None None None **Timings** -These are very rough measurements. See `DiskCache Cache Benchmarks`_ for more +These are rough measurements. See `DiskCache Cache Benchmarks`_ for more rigorous data. ================ ============= ========= ========= ============ ============ @@ -279,8 +279,8 @@ Pure-Python Databases ..................... * `ZODB`_ supports an isomorphic interface for database operations which means - there's very little impact on your code to make objects persistent and - there's no database mapper that partially hides the datbase. + there's little impact on your code to make objects persistent and there's no + database mapper that partially hides the datbase. * `CodernityDB`_ is an open source, pure-Python, multi-platform, schema-less, NoSQL database and includes an HTTP server version, and a Python client library that aims to be 100% compatible with the embedded version. @@ -332,7 +332,7 @@ SQL Databases PostgreSQL adapter for the Python programming language. * `Oracle DB`_ is a relational database management system (RDBMS) from the Oracle Corporation. Originally developed in 1977, Oracle DB is one of the - most trusted and widely-used enterprise relational database engines. + most trusted and widely used enterprise relational database engines. * `Microsoft SQL Server`_ is a relational database management system developed by Microsoft. As a database server, it stores and retrieves data as requested by other software applications. @@ -398,7 +398,7 @@ License at Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _`DiskCache`: http://www.grantjenks.com/docs/diskcache/ diff --git a/docs/cache-benchmarks.rst b/docs/cache-benchmarks.rst index 3f0343d..5b125e8 100644 --- a/docs/cache-benchmarks.rst +++ b/docs/cache-benchmarks.rst @@ -116,7 +116,7 @@ Timings for pylibmc.Client Total 98999 2.669s ========= ========= ========= ========= ========= ========= ========= ========= -Memcached performance is low latency and very stable. +Memcached performance is low latency and stable. ========= ========= ========= ========= ========= ========= ========= ========= Timings for redis.StrictRedis @@ -144,8 +144,8 @@ Get .. image:: _static/core-p8-get.png -Under heavy load, :doc:`DiskCache ` gets are very low latency. At the -90th percentile, they are less than half the latency of Memcached. +Under heavy load, :doc:`DiskCache ` gets are low latency. At the 90th +percentile, they are less than half the latency of Memcached. Set ... diff --git a/docs/case-study-landing-page-caching.rst b/docs/case-study-landing-page-caching.rst index 14b2de0..677186e 100644 --- a/docs/case-study-landing-page-caching.rst +++ b/docs/case-study-landing-page-caching.rst @@ -75,7 +75,7 @@ the result and the other ten benefit. The maximum latency has increased however as three layers of caching and locking wrap the function. Ideally, the system would anticipate the pending expiration of the cached item -and would recompute the result in a separate thread of execution. Coordinating +and would recompute the result in a separate thread of execution. Coordinating recomputation would be a function of the number of workers, the expiration time, and the duration of computation. Fortunately, Vattani, et al. published the solution in "Optimal Probabilistic Cache Stampede Prevention" in 2015. @@ -135,7 +135,7 @@ real cache miss then causes the workers to synchronously recompute the landing page and cache the result. With no barrier in place, eleven workers cause a cache stampede. The eleven workers are composed of ten synchronous workers and one in a background thread. The best way to customize :math:`\beta` is through -experimentation, otherwise the default is very reasonable. +experimentation, otherwise the default is reasonable. :doc:`DiskCache ` provides data types and recipes for memoization and mitigation of cache stampedes. The decorators provided are composable for a diff --git a/docs/case-study-web-crawler.rst b/docs/case-study-web-crawler.rst index 982a3c8..c37e2a5 100644 --- a/docs/case-study-web-crawler.rst +++ b/docs/case-study-web-crawler.rst @@ -116,7 +116,7 @@ the crawl function and query it. >>> len(results) 99 -As an added benefit, our code also now works in parallel. For free! +As an added benefit, our code also now works in parallel. >>> results.clear() >>> from multiprocessing import Process diff --git a/docs/djangocache-benchmarks.rst b/docs/djangocache-benchmarks.rst index 4f791ff..88d1fec 100644 --- a/docs/djangocache-benchmarks.rst +++ b/docs/djangocache-benchmarks.rst @@ -102,8 +102,8 @@ Get .. image:: _static/djangocache-get.png -Under heavy load, :class:`DjangoCache ` gets are very -low latency. At the 99th percentile they are on par with the Memcached cache +Under heavy load, :class:`DjangoCache ` gets are low +latency. At the 99th percentile they are on par with the Memcached cache backend. Set @@ -157,7 +157,7 @@ Timings for memcached Total 791992 68.825s ========= ========= ========= ========= ========= ========= ========= ========= -Memcached performance is low latency and very stable. +Memcached performance is low latency and stable. ========= ========= ========= ========= ========= ========= ========= ========= Timings for redis diff --git a/docs/sf-python-2017-meetup-talk.rst b/docs/sf-python-2017-meetup-talk.rst index 214b66b..5f79000 100644 --- a/docs/sf-python-2017-meetup-talk.rst +++ b/docs/sf-python-2017-meetup-talk.rst @@ -20,7 +20,7 @@ Landscape Backends -------- -* Backends have very different designs and tradeoffs. +* Backends have different designs and tradeoffs. Frameworks @@ -165,7 +165,7 @@ SQLite * Use a context manager for isolation level management. * Pragmas tune the behavior and performance of SQLite. - * Default is very robust and slow. + * Default is robust and slow. * Use write-ahead-log so writers don't block readers. * Memory-map pages for fast lookups. diff --git a/docs/tutorial.rst b/docs/tutorial.rst index a4c3317..f704cb6 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -53,8 +53,8 @@ or install it into your site-packages easily:: :doc:`DiskCache ` is looking for a Debian package maintainer. If you can help, please open an issue in the `DiskCache Issue Tracker`_. -:doc:`DiskCache ` is looking for a CentOS/RPM package maintainer. If -you can help, please open an issue in the `DiskCache Issue Tracker`_. +:doc:`DiskCache ` is looking for a CentOS/RPM package maintainer. If you +can help, please open an issue in the `DiskCache Issue Tracker`_. .. _`DiskCache Issue Tracker`: https://github.com/grantjenks/python-diskcache/issues/ @@ -778,11 +778,11 @@ tradeoffs for accessing and storing items. the access count field stored in the cache database. On every access, the field is incremented. Every access therefore requires writing the database which slows accesses. -* ``"none"`` disables cache evictions. Caches will grow in size without - bound. Cache items will still be lazily removed if they expire. The - persistent data types, :class:`.Deque` and :class:`.Index`, use the - ``"none"`` eviction policy. For :ref:`lazy culling ` use - the :ref:`cull_limit ` setting instead. +* ``"none"`` disables cache evictions. Caches will grow without bound. Cache + items will still be lazily removed if they expire. The persistent data types, + :class:`.Deque` and :class:`.Index`, use the ``"none"`` eviction policy. For + :ref:`lazy culling ` use the :ref:`cull_limit ` + setting instead. All clients accessing the cache are expected to use the same eviction policy. The policy can be set during initialization using a keyword argument. From 912f5b6c310ae62c6bd0bf12b196b2a11fd57e63 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 2 Jul 2019 21:38:37 -0700 Subject: [PATCH 412/550] Move zero-expiration logic into memoize (rather than Cache.set) --- diskcache/core.py | 13 ++++++------- diskcache/djangocache.py | 11 ++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index c74241e..69b1952 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -737,9 +737,6 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. - If `expire` is less than or equal to zero then immediately returns - `False`. - Raises :exc:`Timeout` error when database timeout occurs and `retry` is `False` (default). @@ -754,9 +751,6 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): :raises Timeout: if database timeout occurs """ - if expire is not None and expire <= 0: - return False - now = time.time() db_key, raw = self._disk.put(key) expire_time = None if expire is None else now + expire @@ -1779,6 +1773,10 @@ def memoize(self, name=None, typed=False, expire=None, tag=None): 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. @@ -1843,7 +1841,8 @@ def wrapper(*args, **kwargs): if result is ENOVAL: result = func(*args, **kwargs) - self.set(key, result, expire=expire, tag=tag, retry=True) + if expire is None or expire > 0: + self.set(key, result, expire, tag=tag, retry=True) return result diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index a2d863c..00a96bf 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -368,6 +368,10 @@ def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, If name is set to None (default), the callable name will be determined automatically. + When timeout 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. @@ -407,9 +411,10 @@ def wrapper(*args, **kwargs): if result is ENOVAL: result = func(*args, **kwargs) - self.set( - key, result, timeout, version, tag=tag, retry=True, - ) + if timeout is None or timeout > 0: + self.set( + key, result, timeout, version, tag=tag, retry=True, + ) return result From 9cfe5df3fa0a3d98b522bb3426aa9934bb481b2c Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Sun, 13 Oct 2019 15:42:48 -0700 Subject: [PATCH 413/550] Provide JSONDisk with diskcache (#124) --- .gitignore | 1 + diskcache/__init__.py | 3 ++- diskcache/core.py | 29 +++++++++++++++++++++++++++++ docs/tutorial.rst | 3 ++- tests/test_core.py | 31 +------------------------------ 5 files changed, 35 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 0ef1c27..c496721 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # virutalenv directories /env*/ +/.venv*/ # test files/directories /.cache/ diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 46536bb..b392164 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -6,7 +6,7 @@ """ -from .core import Cache, Disk, EmptyDirWarning, UnknownFileWarning, Timeout +from .core import Cache, Disk, EmptyDirWarning, JSONDisk, UnknownFileWarning, Timeout from .core import DEFAULT_SETTINGS, ENOVAL, EVICTION_POLICY, UNKNOWN from .fanout import FanoutCache from .persistent import Deque, Index @@ -25,6 +25,7 @@ 'EmptyDirWarning', 'FanoutCache', 'Index', + "JSONDisk", 'Lock', 'RLock', 'Timeout', diff --git a/diskcache/core.py b/diskcache/core.py index 69b1952..5454924 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -7,6 +7,7 @@ import errno import functools as ft import io +import json import os import os.path as op import pickletools @@ -361,6 +362,34 @@ def remove(self, filename): raise +class JSONDisk(Disk): + """Cache key and value (de)serialized as JSON.""" + def __init__(self, directory, compress_level=1, **kwargs): + self.compress_level = compress_level + super(JSONDisk, self).__init__(directory, **kwargs) + + def put(self, key): + json_bytes = json.dumps(key).encode('utf-8') + data = zlib.compress(json_bytes, self.compress_level) + return super(JSONDisk, self).put(data) + + def get(self, key, raw): + data = super(JSONDisk, self).get(key, raw) + return json.loads(zlib.decompress(data).decode('utf-8')) + + def store(self, value, read, key=UNKNOWN): + if not read: + json_bytes = json.dumps(value).encode('utf-8') + value = zlib.compress(json_bytes, self.compress_level) + return super(JSONDisk, self).store(value, read, key=key) + + def fetch(self, mode, filename, value, read): + data = super(JSONDisk, self).fetch(mode, filename, value, read) + if not read: + data = json.loads(zlib.decompress(data).decode('utf-8')) + return data + + class Timeout(Exception): "Database timeout expired." diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f704cb6..7b65f99 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -813,7 +813,8 @@ database while values are sometimes stored separately in files. To customize serialization, you may pass in a :class:`Disk ` subclass to initialize the cache. All clients accessing the cache are expected to use the same serialization. The default implementation uses Pickle and the -example below uses compressed JSON. +example below uses compressed JSON, +available for convenience as :class:`Disk `. .. code-block:: python diff --git a/tests/test_core.py b/tests/test_core.py index acf6b3f..7e79995 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -7,7 +7,6 @@ import functools as ft import hashlib import io -import json import mock import os import os.path as op @@ -22,7 +21,6 @@ import time import unittest import warnings -import zlib try: import cPickle as pickle @@ -94,35 +92,8 @@ def test_disk_valueerror(): pass -class JSONDisk(diskcache.Disk): - def __init__(self, directory, compress_level=1, **kwargs): - self.compress_level = compress_level - super(JSONDisk, self).__init__(directory, **kwargs) - - def put(self, key): - json_bytes = json.dumps(key).encode('utf-8') - data = zlib.compress(json_bytes, self.compress_level) - return super(JSONDisk, self).put(data) - - def get(self, key, raw): - data = super(JSONDisk, self).get(key, raw) - return json.loads(zlib.decompress(data).decode('utf-8')) - - def store(self, value, read, key=dc.UNKNOWN): - if not read: - json_bytes = json.dumps(value).encode('utf-8') - value = zlib.compress(json_bytes, self.compress_level) - return super(JSONDisk, self).store(value, read, key=key) - - def fetch(self, mode, filename, value, read): - data = super(JSONDisk, self).fetch(mode, filename, value, read) - if not read: - data = json.loads(zlib.decompress(data).decode('utf-8')) - return data - - def test_custom_disk(): - with dc.Cache(disk=JSONDisk, disk_compress_level=6) as cache: + with dc.Cache(disk=dc.JSONDisk, disk_compress_level=6) as cache: values = [None, True, 0, 1.23, {}, [None] * 10000] for value in values: From 19f908bd5d6d90c4f69710b27a1cbad22eb93e31 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 13 Oct 2019 16:07:48 -0700 Subject: [PATCH 414/550] Add docs for JSONDisk. --- diskcache/__init__.py | 2 +- diskcache/core.py | 18 +++++++++++++++++- docs/api.rst | 10 ++++++++++ docs/tutorial.rst | 4 ++-- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index b392164..2f11ea4 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -25,7 +25,7 @@ 'EmptyDirWarning', 'FanoutCache', 'Index', - "JSONDisk", + 'JSONDisk', 'Lock', 'RLock', 'Timeout', diff --git a/diskcache/core.py b/diskcache/core.py index 5454924..0c8fd2c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -363,26 +363,42 @@ def remove(self, filename): class JSONDisk(Disk): - """Cache key and value (de)serialized as JSON.""" + "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 + + """ self.compress_level = compress_level super(JSONDisk, self).__init__(directory, **kwargs) + def put(self, key): json_bytes = json.dumps(key).encode('utf-8') data = zlib.compress(json_bytes, self.compress_level) return super(JSONDisk, self).put(data) + def get(self, key, raw): data = super(JSONDisk, self).get(key, raw) return json.loads(zlib.decompress(data).decode('utf-8')) + def store(self, value, read, key=UNKNOWN): if not read: json_bytes = json.dumps(value).encode('utf-8') value = zlib.compress(json_bytes, self.compress_level) return super(JSONDisk, self).store(value, read, key=key) + def fetch(self, mode, filename, value, read): data = super(JSONDisk, self).fetch(mode, filename, value, read) if not read: diff --git a/docs/api.rst b/docs/api.rst index 99686bb..e38f254 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -116,6 +116,16 @@ Read the :ref:`Disk tutorial ` for details. :special-members: :exclude-members: __weakref__ +JSONDisk +-------- + +Read the :ref:`Disk tutorial ` for details. + +.. autoclass:: diskcache.JSONDisk + :members: + :special-members: + :exclude-members: __weakref__ + Timeout ------- diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 7b65f99..b29322c 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -813,8 +813,8 @@ database while values are sometimes stored separately in files. To customize serialization, you may pass in a :class:`Disk ` subclass to initialize the cache. All clients accessing the cache are expected to use the same serialization. The default implementation uses Pickle and the -example below uses compressed JSON, -available for convenience as :class:`Disk `. +example below uses compressed JSON, available for convenience as +:class:`JSONDisk `. .. code-block:: python From 9bee7b9ff43f2caf6f84fc6bdf1b6d7a695c0fe8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 13 Oct 2019 16:09:20 -0700 Subject: [PATCH 415/550] Add check for DEFAULT_TIMEOUT in DjangoCache.memoize --- diskcache/djangocache.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 00a96bf..997b852 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -411,7 +411,12 @@ def wrapper(*args, **kwargs): if result is ENOVAL: result = func(*args, **kwargs) - if timeout is None or timeout > 0: + valid_timeout = ( + timeout is None + or timeout == DEFAULT_TIMEOUT + or timeout > 0 + ) + if valid_timeout: self.set( key, result, timeout, version, tag=tag, retry=True, ) From 0e273fb54f9bfec6a33e6be7cdf9a68f0ea2adaa Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 13 Oct 2019 16:59:15 -0700 Subject: [PATCH 416/550] Bump version to 4.1.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 2f11ea4..192524e 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -44,8 +44,8 @@ pass __title__ = 'diskcache' -__version__ = '4.0.0' -__build__ = 0x040000 +__version__ = '4.1.0' +__build__ = 0x040100 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2018 Grant Jenks' From b0451e084ea403c29980f683b8f0d8c9ac2a2dea Mon Sep 17 00:00:00 2001 From: Mengyang Li Date: Wed, 20 Nov 2019 14:11:10 -0800 Subject: [PATCH 417/550] peekleft: fix docstr typo (#132) --- diskcache/persistent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 961f773..9de5835 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -442,7 +442,7 @@ def peek(self): def peekleft(self): - """Peek at value at back of deque. + """Peek at value at front of deque. Faster than indexing deque at 0. From 727037065e64a698147171f9553a6ee08be02950 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 27 Jun 2020 11:19:13 -0700 Subject: [PATCH 418/550] Bump Django to 2.2.* --- requirements.txt | 2 +- tox.ini | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 659180d..ae05547 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ coverage -django==1.11.* +django==2.2.* django_redis doc8 gj diff --git a/tox.ini b/tox.ini index 43839bf..5813859 100644 --- a/tox.ini +++ b/tox.ini @@ -4,9 +4,8 @@ skip_missing_interpreters=True [testenv] deps= - django==1.11.* - mock - pytest==4.6.* + django==2.2.* + pytest pytest-django pytest-xdist commands=python -m pytest @@ -28,9 +27,9 @@ env = DJANGO_SETTINGS_MODULE=tests.settings PYTHONPATH={PWD}:{PWD}/tests -[testenv:lint] +[testenv:pylint] deps= - django==1.11.* + django==2.2.* pylint commands=pylint diskcache From eb8033c61b59f23772801c8bfa22e269e70cb115 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 27 Jun 2020 11:21:18 -0700 Subject: [PATCH 419/550] Drop Python 2.7 and 3.4 testing and add 3.8 --- .travis.yml | 7 +++---- appveyor.yml | 6 ++---- tox.ini | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index c67140c..6c1bd8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,6 @@ install: python -m pip install tox script: python -m tox -e py matrix: include: - - python: 2.7 - env: TOXENV=py27 - - python: 3.4 - env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 - python: 3.6 @@ -15,6 +11,9 @@ matrix: - python: 3.7 dist: xenial env: TOXENV=py37 + - python: 3.8 + dist: xenial + env: TOXENV=py38 - python: pypy env: TOXENV=pypy - python: 3.7 diff --git a/appveyor.yml b/appveyor.yml index dc376f1..1f52a08 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,16 +2,14 @@ environment: matrix: - - PYTHON: "C:\\Python27" - - PYTHON: "C:\\Python34" - PYTHON: "C:\\Python35" - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python37" - - PYTHON: "C:\\Python27-x64" - - PYTHON: "C:\\Python34-x64" + - PYTHON: "C:\\Python38" - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36-x64" - PYTHON: "C:\\Python37-x64" + - PYTHON: "C:\\Python38-x64" install: diff --git a/tox.ini b/tox.ini index 5813859..ea3996d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,py34,py35,py36,py37,pypy,lint +envlist=py35,py36,py37,py38,pypy,pylint skip_missing_interpreters=True [testenv] From 8130ebca2c72a90b9d25feeecfeed8efa547025f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 27 Jun 2020 11:31:15 -0700 Subject: [PATCH 420/550] Update DjangoCache tests for Django 2.2 --- tests/test_djangocache.py | 222 +++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 124 deletions(-) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index f70b87c..100f998 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -1,15 +1,12 @@ -# -*- coding: utf-8 -*- - # Most of this file was copied from: -# https://raw.githubusercontent.com/django/django/1.11.12/tests/cache/tests.py +# https://raw.githubusercontent.com/django/django/stable/2.2.x/tests/cache/tests.py # Unit tests for cache framework # Uses whatever cache backend is set in the test settings file. -from __future__ import unicode_literals - import copy import io import os +import pickle import re import shutil import tempfile @@ -17,11 +14,12 @@ import time import unittest import warnings +from unittest import mock from django.conf import settings from django.core import management, signals from django.core.cache import ( - DEFAULT_CACHE_ALIAS, CacheKeyWarning, cache, caches, + DEFAULT_CACHE_ALIAS, CacheKeyWarning, InvalidCacheKey, cache, caches, ) from django.core.cache.utils import make_template_fragment_key from django.db import close_old_connections, connection, connections @@ -37,15 +35,13 @@ from django.template.response import TemplateResponse from django.test import ( RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, - ignore_warnings, mock, override_settings, + override_settings, ) from django.test.signals import setting_changed -from django.utils import six, timezone, translation +from django.utils import timezone, translation from django.utils.cache import ( - get_cache_key, learn_cache_key, patch_cache_control, - patch_response_headers, patch_vary_headers, + get_cache_key, learn_cache_key, patch_cache_control, patch_vary_headers, ) -from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_text from django.views.decorators.cache import cache_page @@ -55,27 +51,12 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') -############################################################################ -# GrantJ 2017-03-27 Ignore deprecation warnings. Django's metaclass magic does -# not always play well with Python 3.6. Read -# http://stackoverflow.com/questions/41343263/ for details -############################################################################ - -import warnings -warnings.filterwarnings('ignore', category=DeprecationWarning) - import django django.setup() from .models import Poll, expensive_calculation -try: # Use the same idiom as in cache backends - from django.utils.six.moves import cPickle as pickle -except ImportError: - import pickle - - # functions/classes for complex data type tests def f(): return 42 @@ -86,11 +67,17 @@ def m(n): return 24 -class Unpicklable(object): +class Unpicklable: def __getstate__(self): raise pickle.PickleError() +KEY_ERRORS_WITH_MEMCACHED_MSG = ( + 'Cache key contains characters that will cause errors if used with ' + 'memcached: %r' +) + + class UnpicklableType(object): # Unpicklable using the default pickling protocol on Python 2. __slots__ = 'a', @@ -101,17 +88,12 @@ def custom_key_func(key, key_prefix, version): return 'CUSTOM-' + '-'.join([key_prefix, str(version), key]) -def custom_key_func2(key, key_prefix, version): - "Another customized cache key function" - return '-'.join(['CUSTOM', key_prefix, str(version), key]) - - _caches_setting_base = { 'default': {}, 'prefix': {'KEY_PREFIX': 'cacheprefix{}'.format(os.getpid())}, 'v2': {'VERSION': 2}, 'custom_key': {'KEY_FUNCTION': custom_key_func}, - 'custom_key2': {'KEY_FUNCTION': custom_key_func2}, + 'custom_key2': {'KEY_FUNCTION': 'tests.test_djangocache.custom_key_func'}, 'cull': {'OPTIONS': {'MAX_ENTRIES': 30}}, 'zero_cull': {'OPTIONS': {'CULL_FREQUENCY': 0, 'MAX_ENTRIES': 30}}, } @@ -127,18 +109,16 @@ def caches_setting_for_tests(base=None, exclude=None, **params): # params -> _caches_setting_base -> base base = base or {} exclude = exclude or set() - setting = {k: base.copy() for k in _caches_setting_base.keys() if k not in exclude} + setting = {k: base.copy() for k in _caches_setting_base if k not in exclude} for key, cache_params in setting.items(): cache_params.update(_caches_setting_base[key]) cache_params.update(params) return setting -class BaseCacheTests(object): +class BaseCacheTests: # A common set of tests to apply to all cache backends - - def setUp(self): - self.factory = RequestFactory() + factory = RequestFactory() def tearDown(self): cache.clear() @@ -168,24 +148,20 @@ def test_prefix(self): self.assertEqual(caches['prefix'].get('somekey'), 'value2') def test_non_existent(self): - # Non-existent cache keys return as None/default - # get with non-existent keys + """Nonexistent cache keys return as None/default.""" self.assertIsNone(cache.get("does_not_exist")) self.assertEqual(cache.get("does_not_exist", "bang!"), "bang!") def test_get_many(self): # Multiple cache keys can be returned using get_many - cache.set('a', 'a') - cache.set('b', 'b') - cache.set('c', 'c') - cache.set('d', 'd') - self.assertDictEqual(cache.get_many(['a', 'c', 'd']), {'a': 'a', 'c': 'c', 'd': 'd'}) - self.assertDictEqual(cache.get_many(['a', 'b', 'e']), {'a': 'a', 'b': 'b'}) + cache.set_many({'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd'}) + self.assertEqual(cache.get_many(['a', 'c', 'd']), {'a': 'a', 'c': 'c', 'd': 'd'}) + self.assertEqual(cache.get_many(['a', 'b', 'e']), {'a': 'a', 'b': 'b'}) + self.assertEqual(cache.get_many(iter(['a', 'b', 'e'])), {'a': 'a', 'b': 'b'}) def test_delete(self): # Cache keys can be deleted - cache.set("key1", "spam") - cache.set("key2", "eggs") + cache.set_many({'key1': 'spam', 'key2': 'eggs'}) self.assertEqual(cache.get("key1"), "spam") cache.delete("key1") self.assertIsNone(cache.get("key1")) @@ -286,23 +262,6 @@ def test_cache_read_for_model_instance_with_deferred(self): # We only want the default expensive calculation run on creation and set self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read) - def test_touch(self): - # cache.touch() updates the timeout. - cache.set('expire1', 'very quickly', timeout=1) - self.assertTrue(cache.touch('expire1', timeout=2)) - time.sleep(1) - self.assertTrue(cache.has_key('expire1')) - time.sleep(2) - self.assertFalse(cache.has_key('expire1')) - - # cache.touch() works without the timeout argument. - cache.set('expire1', 'very quickly', timeout=1) - self.assertTrue(cache.touch('expire1')) - time.sleep(2) - self.assertTrue(cache.has_key('expire1')) - - self.assertFalse(cache.touch('nonexistent')) - def test_expiration(self): # Cache values can be set to expire cache.set('expire1', 'very quickly', 1) @@ -316,6 +275,23 @@ def test_expiration(self): self.assertEqual(cache.get("expire2"), "newvalue") self.assertFalse(cache.has_key("expire3")) + def test_touch(self): + # cache.touch() updates the timeout. + cache.set('expire1', 'very quickly', timeout=1) + self.assertIs(cache.touch('expire1', timeout=4), True) + time.sleep(2) + self.assertTrue(cache.has_key('expire1')) + time.sleep(3) + self.assertFalse(cache.has_key('expire1')) + + # cache.touch() works without the timeout argument. + cache.set('expire1', 'very quickly', timeout=1) + self.assertIs(cache.touch('expire1'), True) + time.sleep(2) + self.assertTrue(cache.has_key('expire1')) + + self.assertIs(cache.touch('nonexistent'), False) + def test_unicode(self): # Unicode values can be cached stuff = { @@ -326,21 +302,24 @@ def test_unicode(self): } # Test `set` for (key, value) in stuff.items(): - cache.set(key, value) - self.assertEqual(cache.get(key), value) + with self.subTest(key=key): + cache.set(key, value) + self.assertEqual(cache.get(key), value) # Test `add` for (key, value) in stuff.items(): - cache.delete(key) - cache.add(key, value) - self.assertEqual(cache.get(key), value) + with self.subTest(key=key): + cache.delete(key) + cache.add(key, value) + self.assertEqual(cache.get(key), value) # Test `set_many` for (key, value) in stuff.items(): cache.delete(key) cache.set_many(stuff) for (key, value) in stuff.items(): - self.assertEqual(cache.get(key), value) + with self.subTest(key=key): + self.assertEqual(cache.get(key), value) def test_binary_string(self): # Binary strings should be cacheable @@ -372,6 +351,11 @@ def test_set_many(self): self.assertEqual(cache.get("key1"), "spam") self.assertEqual(cache.get("key2"), "eggs") + def test_set_many_returns_empty_list_on_success(self): + """set_many() returns an empty list when all keys are inserted.""" + failing_keys = cache.set_many({'key1': 'spam', 'key2': 'eggs'}) + self.assertEqual(failing_keys, []) + def test_set_many_expiration(self): # set_many takes a second ``timeout`` parameter cache.set_many({"key1": "spam", "key2": "eggs"}, 1) @@ -381,9 +365,7 @@ def test_set_many_expiration(self): def test_delete_many(self): # Multiple keys can be deleted using delete_many - cache.set("key1", "spam") - cache.set("key2", "eggs") - cache.set("key3", "ham") + cache.set_many({'key1': 'spam', 'key2': 'eggs', 'key3': 'ham'}) cache.delete_many(["key1", "key2"]) self.assertIsNone(cache.get("key1")) self.assertIsNone(cache.get("key2")) @@ -391,8 +373,7 @@ def test_delete_many(self): def test_clear(self): # The cache can be emptied using clear - cache.set("key1", "spam") - cache.set("key2", "eggs") + cache.set_many({'key1': 'spam', 'key2': 'eggs'}) cache.clear() self.assertIsNone(cache.get("key1")) self.assertIsNone(cache.get("key2")) @@ -478,10 +459,10 @@ def test_zero_cull(self): def _perform_invalid_key_test(self, key, expected_warning): """ - All the builtin backends (except memcached, see below) should warn on - keys that would be refused by memcached. This encourages portable - caching code without making it too difficult to use production backends - with more liberal key rules. Refs #6447. + All the builtin backends should warn (except memcached that should + error) on keys that would be refused by memcached. This encourages + portable caching code without making it too difficult to use production + backends with more liberal key rules. Refs #6447. """ # mimic custom ``make_key`` method being defined since the default will # never show the below warnings @@ -492,23 +473,16 @@ def func(key, *args): cache.key_func = func try: - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") + with self.assertWarns(CacheKeyWarning) as cm: cache.set(key, 'value') - self.assertEqual(len(w), 1) - self.assertIsInstance(w[0].message, CacheKeyWarning) - self.assertEqual(str(w[0].message.args[0]), expected_warning) + self.assertEqual(str(cm.warning), expected_warning) finally: cache.key_func = old_func def test_invalid_key_characters(self): # memcached doesn't allow whitespace or control characters in keys. key = 'key with spaces and 清' - expected_warning = ( - "Cache key contains characters that will cause errors if used " - "with memcached: %r" % key - ) - self._perform_invalid_key_test(key, expected_warning) + self._perform_invalid_key_test(key, KEY_ERRORS_WITH_MEMCACHED_MSG % key) def test_invalid_key_length(self): # memcached limits key length to 250. @@ -678,43 +652,43 @@ def test_cache_versioning_incr_decr(self): def test_cache_versioning_get_set_many(self): # set, using default version = 1 cache.set_many({'ford1': 37, 'arthur1': 42}) - self.assertDictEqual(cache.get_many(['ford1', 'arthur1']), {'ford1': 37, 'arthur1': 42}) - self.assertDictEqual(cache.get_many(['ford1', 'arthur1'], version=1), {'ford1': 37, 'arthur1': 42}) - self.assertDictEqual(cache.get_many(['ford1', 'arthur1'], version=2), {}) + self.assertEqual(cache.get_many(['ford1', 'arthur1']), {'ford1': 37, 'arthur1': 42}) + self.assertEqual(cache.get_many(['ford1', 'arthur1'], version=1), {'ford1': 37, 'arthur1': 42}) + self.assertEqual(cache.get_many(['ford1', 'arthur1'], version=2), {}) - self.assertDictEqual(caches['v2'].get_many(['ford1', 'arthur1']), {}) - self.assertDictEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=1), {'ford1': 37, 'arthur1': 42}) - self.assertDictEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=2), {}) + self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1']), {}) + self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=1), {'ford1': 37, 'arthur1': 42}) + self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=2), {}) # set, default version = 1, but manually override version = 2 cache.set_many({'ford2': 37, 'arthur2': 42}, version=2) - self.assertDictEqual(cache.get_many(['ford2', 'arthur2']), {}) - self.assertDictEqual(cache.get_many(['ford2', 'arthur2'], version=1), {}) - self.assertDictEqual(cache.get_many(['ford2', 'arthur2'], version=2), {'ford2': 37, 'arthur2': 42}) + self.assertEqual(cache.get_many(['ford2', 'arthur2']), {}) + self.assertEqual(cache.get_many(['ford2', 'arthur2'], version=1), {}) + self.assertEqual(cache.get_many(['ford2', 'arthur2'], version=2), {'ford2': 37, 'arthur2': 42}) - self.assertDictEqual(caches['v2'].get_many(['ford2', 'arthur2']), {'ford2': 37, 'arthur2': 42}) - self.assertDictEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=1), {}) - self.assertDictEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=2), {'ford2': 37, 'arthur2': 42}) + self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2']), {'ford2': 37, 'arthur2': 42}) + self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=1), {}) + self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=2), {'ford2': 37, 'arthur2': 42}) # v2 set, using default version = 2 caches['v2'].set_many({'ford3': 37, 'arthur3': 42}) - self.assertDictEqual(cache.get_many(['ford3', 'arthur3']), {}) - self.assertDictEqual(cache.get_many(['ford3', 'arthur3'], version=1), {}) - self.assertDictEqual(cache.get_many(['ford3', 'arthur3'], version=2), {'ford3': 37, 'arthur3': 42}) + self.assertEqual(cache.get_many(['ford3', 'arthur3']), {}) + self.assertEqual(cache.get_many(['ford3', 'arthur3'], version=1), {}) + self.assertEqual(cache.get_many(['ford3', 'arthur3'], version=2), {'ford3': 37, 'arthur3': 42}) - self.assertDictEqual(caches['v2'].get_many(['ford3', 'arthur3']), {'ford3': 37, 'arthur3': 42}) - self.assertDictEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=1), {}) - self.assertDictEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=2), {'ford3': 37, 'arthur3': 42}) + self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3']), {'ford3': 37, 'arthur3': 42}) + self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=1), {}) + self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=2), {'ford3': 37, 'arthur3': 42}) # v2 set, default version = 2, but manually override version = 1 caches['v2'].set_many({'ford4': 37, 'arthur4': 42}, version=1) - self.assertDictEqual(cache.get_many(['ford4', 'arthur4']), {'ford4': 37, 'arthur4': 42}) - self.assertDictEqual(cache.get_many(['ford4', 'arthur4'], version=1), {'ford4': 37, 'arthur4': 42}) - self.assertDictEqual(cache.get_many(['ford4', 'arthur4'], version=2), {}) + self.assertEqual(cache.get_many(['ford4', 'arthur4']), {'ford4': 37, 'arthur4': 42}) + self.assertEqual(cache.get_many(['ford4', 'arthur4'], version=1), {'ford4': 37, 'arthur4': 42}) + self.assertEqual(cache.get_many(['ford4', 'arthur4'], version=2), {}) - self.assertDictEqual(caches['v2'].get_many(['ford4', 'arthur4']), {}) - self.assertDictEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=1), {'ford4': 37, 'arthur4': 42}) - self.assertDictEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=2), {}) + self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4']), {}) + self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=1), {'ford4': 37, 'arthur4': 42}) + self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=2), {}) def test_incr_version(self): cache.set('answer', 42, version=2) @@ -801,13 +775,13 @@ def test_cache_write_unpicklable_object(self): get_cache_data = fetch_middleware.process_request(request) self.assertIsNotNone(get_cache_data) - self.assertEqual(get_cache_data.content, content.encode('utf-8')) + self.assertEqual(get_cache_data.content, content.encode()) self.assertEqual(get_cache_data.cookies, response.cookies) update_middleware.process_response(request, get_cache_data) get_cache_data = fetch_middleware.process_request(request) self.assertIsNotNone(get_cache_data) - self.assertEqual(get_cache_data.content, content.encode('utf-8')) + self.assertEqual(get_cache_data.content, content.encode()) self.assertEqual(get_cache_data.cookies, response.cookies) def test_add_fail_on_pickleerror(self): @@ -838,10 +812,11 @@ def test_get_or_set_callable_returning_none(self): self.assertEqual(cache.get('mykey', 'default'), 'default') def test_get_or_set_version(self): + msg = "get_or_set() missing 1 required positional argument: 'default'" cache.get_or_set('brian', 1979, version=2) - with self.assertRaises(TypeError): + with self.assertRaisesMessage(TypeError, msg): cache.get_or_set('brian') - with self.assertRaises(TypeError): + with self.assertRaisesMessage(TypeError, msg): cache.get_or_set('brian', version=1) self.assertIsNone(cache.get('brian', version=1)) self.assertEqual(cache.get_or_set('brian', 42, version=1), 42) @@ -856,15 +831,14 @@ def test_get_or_set_racing(self): self.assertEqual(cache.get_or_set('key', 'default'), 'default') -class PicklingSideEffect(object): +class PicklingSideEffect: def __init__(self, cache): self.cache = cache self.locked = False def __getstate__(self): - if self.cache._lock.active_writers: - self.locked = True + self.locked = self.cache._lock.locked() return {} @@ -874,9 +848,9 @@ def __getstate__(self): class DiskCacheTests(BaseCacheTests, TestCase): "Specific test cases for diskcache.DjangoCache." def setUp(self): - super(DiskCacheTests, self).setUp() + super().setUp() self.dirname = tempfile.mkdtemp() - # Cache location cannot be modified through override_settings / modify_settings, + # Caches location cannot be modified through override_settings / modify_settings, # hence settings are manipulated directly here and the setting_changed signal # is triggered manually. for cache_params in settings.CACHES.values(): @@ -884,7 +858,7 @@ def setUp(self): setting_changed.send(self.__class__, setting='CACHES', enter=False) def tearDown(self): - super(DiskCacheTests, self).tearDown() + super().tearDown() cache.close() shutil.rmtree(self.dirname, ignore_errors=True) From e8f965945e5e82353080bb3454de68084c7e34db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 27 Jun 2020 20:35:13 +0200 Subject: [PATCH 421/550] tests: Use unittest.mock over external mock (#144) Mocking support is built-in since Python 3.3. Use it over external mock when available. This helps distributions that are phasing Python 2 out and would like to remove mock package as part of that. Co-authored-by: Grant Jenks --- requirements.txt | 2 +- tests/test_core.py | 6 +++++- tests/test_deque.py | 6 +++++- tests/test_fanout.py | 6 +++++- tests/test_index.py | 6 +++++- tests/test_recipes.py | 6 +++++- 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index ae05547..ef73654 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ django==2.2.* django_redis doc8 gj -mock +mock;python_version<"3.3" nose pylibmc pylint diff --git a/tests/test_core.py b/tests/test_core.py index 7e79995..1bc4e15 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -7,7 +7,6 @@ import functools as ft import hashlib import io -import mock import os import os.path as op import pytest @@ -27,6 +26,11 @@ except: import pickle +try: + from unittest import mock +except: + import mock + import diskcache import diskcache as dc diff --git a/tests/test_deque.py b/tests/test_deque.py index 640cee2..a00b7fc 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -1,11 +1,15 @@ "Test diskcache.persistent.Deque." import functools as ft -import mock import pickle import pytest import shutil +try: + from unittest import mock +except: + import mock + import diskcache as dc from diskcache.core import ENOVAL diff --git a/tests/test_fanout.py b/tests/test_fanout.py index a62f8a2..10919aa 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -7,7 +7,6 @@ import functools as ft import hashlib import io -import mock import os import os.path as op import pytest @@ -21,6 +20,11 @@ import time import warnings +try: + from unittest import mock +except: + import mock + try: import cPickle as pickle except: diff --git a/tests/test_index.py b/tests/test_index.py index f4232c8..308f894 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,12 +1,16 @@ "Test diskcache.persistent.Index." import functools as ft -import mock import pickle import pytest import shutil import sys +try: + from unittest import mock +except: + import mock + import diskcache as dc diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 35de332..4d908c7 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -1,12 +1,16 @@ "Test diskcache.recipes." import diskcache as dc -import mock import pytest import shutil import threading import time +try: + from unittest import mock +except: + import mock + @pytest.fixture def cache(): From 1794c74ffcb9a5d4a174c8beac46f687884d0956 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 27 Jun 2020 11:40:21 -0700 Subject: [PATCH 422/550] Use unittest.mock in tests --- requirements.txt | 1 - tests/test_core.py | 13 ++----------- tests/test_deque.py | 5 +---- tests/test_djangocache.py | 1 + tests/test_fanout.py | 11 ++--------- tests/test_index.py | 5 +---- tests/test_recipes.py | 5 +---- 7 files changed, 8 insertions(+), 33 deletions(-) diff --git a/requirements.txt b/requirements.txt index ef73654..6c1a277 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ django==2.2.* django_redis doc8 gj -mock;python_version<"3.3" nose pylibmc pylint diff --git a/tests/test_core.py b/tests/test_core.py index 1bc4e15..7c38874 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,7 +1,5 @@ "Test diskcache.core.Cache." -from __future__ import print_function - import collections as co import errno import functools as ft @@ -9,6 +7,7 @@ import io import os import os.path as op +import pickle import pytest import random import shutil @@ -21,15 +20,7 @@ import unittest import warnings -try: - import cPickle as pickle -except: - import pickle - -try: - from unittest import mock -except: - import mock +from unittest import mock import diskcache import diskcache as dc diff --git a/tests/test_deque.py b/tests/test_deque.py index a00b7fc..ddf2338 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -5,10 +5,7 @@ import pytest import shutil -try: - from unittest import mock -except: - import mock +from unittest import mock import diskcache as dc from diskcache.core import ENOVAL diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 100f998..84d2f95 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -14,6 +14,7 @@ import time import unittest import warnings + from unittest import mock from django.conf import settings diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 10919aa..f5d9b70 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -9,6 +9,7 @@ import io import os import os.path as op +import pickle import pytest import random import shutil @@ -20,15 +21,7 @@ import time import warnings -try: - from unittest import mock -except: - import mock - -try: - import cPickle as pickle -except: - import pickle +from unittest import mock import diskcache as dc diff --git a/tests/test_index.py b/tests/test_index.py index 308f894..ef2a0d0 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -6,10 +6,7 @@ import shutil import sys -try: - from unittest import mock -except: - import mock +from unittest import mock import diskcache as dc diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 4d908c7..b26a239 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -6,10 +6,7 @@ import threading import time -try: - from unittest import mock -except: - import mock +from unittest import mock @pytest.fixture From 7e917912f3d1eb1b5263110687b25ce8d231505a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 5 Jul 2020 14:45:46 -0700 Subject: [PATCH 423/550] Run pylint in 3.8 venv --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6c1bd8e..a2ebaf2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,6 @@ matrix: env: TOXENV=py38 - python: pypy env: TOXENV=pypy - - python: 3.7 + - python: 3.8 dist: xenial - env: TOXENV=lint + env: TOXENV=pylint From de987e377616806303a166f27715c468a4421b86 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 5 Jul 2020 14:46:01 -0700 Subject: [PATCH 424/550] Drop pypy for Django 2 support --- .travis.yml | 2 -- tox.ini | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a2ebaf2..5e5c760 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,6 @@ matrix: - python: 3.8 dist: xenial env: TOXENV=py38 - - python: pypy - env: TOXENV=pypy - python: 3.8 dist: xenial env: TOXENV=pylint diff --git a/tox.ini b/tox.ini index ea3996d..b2b6d45 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py35,py36,py37,py38,pypy,pylint +envlist=py35,py36,py37,py38,pylint skip_missing_interpreters=True [testenv] From a6212df9d45b7df157044b1b1720cd997ef0f69f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 22 Aug 2020 21:59:29 -0700 Subject: [PATCH 425/550] Remove unused "env" setting from pytest section --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index b2b6d45..8e9c2e0 100644 --- a/tox.ini +++ b/tox.ini @@ -23,9 +23,6 @@ addopts= --ignore tests/plot.py norecursedirs=site-packages testpaths=docs diskcache tests -env = - DJANGO_SETTINGS_MODULE=tests.settings - PYTHONPATH={PWD}:{PWD}/tests [testenv:pylint] deps= From 82b45e8e342ee597dba56f0a42c0d6088b0c2037 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 22 Aug 2020 21:59:59 -0700 Subject: [PATCH 426/550] Remove nose references --- requirements.txt | 1 - tests/test_core.py | 5 ----- tests/test_fanout.py | 5 ----- 3 files changed, 11 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6c1a277..835f48a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ django==2.2.* django_redis doc8 gj -nose pylibmc pylint pytest diff --git a/tests/test_core.py b/tests/test_core.py index 7c38874..36311e5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1464,8 +1464,3 @@ def fibrec(num): assert hits2 == (hits1 + count) assert misses2 == misses1 - - -if __name__ == '__main__': - import nose - nose.runmodule() diff --git a/tests/test_fanout.py b/tests/test_fanout.py index f5d9b70..390961b 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -685,8 +685,3 @@ def test_custom_filename_disk(): assert content == str(count) * int(1e5) shutil.rmtree(cache.directory, ignore_errors=True) - - -if __name__ == '__main__': - import nose - nose.runmodule() From 83fe89c80371278d45917b6adde58c179fc5f5e2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 22 Aug 2020 22:01:36 -0700 Subject: [PATCH 427/550] Fixes for pylint in Python 3.8 --- diskcache/core.py | 32 ++++++++++++++------------------ diskcache/djangocache.py | 4 ++-- diskcache/persistent.py | 2 +- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 0c8fd2c..0cf95a1 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -180,7 +180,7 @@ def put(self, key): :return: (database key, raw boolean) pair """ - # pylint: disable=bad-continuation,unidiomatic-typecheck + # pylint: disable=unidiomatic-typecheck type_key = type(key) if type_key is BytesType: @@ -378,17 +378,17 @@ def __init__(self, directory, compress_level=1, **kwargs): """ self.compress_level = compress_level - super(JSONDisk, self).__init__(directory, **kwargs) + super().__init__(directory, **kwargs) def put(self, key): json_bytes = json.dumps(key).encode('utf-8') data = zlib.compress(json_bytes, self.compress_level) - return super(JSONDisk, self).put(data) + return super().put(data) def get(self, key, raw): - data = super(JSONDisk, self).get(key, raw) + data = super().get(key, raw) return json.loads(zlib.decompress(data).decode('utf-8')) @@ -396,11 +396,11 @@ def store(self, value, read, key=UNKNOWN): if not read: json_bytes = json.dumps(value).encode('utf-8') value = zlib.compress(json_bytes, self.compress_level) - return super(JSONDisk, self).store(value, read, key=key) + return super().store(value, read, key=key) def fetch(self, mode, filename, value, read): - data = super(JSONDisk, self).fetch(mode, filename, value, read) + data = super().fetch(mode, filename, value, read) if not read: data = json.loads(zlib.decompress(data).decode('utf-8')) return data @@ -448,7 +448,6 @@ def args_to_key(base, args, kwargs, typed): class Cache(object): "Disk and file backed cache." - # pylint: disable=bad-continuation def __init__(self, directory=None, timeout=60, disk=Disk, **settings): """Initialize cache instance. @@ -461,7 +460,7 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): try: assert issubclass(disk, Disk) except (TypeError, AssertionError): - raise ValueError('disk must subclass diskcache.Disk') + raise ValueError('disk must subclass diskcache.Disk') from None if directory is None: directory = tempfile.mkdtemp(prefix='diskcache-') @@ -482,7 +481,7 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): error.errno, 'Cache directory "%s" does not exist' ' and could not be created' % self._directory - ) + ) from None sql = self._sql_retry @@ -756,7 +755,7 @@ def _transact(self, retry=False, filename=None): continue if filename is not None: _disk_remove(filename) - raise Timeout + raise Timeout from None try: yield sql, filenames.append @@ -1609,8 +1608,7 @@ def pull(self, prefix=None, default=(None, None), side='front', if error.errno == errno.ENOENT: # Key was deleted before we could retrieve result. continue - else: - raise + raise finally: if name is not None: self._disk.remove(name) @@ -1719,8 +1717,7 @@ def peek(self, prefix=None, default=(None, None), side='front', if error.errno == errno.ENOENT: # Key was deleted before we could retrieve result. continue - else: - raise + raise finally: if name is not None: self._disk.remove(name) @@ -1794,8 +1791,7 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): if error.errno == errno.ENOENT: # Key was deleted before we could retrieve result. continue - else: - raise + raise break if expire_time and tag: @@ -2162,7 +2158,7 @@ def cull(self, retry=False): for filename, in rows: cleanup(filename) except Timeout: - raise Timeout(count) + raise Timeout(count) from None return count @@ -2215,7 +2211,7 @@ def _select_delete(self, select, args, row_index=0, arg_index=0, cleanup(row[-1]) except Timeout: - raise Timeout(count) + raise Timeout(count) from None return count diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 997b852..329b966 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -22,7 +22,7 @@ def __init__(self, directory, params): :param dict params: cache parameters """ - super(DjangoCache, self).__init__(params) + super().__init__(params) shards = params.get('SHARDS', 8) timeout = params.get('DATABASE_TIMEOUT', 0.010) options = params.get('OPTIONS', {}) @@ -228,7 +228,7 @@ def incr(self, key, delta=1, version=None, default=None, retry=True): try: return self._cache.incr(key, delta, default, retry) except KeyError: - raise ValueError("Key '%s' not found" % key) + raise ValueError("Key '%s' not found" % key) from None def decr(self, key, delta=1, version=None, default=None, retry=True): diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 9de5835..76eb9a2 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -915,7 +915,7 @@ def popitem(self, last=True): :raises KeyError: if index is empty """ - # pylint: disable=arguments-differ + # pylint: disable=arguments-differ,unbalanced-tuple-unpacking _cache = self._cache with _cache.transact(retry=True): From e197a93e60a10fb8ed6030fc6b556b122a664573 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 22 Aug 2020 22:25:17 -0700 Subject: [PATCH 428/550] Update 2019 date references to 2020 --- LICENSE | 2 +- README.rst | 4 ++-- docs/conf.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 3259b98..d31985f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2016-2019 Grant Jenks +Copyright 2016-2020 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/README.rst b/README.rst index 57b7a2e..ba53988 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ DiskCache: Disk Backed Cache `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. -The cloud-based computing of 2019 puts a premium on memory. Gigabytes of empty +The cloud-based computing of 2020 puts a premium on memory. Gigabytes of empty space is left on disks as processes vie for memory. Among these processes is Memcached (and sometimes Redis) which is used as a cache. Wouldn't it be nice to leverage empty disk space for caching? @@ -388,7 +388,7 @@ Reference License ------- -Copyright 2016-2019 Grant Jenks +Copyright 2016-2020 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/docs/conf.py b/docs/conf.py index 683dd3e..2556188 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,7 +51,7 @@ # General information about the project. project = u'DiskCache' -copyright = u'2019, Grant Jenks' +copyright = u'2020, Grant Jenks' author = u'Grant Jenks' # The version info for the project you're documenting, acts as replacement for From 2ba5b7985d17141afa7fb5fb3bf5913b0e496fe6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 22 Aug 2020 22:44:32 -0700 Subject: [PATCH 429/550] Update references to Python 2 support --- README.rst | 4 ++-- docs/development.rst | 4 +--- setup.py | 5 +---- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index ba53988..847b2cf 100644 --- a/README.rst +++ b/README.rst @@ -77,8 +77,8 @@ Features - Thread-safe and process-safe - Supports multiple eviction policies (LRU and LFU included) - Keys support "tag" metadata and eviction -- Developed on Python 3.7 -- Tested on CPython 2.7, 3.4, 3.5, 3.6, 3.7 and PyPy +- Developed on Python 3.8 +- Tested on CPython 3.5, 3.6, 3.7, 3.8 - Tested on Linux, Mac OS X, and Windows - Tested using Travis CI and AppVeyor CI diff --git a/docs/development.rst b/docs/development.rst index 6d16d44..828c3be 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -67,12 +67,10 @@ Testing :doc:`DiskCache ` currently tests against five versions of Python: -* CPython 2.7 -* CPython 3.4 * CPython 3.5 * CPython 3.6 * CPython 3.7 -* PyPy2 +* CPython 3.8 Testing uses `tox `_. If you don't want to install all the development requirements, then, after downloading, you can diff --git a/setup.py b/setup.py index 90dc280..d9be963 100644 --- a/setup.py +++ b/setup.py @@ -38,14 +38,11 @@ def run_tests(self): 'License :: OSI Approved :: Apache Software License', 'Natural Language :: English', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', ), ) From 4497cfc85197d57298120dbd238d263bfb9e9557 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 22 Aug 2020 22:58:07 -0700 Subject: [PATCH 430/550] Remove Python 2 shims --- diskcache/core.py | 68 +++++++++++------------------------------ diskcache/fanout.py | 29 ++---------------- diskcache/persistent.py | 25 +++------------ diskcache/recipes.py | 17 ++--------- 4 files changed, 27 insertions(+), 112 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 0cf95a1..1ec6acc 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -10,6 +10,7 @@ import json import os import os.path as op +import pickle import pickletools import sqlite3 import struct @@ -20,42 +21,9 @@ import warnings import zlib -############################################################################ -# BEGIN Python 2/3 Shims -############################################################################ - -if sys.hexversion < 0x03000000: - import cPickle as pickle # pylint: disable=import-error - # ISSUE #25 Fix for http://bugs.python.org/issue10211 - from cStringIO import StringIO as BytesIO # pylint: disable=import-error - from thread import get_ident # pylint: disable=import-error,no-name-in-module - TextType = unicode # pylint: disable=invalid-name,undefined-variable - BytesType = str - INT_TYPES = int, long # pylint: disable=undefined-variable - range = xrange # pylint: disable=redefined-builtin,invalid-name,undefined-variable - io_open = io.open # pylint: disable=invalid-name -else: - import pickle - from io import BytesIO # pylint: disable=ungrouped-imports - from threading import get_ident - TextType = str - BytesType = bytes - INT_TYPES = (int,) - io_open = open # pylint: disable=invalid-name - def full_name(func): "Return full name of `func` by adding the module and function name." - try: - # The __qualname__ attribute is only available in Python 3.3 and later. - # GrantJ 2019-03-29 Remove after support for Python 2 is dropped. - name = func.__qualname__ - except AttributeError: - name = func.__name__ - return func.__module__ + '.' + name - -############################################################################ -# END Python 2/3 Shims -############################################################################ + return func.__module__ + '.' + func.__qualname__ try: WindowsError @@ -164,9 +132,9 @@ def hash(self, key): if type_disk_key is sqlite3.Binary: return zlib.adler32(disk_key) & mask - elif type_disk_key is TextType: + elif type_disk_key is str: return zlib.adler32(disk_key.encode('utf-8')) & mask # pylint: disable=no-member - elif type_disk_key in INT_TYPES: + elif type_disk_key is int: return disk_key % mask else: assert type_disk_key is float @@ -183,10 +151,10 @@ def put(self, key): # pylint: disable=unidiomatic-typecheck type_key = type(key) - if type_key is BytesType: + if type_key is bytes: return sqlite3.Binary(key), True - elif ((type_key is TextType) - or (type_key in INT_TYPES + elif ((type_key is str) + or (type_key is int and -9223372036854775808 <= key <= 9223372036854775807) or (type_key is float)): return key, True @@ -206,9 +174,9 @@ def get(self, key, raw): """ # pylint: disable=no-self-use,unidiomatic-typecheck if raw: - return BytesType(key) if type(key) is sqlite3.Binary else key + return bytes(key) if type(key) is sqlite3.Binary else key else: - return pickle.load(BytesIO(key)) + return pickle.load(io.BytesIO(key)) def store(self, value, read, key=UNKNOWN): @@ -225,12 +193,12 @@ def store(self, value, read, key=UNKNOWN): type_value = type(value) min_file_size = self.min_file_size - if ((type_value is TextType and len(value) < min_file_size) - or (type_value in INT_TYPES + if ((type_value is str and len(value) < min_file_size) + or (type_value is int and -9223372036854775808 <= value <= 9223372036854775807) or (type_value is float)): return 0, MODE_RAW, None, value - elif type_value is BytesType: + elif type_value is bytes: if len(value) < min_file_size: return 0, MODE_RAW, None, sqlite3.Binary(value) else: @@ -240,10 +208,10 @@ def store(self, value, read, key=UNKNOWN): writer.write(value) return len(value), MODE_BINARY, filename, None - elif type_value is TextType: + elif type_value is str: filename, full_path = self.filename(key, value) - with io_open(full_path, 'w', encoding='UTF-8') as writer: + with open(full_path, 'w', encoding='UTF-8') as writer: writer.write(value) size = op.getsize(full_path) @@ -286,7 +254,7 @@ def fetch(self, mode, filename, value, read): """ # pylint: disable=no-self-use,unidiomatic-typecheck if mode == MODE_RAW: - return BytesType(value) if type(value) is sqlite3.Binary else value + 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') @@ -295,14 +263,14 @@ def fetch(self, mode, filename, value, read): return reader.read() elif mode == MODE_TEXT: full_path = op.join(self._directory, filename) - with io_open(full_path, 'r', encoding='UTF-8') as reader: + with open(full_path, 'r', encoding='UTF-8') as reader: return reader.read() elif mode == MODE_PICKLE: if value is None: with open(op.join(self._directory, filename), 'rb') as reader: return pickle.load(reader) else: - return pickle.load(BytesIO(value)) + return pickle.load(io.BytesIO(value)) def filename(self, key=UNKNOWN, value=UNKNOWN): @@ -738,7 +706,7 @@ def _transact(self, retry=False, filename=None): sql = self._sql filenames = [] _disk_remove = self._disk.remove - tid = get_ident() + tid = threading.get_ident() txn_id = self._txn_id if tid == txn_id: diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 8a0a722..1a59aa2 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -1,5 +1,6 @@ "Fanout cache automatically shards keys and values." +import functools import itertools as it import operator import os.path as op @@ -11,17 +12,6 @@ from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout from .persistent import Deque, Index -############################################################################ -# BEGIN Python 2/3 Shims -############################################################################ - -if sys.hexversion >= 0x03000000: - from functools import reduce - -############################################################################ -# END Python 2/3 Shims -############################################################################ - class FanoutCache(object): "Cache that shards keys and values." @@ -383,7 +373,7 @@ def check(self, fix=False, retry=False): """ warnings = (shard.check(fix, retry) for shard in self._shards) - return reduce(operator.iadd, warnings, []) + return functools.reduce(operator.iadd, warnings, []) def expire(self, retry=False): @@ -661,17 +651,4 @@ def index(self, name): return temp -############################################################################ -# BEGIN Python 2/3 Shims -############################################################################ - -if sys.hexversion < 0x03000000: - import types - memoize_func = Cache.__dict__['memoize'] # pylint: disable=invalid-name - FanoutCache.memoize = types.MethodType(memoize_func, None, FanoutCache) -else: - FanoutCache.memoize = Cache.memoize - -############################################################################ -# END Python 2/3 Shims -############################################################################ +FanoutCache.memoize = Cache.memoize diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 76eb9a2..5047df5 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -6,29 +6,12 @@ import sys from collections import OrderedDict +from collections.abc import MutableMapping, Sequence +from collections.abc import KeysView, ValuesView, ItemsView from contextlib import contextmanager from shutil import rmtree -from .core import BytesType, Cache, ENOVAL, TextType - -############################################################################ -# BEGIN Python 2/3 Shims -############################################################################ - -try: - from collections.abc import MutableMapping, Sequence - from collections.abc import KeysView, ValuesView, ItemsView -except ImportError: - from collections import MutableMapping, Sequence - from collections import KeysView, ValuesView, ItemsView - -if sys.hexversion < 0x03000000: - from itertools import izip as zip # pylint: disable=redefined-builtin,no-name-in-module,ungrouped-imports - range = xrange # pylint: disable=redefined-builtin,invalid-name,undefined-variable - -############################################################################ -# END Python 2/3 Shims -############################################################################ +from .core import Cache, ENOVAL def _make_compare(seq_op, doc): @@ -699,7 +682,7 @@ def __init__(self, *args, **kwargs): 4 """ - if args and isinstance(args[0], (BytesType, TextType)): + if args and isinstance(args[0], (bytes, str)): directory = args[0] args = args[1:] else: diff --git a/diskcache/recipes.py b/diskcache/recipes.py index fb64250..010f1f2 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -12,19 +12,6 @@ from .core import ENOVAL, args_to_key, full_name -############################################################################ -# BEGIN Python 2/3 Shims -############################################################################ - -if sys.hexversion < 0x03000000: - from thread import get_ident # pylint: disable=import-error -else: - from threading import get_ident - -############################################################################ -# END Python 2/3 Shims -############################################################################ - class Averager(object): """Recipe for calculating a running average. @@ -139,7 +126,7 @@ def __init__(self, cache, key, expire=None, tag=None): def acquire(self): "Acquire lock by incrementing count using spin-lock algorithm." pid = os.getpid() - tid = get_ident() + tid = threading.get_ident() pid_tid = '{}-{}'.format(pid, tid) while True: @@ -156,7 +143,7 @@ def acquire(self): def release(self): "Release lock by decrementing count." pid = os.getpid() - tid = get_ident() + tid = threading.get_ident() pid_tid = '{}-{}'.format(pid, tid) with self._cache.transact(retry=True): From 8e0f545a986c8dbb514aaa3c526472e1961934b6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 22 Aug 2020 23:13:45 -0700 Subject: [PATCH 431/550] Add flake8 to linters and fix issues --- diskcache/__init__.py | 6 ++++-- diskcache/core.py | 22 +++++++++++++++------- diskcache/fanout.py | 3 +-- diskcache/persistent.py | 5 +++-- diskcache/recipes.py | 7 +++++-- tox.ini | 13 +++++++++++-- 6 files changed, 39 insertions(+), 17 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 192524e..a301f26 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -6,7 +6,9 @@ """ -from .core import Cache, Disk, EmptyDirWarning, JSONDisk, UnknownFileWarning, Timeout +from .core import ( + Cache, Disk, EmptyDirWarning, JSONDisk, UnknownFileWarning, Timeout +) from .core import DEFAULT_SETTINGS, ENOVAL, EVICTION_POLICY, UNKNOWN from .fanout import FanoutCache from .persistent import Deque, Index @@ -37,7 +39,7 @@ ] try: - from .djangocache import DjangoCache # pylint: disable=wrong-import-position + from .djangocache import DjangoCache # noqa __all__.append('DjangoCache') except Exception: # pylint: disable=broad-except # Django not installed or not setup so ignore. diff --git a/diskcache/core.py b/diskcache/core.py index 1ec6acc..4317160 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -14,23 +14,25 @@ import pickletools import sqlite3 import struct -import sys import tempfile import threading import time import warnings import zlib + def full_name(func): "Return full name of `func` by adding the module and function name." return func.__module__ + '.' + func.__qualname__ + try: WindowsError except NameError: class WindowsError(Exception): "Windows error place-holder on platforms without support." + class Constant(tuple): "Pretty display of immutable constant." def __new__(cls, name): @@ -39,6 +41,7 @@ def __new__(cls, name): def __repr__(self): return '%s' % self[0] + DBNAME = 'cache.db' ENOVAL = Constant('ENOVAL') UNKNOWN = Constant('UNKNOWN') @@ -133,7 +136,7 @@ def hash(self, key): if type_disk_key is sqlite3.Binary: return zlib.adler32(disk_key) & mask elif type_disk_key is str: - return zlib.adler32(disk_key.encode('utf-8')) & mask # pylint: disable=no-member + return zlib.adler32(disk_key.encode('utf-8')) & mask # noqa elif type_disk_key is int: return disk_key % mask else: @@ -1056,7 +1059,9 @@ def incr(self, key, delta=1, default=0, retry=False): raise KeyError(key) value = default + delta - columns = (None, None) + self._disk.store(value, False, key=key) + columns = ( + (None, None) + self._disk.store(value, False, key=key) + ) self._row_insert(db_key, raw, now, columns) self._cull(now, sql, cleanup) return value @@ -1068,7 +1073,9 @@ def incr(self, key, delta=1, default=0, retry=False): raise KeyError(key) value = default + delta - columns = (None, None) + self._disk.store(value, False, key=key) + columns = ( + (None, None) + self._disk.store(value, False, key=key) + ) self._row_update(rowid, now, columns) self._cull(now, sql, cleanup) cleanup(filename) @@ -1184,7 +1191,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, return default (rowid, db_expire_time, db_tag, - mode, filename, db_value), = rows + mode, filename, db_value), = rows # noqa: E127 try: value = self._disk.fetch(mode, filename, db_value, read) @@ -1269,7 +1276,7 @@ def __contains__(self, key): return bool(rows) - def pop(self, key, default=None, expire_time=False, tag=False, retry=False): + 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`. @@ -2341,7 +2348,8 @@ def close(self): def __enter__(self): # Create connection in thread. - connection = self._con # pylint: disable=unused-variable + # pylint: disable=unused-variable + connection = self._con # noqa return self diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 1a59aa2..a579a17 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -5,7 +5,6 @@ import operator import os.path as op import sqlite3 -import sys import tempfile import time @@ -290,7 +289,7 @@ def __contains__(self, key): return key in shard - def pop(self, key, default=None, expire_time=False, tag=False, retry=False): + 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`. diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 5047df5..2e40074 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -197,8 +197,9 @@ def __setitem__(self, index, value): :raises IndexError: if index out of range """ - set_value = lambda key: self._cache.__setitem__(key, value) - self._index(index, set_value) + def _set_value(key): + return self._cache.__setitem__(key, value) + self._index(index, _set_value) def __delitem__(self, index): diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 010f1f2..1a5890d 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -6,7 +6,6 @@ import math import os import random -import sys import threading import time @@ -82,7 +81,11 @@ def acquire(self): "Acquire lock using spin-lock algorithm." while True: added = self._cache.add( - self._key, None, expire=self._expire, tag=self._tag, retry=True, + self._key, + None, + expire=self._expire, + tag=self._tag, + retry=True, ) if added: break diff --git a/tox.ini b/tox.ini index 8e9c2e0..b54e574 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ deps= pytest-django pytest-xdist commands=python -m pytest -setenv = +setenv= DJANGO_SETTINGS_MODULE=tests.settings PYTHONPATH={toxinidir} @@ -27,8 +27,17 @@ testpaths=docs diskcache tests [testenv:pylint] deps= django==2.2.* + flake8 pylint -commands=pylint diskcache +commands= + flake8 diskcache + pylint diskcache [doc8] ignore=D000 + +[flake8] +ignore= + E124 + E303 + W503 From 4f2ff540155c0182ff07a5a3408aceb8c10cff1e Mon Sep 17 00:00:00 2001 From: ume Date: Sat, 1 Aug 2020 10:26:23 +0900 Subject: [PATCH 432/550] Add locked method to Lock --- diskcache/recipes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 1a5890d..85509c7 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -95,6 +95,10 @@ def release(self): "Release lock by deleting key." self._cache.delete(self._key, retry=True) + def locked(self): + "Return true if the lock is acquired." + return self._key in self._cache + def __enter__(self): self.acquire() From a8a014cb96ed0d96c12d07b6b7d4729b54c0c011 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 23 Aug 2020 15:47:49 -0700 Subject: [PATCH 433/550] Add paragraph to caveats about cache volume for Issue #140 --- docs/tutorial.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index b29322c..8f8dc0f 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -892,6 +892,12 @@ thread-pool executor asynchronously. For example:: asyncio.run(set_async('test-key', 'test-value')) +The cache :meth:`volume ` is based on the size of the +database that stores metadata and the size of the values stored in files. It +does not account the size of directories themselves or other filesystem +metadata. If directory count or size is a concern then consider implementing an +alternative :class:`Disk `. + .. _`hash protocol`: https://docs.python.org/library/functions.html#hash .. _`not recommended`: https://www.sqlite.org/faq.html#q5 .. _`performs poorly`: https://www.pythonanywhere.com/forums/topic/1847/ From b1ab12c7a85bf5473fdc491d2d03c9adc6de5bba Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 23 Aug 2020 15:56:19 -0700 Subject: [PATCH 434/550] Bump version to 5.0.0 --- diskcache/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index a301f26..62766c5 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -46,8 +46,8 @@ pass __title__ = 'diskcache' -__version__ = '4.1.0' -__build__ = 0x040100 +__version__ = '5.0.0' +__build__ = 0x050000 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2016-2018 Grant Jenks' +__copyright__ = 'Copyright 2016-2020 Grant Jenks' From 38bd9019e1b8d5adec52f7bc893a6f662c49cd20 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 23 Aug 2020 16:23:02 -0700 Subject: [PATCH 435/550] Remove Python 2 mapping methods keys/values/items/iter*/view* --- diskcache/persistent.py | 207 ++++++---------------------------------- 1 file changed, 30 insertions(+), 177 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 2e40074..4324660 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -1031,196 +1031,49 @@ def __len__(self): return len(self._cache) - if sys.hexversion < 0x03000000: - def keys(self): - """List of index keys. + def keys(self): + """Set-like object providing a view of index keys. - >>> index = Index() - >>> index.update([('a', 1), ('b', 2), ('c', 3)]) - >>> index.keys() - ['a', 'b', 'c'] - - :return: list of keys - - """ - return list(self._cache) - - - def values(self): - """List of index values. - - >>> index = Index() - >>> index.update([('a', 1), ('b', 2), ('c', 3)]) - >>> index.values() - [1, 2, 3] - - :return: list of values - - """ - return list(self.itervalues()) - - - def items(self): - """List of index items. - - >>> index = Index() - >>> index.update([('a', 1), ('b', 2), ('c', 3)]) - >>> index.items() - [('a', 1), ('b', 2), ('c', 3)] - - :return: list of items - - """ - return list(self.iteritems()) - - - def iterkeys(self): - """Iterator of index keys. - - >>> index = Index() - >>> index.update([('a', 1), ('b', 2), ('c', 3)]) - >>> list(index.iterkeys()) - ['a', 'b', 'c'] - - :return: iterator of keys - - """ - return iter(self._cache) - - - def itervalues(self): - """Iterator of index values. - - >>> index = Index() - >>> index.update([('a', 1), ('b', 2), ('c', 3)]) - >>> list(index.itervalues()) - [1, 2, 3] - - :return: iterator of values - - """ - _cache = self._cache - - for key in _cache: - while True: - try: - yield _cache[key] - except KeyError: - pass - break - - - def iteritems(self): - """Iterator of index items. - - >>> index = Index() - >>> index.update([('a', 1), ('b', 2), ('c', 3)]) - >>> list(index.iteritems()) - [('a', 1), ('b', 2), ('c', 3)] - - :return: iterator of items - - """ - _cache = self._cache - - for key in _cache: - while True: - try: - yield key, _cache[key] - except KeyError: - pass - break - - - def viewkeys(self): - """Set-like object providing a view of index keys. - - >>> index = Index() - >>> index.update({'a': 1, 'b': 2, 'c': 3}) - >>> keys_view = index.viewkeys() - >>> 'b' in keys_view - True - - :return: keys view - - """ - return KeysView(self) - - - def viewvalues(self): - """Set-like object providing a view of index values. - - >>> index = Index() - >>> index.update({'a': 1, 'b': 2, 'c': 3}) - >>> values_view = index.viewvalues() - >>> 2 in values_view - True - - :return: values view - - """ - return ValuesView(self) - - - def viewitems(self): - """Set-like object providing a view of index items. - - >>> index = Index() - >>> index.update({'a': 1, 'b': 2, 'c': 3}) - >>> items_view = index.viewitems() - >>> ('b', 2) in items_view - True - - :return: items view - - """ - return ItemsView(self) - - - else: - def keys(self): - """Set-like object providing a view of index keys. - - >>> index = Index() - >>> index.update({'a': 1, 'b': 2, 'c': 3}) - >>> keys_view = index.keys() - >>> 'b' in keys_view - True + >>> index = Index() + >>> index.update({'a': 1, 'b': 2, 'c': 3}) + >>> keys_view = index.keys() + >>> 'b' in keys_view + True - :return: keys view + :return: keys view - """ - return KeysView(self) + """ + return KeysView(self) - def values(self): - """Set-like object providing a view of index values. + def values(self): + """Set-like object providing a view of index values. - >>> index = Index() - >>> index.update({'a': 1, 'b': 2, 'c': 3}) - >>> values_view = index.values() - >>> 2 in values_view - True + >>> index = Index() + >>> index.update({'a': 1, 'b': 2, 'c': 3}) + >>> values_view = index.values() + >>> 2 in values_view + True - :return: values view + :return: values view - """ - return ValuesView(self) + """ + return ValuesView(self) - def items(self): - """Set-like object providing a view of index items. + def items(self): + """Set-like object providing a view of index items. - >>> index = Index() - >>> index.update({'a': 1, 'b': 2, 'c': 3}) - >>> items_view = index.items() - >>> ('b', 2) in items_view - True + >>> index = Index() + >>> index.update({'a': 1, 'b': 2, 'c': 3}) + >>> items_view = index.items() + >>> ('b', 2) in items_view + True - :return: items view + :return: items view - """ - return ItemsView(self) + """ + return ItemsView(self) __hash__ = None From a1bfadd2c72304cea6aa324a0b40b07b35568ed8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 23 Aug 2020 16:23:36 -0700 Subject: [PATCH 436/550] Update tests for Python 3 --- tests/benchmark_core.py | 9 +-------- tests/benchmark_djangocache.py | 9 +-------- tests/benchmark_glob.py | 2 -- tests/benchmark_incr.py | 2 -- tests/stress_test_core.py | 17 +++-------------- tests/stress_test_deque.py | 2 -- tests/stress_test_deque_mp.py | 2 -- tests/stress_test_fanout.py | 17 +++-------------- tests/stress_test_index.py | 2 -- tests/stress_test_index_mp.py | 2 -- tests/test_core.py | 6 +----- tests/test_fanout.py | 5 ----- tests/utils.py | 2 -- 13 files changed, 9 insertions(+), 68 deletions(-) diff --git a/tests/benchmark_core.py b/tests/benchmark_core.py index 1811de0..2eeae3b 100644 --- a/tests/benchmark_core.py +++ b/tests/benchmark_core.py @@ -6,23 +6,16 @@ """ -from __future__ import print_function - import collections as co import multiprocessing as mp import os +import pickle import random import shutil import sys import time import warnings -if sys.hexversion < 0x03000000: - range = xrange - import cPickle as pickle -else: - import pickle - from utils import display PROCS = 8 diff --git a/tests/benchmark_djangocache.py b/tests/benchmark_djangocache.py index 898188f..0512752 100644 --- a/tests/benchmark_djangocache.py +++ b/tests/benchmark_djangocache.py @@ -6,23 +6,16 @@ """ -from __future__ import print_function - import collections as co import multiprocessing as mp import os +import pickle import random import shutil import sys import time import warnings -if sys.hexversion < 0x03000000: - range = xrange - import cPickle as pickle -else: - import pickle - from utils import display PROCS = 8 diff --git a/tests/benchmark_glob.py b/tests/benchmark_glob.py index 0402ef8..82237de 100644 --- a/tests/benchmark_glob.py +++ b/tests/benchmark_glob.py @@ -1,7 +1,5 @@ "Benchmark glob.glob1 as used by django.core.cache.backends.filebased." -from __future__ import print_function - import os import os.path as op import shutil diff --git a/tests/benchmark_incr.py b/tests/benchmark_incr.py index 4a01628..9c8e2fa 100644 --- a/tests/benchmark_incr.py +++ b/tests/benchmark_incr.py @@ -2,8 +2,6 @@ """ -from __future__ import print_function - import json import multiprocessing as mp import shutil diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index bce0d16..68f2ebd 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -1,11 +1,11 @@ "Stress test diskcache.core.Cache." -from __future__ import print_function - import collections as co from diskcache import Cache, UnknownFileWarning, EmptyDirWarning, Timeout import multiprocessing as mp import os +import pickle +import queue import random import shutil import sys @@ -13,17 +13,6 @@ import time import warnings -try: - import Queue -except ImportError: - import queue as Queue - -if sys.hexversion < 0x03000000: - range = xrange - import cPickle as pickle -else: - import pickle - from .utils import display OPERATIONS = int(1e4) @@ -163,7 +152,7 @@ def dispatch(num, eviction_policy, processes, threads): with open('input-%s.pkl' % num, 'rb') as reader: process_queue = pickle.load(reader) - thread_queues = [Queue.Queue() for _ in range(threads)] + thread_queues = [queue.Queue() for _ in range(threads)] subthreads = [ threading.Thread( target=worker, args=(thread_queue, eviction_policy, processes, threads) diff --git a/tests/stress_test_deque.py b/tests/stress_test_deque.py index cf48812..7b3ac2f 100644 --- a/tests/stress_test_deque.py +++ b/tests/stress_test_deque.py @@ -1,7 +1,5 @@ """Stress test diskcache.persistent.Deque.""" -from __future__ import print_function - import collections as co import functools as ft import itertools as it diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py index 4624d71..db7b5c4 100644 --- a/tests/stress_test_deque_mp.py +++ b/tests/stress_test_deque_mp.py @@ -1,7 +1,5 @@ """Stress test diskcache.persistent.Deque.""" -from __future__ import print_function - import functools as ft import itertools as it import multiprocessing as mp diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index 080b8d8..422e874 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -1,11 +1,11 @@ "Stress test diskcache.core.Cache." -from __future__ import print_function - import collections as co from diskcache import FanoutCache, UnknownFileWarning, EmptyDirWarning import multiprocessing as mp import os +import pickle +import queue import random import shutil import sys @@ -13,17 +13,6 @@ import time import warnings -try: - import Queue -except ImportError: - import queue as Queue - -if sys.hexversion < 0x03000000: - range = xrange - import cPickle as pickle -else: - import pickle - from .utils import display OPERATIONS = int(1e4) @@ -155,7 +144,7 @@ def dispatch(num, eviction_policy, processes, threads): with open('input-%s.pkl' % num, 'rb') as reader: process_queue = pickle.load(reader) - thread_queues = [Queue.Queue() for _ in range(threads)] + thread_queues = [queue.Queue() for _ in range(threads)] subthreads = [ threading.Thread( target=worker, args=(thread_queue, eviction_policy, processes, threads) diff --git a/tests/stress_test_index.py b/tests/stress_test_index.py index 2846d9c..e7ba3f6 100644 --- a/tests/stress_test_index.py +++ b/tests/stress_test_index.py @@ -1,7 +1,5 @@ """Stress test diskcache.persistent.Index.""" -from __future__ import print_function - import collections as co import itertools as it import random diff --git a/tests/stress_test_index_mp.py b/tests/stress_test_index_mp.py index b3ed813..f8718f0 100644 --- a/tests/stress_test_index_mp.py +++ b/tests/stress_test_index_mp.py @@ -1,7 +1,5 @@ """Stress test diskcache.persistent.Index.""" -from __future__ import print_function - import itertools as it import multiprocessing as mp import os diff --git a/tests/test_core.py b/tests/test_core.py index 36311e5..31b4034 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -22,14 +22,10 @@ from unittest import mock -import diskcache import diskcache as dc pytestmark = pytest.mark.filterwarnings('ignore', category=dc.EmptyDirWarning) -if sys.hexversion < 0x03000000: - range = xrange - @pytest.fixture def cache(): with dc.Cache() as cache: @@ -100,7 +96,7 @@ def test_custom_disk(): shutil.rmtree(cache.directory, ignore_errors=True) -class SHA256FilenameDisk(diskcache.Disk): +class SHA256FilenameDisk(dc.Disk): def filename(self, key=dc.UNKNOWN, value=dc.UNKNOWN): filename = hashlib.sha256(key).hexdigest()[:32] full_path = op.join(self._directory, filename) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 390961b..aa3c833 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -1,7 +1,5 @@ "Test diskcache.fanout.FanoutCache." -from __future__ import print_function - import collections as co import errno import functools as ft @@ -28,9 +26,6 @@ warnings.simplefilter('error') warnings.simplefilter('ignore', category=dc.EmptyDirWarning) -if sys.hexversion < 0x03000000: - range = xrange - @pytest.fixture def cache(): diff --git a/tests/utils.py b/tests/utils.py index f2370da..47da791 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import os import subprocess as sp From cdeee61aa15335fd95ef923f6807cc451d05209b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 23 Aug 2020 16:30:49 -0700 Subject: [PATCH 437/550] Bump version to 5.0.1 --- diskcache/__init__.py | 4 ++-- diskcache/persistent.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 62766c5..e1eb93f 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -46,8 +46,8 @@ pass __title__ = 'diskcache' -__version__ = '5.0.0' -__build__ = 0x050000 +__version__ = '5.0.1' +__build__ = 0x050001 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2020 Grant Jenks' diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 4324660..d0a452e 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -3,7 +3,6 @@ """ import operator as op -import sys from collections import OrderedDict from collections.abc import MutableMapping, Sequence From 1f339c12fec3baf6955cd6e1596a16bb6bba447b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 25 Aug 2020 21:54:32 -0700 Subject: [PATCH 438/550] Bump version to 5.0.2 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index e1eb93f..01efa1b 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -46,8 +46,8 @@ pass __title__ = 'diskcache' -__version__ = '5.0.1' -__build__ = 0x050001 +__version__ = '5.0.2' +__build__ = 0x050002 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2020 Grant Jenks' From d6c3a4950beeca124575a5b6d039af381ec00ba0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 8 Sep 2020 21:45:45 -0700 Subject: [PATCH 439/550] Add python_requires kwarg to setup --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index d9be963..f8c465e 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ def run_tests(self): packages=['diskcache'], tests_require=['tox'], cmdclass={'test': Tox}, + python_requires='>=3', install_requires=[], classifiers=( 'Development Status :: 5 - Production/Stable', From 9670fbb957af8caac826ddc1144da913682de447 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 8 Sep 2020 21:46:12 -0700 Subject: [PATCH 440/550] Bump version to 5.0.3 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 01efa1b..a98cfee 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -46,8 +46,8 @@ pass __title__ = 'diskcache' -__version__ = '5.0.2' -__build__ = 0x050002 +__version__ = '5.0.3' +__build__ = 0x050003 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2020 Grant Jenks' From 49205f5fc25bb0886e9dcc764ec2dcc3d8afe36d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 8 Nov 2020 19:25:54 -0800 Subject: [PATCH 441/550] Prevent cache shard attribute access when unsafe --- diskcache/fanout.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index a579a17..3b8ae30 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -58,6 +58,9 @@ def directory(self): def __getattr__(self, name): + safe_names = {'timeout', 'disk'} + valid_name = name in DEFAULT_SETTINGS or name in safe_names + assert valid_name, f'cannot access {name} in cache shard' return getattr(self._shards[0], name) From 77ca254ae2fda78550ffd678c7666d62ea0c0af1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 8 Nov 2020 19:26:11 -0800 Subject: [PATCH 442/550] Support transactions in FanoutCache (probably a bad idea) --- diskcache/fanout.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 3b8ae30..7b85df5 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -1,5 +1,6 @@ "Fanout cache automatically shards keys and values." +import contextlib as cl import functools import itertools as it import operator @@ -64,6 +65,40 @@ def __getattr__(self, name): return getattr(self._shards[0], name) + @cl.contextmanager + def transact(self, retry=True): + """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. + + Blocks until transactions are held on all cache shards by retrying as + necessary. + + >>> cache = FanoutCache() + >>> 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 + + :return: context manager for use in `with` statement + + """ + assert retry, 'retry must be True in FanoutCache' + with cl.ExitStack() as stack: + for shard in self._shards: + shard_transaction = shard.transact(retry=True) + stack.enter_context(shard_transaction) + yield + + def set(self, key, value, expire=None, read=False, tag=None, retry=False): """Set `key` and `value` item in cache. From c5259bd6b6046789ae2bcdf1955e1781adb07bd4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 8 Nov 2020 19:27:27 -0800 Subject: [PATCH 443/550] Bump version to 5.1.0 --- diskcache/__init__.py | 4 ++-- diskcache/fanout.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index a98cfee..e4d747b 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -46,8 +46,8 @@ pass __title__ = 'diskcache' -__version__ = '5.0.3' -__build__ = 0x050003 +__version__ = '5.1.0' +__build__ = 0x050100 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2020 Grant Jenks' diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 7b85df5..7e227c8 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -61,7 +61,7 @@ def directory(self): def __getattr__(self, name): safe_names = {'timeout', 'disk'} valid_name = name in DEFAULT_SETTINGS or name in safe_names - assert valid_name, f'cannot access {name} in cache shard' + assert valid_name, 'cannot access {} in cache shard'.format(name) return getattr(self._shards[0], name) From 40ce0dedb90ccefacaea41ac11c19ddd491d5a6d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 8 Nov 2020 20:24:02 -0800 Subject: [PATCH 444/550] Use no hardcoded /tmp/diskcache/... paths in tests --- tests/test_deque.py | 5 +++-- tests/test_index.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_deque.py b/tests/test_deque.py index ddf2338..1ca966d 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -4,6 +4,7 @@ import pickle import pytest import shutil +import tempfile from unittest import mock @@ -26,7 +27,7 @@ def deque(): def test_init(): - directory = '/tmp/diskcache/deque' + directory = tempfile.mkdtemp() sequence = list('abcde') deque = dc.Deque(sequence, None) @@ -156,7 +157,7 @@ def test_indexerror(deque): def test_repr(): - directory = '/tmp/diskcache/deque' + directory = tempfile.mkdtemp() deque = dc.Deque(directory=directory) assert repr(deque) == 'Deque(directory=%r)' % directory diff --git a/tests/test_index.py b/tests/test_index.py index ef2a0d0..575558c 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -5,6 +5,7 @@ import pytest import shutil import sys +import tempfile from unittest import mock @@ -26,7 +27,7 @@ def index(): def test_init(): - directory = '/tmp/diskcache/index' + directory = tempfile.mkdtemp() mapping = {'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1} index = dc.Index(None, mapping) From c4ba1f78bb8494bcf6aba9d7d1c3aa49a1093508 Mon Sep 17 00:00:00 2001 From: Cologler Date: Sat, 12 Dec 2020 00:36:01 +0800 Subject: [PATCH 445/550] replace open mode 'w' to 'x' --- diskcache/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 4317160..1fae11b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -207,14 +207,14 @@ def store(self, value, read, key=UNKNOWN): else: filename, full_path = self.filename(key, value) - with open(full_path, 'wb') as writer: + with open(full_path, 'xb') as writer: writer.write(value) return len(value), MODE_BINARY, filename, None elif type_value is str: filename, full_path = self.filename(key, value) - with open(full_path, 'w', encoding='UTF-8') as writer: + with open(full_path, 'x', encoding='UTF-8') as writer: writer.write(value) size = op.getsize(full_path) @@ -224,7 +224,7 @@ def store(self, value, read, key=UNKNOWN): reader = ft.partial(value.read, 2 ** 22) filename, full_path = self.filename(key, value) - with open(full_path, 'wb') as writer: + with open(full_path, 'xb') as writer: for chunk in iter(reader, b''): size += len(chunk) writer.write(chunk) @@ -238,7 +238,7 @@ def store(self, value, read, key=UNKNOWN): else: filename, full_path = self.filename(key, value) - with open(full_path, 'wb') as writer: + with open(full_path, 'xb') as writer: writer.write(result) return len(result), MODE_PICKLE, filename, None From ce44a42cfe01ac48e167efeff2612a6e768fdf55 Mon Sep 17 00:00:00 2001 From: C2D <50617709+i404788@users.noreply.github.com> Date: Wed, 20 Jan 2021 16:34:02 +0000 Subject: [PATCH 446/550] Use disk provided by the user whenever possible --- diskcache/fanout.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 7e227c8..b39c744 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -36,6 +36,7 @@ def __init__(self, directory=None, shards=8, timeout=0.010, disk=Disk, self._count = shards self._directory = directory + self._disk = disk self._shards = tuple( Cache( directory=op.join(directory, '%03d' % num), @@ -622,7 +623,7 @@ def cache(self, name): except KeyError: parts = name.split('/') directory = op.join(self._directory, 'cache', *parts) - temp = Cache(directory=directory) + temp = Cache(directory=directory, disk=self._disk) _caches[name] = temp return temp From 9a300bd4ab4896cfc4eb90703d155d84924be447 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 20 Jan 2021 20:27:27 -0800 Subject: [PATCH 447/550] Remove transaction from Deque.__init__ When initializing a Deque, a transaction was used to extend elements from the given iterable. The transaction is not used in Index.__init__ or in the FanoutCache.fromcache API. Users that want Deque.__init__ to use a transaction as before should use: d = Deque() with d.transact(): d.extend(iterable) The transaction is therefore explicit and consistent with other APIs. --- diskcache/persistent.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index d0a452e..6fc072b 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -81,8 +81,7 @@ def __init__(self, iterable=(), directory=None): """ self._cache = Cache(directory, eviction_policy='none') - with self.transact(): - self.extend(iterable) + self.extend(iterable) @classmethod From 8b0f68b8c208ddd5a6c06e5c6a9d55ef37f5fb1a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 20 Jan 2021 20:37:55 -0800 Subject: [PATCH 448/550] Use the same Disk in FanoutCache as in Index and Deque subdirs --- diskcache/fanout.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index b39c744..9870c06 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -652,9 +652,10 @@ def deque(self, name): except KeyError: parts = name.split('/') directory = op.join(self._directory, 'deque', *parts) - temp = Deque(directory=directory) - _deques[name] = temp - return temp + cache = Cache(directory=directory, disk=self._disk) + deque = Deque.fromcache(cache) + _deques[name] = deque + return deque def index(self, name): @@ -684,9 +685,10 @@ def index(self, name): except KeyError: parts = name.split('/') directory = op.join(self._directory, 'index', *parts) - temp = Index(directory) - _indexes[name] = temp - return temp + cache = Cache(directory=directory, disk=self._disk) + index = Index.fromcache(cache) + _indexes[name] = index + return index FanoutCache.memoize = Cache.memoize From fcd31646cf81b6762d1d31cffafb8fd97510f024 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 10:29:07 -0800 Subject: [PATCH 449/550] Remove travis and appveyor in favor of GitHub Actions --- .github/workflows/integration.yml | 52 ++++++++++++++++++ .github/workflows/release.yml | 38 +++++++++++++ .travis.yml | 19 ------- appveyor.yml | 22 -------- tox.ini | 88 ++++++++++++++++++++++++------- 5 files changed, 159 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/integration.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000..7dfc198 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,52 @@ +name: integration + +on: [push] + +jobs: + + checks: + runs-on: ubuntu-latest + strategy: + max-parallel: 8 + matrix: + check: [bluecheck, doc8, docs, flake8, isortcheck, mypy, pylint, rstcheck] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + pip install --upgrade pip + pip install tox + - name: Run checks with tox + run: | + tox -e ${{ matrix.check }} + + tests: + needs: checks + runs-on: ${{ matrix.os }} + strategy: + max-parallel: 8 + matrix: + os: [ubuntu-latest, macos-latest, windows-latest, ubuntu-16.04] + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - name: Set up Python ${{ matrix.python-version }} x64 + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + + - uses: actions/checkout@v2 + + - name: Install tox + run: | + pip install --upgrade pip + pip install tox + + - name: Test with tox + run: tox -e py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..57c68e5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,38 @@ +name: release + +on: + push: + tags: + - v* + +jobs: + + upload: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -r requirements.txt + + - name: Create source dist + run: python setup.py sdist + + - name: Create wheel dist + run: python setup.py bdist_wheel + + - name: Upload with twine + env: + TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} + TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} + run: | + ls -l dist/* + twine upload dist/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5e5c760..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -sudo: false -language: python -install: python -m pip install tox -script: python -m tox -e py -matrix: - include: - - python: 3.5 - env: TOXENV=py35 - - python: 3.6 - env: TOXENV=py36 - - python: 3.7 - dist: xenial - env: TOXENV=py37 - - python: 3.8 - dist: xenial - env: TOXENV=py38 - - python: 3.8 - dist: xenial - env: TOXENV=pylint diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 1f52a08..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,22 +0,0 @@ -environment: - - matrix: - - - PYTHON: "C:\\Python35" - - PYTHON: "C:\\Python36" - - PYTHON: "C:\\Python37" - - PYTHON: "C:\\Python38" - - PYTHON: "C:\\Python35-x64" - - PYTHON: "C:\\Python36-x64" - - PYTHON: "C:\\Python37-x64" - - PYTHON: "C:\\Python38-x64" - -install: - - - "%PYTHON%\\python.exe -m pip install tox" - -build: off - -test_script: - - - "%PYTHON%\\python.exe -m tox -e py" diff --git a/tox.ini b/tox.ini index b54e574..6bbe69c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,43 +1,93 @@ [tox] -envlist=py35,py36,py37,py38,pylint +envlist=bluecheck,doc8,docs,isortcheck,flake8,mypy,pylint,rstcheck,py36,py37,py38,py39 skip_missing_interpreters=True [testenv] +commands=pytest deps= django==2.2.* pytest pytest-django pytest-xdist -commands=python -m pytest setenv= DJANGO_SETTINGS_MODULE=tests.settings - PYTHONPATH={toxinidir} + +[testenv:blue] +commands=blue {toxinidir}/setup.py {toxinidir}/diskcache {toxinidir}/tests +deps=blue + +[testenv:bluecheck] +commands=blue --check {toxinidir}/setup.py {toxinidir}/diskcache {toxinidir}/tests +deps=blue + +[testenv:doc8] +deps=doc8 +commands=doc8 docs + +[testenv:docs] +allowlist_externals=make +changedir=docs +commands=make html +deps=sphinx + +[testenv:flake8] +commands=flake8 {toxinidir}/setup.py {toxinidir}/diskcache {toxinidir}/tests +deps=flake8 + +[testenv:isort] +commands=isort {toxinidir}/setup.py {toxinidir}/diskcache {toxinidir}/tests +deps=isort + +[testenv:isortcheck] +commands=isort --check {toxinidir}/setup.py {toxinidir}/diskcache {toxinidir}/tests +deps=isort + +[testenv:mypy] +commands=mypy {toxinidir}/diskcache +deps=mypy + +[testenv:pylint] +commands=pylint {toxinidir}/diskcache +deps=pylint + +[testenv:rstcheck] +commands=rstcheck {toxinidir}/README.rst +deps=rstcheck + +[testenv:uploaddocs] +allowlist_externals=rsync +changedir=docs +commands= + rsync -azP --stats --delete _build/html/ \ + grantjenks.com:/srv/www/www.grantjenks.com/public/docs/diskcache/ + +[isort] +multi_line_output = 3 +include_trailing_comma = True +force_grid_wrap = 0 +use_parentheses = True +ensure_newline_before_comments = True +line_length = 79 [pytest] addopts= -n auto + --cov-branch + --cov-fail-under=100 + --cov-report=term-missing + --cov=diskcache + --doctest-glob="*.rst" --ignore tests/benchmark_core.py --ignore tests/benchmark_djangocache.py --ignore tests/benchmark_glob.py --ignore tests/issue_85.py --ignore tests/plot.py -norecursedirs=site-packages -testpaths=docs diskcache tests - -[testenv:pylint] -deps= - django==2.2.* - flake8 - pylint -commands= - flake8 diskcache - pylint diskcache [doc8] -ignore=D000 +# ignore=D000 [flake8] -ignore= - E124 - E303 - W503 +# ignore= +# E124 +# E303 +# W503 From 8d2009abd1e92875a31b21bdf2799ea757fb6b24 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:19:45 -0800 Subject: [PATCH 450/550] Rewrite k/v store benchmarking to avoid IPython "magic" syntax --- tests/benchmark_kv_store.py | 110 +++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 40 deletions(-) diff --git a/tests/benchmark_kv_store.py b/tests/benchmark_kv_store.py index d1459e7..e214f1a 100644 --- a/tests/benchmark_kv_store.py +++ b/tests/benchmark_kv_store.py @@ -1,48 +1,78 @@ -import dbm +"""Benchmarking Key-Value Stores + +$ python -m IPython tests/benchmark_kv_store.py + +""" + import diskcache -import pickledb -import shelve -import sqlitedict -import timeit + +from IPython import get_ipython + +ipython = get_ipython() +assert ipython is not None, 'No IPython! Run with $ ipython ...' value = 'value' print('diskcache set') dc = diskcache.FanoutCache('/tmp/diskcache') -%timeit -n 100 -r 7 dc['key'] = value +ipython.magic("timeit -n 100 -r 7 dc['key'] = value") print('diskcache get') -%timeit -n 100 -r 7 dc['key'] +ipython.magic("timeit -n 100 -r 7 dc['key']") print('diskcache set/delete') -%timeit -n 100 -r 7 dc['key'] = value; del dc['key'] - -print('dbm set') -d = dbm.open('/tmp/dbm', 'c') -%timeit -n 100 -r 7 d['key'] = value; d.sync() -print('dbm get') -%timeit -n 100 -r 7 d['key'] -print('dbm set/delete') -%timeit -n 100 -r 7 d['key'] = value; del d['key']; d.sync() - -print('shelve set') -s = shelve.open('/tmp/shelve') -%timeit -n 100 -r 7 s['key'] = value; s.sync() -print('shelve get') -%timeit -n 100 -r 7 s['key'] -print('shelve set/delete') -%timeit -n 100 -r 7 s['key'] = value; del s['key']; s.sync() - -print('sqlitedict set') -sd = sqlitedict.SqliteDict('/tmp/sqlitedict', autocommit=True) -%timeit -n 100 -r 7 sd['key'] = value -print('sqlitedict get') -%timeit -n 100 -r 7 sd['key'] -print('sqlitedict set/delete') -%timeit -n 100 -r 7 sd['key'] = value; del sd['key'] - -print('pickledb set') -p = pickledb.load('/tmp/pickledb', True) -%timeit -n 100 -r 7 p['key'] = value -print('pickledb get') -%timeit -n 100 -r 7 p = pickledb.load('/tmp/pickledb', True); p['key'] -print('pickledb set/delete') -%timeit -n 100 -r 7 p['key'] = value; del p['key'] +ipython.magic("timeit -n 100 -r 7 dc['key'] = value; del dc['key']") + +try: + import dbm.gnu # Only trust GNU DBM +except ImportError: + print('Error: Cannot import dbm.gnu') + print('Error: Skipping import shelve') +else: + print('dbm set') + d = dbm.gnu.open('/tmp/dbm', 'c') + ipython.magic("timeit -n 100 -r 7 d['key'] = value; d.sync()") + print('dbm get') + ipython.magic("timeit -n 100 -r 7 d['key']") + print('dbm set/delete') + ipython.magic( + "timeit -n 100 -r 7 d['key'] = value; d.sync(); del d['key']; d.sync()" + ) + + import shelve + + print('shelve set') + s = shelve.open('/tmp/shelve') + ipython.magic("timeit -n 100 -r 7 s['key'] = value; s.sync()") + print('shelve get') + ipython.magic("timeit -n 100 -r 7 s['key']") + print('shelve set/delete') + ipython.magic( + "timeit -n 100 -r 7 s['key'] = value; s.sync(); del s['key']; s.sync()" + ) + +try: + import sqlitedict +except ImportError: + print('Error: Cannot import sqlitedict') +else: + print('sqlitedict set') + sd = sqlitedict.SqliteDict('/tmp/sqlitedict', autocommit=True) + ipython.magic("timeit -n 100 -r 7 sd['key'] = value") + print('sqlitedict get') + ipython.magic("timeit -n 100 -r 7 sd['key']") + print('sqlitedict set/delete') + ipython.magic("timeit -n 100 -r 7 sd['key'] = value; del sd['key']") + +try: + import pickledb +except ImportError: + print('Error: Cannot import pickledb') +else: + print('pickledb set') + p = pickledb.load('/tmp/pickledb', True) + ipython.magic("timeit -n 100 -r 7 p['key'] = value") + print('pickledb get') + ipython.magic( + "timeit -n 100 -r 7 p = pickledb.load('/tmp/pickledb', True); p['key']" + ) + print('pickledb set/delete') + ipython.magic("timeit -n 100 -r 7 p['key'] = value; del p['key']") From b7ecbe9e9d9d51d78b49058c6bea68400ff1ecf5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:23:00 -0800 Subject: [PATCH 451/550] Update development requirements for editable install --- requirements.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 835f48a..a9023a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,12 @@ +-e . +blue coverage django==2.2.* django_redis doc8 -gj +flake8 +ipython +pickleDB pylibmc pylint pytest @@ -12,6 +16,7 @@ pytest-env pytest-xdist rstcheck sphinx +sqlitedict tox twine wheel From 2ed7787817d1a7587dba077e972faea16fb6f5d8 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:25:53 -0800 Subject: [PATCH 452/550] I blue it --- diskcache/__init__.py | 8 +- diskcache/core.py | 323 +++++++++++++++++---------------- diskcache/djangocache.py | 89 +++++---- diskcache/fanout.py | 59 ++---- diskcache/persistent.py | 62 +------ diskcache/recipes.py | 63 +++++-- setup.py | 2 + tests/benchmark_core.py | 90 ++++++--- tests/benchmark_djangocache.py | 23 ++- tests/benchmark_glob.py | 6 +- tests/issue_109.py | 3 +- tests/issue_85.py | 1 + tests/models.py | 4 +- tests/plot.py | 23 ++- tests/plot_early_recompute.py | 8 + tests/settings_benchmark.py | 6 +- tests/stress_test_core.py | 116 +++++++++--- tests/stress_test_fanout.py | 116 +++++++++--- tests/test_core.py | 12 +- tests/test_djangocache.py | 245 ++++++++++++++++--------- tests/test_recipes.py | 6 + tests/utils.py | 23 ++- 22 files changed, 787 insertions(+), 501 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index e4d747b..0300123 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -7,7 +7,12 @@ """ from .core import ( - Cache, Disk, EmptyDirWarning, JSONDisk, UnknownFileWarning, Timeout + Cache, + Disk, + EmptyDirWarning, + JSONDisk, + UnknownFileWarning, + Timeout, ) from .core import DEFAULT_SETTINGS, ENOVAL, EVICTION_POLICY, UNKNOWN from .fanout import FanoutCache @@ -40,6 +45,7 @@ try: from .djangocache import DjangoCache # noqa + __all__.append('DjangoCache') except Exception: # pylint: disable=broad-except # Django not installed or not setup so ignore. diff --git a/diskcache/core.py b/diskcache/core.py index 1fae11b..419c64f 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -29,12 +29,14 @@ def full_name(func): try: WindowsError except NameError: + class WindowsError(Exception): "Windows error place-holder on platforms without support." class Constant(tuple): "Pretty display of immutable constant." + def __new__(cls, name): return tuple.__new__(cls, (name,)) @@ -54,15 +56,15 @@ def __repr__(self): DEFAULT_SETTINGS = { u'statistics': 0, # False - u'tag_index': 0, # False + u'tag_index': 0, # False u'eviction_policy': u'least-recently-stored', u'size_limit': 2 ** 30, # 1gb u'cull_limit': 10, - u'sqlite_auto_vacuum': 1, # FULL - u'sqlite_cache_size': 2 ** 13, # 8,192 pages + u'sqlite_auto_vacuum': 1, # FULL + u'sqlite_cache_size': 2 ** 13, # 8,192 pages u'sqlite_journal_mode': u'wal', - u'sqlite_mmap_size': 2 ** 26, # 64mb - u'sqlite_synchronous': 1, # NORMAL + u'sqlite_mmap_size': 2 ** 26, # 64mb + u'sqlite_synchronous': 1, # NORMAL u'disk_min_file_size': 2 ** 15, # 32kb u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, } @@ -109,6 +111,7 @@ def __repr__(self): class Disk(object): "Cache key and value serialization for SQLite database and files." + def __init__(self, directory, min_file_size=0, pickle_protocol=0): """Initialize disk instance. @@ -121,7 +124,6 @@ def __init__(self, directory, min_file_size=0, pickle_protocol=0): self.min_file_size = min_file_size self.pickle_protocol = pickle_protocol - def hash(self, key): """Compute portable hash for `key`. @@ -143,7 +145,6 @@ def hash(self, key): assert type_disk_key is float return zlib.adler32(struct.pack('!d', disk_key)) & mask - def put(self, key): """Convert `key` to fields key and raw for Cache table. @@ -156,17 +157,20 @@ def put(self, key): 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)): + 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. @@ -181,7 +185,6 @@ def get(self, key, raw): 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. @@ -196,10 +199,14 @@ def store(self, value, read, key=UNKNOWN): type_value = type(value) min_file_size = self.min_file_size - if ((type_value is str and len(value) < min_file_size) - or (type_value is int - and -9223372036854775808 <= value <= 9223372036854775807) - or (type_value is float)): + if ( + (type_value is str and len(value) < min_file_size) + or ( + type_value is int + and -9223372036854775808 <= value <= 9223372036854775807 + ) + or (type_value is float) + ): return 0, MODE_RAW, None, value elif type_value is bytes: if len(value) < min_file_size: @@ -243,7 +250,6 @@ def store(self, value, read, key=UNKNOWN): return len(result), MODE_PICKLE, filename, None - def fetch(self, mode, filename, value, read): """Convert fields `mode`, `filename`, and `value` from Cache table to value. @@ -275,7 +281,6 @@ def fetch(self, mode, filename, value, read): else: return pickle.load(io.BytesIO(value)) - def filename(self, key=UNKNOWN, value=UNKNOWN): """Return filename and full-path tuple for file storage. @@ -310,7 +315,6 @@ def filename(self, key=UNKNOWN, value=UNKNOWN): full_path = op.join(self._directory, filename) return filename, full_path - def remove(self, filename): """Remove a file given by `filename`. @@ -335,6 +339,7 @@ def remove(self, filename): 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. @@ -351,25 +356,21 @@ def __init__(self, directory, compress_level=1, **kwargs): self.compress_level = compress_level super().__init__(directory, **kwargs) - def put(self, key): json_bytes = json.dumps(key).encode('utf-8') data = zlib.compress(json_bytes, self.compress_level) return super().put(data) - def get(self, key, raw): data = super().get(key, raw) return json.loads(zlib.decompress(data).decode('utf-8')) - def store(self, value, read, key=UNKNOWN): if not read: json_bytes = json.dumps(value).encode('utf-8') value = zlib.compress(json_bytes, self.compress_level) return super().store(value, read, key=key) - def fetch(self, mode, filename, value, read): data = super().fetch(mode, filename, value, read) if not read: @@ -419,6 +420,7 @@ def args_to_key(base, args, kwargs, typed): class Cache(object): "Disk and file backed cache." + def __init__(self, directory=None, timeout=60, disk=Disk, **settings): """Initialize cache instance. @@ -451,7 +453,7 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): raise EnvironmentError( error.errno, 'Cache directory "%s" does not exist' - ' and could not be created' % self._directory + ' and could not be created' % self._directory, ) from None sql = self._sql_retry @@ -459,9 +461,9 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): # Setup Settings table. try: - current_settings = dict(sql( - 'SELECT key, value FROM Settings' - ).fetchall()) + current_settings = dict( + sql('SELECT key, value FROM Settings').fetchall() + ) except sqlite3.OperationalError: current_settings = {} @@ -478,7 +480,8 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): if key.startswith('sqlite_'): self.reset(key, value, update=False) - sql('CREATE TABLE IF NOT EXISTS Settings (' + sql( + 'CREATE TABLE IF NOT EXISTS Settings (' ' key TEXT NOT NULL UNIQUE,' ' value)' ) @@ -486,7 +489,8 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): # Setup Disk object (must happen after settings initialized). kwargs = { - key[5:]: value for key, value in sets.items() + key[5:]: value + for key, value in sets.items() if key.startswith('disk_') } self._disk = disk(directory, **kwargs) @@ -503,11 +507,12 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): sql(query, (key, value)) self.reset(key) - (self._page_size,), = sql('PRAGMA page_size').fetchall() + ((self._page_size,),) = sql('PRAGMA page_size').fetchall() # Setup Cache table. - sql('CREATE TABLE IF NOT EXISTS Cache (' + sql( + 'CREATE TABLE IF NOT EXISTS Cache (' ' rowid INTEGER PRIMARY KEY,' ' key BLOB,' ' raw INTEGER,' @@ -522,11 +527,13 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): ' value BLOB)' ) - sql('CREATE UNIQUE INDEX IF NOT EXISTS Cache_key_raw ON' + sql( + 'CREATE UNIQUE INDEX IF NOT EXISTS Cache_key_raw ON' ' Cache(key, raw)' ) - sql('CREATE INDEX IF NOT EXISTS Cache_expire_time ON' + sql( + 'CREATE INDEX IF NOT EXISTS Cache_expire_time ON' ' Cache (expire_time)' ) @@ -537,32 +544,37 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): # Use triggers to keep Metadata updated. - sql('CREATE TRIGGER IF NOT EXISTS Settings_count_insert' + 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' + 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' + 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' + 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' + 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' @@ -581,25 +593,21 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): self._timeout = timeout self._sql # pylint: disable=pointless-statement - @property def directory(self): """Cache directory.""" return self._directory - @property def timeout(self): """SQLite connection timeout value in seconds.""" return self._timeout - @property def disk(self): """Disk used for serialization.""" return self._disk - @property def _con(self): # Check process ID to support process forking. If the process @@ -638,12 +646,10 @@ def _con(self): return con - @property def _sql(self): return self._con.execute - @property def _sql_retry(self): sql = self._sql @@ -671,7 +677,6 @@ def _execute_with_retry(statement, *args, **kwargs): return _execute_with_retry - @cl.contextmanager def transact(self, retry=False): """Context manager to perform a transaction by locking the cache. @@ -703,7 +708,6 @@ def transact(self, retry=False): with self._transact(retry=retry): yield - @cl.contextmanager def _transact(self, retry=False, filename=None): sql = self._sql @@ -745,7 +749,6 @@ def _transact(self, retry=False, filename=None): if name is not None: _disk_remove(name) - def set(self, key, value, expire=None, read=False, tag=None, retry=False): """Set `key` and `value` item in cache. @@ -801,7 +804,7 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): ).fetchall() if rows: - (rowid, old_filename), = rows + ((rowid, old_filename),) = rows cleanup(old_filename) self._row_update(rowid, now, columns) else: @@ -811,7 +814,6 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): return True - def __setitem__(self, key, value): """Set corresponding `value` for `key` in cache. @@ -823,11 +825,11 @@ def __setitem__(self, key, value): """ self.set(key, value, retry=True) - def _row_update(self, rowid, now, columns): sql = self._sql expire_time, tag, size, mode, filename, value = columns - sql('UPDATE Cache SET' + sql( + 'UPDATE Cache SET' ' store_time = ?,' ' expire_time = ?,' ' access_time = ?,' @@ -837,11 +839,12 @@ def _row_update(self, rowid, now, columns): ' mode = ?,' ' filename = ?,' ' value = ?' - ' WHERE rowid = ?', ( - now, # store_time + ' WHERE rowid = ?', + ( + now, # store_time expire_time, - now, # access_time - 0, # access_count + now, # access_time + 0, # access_count tag, size, mode, @@ -851,20 +854,21 @@ def _row_update(self, rowid, now, columns): ), ) - def _row_insert(self, key, raw, now, columns): sql = self._sql expire_time, tag, size, mode, filename, value = columns - sql('INSERT INTO Cache(' + sql( + 'INSERT INTO Cache(' ' key, raw, store_time, expire_time, access_time,' ' access_count, tag, size, mode, filename, value' - ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( + ') VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + ( key, raw, - now, # store_time + now, # store_time expire_time, - now, # access_time - 0, # access_count + now, # access_time + 0, # access_count tag, size, mode, @@ -873,7 +877,6 @@ def _row_insert(self, key, raw, now, columns): ), ) - def _cull(self, now, sql, cleanup, limit=None): cull_limit = self.cull_limit if limit is None else limit @@ -892,13 +895,12 @@ def _cull(self, now, sql, cleanup, limit=None): rows = sql(select_expired, (now, cull_limit)).fetchall() if rows: - delete_expired = ( - 'DELETE FROM Cache WHERE rowid IN (%s)' - % (select_expired_template % 'rowid') + delete_expired = 'DELETE FROM Cache WHERE rowid IN (%s)' % ( + select_expired_template % 'rowid' ) sql(delete_expired, (now, cull_limit)) - for filename, in rows: + for (filename,) in rows: cleanup(filename) cull_limit -= len(rows) @@ -917,16 +919,14 @@ def _cull(self, now, sql, cleanup, limit=None): rows = sql(select_filename, (cull_limit,)).fetchall() if rows: - delete = ( - 'DELETE FROM Cache WHERE rowid IN (%s)' - % (select_policy.format(fields='rowid', now=now)) + delete = 'DELETE FROM Cache WHERE rowid IN (%s)' % ( + select_policy.format(fields='rowid', now=now) ) sql(delete, (cull_limit,)) - for filename, in rows: + for (filename,) in rows: cleanup(filename) - def touch(self, key, expire=None, retry=False): """Touch `key` in cache and update `expire` time. @@ -953,17 +953,17 @@ def touch(self, key, expire=None, retry=False): ).fetchall() if rows: - (rowid, old_expire_time), = rows + ((rowid, old_expire_time),) = rows if old_expire_time is None or old_expire_time > now: - sql('UPDATE Cache SET expire_time = ? WHERE rowid = ?', + sql( + 'UPDATE Cache SET expire_time = ? WHERE rowid = ?', (expire_time, rowid), ) return True return False - def add(self, key, value, expire=None, read=False, tag=None, retry=False): """Add `key` and `value` item to cache. @@ -1003,7 +1003,7 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): ).fetchall() if rows: - (rowid, old_filename, old_expire_time), = rows + ((rowid, old_filename, old_expire_time),) = rows if old_expire_time is None or old_expire_time > now: cleanup(filename) @@ -1018,7 +1018,6 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): return True - def incr(self, key, delta=1, default=0, retry=False): """Increment value by delta for item with key. @@ -1059,22 +1058,22 @@ def incr(self, key, delta=1, default=0, retry=False): raise KeyError(key) value = default + delta - columns = ( - (None, None) + self._disk.store(value, False, key=key) + columns = (None, None) + self._disk.store( + value, False, key=key ) self._row_insert(db_key, raw, now, columns) self._cull(now, sql, cleanup) return value - (rowid, expire_time, filename, value), = rows + ((rowid, expire_time, filename, value),) = rows if expire_time is not None and expire_time < now: if default is None: raise KeyError(key) value = default + delta - columns = ( - (None, None) + self._disk.store(value, False, key=key) + columns = (None, None) + self._disk.store( + value, False, key=key ) self._row_update(rowid, now, columns) self._cull(now, sql, cleanup) @@ -1094,7 +1093,6 @@ def incr(self, key, delta=1, default=0, retry=False): return value - def decr(self, key, delta=1, default=0, retry=False): """Decrement value by delta for item with key. @@ -1125,9 +1123,15 @@ def decr(self, key, delta=1, default=0, retry=False): """ return self.incr(key, -delta, default, retry) - - def get(self, key, default=None, read=False, expire_time=False, tag=False, - retry=False): + 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 @@ -1166,7 +1170,7 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, if not rows: return default - (rowid, db_expire_time, db_tag, mode, filename, db_value), = rows + ((rowid, db_expire_time, db_tag, mode, filename, db_value),) = rows try: value = self._disk.fetch(mode, filename, db_value, read) @@ -1190,8 +1194,9 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, sql(cache_miss) return default - (rowid, db_expire_time, db_tag, - mode, filename, db_value), = rows # noqa: E127 + ( + (rowid, db_expire_time, db_tag, mode, filename, db_value), + ) = rows # noqa: E127 try: value = self._disk.fetch(mode, filename, db_value, read) @@ -1222,7 +1227,6 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, else: return value - def __getitem__(self, key): """Return corresponding value for `key` from cache. @@ -1236,7 +1240,6 @@ def __getitem__(self, key): raise KeyError(key) return value - def read(self, key, retry=False): """Return file handle value corresponding to `key` from cache. @@ -1255,7 +1258,6 @@ def read(self, key, retry=False): raise KeyError(key) return handle - def __contains__(self, key): """Return `True` if `key` matching item is found in cache. @@ -1275,8 +1277,9 @@ def __contains__(self, key): return bool(rows) - - def pop(self, key, default=None, expire_time=False, tag=False, retry=False): # noqa: E501 + 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`. @@ -1314,7 +1317,7 @@ def pop(self, key, default=None, expire_time=False, tag=False, retry=False): # if not rows: return default - (rowid, db_expire_time, db_tag, mode, filename, db_value), = rows + ((rowid, db_expire_time, db_tag, mode, filename, db_value),) = rows sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) @@ -1339,7 +1342,6 @@ def pop(self, key, default=None, expire_time=False, tag=False, retry=False): # else: return value - def __delitem__(self, key, retry=True): """Delete corresponding item for `key` from cache. @@ -1365,13 +1367,12 @@ def __delitem__(self, key, retry=True): if not rows: raise KeyError(key) - (rowid, filename), = rows + ((rowid, filename),) = rows sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) cleanup(filename) return True - def delete(self, key, retry=False): """Delete corresponding item for `key` from cache. @@ -1391,9 +1392,16 @@ def delete(self, key, retry=False): except KeyError: return False - - def push(self, value, prefix=None, side='back', expire=None, read=False, - tag=None, retry=False): + def push( + self, + value, + prefix=None, + side='back', + expire=None, + read=False, + tag=None, + retry=False, + ): """Push `value` onto `side` of queue identified by `prefix` in cache. When prefix is None, integer keys are used. Otherwise, string keys are @@ -1459,10 +1467,10 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, rows = sql(select, (min_key, max_key, raw)).fetchall() if rows: - (key,), = rows + ((key,),) = rows if prefix is not None: - num = int(key[(key.rfind('-') + 1):]) + num = int(key[(key.rfind('-') + 1) :]) else: num = key @@ -1484,9 +1492,15 @@ def push(self, value, prefix=None, side='back', expire=None, read=False, return db_key - - def pull(self, prefix=None, default=(None, None), side='front', - expire_time=False, tag=False, retry=False): + 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 is None, integer keys are used. Otherwise, string keys are @@ -1567,8 +1581,9 @@ def pull(self, prefix=None, default=(None, None), side='front', if not rows: return default - (rowid, key, db_expire, db_tag, mode, name, - db_value), = rows + ( + (rowid, key, db_expire, db_tag, mode, name, db_value), + ) = rows sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) @@ -1598,9 +1613,15 @@ def pull(self, prefix=None, default=(None, None), side='front', else: return key, value - - def peek(self, prefix=None, default=(None, None), side='front', - expire_time=False, tag=False, retry=False): + 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 is None, integer keys are used. Otherwise, string keys are @@ -1677,8 +1698,9 @@ def peek(self, prefix=None, default=(None, None), side='front', if not rows: return default - (rowid, key, db_expire, db_tag, mode, name, - db_value), = rows + ( + (rowid, key, db_expire, db_tag, mode, name, db_value), + ) = rows if db_expire is not None and db_expire < time.time(): sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) @@ -1707,7 +1729,6 @@ def peek(self, prefix=None, default=(None, None), side='front', 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. @@ -1749,8 +1770,18 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): if not rows: raise KeyError('dictionary is empty') - (rowid, db_key, raw, db_expire, db_tag, mode, name, - db_value), = rows + ( + ( + rowid, + db_key, + raw, + db_expire, + db_tag, + mode, + name, + db_value, + ), + ) = rows if db_expire is not None and db_expire < time.time(): sql('DELETE FROM Cache WHERE rowid = ?', (rowid,)) @@ -1778,7 +1809,6 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): else: return key, value - def memoize(self, name=None, typed=False, expire=None, tag=None): """Memoizing cache decorator. @@ -1871,7 +1901,6 @@ def __cache_key__(*args, **kwargs): return decorator - def check(self, fix=False, retry=False): """Check database and file system consistency. @@ -1901,7 +1930,7 @@ def check(self, fix=False, retry=False): rows = sql('PRAGMA integrity_check').fetchall() if len(rows) != 1 or rows[0][0] != u'ok': - for message, in rows: + for (message,) in rows: warnings.warn(message) if fix: @@ -1932,7 +1961,8 @@ def check(self, fix=False, retry=False): warnings.warn(message % args) if fix: - sql('UPDATE Cache SET size = ?' + sql( + 'UPDATE Cache SET size = ?' ' WHERE rowid = ?', (real_size, rowid), ) @@ -1973,14 +2003,15 @@ def check(self, fix=False, retry=False): # Check Settings.count against count of Cache rows. self.reset('count') - (count,), = sql('SELECT COUNT(key) FROM Cache').fetchall() + ((count,),) = sql('SELECT COUNT(key) FROM Cache').fetchall() if self.count != count: message = 'Settings.count != COUNT(Cache.key); %d != %d' warnings.warn(message % (self.count, count)) if fix: - sql('UPDATE Settings SET value = ? WHERE key = ?', + sql( + 'UPDATE Settings SET value = ? WHERE key = ?', (count, 'count'), ) @@ -1988,20 +2019,20 @@ def check(self, fix=False, retry=False): self.reset('size') select_size = 'SELECT COALESCE(SUM(size), 0) FROM Cache' - (size,), = sql(select_size).fetchall() + ((size,),) = sql(select_size).fetchall() if self.size != size: message = 'Settings.size != SUM(Cache.size); %d != %d' warnings.warn(message % (self.size, size)) if fix: - sql('UPDATE Settings SET value = ? WHERE key =?', + sql( + 'UPDATE Settings SET value = ? WHERE key =?', (size, 'size'), ) return warns - def create_tag_index(self): """Create tag index on cache database. @@ -2014,7 +2045,6 @@ def create_tag_index(self): sql('CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON Cache(tag, rowid)') self.reset('tag_index', 1) - def drop_tag_index(self): """Drop tag index on cache database. @@ -2025,7 +2055,6 @@ def drop_tag_index(self): sql('DROP INDEX IF EXISTS Cache_tag_rowid') self.reset('tag_index', 0) - def evict(self, tag, retry=False): """Remove items with matching `tag` from cache. @@ -2053,7 +2082,6 @@ def evict(self, tag, retry=False): args = [tag, 0, 100] return self._select_delete(select, args, arg_index=1, retry=retry) - def expire(self, now=None, retry=False): """Remove expired items from cache. @@ -2081,7 +2109,6 @@ def expire(self, now=None, retry=False): args = [0, now or time.time(), 100] return self._select_delete(select, args, row_index=1, retry=retry) - def cull(self, retry=False): """Cull items from cache until volume is less than size limit. @@ -2130,14 +2157,13 @@ def cull(self, retry=False): ) sql(delete, (10,)) - for filename, in rows: + for (filename,) in rows: cleanup(filename) except Timeout: raise Timeout(count) from None return count - def clear(self, retry=False): """Remove all items from cache. @@ -2164,9 +2190,9 @@ def clear(self, retry=False): args = [0, 100] return self._select_delete(select, args, retry=retry) - - def _select_delete(self, select, args, row_index=0, arg_index=0, - retry=False): + def _select_delete( + self, select, args, row_index=0, arg_index=0, retry=False + ): count = 0 delete = 'DELETE FROM Cache WHERE rowid IN (%s)' @@ -2190,7 +2216,6 @@ def _select_delete(self, select, args, row_index=0, arg_index=0, return count - def iterkeys(self, reverse=False): """Iterate Cache keys in database sort order. @@ -2234,7 +2259,7 @@ def iterkeys(self, reverse=False): row = sql(select).fetchall() if row: - (key, raw), = row + ((key, raw),) = row else: return @@ -2249,11 +2274,10 @@ def iterkeys(self, reverse=False): for key, raw in rows: yield _disk_get(key, raw) - def _iter(self, ascending=True): sql = self._sql rows = sql('SELECT MAX(rowid) FROM Cache').fetchall() - (max_rowid,), = rows + ((max_rowid,),) = rows yield # Signal ready. if max_rowid is None: @@ -2283,21 +2307,18 @@ def _iter(self, ascending=True): for rowid, key, raw in rows: yield _disk_get(key, raw) - def __iter__(self): "Iterate keys in cache including expired items." iterator = self._iter() next(iterator) return iterator - def __reversed__(self): "Reverse iterate keys in cache including expired items." iterator = self._iter(ascending=False) next(iterator) return iterator - def stats(self, enable=True, reset=False): """Return cache statistics hits and misses. @@ -2317,22 +2338,18 @@ def stats(self, enable=True, reset=False): return result - def volume(self): """Return estimated total size of cache on disk. :return: size in bytes """ - (page_count,), = self._sql('PRAGMA page_count').fetchall() + ((page_count,),) = self._sql('PRAGMA page_count').fetchall() total_size = self._page_size * page_count + self.reset('size') return total_size - def close(self): - """Close database connection. - - """ + """Close database connection.""" con = getattr(self._local, 'con', None) if con is None: @@ -2345,31 +2362,25 @@ def close(self): except AttributeError: pass - def __enter__(self): # Create connection in thread. # pylint: disable=unused-variable connection = self._con # noqa return self - def __exit__(self, *exception): self.close() - def __len__(self): "Count of items in cache including expired items." return self.reset('count') - def __getstate__(self): return (self.directory, self.timeout, type(self.disk)) - def __setstate__(self, state): self.__init__(*state) - def reset(self, key, value=ENOVAL, update=True): """Reset `key` and `value` item from Settings table. @@ -2403,7 +2414,7 @@ def reset(self, key, value=ENOVAL, update=True): if value is ENOVAL: select = 'SELECT value FROM Settings WHERE key = ?' - (value,), = sql_retry(select, (key,)).fetchall() + ((value,),) = sql_retry(select, (key,)).fetchall() setattr(self, key, value) return value @@ -2431,7 +2442,9 @@ def reset(self, key, value=ENOVAL, update=True): while True: try: try: - (old_value,), = sql('PRAGMA %s' % (pragma)).fetchall() + ((old_value,),) = sql( + 'PRAGMA %s' % (pragma) + ).fetchall() update = old_value != value except ValueError: update = True diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 329b966..2f1db07 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -15,6 +15,7 @@ class DjangoCache(BaseCache): "Django-compatible disk and file backed cache." + def __init__(self, directory, params): """Initialize DjangoCache instance. @@ -28,13 +29,11 @@ def __init__(self, directory, params): options = params.get('OPTIONS', {}) self._cache = FanoutCache(directory, shards, timeout, **options) - @property def directory(self): """Cache directory.""" return self._cache.directory - def cache(self, name): """Return Cache with given `name` in subdirectory. @@ -44,7 +43,6 @@ def cache(self, name): """ return self._cache.cache(name) - def deque(self, name): """Return Deque with given `name` in subdirectory. @@ -54,7 +52,6 @@ def deque(self, name): """ return self._cache.deque(name) - def index(self, name): """Return Index with given `name` in subdirectory. @@ -64,9 +61,16 @@ def index(self, name): """ return self._cache.index(name) - - def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, - read=False, tag=None, retry=True): + def add( + self, + key, + value, + timeout=DEFAULT_TIMEOUT, + version=None, + read=False, + tag=None, + retry=True, + ): """Set a value in the cache if the key does not already exist. If timeout is given, that timeout will be used for the key; otherwise the default cache timeout will be used. @@ -89,9 +93,16 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, timeout = self.get_backend_timeout(timeout=timeout) return self._cache.add(key, value, timeout, read, tag, retry) - - def get(self, key, default=None, version=None, read=False, - expire_time=False, tag=False, retry=False): + def get( + self, + key, + default=None, + version=None, + read=False, + expire_time=False, + tag=False, + retry=False, + ): """Fetch a given key from the cache. If the key does not exist, return default, which itself defaults to None. @@ -111,7 +122,6 @@ def get(self, key, default=None, version=None, read=False, key = self.make_key(key, version=version) return self._cache.get(key, default, read, expire_time, tag, retry) - def read(self, key, version=None): """Return file handle corresponding to `key` from Cache. @@ -124,9 +134,16 @@ def read(self, key, version=None): key = self.make_key(key, version=version) return self._cache.read(key) - - def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, - read=False, tag=None, retry=True): + def set( + self, + key, + value, + timeout=DEFAULT_TIMEOUT, + version=None, + read=False, + tag=None, + retry=True, + ): """Set a value in the cache. If timeout is given, that timeout will be used for the key; otherwise the default cache timeout will be used. @@ -146,7 +163,6 @@ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, timeout = self.get_backend_timeout(timeout=timeout) return self._cache.set(key, value, timeout, read, tag, retry) - def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None, retry=True): """Touch a key in the cache. If timeout is given, that timeout will be used for the key; otherwise the default cache timeout will be used. @@ -164,9 +180,15 @@ def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None, retry=True): timeout = self.get_backend_timeout(timeout=timeout) return self._cache.touch(key, timeout, retry) - - def pop(self, key, default=None, version=None, expire_time=False, - tag=False, retry=True): + def pop( + self, + key, + default=None, + version=None, + expire_time=False, + tag=False, + retry=True, + ): """Remove corresponding item for `key` from cache and return value. If `key` is missing, return `default`. @@ -186,7 +208,6 @@ def pop(self, key, default=None, version=None, expire_time=False, key = self.make_key(key, version=version) return self._cache.pop(key, default, expire_time, tag, retry) - def delete(self, key, version=None, retry=True): """Delete a key from the cache, failing silently. @@ -200,7 +221,6 @@ def delete(self, key, version=None, retry=True): key = self.make_key(key, version=version) self._cache.delete(key, retry) - def incr(self, key, delta=1, version=None, default=None, retry=True): """Increment value by delta for item with key. @@ -230,7 +250,6 @@ def incr(self, key, delta=1, version=None, default=None, retry=True): except KeyError: raise ValueError("Key '%s' not found" % key) from None - def decr(self, key, delta=1, version=None, default=None, retry=True): """Decrement value by delta for item with key. @@ -259,7 +278,6 @@ def decr(self, key, delta=1, version=None, default=None, retry=True): # pylint: disable=arguments-differ return self.incr(key, -delta, version, default, retry) - def has_key(self, key, version=None): """Returns True if the key is in the cache and has not expired. @@ -271,7 +289,6 @@ def has_key(self, key, version=None): key = self.make_key(key, version=version) return key in self._cache - def expire(self): """Remove expired items from cache. @@ -280,7 +297,6 @@ def expire(self): """ return self._cache.expire() - def stats(self, enable=True, reset=False): """Return cache statistics hits and misses. @@ -291,7 +307,6 @@ def stats(self, enable=True, reset=False): """ return self._cache.stats(enable=enable, reset=reset) - def create_tag_index(self): """Create tag index on cache database. @@ -302,7 +317,6 @@ def create_tag_index(self): """ self._cache.create_tag_index() - def drop_tag_index(self): """Drop tag index on cache database. @@ -311,7 +325,6 @@ def drop_tag_index(self): """ self._cache.drop_tag_index() - def evict(self, tag): """Remove items with matching `tag` from cache. @@ -321,7 +334,6 @@ def evict(self, tag): """ return self._cache.evict(tag) - def cull(self): """Cull items from cache until volume is less than size limit. @@ -330,18 +342,15 @@ def cull(self): """ return self._cache.cull() - def clear(self): "Remove *all* values from the cache at once." return self._cache.clear() - def close(self, **kwargs): "Close the cache connection." # pylint: disable=unused-argument self._cache.close() - def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): """Return seconds to expiration. @@ -356,9 +365,14 @@ def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): timeout = -1 return None if timeout is None else timeout - - def memoize(self, name=None, timeout=DEFAULT_TIMEOUT, version=None, - typed=False, tag=None): + def memoize( + self, + name=None, + timeout=DEFAULT_TIMEOUT, + version=None, + typed=False, + tag=None, + ): """Memoizing cache decorator. Decorator to wrap callable with memoizing function using cache. @@ -418,7 +432,12 @@ def wrapper(*args, **kwargs): ) if valid_timeout: self.set( - key, result, timeout, version, tag=tag, retry=True, + key, + result, + timeout, + version, + tag=tag, + retry=True, ) return result diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 9870c06..cc40c07 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -15,8 +15,10 @@ class FanoutCache(object): "Cache that shards keys and values." - def __init__(self, directory=None, shards=8, timeout=0.010, disk=Disk, - **settings): + + def __init__( + self, directory=None, shards=8, timeout=0.010, disk=Disk, **settings + ): """Initialize cache instance. :param str directory: cache directory @@ -52,20 +54,17 @@ def __init__(self, directory=None, shards=8, timeout=0.010, disk=Disk, self._deques = {} self._indexes = {} - @property def directory(self): """Cache directory.""" return self._directory - def __getattr__(self, name): safe_names = {'timeout', 'disk'} valid_name = name in DEFAULT_SETTINGS or name in safe_names assert valid_name, 'cannot access {} in cache shard'.format(name) return getattr(self._shards[0], name) - @cl.contextmanager def transact(self, retry=True): """Context manager to perform a transaction by locking the cache. @@ -99,7 +98,6 @@ def transact(self, retry=True): stack.enter_context(shard_transaction) yield - def set(self, key, value, expire=None, read=False, tag=None, retry=False): """Set `key` and `value` item in cache. @@ -126,7 +124,6 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False): except Timeout: return False - def __setitem__(self, key, value): """Set `key` and `value` item in cache. @@ -140,7 +137,6 @@ def __setitem__(self, key, value): shard = self._shards[index] shard[key] = value - def touch(self, key, expire=None, retry=False): """Touch `key` in cache and update `expire` time. @@ -161,7 +157,6 @@ def touch(self, key, expire=None, retry=False): except Timeout: return False - def add(self, key, value, expire=None, read=False, tag=None, retry=False): """Add `key` and `value` item to cache. @@ -193,7 +188,6 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False): except Timeout: return False - def incr(self, key, delta=1, default=0, retry=False): """Increment value by delta for item with key. @@ -225,7 +219,6 @@ def incr(self, key, delta=1, default=0, retry=False): except Timeout: return None - def decr(self, key, delta=1, default=0, retry=False): """Decrement value by delta for item with key. @@ -260,9 +253,15 @@ def decr(self, key, delta=1, default=0, retry=False): except Timeout: return None - - def get(self, key, default=None, read=False, expire_time=False, tag=False, - retry=False): + 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`. If database timeout occurs then returns `default` unless `retry` is set @@ -286,7 +285,6 @@ def get(self, key, default=None, read=False, expire_time=False, tag=False, except (Timeout, sqlite3.OperationalError): return default - def __getitem__(self, key): """Return corresponding value for `key` from cache. @@ -301,7 +299,6 @@ def __getitem__(self, key): shard = self._shards[index] return shard[key] - def read(self, key): """Return file handle corresponding to `key` from cache. @@ -315,7 +312,6 @@ def read(self, key): raise KeyError(key) return handle - def __contains__(self, key): """Return `True` if `key` matching item is found in cache. @@ -327,8 +323,9 @@ def __contains__(self, key): shard = self._shards[index] return key in shard - - def pop(self, key, default=None, expire_time=False, tag=False, retry=False): # noqa: E501 + 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`. @@ -354,7 +351,6 @@ def pop(self, key, default=None, expire_time=False, tag=False, retry=False): # except Timeout: return default - def delete(self, key, retry=False): """Delete corresponding item for `key` from cache. @@ -375,7 +371,6 @@ def delete(self, key, retry=False): except Timeout: return False - def __delitem__(self, key): """Delete corresponding item for `key` from cache. @@ -389,7 +384,6 @@ def __delitem__(self, key): shard = self._shards[index] del shard[key] - def check(self, fix=False, retry=False): """Check database and file system consistency. @@ -413,7 +407,6 @@ def check(self, fix=False, retry=False): warnings = (shard.check(fix, retry) for shard in self._shards) return functools.reduce(operator.iadd, warnings, []) - def expire(self, retry=False): """Remove expired items from cache. @@ -426,7 +419,6 @@ def expire(self, retry=False): """ return self._remove('expire', args=(time.time(),), retry=retry) - def create_tag_index(self): """Create tag index on cache database. @@ -438,7 +430,6 @@ def create_tag_index(self): for shard in self._shards: shard.create_tag_index() - def drop_tag_index(self): """Drop tag index on cache database. @@ -448,7 +439,6 @@ def drop_tag_index(self): for shard in self._shards: shard.drop_tag_index() - def evict(self, tag, retry=False): """Remove items with matching `tag` from cache. @@ -462,7 +452,6 @@ def evict(self, tag, retry=False): """ return self._remove('evict', args=(tag,), retry=retry) - def cull(self, retry=False): """Cull items from cache until volume is less than size limit. @@ -475,7 +464,6 @@ def cull(self, retry=False): """ return self._remove('cull', retry=retry) - def clear(self, retry=False): """Remove all items from cache. @@ -488,7 +476,6 @@ def clear(self, retry=False): """ return self._remove('clear', retry=retry) - def _remove(self, name, args=(), retry=False): total = 0 for shard in self._shards: @@ -503,7 +490,6 @@ def _remove(self, name, args=(), retry=False): break return total - def stats(self, enable=True, reset=False): """Return cache statistics hits and misses. @@ -517,7 +503,6 @@ def stats(self, enable=True, reset=False): total_misses = sum(misses for _, misses in results) return total_hits, total_misses - def volume(self): """Return estimated total size of cache on disk. @@ -526,7 +511,6 @@ def volume(self): """ return sum(shard.volume() for shard in self._shards) - def close(self): "Close database connection." for shard in self._shards: @@ -535,40 +519,32 @@ def close(self): self._deques.clear() self._indexes.clear() - def __enter__(self): return self - def __exit__(self, *exception): self.close() - def __getstate__(self): return (self._directory, self._count, self.timeout, type(self.disk)) - def __setstate__(self, state): self.__init__(*state) - def __iter__(self): "Iterate keys in cache including expired items." iterators = (iter(shard) for shard in self._shards) return it.chain.from_iterable(iterators) - def __reversed__(self): "Reverse iterate keys in cache including expired items." iterators = (reversed(shard) for shard in reversed(self._shards)) return it.chain.from_iterable(iterators) - def __len__(self): "Count of items in cache including expired items." return sum(len(shard) for shard in self._shards) - def reset(self, key, value=ENOVAL): """Reset `key` and `value` item from Settings table. @@ -597,7 +573,6 @@ def reset(self, key, value=ENOVAL): break return result - def cache(self, name): """Return Cache with given `name` in subdirectory. @@ -627,7 +602,6 @@ def cache(self, name): _caches[name] = temp return temp - def deque(self, name): """Return Deque with given `name` in subdirectory. @@ -657,7 +631,6 @@ def deque(self, name): _deques[name] = deque return deque - def index(self, name): """Return Index with given `name` in subdirectory. diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 6fc072b..ac5a5b0 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -15,6 +15,7 @@ def _make_compare(seq_op, doc): "Make compare method with Sequence semantics." + def compare(self, that): "Compare method for deque and sequence." if not isinstance(that, Sequence): @@ -70,6 +71,7 @@ class Deque(Sequence): [3, 2, 1, 0, 0, -1, -2, -3] """ + def __init__(self, iterable=(), directory=None): """Initialize deque instance. @@ -83,7 +85,6 @@ def __init__(self, iterable=(), directory=None): self._cache = Cache(directory, eviction_policy='none') self.extend(iterable) - @classmethod def fromcache(cls, cache, iterable=()): """Initialize deque using `cache`. @@ -110,19 +111,16 @@ def fromcache(cls, cache, iterable=()): self.extend(iterable) return self - @property def cache(self): "Cache used by deque." return self._cache - @property def directory(self): "Directory path where deque is stored." return self._cache.directory - def _index(self, index, func): len_self = len(self) @@ -153,7 +151,6 @@ def _index(self, index, func): raise IndexError('deque index out of range') - def __getitem__(self, index): """deque.__getitem__(index) <==> deque[index] @@ -176,7 +173,6 @@ def __getitem__(self, index): """ return self._index(index, self._cache.__getitem__) - def __setitem__(self, index, value): """deque.__setitem__(index, value) <==> deque[index] = value @@ -195,10 +191,11 @@ def __setitem__(self, index, value): :raises IndexError: if index out of range """ + def _set_value(key): return self._cache.__setitem__(key, value) - self._index(index, _set_value) + self._index(index, _set_value) def __delitem__(self, index): """deque.__delitem__(index) <==> del deque[index] @@ -219,7 +216,6 @@ def __delitem__(self, index): """ self._index(index, self._cache.__delitem__) - def __repr__(self): """deque.__repr__() <==> repr(deque) @@ -229,7 +225,6 @@ def __repr__(self): name = type(self).__name__ return '{0}(directory={1!r})'.format(name, self.directory) - __eq__ = _make_compare(op.eq, 'equal to') __ne__ = _make_compare(op.ne, 'not equal to') __lt__ = _make_compare(op.lt, 'less than') @@ -237,7 +232,6 @@ def __repr__(self): __le__ = _make_compare(op.le, 'less than or equal to') __ge__ = _make_compare(op.ge, 'greater than or equal to') - def __iadd__(self, iterable): """deque.__iadd__(iterable) <==> deque += iterable @@ -250,7 +244,6 @@ def __iadd__(self, iterable): self.extend(iterable) return self - def __iter__(self): """deque.__iter__() <==> iter(deque) @@ -265,7 +258,6 @@ def __iter__(self): except KeyError: pass - def __len__(self): """deque.__len__() <==> len(deque) @@ -274,7 +266,6 @@ def __len__(self): """ return len(self._cache) - def __reversed__(self): """deque.__reversed__() <==> reversed(deque) @@ -297,15 +288,12 @@ def __reversed__(self): except KeyError: pass - def __getstate__(self): return self.directory - def __setstate__(self, state): self.__init__(directory=state) - def append(self, value): """Add `value` to back of deque. @@ -321,7 +309,6 @@ def append(self, value): """ self._cache.push(value, retry=True) - def appendleft(self, value): """Add `value` to front of deque. @@ -337,7 +324,6 @@ def appendleft(self, value): """ self._cache.push(value, side='front', retry=True) - def clear(self): """Remove all elements from deque. @@ -351,7 +337,6 @@ def clear(self): """ self._cache.clear(retry=True) - def count(self, value): """Return number of occurrences of `value` in deque. @@ -370,7 +355,6 @@ def count(self, value): """ return sum(1 for item in self if value == item) - def extend(self, iterable): """Extend back side of deque with values from `iterable`. @@ -380,7 +364,6 @@ def extend(self, iterable): for value in iterable: self.append(value) - def extendleft(self, iterable): """Extend front side of deque with value from `iterable`. @@ -395,7 +378,6 @@ def extendleft(self, iterable): for value in iterable: self.appendleft(value) - def peek(self): """Peek at value at back of deque. @@ -422,7 +404,6 @@ def peek(self): raise IndexError('peek from an empty deque') return value - def peekleft(self): """Peek at value at front of deque. @@ -449,7 +430,6 @@ def peekleft(self): raise IndexError('peek from an empty deque') return value - def pop(self): """Remove and return value at back of deque. @@ -476,7 +456,6 @@ def pop(self): raise IndexError('pop from an empty deque') return value - def popleft(self): """Remove and return value at front of deque. @@ -501,7 +480,6 @@ def popleft(self): raise IndexError('pop from an empty deque') return value - def remove(self, value): """Remove first occurrence of `value` in deque. @@ -539,7 +517,6 @@ def remove(self, value): raise ValueError('deque.remove(value): value not in deque') - def reverse(self): """Reverse deque in place. @@ -562,7 +539,6 @@ def reverse(self): del temp rmtree(directory) - def rotate(self, steps=1): """Rotate deque right by `steps`. @@ -611,10 +587,8 @@ def rotate(self, steps=1): else: self.append(value) - __hash__ = None - @contextmanager def transact(self): """Context manager to perform a transaction by locking the deque. @@ -664,6 +638,7 @@ class Index(MutableMapping): ('c', 3) """ + def __init__(self, *args, **kwargs): """Initialize index in directory and update items. @@ -691,7 +666,6 @@ def __init__(self, *args, **kwargs): self._cache = Cache(directory, eviction_policy='none') self.update(*args, **kwargs) - @classmethod def fromcache(cls, cache, *args, **kwargs): """Initialize index using `cache` and update items. @@ -719,19 +693,16 @@ def fromcache(cls, cache, *args, **kwargs): self.update(*args, **kwargs) return self - @property def cache(self): "Cache used by index." return self._cache - @property def directory(self): "Directory path where items are stored." return self._cache.directory - def __getitem__(self, key): """index.__getitem__(key) <==> index[key] @@ -755,7 +726,6 @@ def __getitem__(self, key): """ return self._cache[key] - def __setitem__(self, key, value): """index.__setitem__(key, value) <==> index[key] = value @@ -773,7 +743,6 @@ def __setitem__(self, key, value): """ self._cache[key] = value - def __delitem__(self, key): """index.__delitem__(key) <==> del index[key] @@ -796,7 +765,6 @@ def __delitem__(self, key): """ del self._cache[key] - def setdefault(self, key, default=None): """Set and get value for `key` in index using `default`. @@ -821,7 +789,6 @@ def setdefault(self, key, default=None): except KeyError: _cache.add(key, default, retry=True) - def peekitem(self, last=True): """Peek at key and value item pair in index based on iteration order. @@ -840,7 +807,6 @@ def peekitem(self, last=True): """ return self._cache.peekitem(last, retry=True) - def pop(self, key, default=ENOVAL): """Remove corresponding item for `key` from index and return value. @@ -871,7 +837,6 @@ def pop(self, key, default=ENOVAL): raise KeyError(key) return value - def popitem(self, last=True): """Remove and return item pair. @@ -906,7 +871,6 @@ def popitem(self, last=True): return key, value - def push(self, value, prefix=None, side='back'): """Push `value` onto `side` of queue in index identified by `prefix`. @@ -938,7 +902,6 @@ def push(self, value, prefix=None, side='back'): """ return self._cache.push(value, prefix, side, retry=True) - def pull(self, prefix=None, default=(None, None), side='front'): """Pull key and value item pair from `side` of queue in index. @@ -979,7 +942,6 @@ def pull(self, prefix=None, default=(None, None), side='front'): """ return self._cache.pull(prefix, default, side, retry=True) - def clear(self): """Remove all items from index. @@ -993,7 +955,6 @@ def clear(self): """ self._cache.clear(retry=True) - def __iter__(self): """index.__iter__() <==> iter(index) @@ -1002,7 +963,6 @@ def __iter__(self): """ return iter(self._cache) - def __reversed__(self): """index.__reversed__() <==> reversed(index) @@ -1019,7 +979,6 @@ def __reversed__(self): """ return reversed(self._cache) - def __len__(self): """index.__len__() <==> len(index) @@ -1028,7 +987,6 @@ def __len__(self): """ return len(self._cache) - def keys(self): """Set-like object providing a view of index keys. @@ -1043,7 +1001,6 @@ def keys(self): """ return KeysView(self) - def values(self): """Set-like object providing a view of index values. @@ -1058,7 +1015,6 @@ def values(self): """ return ValuesView(self) - def items(self): """Set-like object providing a view of index items. @@ -1073,18 +1029,14 @@ def items(self): """ return ItemsView(self) - __hash__ = None - def __getstate__(self): return self.directory - def __setstate__(self, state): self.__init__(state) - def __eq__(self, other): """index.__eq__(other) <==> index == other @@ -1118,7 +1070,6 @@ def __eq__(self, other): else: return all(self[key] == other.get(key, ENOVAL) for key in self) - def __ne__(self, other): """index.__ne__(other) <==> index != other @@ -1142,7 +1093,6 @@ def __ne__(self, other): """ return not self == other - def memoize(self, name=None, typed=False): """Memoizing cache decorator. @@ -1199,7 +1149,6 @@ def memoize(self, name=None, typed=False): """ return self._cache.memoize(name, typed) - @contextmanager def transact(self): """Context manager to perform a transaction by locking the index. @@ -1227,7 +1176,6 @@ def transact(self): with self._cache.transact(retry=True): yield - def __repr__(self): """index.__repr__() <==> repr(index) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 85509c7..3acddd8 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -32,6 +32,7 @@ class Averager(object): None """ + def __init__(self, cache, key, expire=None, tag=None): self._cache = cache self._key = key @@ -45,7 +46,10 @@ def add(self, value): total += value count += 1 self._cache.set( - self._key, (total, count), expire=self._expire, tag=self._tag, + self._key, + (total, count), + expire=self._expire, + tag=self._tag, ) def get(self): @@ -71,6 +75,7 @@ class Lock(object): ... pass """ + def __init__(self, cache, key, expire=None, tag=None): self._cache = cache self._key = key @@ -124,6 +129,7 @@ class RLock(object): AssertionError: cannot release un-acquired lock """ + def __init__(self, cache, key, expire=None, tag=None): self._cache = cache self._key = key @@ -141,8 +147,10 @@ def acquire(self): value, count = self._cache.get(self._key, default=(None, 0)) if pid_tid == value or count == 0: self._cache.set( - self._key, (pid_tid, count + 1), - expire=self._expire, tag=self._tag, + self._key, + (pid_tid, count + 1), + expire=self._expire, + tag=self._tag, ) return time.sleep(0.001) @@ -158,8 +166,10 @@ def release(self): is_owned = pid_tid == value and count > 0 assert is_owned, 'cannot release un-acquired lock' self._cache.set( - self._key, (value, count - 1), - expire=self._expire, tag=self._tag, + self._key, + (value, count - 1), + expire=self._expire, + tag=self._tag, ) def __enter__(self): @@ -187,6 +197,7 @@ class BoundedSemaphore(object): AssertionError: cannot release un-acquired semaphore """ + def __init__(self, cache, key, value=1, expire=None, tag=None): self._cache = cache self._key = key @@ -201,8 +212,10 @@ def acquire(self): value = self._cache.get(self._key, default=self._value) if value > 0: self._cache.set( - self._key, value - 1, - expire=self._expire, tag=self._tag, + self._key, + value - 1, + expire=self._expire, + tag=self._tag, ) return time.sleep(0.001) @@ -214,7 +227,10 @@ def release(self): assert self._value > value, 'cannot release un-acquired semaphore' value += 1 self._cache.set( - self._key, value, expire=self._expire, tag=self._tag, + self._key, + value, + expire=self._expire, + tag=self._tag, ) def __enter__(self): @@ -224,8 +240,16 @@ def __exit__(self, *exc_info): self.release() -def throttle(cache, count, seconds, name=None, expire=None, tag=None, - time_func=time.time, sleep_func=time.sleep): +def throttle( + cache, + count, + seconds, + name=None, + expire=None, + tag=None, + time_func=time.time, + sleep_func=time.sleep, +): """Decorator to throttle calls to function. >>> import diskcache, time @@ -242,6 +266,7 @@ def throttle(cache, count, seconds, name=None, expire=None, tag=None, True """ + def decorator(func): rate = count / float(seconds) key = full_name(func) if name is None else name @@ -298,6 +323,7 @@ def barrier(cache, lock_factory, name=None, expire=None, tag=None): >>> pool.terminate() """ + def decorator(func): key = full_name(func) if name is None else name lock = lock_factory(cache, key, expire=expire, tag=tag) @@ -385,7 +411,10 @@ def wrapper(*args, **kwargs): "Wrapper for callable to cache arguments and return values." key = wrapper.__cache_key__(*args, **kwargs) pair, expire_time = cache.get( - key, default=ENOVAL, expire_time=True, retry=True, + key, + default=ENOVAL, + expire_time=True, + retry=True, ) if pair is not ENOVAL: @@ -400,7 +429,10 @@ def wrapper(*args, **kwargs): thread_key = key + (ENOVAL,) thread_added = cache.add( - thread_key, None, expire=delta, retry=True, + thread_key, + None, + expire=delta, + retry=True, ) if thread_added: @@ -409,8 +441,13 @@ def recompute(): with cache: pair = timer(*args, **kwargs) cache.set( - key, pair, expire=expire, tag=tag, retry=True, + key, + pair, + expire=expire, + tag=tag, + retry=True, ) + thread = threading.Thread(target=recompute) thread.daemon = True thread.start() diff --git a/setup.py b/setup.py index f8c465e..2d6daa3 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,10 @@ def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True + def run_tests(self): import tox + errno = tox.cmdline(self.test_args) exit(errno) diff --git a/tests/benchmark_core.py b/tests/benchmark_core.py index 2eeae3b..a048b79 100644 --- a/tests/benchmark_core.py +++ b/tests/benchmark_core.py @@ -32,19 +32,30 @@ import diskcache -caches.append(('diskcache.Cache', diskcache.Cache, ('tmp',), {},)) -caches.append(( - 'diskcache.FanoutCache(shards=4, timeout=1.0)', - diskcache.FanoutCache, - ('tmp',), - {'shards': 4, 'timeout': 1.0} -)) -caches.append(( - 'diskcache.FanoutCache(shards=8, timeout=0.010)', - diskcache.FanoutCache, - ('tmp',), - {'shards': 8, 'timeout': 0.010} -)) +caches.append( + ( + 'diskcache.Cache', + diskcache.Cache, + ('tmp',), + {}, + ) +) +caches.append( + ( + 'diskcache.FanoutCache(shards=4, timeout=1.0)', + diskcache.FanoutCache, + ('tmp',), + {'shards': 4, 'timeout': 1.0}, + ) +) +caches.append( + ( + 'diskcache.FanoutCache(shards=8, timeout=0.010)', + diskcache.FanoutCache, + ('tmp',), + {'shards': 8, 'timeout': 0.010}, + ) +) ############################################################################### @@ -54,12 +65,17 @@ try: import pylibmc - caches.append(( - 'pylibmc.Client', - pylibmc.Client, - (['127.0.0.1'],), - {'binary': True, 'behaviors': {'tcp_nodelay': True, 'ketama': True}}, - )) + caches.append( + ( + 'pylibmc.Client', + pylibmc.Client, + (['127.0.0.1'],), + { + 'binary': True, + 'behaviors': {'tcp_nodelay': True, 'ketama': True}, + }, + ) + ) except ImportError: warnings.warn('skipping pylibmc') @@ -71,12 +87,14 @@ try: import redis - caches.append(( - 'redis.StrictRedis', - redis.StrictRedis, - (), - {'host': 'localhost', 'port': 6379, 'db': 0}, - )) + caches.append( + ( + 'redis.StrictRedis', + redis.StrictRedis, + (), + {'host': 'localhost', 'port': 6379, 'db': 0}, + ) + ) except ImportError: warnings.warn('skipping redis') @@ -84,7 +102,7 @@ def worker(num, kind, args, kwargs): random.seed(num) - time.sleep(0.01) # Let other processes start. + time.sleep(0.01) # Let other processes start. obj = kind(*args, **kwargs) @@ -173,19 +191,31 @@ def dispatch(): formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( - '-p', '--processes', type=int, default=PROCS, + '-p', + '--processes', + type=int, + default=PROCS, help='Number of processes to start', ) parser.add_argument( - '-n', '--operations', type=float, default=OPS, + '-n', + '--operations', + type=float, + default=OPS, help='Number of operations to perform', ) parser.add_argument( - '-r', '--range', type=int, default=RANGE, + '-r', + '--range', + type=int, + default=RANGE, help='Range of keys', ) parser.add_argument( - '-w', '--warmup', type=float, default=WARMUP, + '-w', + '--warmup', + type=float, + default=WARMUP, help='Number of warmup operations before timings', ) diff --git a/tests/benchmark_djangocache.py b/tests/benchmark_djangocache.py index 0512752..0de0424 100644 --- a/tests/benchmark_djangocache.py +++ b/tests/benchmark_djangocache.py @@ -27,6 +27,7 @@ def setup(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings_benchmark') import django + django.setup() @@ -41,7 +42,7 @@ def worker(num, name): timings = co.defaultdict(list) - time.sleep(0.01) # Let other processes start. + time.sleep(0.01) # Let other processes start. for count in range(OPS): key = str(random.randrange(RANGE)).encode('utf-8') @@ -140,19 +141,31 @@ def dispatch(): formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( - '-p', '--processes', type=int, default=PROCS, + '-p', + '--processes', + type=int, + default=PROCS, help='Number of processes to start', ) parser.add_argument( - '-n', '--operations', type=float, default=OPS, + '-n', + '--operations', + type=float, + default=OPS, help='Number of operations to perform', ) parser.add_argument( - '-r', '--range', type=int, default=RANGE, + '-r', + '--range', + type=int, + default=RANGE, help='Range of keys', ) parser.add_argument( - '-w', '--warmup', type=float, default=WARMUP, + '-w', + '--warmup', + type=float, + default=WARMUP, help='Number of warmup operations before timings', ) diff --git a/tests/benchmark_glob.py b/tests/benchmark_glob.py index 82237de..9c23104 100644 --- a/tests/benchmark_glob.py +++ b/tests/benchmark_glob.py @@ -26,11 +26,9 @@ for value in range(count): with open(op.join('tmp', '%s.tmp' % value), 'wb') as writer: pass - + delta = timeit.timeit( - stmt="glob.glob1('tmp', '*.tmp')", - setup='import glob', - number=100 + stmt="glob.glob1('tmp', '*.tmp')", setup='import glob', number=100 ) print(template % (count, secs(delta))) diff --git a/tests/issue_109.py b/tests/issue_109.py index a650b4c..a5355b1 100644 --- a/tests/issue_109.py +++ b/tests/issue_109.py @@ -8,6 +8,7 @@ def main(): import argparse + parser = argparse.ArgumentParser() parser.add_argument('--cache-dir', default='/tmp/test') parser.add_argument('--iterations', type=int, default=100) @@ -31,7 +32,7 @@ def main(): delays.append(diff) # Discard warmup delays, first two iterations. - del delays[:(len(values) * 2)] + del delays[: (len(values) * 2)] # Convert seconds to microseconds. delays = sorted(delay * 1e6 for delay in delays) diff --git a/tests/issue_85.py b/tests/issue_85.py index a52de97..db32046 100644 --- a/tests/issue_85.py +++ b/tests/issue_85.py @@ -26,6 +26,7 @@ def init_django(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') django.setup() from django.core.cache import cache + shard = cache._cache._shards[0] diff --git a/tests/models.py b/tests/models.py index 349fd87..a546822 100644 --- a/tests/models.py +++ b/tests/models.py @@ -10,4 +10,6 @@ def expensive_calculation(): class Poll(models.Model): question = models.CharField(max_length=200) answer = models.CharField(max_length=200) - pub_date = models.DateTimeField('date published', default=expensive_calculation) + pub_date = models.DateTimeField( + 'date published', default=expensive_calculation + ) diff --git a/tests/plot.py b/tests/plot.py index d8d1be0..2f83f14 100644 --- a/tests/plot.py +++ b/tests/plot.py @@ -93,12 +93,17 @@ def make_plot(data, action, save=False, show=False, limit=0.005): bars = [] for pos, (name, color) in enumerate(zip(names, colors)): - bars.append(ax.bar( - [val + pos * width for val in index], - [parse_timing(data[name][action][tick], limit) for tick in ticks], - width, - color=color, - )) + bars.append( + ax.bar( + [val + pos * width for val in index], + [ + parse_timing(data[name][action][tick], limit) + for tick in ticks + ], + width, + color=color, + ) + ) ax.set_ylabel('Time (microseconds)') ax.set_title('"%s" Time vs Percentile' % action) @@ -106,12 +111,14 @@ def make_plot(data, action, save=False, show=False, limit=0.005): ax.set_xticklabels(ticks) box = ax.get_position() - ax.set_position([box.x0, box.y0 + box.height * 0.2, box.width, box.height * 0.8]) + ax.set_position( + [box.x0, box.y0 + box.height * 0.2, box.width, box.height * 0.8] + ) ax.legend( [bar[0] for bar in bars], names, loc='lower center', - bbox_to_anchor=(0.5, -0.25) + bbox_to_anchor=(0.5, -0.25), ) if show: diff --git a/tests/plot_early_recompute.py b/tests/plot_early_recompute.py index bbebc1a..da46e3e 100644 --- a/tests/plot_early_recompute.py +++ b/tests/plot_early_recompute.py @@ -16,6 +16,7 @@ def make_timer(times): """ lock = threading.Lock() + def timer(func): @ft.wraps(func) def wrapper(*args, **kwargs): @@ -24,7 +25,9 @@ def wrapper(*args, **kwargs): pair = start, time.time() with lock: times.append(pair) + return wrapper + return timer @@ -33,9 +36,11 @@ def make_worker(times, delay=0.2): `delay` seconds. """ + @make_timer(times) def worker(): time.sleep(delay) + return worker @@ -44,11 +49,13 @@ def make_repeater(func, total=10, delay=0.01): repeatedly until `total` seconds have elapsed. """ + def repeat(num): start = time.time() while time.time() - start < total: func() time.sleep(delay) + return repeat @@ -62,6 +69,7 @@ def frange(start, stop, step=1e-3): def plot(option, filename, cache_times, worker_times): "Plot concurrent workers and latency." import matplotlib.pyplot as plt + fig, (workers, latency) = plt.subplots(2, sharex=True) fig.suptitle(option) diff --git a/tests/settings_benchmark.py b/tests/settings_benchmark.py index 5b614a5..1724cef 100644 --- a/tests/settings_benchmark.py +++ b/tests/settings_benchmark.py @@ -14,7 +14,7 @@ 'LOCATION': 'redis://127.0.0.1:6379/1', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', - } + }, }, 'filebased': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', @@ -22,7 +22,7 @@ 'OPTIONS': { 'CULL_FREQUENCY': 10, 'MAX_ENTRIES': 1000, - } + }, }, 'locmem': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -30,7 +30,7 @@ 'OPTIONS': { 'CULL_FREQUENCY': 10, 'MAX_ENTRIES': 1000, - } + }, }, 'diskcache': { 'BACKEND': 'diskcache.DjangoCache', diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index 68f2ebd..a36ae4e 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -33,13 +33,17 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) - word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)) + word = u''.join( + random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + ) size = random.randint(1, int(200 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) - word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)).encode('utf-8') + word = u''.join( + random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + ).encode('utf-8') size = random.randint(1, int(200 / 13)) return word * size @@ -49,7 +53,14 @@ def make_float(): def make_object(): return (make_float(),) * random.randint(1, 20) - funcs = [make_int, make_long, make_unicode, make_bytes, make_float, make_object] + funcs = [ + make_int, + make_long, + make_unicode, + make_bytes, + make_float, + make_object, + ] while True: func = random.choice(funcs) @@ -66,13 +77,17 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) - word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)) + word = u''.join( + random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + ) size = random.randint(1, int(2 ** 16 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) - word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)).encode('utf-8') + word = u''.join( + random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + ).encode('utf-8') size = random.randint(1, int(2 ** 16 / 13)) return word * size @@ -82,7 +97,14 @@ def make_float(): def make_object(): return [make_float()] * random.randint(1, int(2e3)) - funcs = [make_int, make_long, make_unicode, make_bytes, make_float, make_object] + funcs = [ + make_int, + make_long, + make_unicode, + make_bytes, + make_float, + make_object, + ] while True: func = random.choice(funcs) @@ -134,7 +156,12 @@ def worker(queue, eviction_policy, processes, threads): stop = time.time() - if action == 'get' and processes == 1 and threads == 1 and EXPIRE is None: + if ( + action == 'get' + and processes == 1 + and threads == 1 + and EXPIRE is None + ): assert result == value if index > WARMUP: @@ -155,8 +182,10 @@ def dispatch(num, eviction_policy, processes, threads): thread_queues = [queue.Queue() for _ in range(threads)] subthreads = [ threading.Thread( - target=worker, args=(thread_queue, eviction_policy, processes, threads) - ) for thread_queue in thread_queues + target=worker, + args=(thread_queue, eviction_policy, processes, threads), + ) + for thread_queue in thread_queues ] for index, triplet in enumerate(process_queue): @@ -201,9 +230,13 @@ def percentile(sequence, percent): return values[pos] -def stress_test(create=True, delete=True, - eviction_policy=u'least-recently-stored', - processes=1, threads=1): +def stress_test( + create=True, + delete=True, + eviction_policy=u'least-recently-stored', + processes=1, + threads=1, +): shutil.rmtree('tmp', ignore_errors=True) if processes == 1: @@ -285,51 +318,84 @@ def stress_test_mp(): formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( - '-n', '--operations', type=float, default=OPERATIONS, + '-n', + '--operations', + type=float, + default=OPERATIONS, help='Number of operations to perform', ) parser.add_argument( - '-g', '--get-average', type=float, default=GET_AVERAGE, + '-g', + '--get-average', + type=float, + default=GET_AVERAGE, help='Expected value of exponential variate used for GET count', ) parser.add_argument( - '-k', '--key-count', type=float, default=KEY_COUNT, - help='Number of unique keys' + '-k', + '--key-count', + type=float, + default=KEY_COUNT, + help='Number of unique keys', ) parser.add_argument( - '-d', '--del-chance', type=float, default=DEL_CHANCE, + '-d', + '--del-chance', + type=float, + default=DEL_CHANCE, help='Likelihood of a key deletion', ) parser.add_argument( - '-w', '--warmup', type=float, default=WARMUP, + '-w', + '--warmup', + type=float, + default=WARMUP, help='Number of warmup operations before timings', ) parser.add_argument( - '-e', '--expire', type=float, default=EXPIRE, + '-e', + '--expire', + type=float, + default=EXPIRE, help='Number of seconds before key expires', ) parser.add_argument( - '-t', '--threads', type=int, default=1, + '-t', + '--threads', + type=int, + default=1, help='Number of threads to start in each process', ) parser.add_argument( - '-p', '--processes', type=int, default=1, + '-p', + '--processes', + type=int, + default=1, help='Number of processes to start', ) parser.add_argument( - '-s', '--seed', type=int, default=0, + '-s', + '--seed', + type=int, + default=0, help='Random seed', ) parser.add_argument( - '--no-create', action='store_false', dest='create', + '--no-create', + action='store_false', + dest='create', help='Do not create operations data', ) parser.add_argument( - '--no-delete', action='store_false', dest='delete', + '--no-delete', + action='store_false', + dest='delete', help='Do not delete operations data', ) parser.add_argument( - '-v', '--eviction-policy', type=unicode, + '-v', + '--eviction-policy', + type=unicode, default=u'least-recently-stored', ) diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index 422e874..fc2de39 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -33,13 +33,17 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) - word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)) + word = u''.join( + random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + ) size = random.randint(1, int(200 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) - word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)).encode('utf-8') + word = u''.join( + random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + ).encode('utf-8') size = random.randint(1, int(200 / 13)) return word * size @@ -49,7 +53,14 @@ def make_float(): def make_object(): return (make_float(),) * random.randint(1, 20) - funcs = [make_int, make_long, make_unicode, make_bytes, make_float, make_object] + funcs = [ + make_int, + make_long, + make_unicode, + make_bytes, + make_float, + make_object, + ] while True: func = random.choice(funcs) @@ -66,13 +77,17 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) - word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)) + word = u''.join( + random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + ) size = random.randint(1, int(2 ** 16 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) - word = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size)).encode('utf-8') + word = u''.join( + random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + ).encode('utf-8') size = random.randint(1, int(2 ** 16 / 13)) return word * size @@ -82,7 +97,14 @@ def make_float(): def make_object(): return [make_float()] * random.randint(1, int(2e3)) - funcs = [make_int, make_long, make_unicode, make_bytes, make_float, make_object] + funcs = [ + make_int, + make_long, + make_unicode, + make_bytes, + make_float, + make_object, + ] while True: func = random.choice(funcs) @@ -129,7 +151,12 @@ def worker(queue, eviction_policy, processes, threads): stop = time.time() - if action == 'get' and processes == 1 and threads == 1 and EXPIRE is None: + if ( + action == 'get' + and processes == 1 + and threads == 1 + and EXPIRE is None + ): assert result == value if index > WARMUP: @@ -147,8 +174,10 @@ def dispatch(num, eviction_policy, processes, threads): thread_queues = [queue.Queue() for _ in range(threads)] subthreads = [ threading.Thread( - target=worker, args=(thread_queue, eviction_policy, processes, threads) - ) for thread_queue in thread_queues + target=worker, + args=(thread_queue, eviction_policy, processes, threads), + ) + for thread_queue in thread_queues ] for index, triplet in enumerate(process_queue): @@ -193,9 +222,13 @@ def percentile(sequence, percent): return values[pos] -def stress_test(create=True, delete=True, - eviction_policy=u'least-recently-stored', - processes=1, threads=1): +def stress_test( + create=True, + delete=True, + eviction_policy=u'least-recently-stored', + processes=1, + threads=1, +): shutil.rmtree('tmp', ignore_errors=True) if processes == 1: @@ -277,51 +310,84 @@ def stress_test_mp(): formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( - '-n', '--operations', type=float, default=OPERATIONS, + '-n', + '--operations', + type=float, + default=OPERATIONS, help='Number of operations to perform', ) parser.add_argument( - '-g', '--get-average', type=float, default=GET_AVERAGE, + '-g', + '--get-average', + type=float, + default=GET_AVERAGE, help='Expected value of exponential variate used for GET count', ) parser.add_argument( - '-k', '--key-count', type=float, default=KEY_COUNT, - help='Number of unique keys' + '-k', + '--key-count', + type=float, + default=KEY_COUNT, + help='Number of unique keys', ) parser.add_argument( - '-d', '--del-chance', type=float, default=DEL_CHANCE, + '-d', + '--del-chance', + type=float, + default=DEL_CHANCE, help='Likelihood of a key deletion', ) parser.add_argument( - '-w', '--warmup', type=float, default=WARMUP, + '-w', + '--warmup', + type=float, + default=WARMUP, help='Number of warmup operations before timings', ) parser.add_argument( - '-e', '--expire', type=float, default=EXPIRE, + '-e', + '--expire', + type=float, + default=EXPIRE, help='Number of seconds before key expires', ) parser.add_argument( - '-t', '--threads', type=int, default=1, + '-t', + '--threads', + type=int, + default=1, help='Number of threads to start in each process', ) parser.add_argument( - '-p', '--processes', type=int, default=1, + '-p', + '--processes', + type=int, + default=1, help='Number of processes to start', ) parser.add_argument( - '-s', '--seed', type=int, default=0, + '-s', + '--seed', + type=int, + default=0, help='Random seed', ) parser.add_argument( - '--no-create', action='store_false', dest='create', + '--no-create', + action='store_false', + dest='create', help='Do not create operations data', ) parser.add_argument( - '--no-delete', action='store_false', dest='delete', + '--no-delete', + action='store_false', + dest='delete', help='Do not delete operations data', ) parser.add_argument( - '-v', '--eviction-policy', type=unicode, + '-v', + '--eviction-policy', + type=unicode, default=u'least-recently-stored', ) diff --git a/tests/test_core.py b/tests/test_core.py index 31b4034..cb4e34f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -26,6 +26,7 @@ pytestmark = pytest.mark.filterwarnings('ignore', category=dc.EmptyDirWarning) + @pytest.fixture def cache(): with dc.Cache() as cache: @@ -161,6 +162,7 @@ def test_close_error(cache): class LocalTest(object): def __init__(self): self._calls = 0 + def __getattr__(self, name): if self._calls: raise AttributeError @@ -303,6 +305,7 @@ def test_get(cache): assert cache.get(0, tag=True) == (0, u'number') assert cache.get(0, expire_time=True, tag=True) == (0, None, u'number') + def test_get_expired_fast_path(cache): assert cache.set(0, 0, expire=0.001) time.sleep(0.01) @@ -642,7 +645,7 @@ def test_check(cache): cache.check() cache.check(fix=True) - assert len(cache.check()) == 0 # Should display no warnings. + assert len(cache.check()) == 0 # Should display no warnings. def test_integrity_check(cache): @@ -653,7 +656,7 @@ def test_integrity_check(cache): with io.open(op.join(cache.directory, 'cache.db'), 'r+b') as writer: writer.seek(52) - writer.write(b'\x00\x01') # Should be 0, change it. + writer.write(b'\x00\x01') # Should be 0, change it. cache = dc.Cache(cache.directory) @@ -1262,8 +1265,8 @@ def test_cull_timeout(cache): def test_key_roundtrip(cache): - key_part_0 = u"part0" - key_part_1 = u"part1" + key_part_0 = u'part0' + key_part_1 = u'part1' to_test = [ (key_part_0, key_part_1), [key_part_0, key_part_1], @@ -1281,6 +1284,7 @@ def test_key_roundtrip(cache): def test_constant(): import diskcache.core + assert repr(diskcache.core.ENOVAL) == 'ENOVAL' diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 84d2f95..5cb0047 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -20,28 +20,43 @@ from django.conf import settings from django.core import management, signals from django.core.cache import ( - DEFAULT_CACHE_ALIAS, CacheKeyWarning, InvalidCacheKey, cache, caches, + DEFAULT_CACHE_ALIAS, + CacheKeyWarning, + InvalidCacheKey, + cache, + caches, ) from django.core.cache.utils import make_template_fragment_key from django.db import close_old_connections, connection, connections from django.http import ( - HttpRequest, HttpResponse, HttpResponseNotModified, StreamingHttpResponse, + HttpRequest, + HttpResponse, + HttpResponseNotModified, + StreamingHttpResponse, ) from django.middleware.cache import ( - CacheMiddleware, FetchFromCacheMiddleware, UpdateCacheMiddleware, + CacheMiddleware, + FetchFromCacheMiddleware, + UpdateCacheMiddleware, ) from django.middleware.csrf import CsrfViewMiddleware from django.template import engines from django.template.context_processors import csrf from django.template.response import TemplateResponse from django.test import ( - RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, + RequestFactory, + SimpleTestCase, + TestCase, + TransactionTestCase, override_settings, ) from django.test.signals import setting_changed from django.utils import timezone, translation from django.utils.cache import ( - get_cache_key, learn_cache_key, patch_cache_control, patch_vary_headers, + get_cache_key, + learn_cache_key, + patch_cache_control, + patch_vary_headers, ) from django.utils.encoding import force_text from django.views.decorators.cache import cache_page @@ -53,6 +68,7 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') import django + django.setup() from .models import Poll, expensive_calculation @@ -81,7 +97,7 @@ def __getstate__(self): class UnpicklableType(object): # Unpicklable using the default pickling protocol on Python 2. - __slots__ = 'a', + __slots__ = ('a',) def custom_key_func(key, key_prefix, version): @@ -110,7 +126,9 @@ def caches_setting_for_tests(base=None, exclude=None, **params): # params -> _caches_setting_base -> base base = base or {} exclude = exclude or set() - setting = {k: base.copy() for k in _caches_setting_base if k not in exclude} + setting = { + k: base.copy() for k in _caches_setting_base if k not in exclude + } for key, cache_params in setting.items(): cache_params.update(_caches_setting_base[key]) cache_params.update(params) @@ -126,15 +144,15 @@ def tearDown(self): def test_simple(self): # Simple cache set/get works - cache.set("key", "value") - self.assertEqual(cache.get("key"), "value") + cache.set('key', 'value') + self.assertEqual(cache.get('key'), 'value') def test_add(self): # A key can be added to a cache - cache.add("addkey1", "value") - result = cache.add("addkey1", "newvalue") + cache.add('addkey1', 'value') + result = cache.add('addkey1', 'newvalue') self.assertFalse(result) - self.assertEqual(cache.get("addkey1"), "value") + self.assertEqual(cache.get('addkey1'), 'value') def test_prefix(self): # Test for same cache key conflicts between shared backend @@ -150,37 +168,41 @@ def test_prefix(self): def test_non_existent(self): """Nonexistent cache keys return as None/default.""" - self.assertIsNone(cache.get("does_not_exist")) - self.assertEqual(cache.get("does_not_exist", "bang!"), "bang!") + self.assertIsNone(cache.get('does_not_exist')) + self.assertEqual(cache.get('does_not_exist', 'bang!'), 'bang!') def test_get_many(self): # Multiple cache keys can be returned using get_many cache.set_many({'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd'}) - self.assertEqual(cache.get_many(['a', 'c', 'd']), {'a': 'a', 'c': 'c', 'd': 'd'}) + self.assertEqual( + cache.get_many(['a', 'c', 'd']), {'a': 'a', 'c': 'c', 'd': 'd'} + ) self.assertEqual(cache.get_many(['a', 'b', 'e']), {'a': 'a', 'b': 'b'}) - self.assertEqual(cache.get_many(iter(['a', 'b', 'e'])), {'a': 'a', 'b': 'b'}) + self.assertEqual( + cache.get_many(iter(['a', 'b', 'e'])), {'a': 'a', 'b': 'b'} + ) def test_delete(self): # Cache keys can be deleted cache.set_many({'key1': 'spam', 'key2': 'eggs'}) - self.assertEqual(cache.get("key1"), "spam") - cache.delete("key1") - self.assertIsNone(cache.get("key1")) - self.assertEqual(cache.get("key2"), "eggs") + self.assertEqual(cache.get('key1'), 'spam') + cache.delete('key1') + self.assertIsNone(cache.get('key1')) + self.assertEqual(cache.get('key2'), 'eggs') def test_has_key(self): # The cache can be inspected for cache keys - cache.set("hello1", "goodbye1") - self.assertTrue(cache.has_key("hello1")) - self.assertFalse(cache.has_key("goodbye1")) - cache.set("no_expiry", "here", None) - self.assertTrue(cache.has_key("no_expiry")) + cache.set('hello1', 'goodbye1') + self.assertTrue(cache.has_key('hello1')) + self.assertFalse(cache.has_key('goodbye1')) + cache.set('no_expiry', 'here', None) + self.assertTrue(cache.has_key('no_expiry')) def test_in(self): # The in operator can be used to inspect cache contents - cache.set("hello2", "goodbye2") - self.assertIn("hello2", cache) - self.assertNotIn("goodbye2", cache) + cache.set('hello2', 'goodbye2') + self.assertIn('hello2', cache) + self.assertNotIn('goodbye2', cache) def test_incr(self): # Cache values can be incremented @@ -219,14 +241,14 @@ def test_data_types(self): 'function': f, 'class': C, } - cache.set("stuff", stuff) - self.assertEqual(cache.get("stuff"), stuff) + cache.set('stuff', stuff) + self.assertEqual(cache.get('stuff'), stuff) def test_cache_read_for_model_instance(self): # Don't want fields with callable as default to be called on cache read expensive_calculation.num_runs = 0 Poll.objects.all().delete() - my_poll = Poll.objects.create(question="Well?") + my_poll = Poll.objects.create(question='Well?') self.assertEqual(Poll.objects.count(), 1) pub_date = my_poll.pub_date cache.set('question', my_poll) @@ -239,7 +261,7 @@ def test_cache_write_for_model_instance_with_deferred(self): # Don't want fields with callable as default to be called on cache write expensive_calculation.num_runs = 0 Poll.objects.all().delete() - Poll.objects.create(question="What?") + Poll.objects.create(question='What?') self.assertEqual(expensive_calculation.num_runs, 1) defer_qs = Poll.objects.all().defer('question') self.assertEqual(defer_qs.count(), 1) @@ -252,7 +274,7 @@ def test_cache_read_for_model_instance_with_deferred(self): # Don't want fields with callable as default to be called on cache read expensive_calculation.num_runs = 0 Poll.objects.all().delete() - Poll.objects.create(question="What?") + Poll.objects.create(question='What?') self.assertEqual(expensive_calculation.num_runs, 1) defer_qs = Poll.objects.all().defer('question') self.assertEqual(defer_qs.count(), 1) @@ -261,7 +283,9 @@ def test_cache_read_for_model_instance_with_deferred(self): runs_before_cache_read = expensive_calculation.num_runs cache.get('deferred_queryset') # We only want the default expensive calculation run on creation and set - self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read) + self.assertEqual( + expensive_calculation.num_runs, runs_before_cache_read + ) def test_expiration(self): # Cache values can be set to expire @@ -270,11 +294,11 @@ def test_expiration(self): cache.set('expire3', 'very quickly', 1) time.sleep(2) - self.assertIsNone(cache.get("expire1")) + self.assertIsNone(cache.get('expire1')) - cache.add("expire2", "newvalue") - self.assertEqual(cache.get("expire2"), "newvalue") - self.assertFalse(cache.has_key("expire3")) + cache.add('expire2', 'newvalue') + self.assertEqual(cache.get('expire2'), 'newvalue') + self.assertFalse(cache.has_key('expire3')) def test_touch(self): # cache.touch() updates the timeout. @@ -299,7 +323,7 @@ def test_unicode(self): 'ascii': 'ascii_value', 'unicode_ascii': 'Iñtërnâtiônàlizætiøn1', 'Iñtërnâtiônàlizætiøn': 'Iñtërnâtiônàlizætiøn2', - 'ascii2': {'x': 1} + 'ascii2': {'x': 1}, } # Test `set` for (key, value) in stuff.items(): @@ -325,6 +349,7 @@ def test_unicode(self): def test_binary_string(self): # Binary strings should be cacheable from zlib import compress, decompress + value = 'value_to_be_compressed' compressed_value = compress(value.encode()) @@ -348,9 +373,9 @@ def test_binary_string(self): def test_set_many(self): # Multiple keys can be set using set_many - cache.set_many({"key1": "spam", "key2": "eggs"}) - self.assertEqual(cache.get("key1"), "spam") - self.assertEqual(cache.get("key2"), "eggs") + cache.set_many({'key1': 'spam', 'key2': 'eggs'}) + self.assertEqual(cache.get('key1'), 'spam') + self.assertEqual(cache.get('key2'), 'eggs') def test_set_many_returns_empty_list_on_success(self): """set_many() returns an empty list when all keys are inserted.""" @@ -359,25 +384,25 @@ def test_set_many_returns_empty_list_on_success(self): def test_set_many_expiration(self): # set_many takes a second ``timeout`` parameter - cache.set_many({"key1": "spam", "key2": "eggs"}, 1) + cache.set_many({'key1': 'spam', 'key2': 'eggs'}, 1) time.sleep(2) - self.assertIsNone(cache.get("key1")) - self.assertIsNone(cache.get("key2")) + self.assertIsNone(cache.get('key1')) + self.assertIsNone(cache.get('key2')) def test_delete_many(self): # Multiple keys can be deleted using delete_many cache.set_many({'key1': 'spam', 'key2': 'eggs', 'key3': 'ham'}) - cache.delete_many(["key1", "key2"]) - self.assertIsNone(cache.get("key1")) - self.assertIsNone(cache.get("key2")) - self.assertEqual(cache.get("key3"), "ham") + cache.delete_many(['key1', 'key2']) + self.assertIsNone(cache.get('key1')) + self.assertIsNone(cache.get('key2')) + self.assertEqual(cache.get('key3'), 'ham') def test_clear(self): # The cache can be emptied using clear cache.set_many({'key1': 'spam', 'key2': 'eggs'}) cache.clear() - self.assertIsNone(cache.get("key1")) - self.assertIsNone(cache.get("key2")) + self.assertIsNone(cache.get('key1')) + self.assertIsNone(cache.get('key2')) def test_long_timeout(self): """ @@ -391,7 +416,10 @@ def test_long_timeout(self): cache.add('key2', 'ham', 60 * 60 * 24 * 30 + 1) self.assertEqual(cache.get('key2'), 'ham') - cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 60 * 60 * 24 * 30 + 1) + cache.set_many( + {'key3': 'sausage', 'key4': 'lobster bisque'}, + 60 * 60 * 24 * 30 + 1, + ) self.assertEqual(cache.get('key3'), 'sausage') self.assertEqual(cache.get('key4'), 'lobster bisque') @@ -437,8 +465,8 @@ def test_zero_timeout(self): def test_float_timeout(self): # Make sure a timeout given as a float doesn't crash anything. - cache.set("key1", "spam", 100.2) - self.assertEqual(cache.get("key1"), "spam") + cache.set('key1', 'spam', 100.2) + self.assertEqual(cache.get('key1'), 'spam') def _perform_cull_test(self, cull_cache, initial_count, final_count): # Create initial cache key entries. This will overflow the cache, @@ -483,7 +511,9 @@ def func(key, *args): def test_invalid_key_characters(self): # memcached doesn't allow whitespace or control characters in keys. key = 'key with spaces and 清' - self._perform_invalid_key_test(key, KEY_ERRORS_WITH_MEMCACHED_MSG % key) + self._perform_invalid_key_test( + key, KEY_ERRORS_WITH_MEMCACHED_MSG % key + ) def test_invalid_key_length(self): # memcached limits key length to 250. @@ -653,43 +683,85 @@ def test_cache_versioning_incr_decr(self): def test_cache_versioning_get_set_many(self): # set, using default version = 1 cache.set_many({'ford1': 37, 'arthur1': 42}) - self.assertEqual(cache.get_many(['ford1', 'arthur1']), {'ford1': 37, 'arthur1': 42}) - self.assertEqual(cache.get_many(['ford1', 'arthur1'], version=1), {'ford1': 37, 'arthur1': 42}) + self.assertEqual( + cache.get_many(['ford1', 'arthur1']), {'ford1': 37, 'arthur1': 42} + ) + self.assertEqual( + cache.get_many(['ford1', 'arthur1'], version=1), + {'ford1': 37, 'arthur1': 42}, + ) self.assertEqual(cache.get_many(['ford1', 'arthur1'], version=2), {}) self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1']), {}) - self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=1), {'ford1': 37, 'arthur1': 42}) - self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=2), {}) + self.assertEqual( + caches['v2'].get_many(['ford1', 'arthur1'], version=1), + {'ford1': 37, 'arthur1': 42}, + ) + self.assertEqual( + caches['v2'].get_many(['ford1', 'arthur1'], version=2), {} + ) # set, default version = 1, but manually override version = 2 cache.set_many({'ford2': 37, 'arthur2': 42}, version=2) self.assertEqual(cache.get_many(['ford2', 'arthur2']), {}) self.assertEqual(cache.get_many(['ford2', 'arthur2'], version=1), {}) - self.assertEqual(cache.get_many(['ford2', 'arthur2'], version=2), {'ford2': 37, 'arthur2': 42}) + self.assertEqual( + cache.get_many(['ford2', 'arthur2'], version=2), + {'ford2': 37, 'arthur2': 42}, + ) - self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2']), {'ford2': 37, 'arthur2': 42}) - self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=1), {}) - self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=2), {'ford2': 37, 'arthur2': 42}) + self.assertEqual( + caches['v2'].get_many(['ford2', 'arthur2']), + {'ford2': 37, 'arthur2': 42}, + ) + self.assertEqual( + caches['v2'].get_many(['ford2', 'arthur2'], version=1), {} + ) + self.assertEqual( + caches['v2'].get_many(['ford2', 'arthur2'], version=2), + {'ford2': 37, 'arthur2': 42}, + ) # v2 set, using default version = 2 caches['v2'].set_many({'ford3': 37, 'arthur3': 42}) self.assertEqual(cache.get_many(['ford3', 'arthur3']), {}) self.assertEqual(cache.get_many(['ford3', 'arthur3'], version=1), {}) - self.assertEqual(cache.get_many(['ford3', 'arthur3'], version=2), {'ford3': 37, 'arthur3': 42}) + self.assertEqual( + cache.get_many(['ford3', 'arthur3'], version=2), + {'ford3': 37, 'arthur3': 42}, + ) - self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3']), {'ford3': 37, 'arthur3': 42}) - self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=1), {}) - self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=2), {'ford3': 37, 'arthur3': 42}) + self.assertEqual( + caches['v2'].get_many(['ford3', 'arthur3']), + {'ford3': 37, 'arthur3': 42}, + ) + self.assertEqual( + caches['v2'].get_many(['ford3', 'arthur3'], version=1), {} + ) + self.assertEqual( + caches['v2'].get_many(['ford3', 'arthur3'], version=2), + {'ford3': 37, 'arthur3': 42}, + ) # v2 set, default version = 2, but manually override version = 1 caches['v2'].set_many({'ford4': 37, 'arthur4': 42}, version=1) - self.assertEqual(cache.get_many(['ford4', 'arthur4']), {'ford4': 37, 'arthur4': 42}) - self.assertEqual(cache.get_many(['ford4', 'arthur4'], version=1), {'ford4': 37, 'arthur4': 42}) + self.assertEqual( + cache.get_many(['ford4', 'arthur4']), {'ford4': 37, 'arthur4': 42} + ) + self.assertEqual( + cache.get_many(['ford4', 'arthur4'], version=1), + {'ford4': 37, 'arthur4': 42}, + ) self.assertEqual(cache.get_many(['ford4', 'arthur4'], version=2), {}) self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4']), {}) - self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=1), {'ford4': 37, 'arthur4': 42}) - self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=2), {}) + self.assertEqual( + caches['v2'].get_many(['ford4', 'arthur4'], version=1), + {'ford4': 37, 'arthur4': 42}, + ) + self.assertEqual( + caches['v2'].get_many(['ford4', 'arthur4'], version=2), {} + ) def test_incr_version(self): cache.set('answer', 42, version=2) @@ -825,7 +897,9 @@ def test_get_or_set_version(self): self.assertIsNone(cache.get('brian', version=3)) def test_get_or_set_racing(self): - with mock.patch('%s.%s' % (settings.CACHES['default']['BACKEND'], 'add')) as cache_add: + with mock.patch( + '%s.%s' % (settings.CACHES['default']['BACKEND'], 'add') + ) as cache_add: # Simulate cache.add() failing to add a value. In that case, the # default value should be returned. cache_add.return_value = False @@ -833,7 +907,6 @@ def test_get_or_set_racing(self): class PicklingSideEffect: - def __init__(self, cache): self.cache = cache self.locked = False @@ -843,11 +916,14 @@ def __getstate__(self): return {} -@override_settings(CACHES=caches_setting_for_tests( - BACKEND='diskcache.DjangoCache', -)) +@override_settings( + CACHES=caches_setting_for_tests( + BACKEND='diskcache.DjangoCache', + ) +) class DiskCacheTests(BaseCacheTests, TestCase): "Specific test cases for diskcache.DjangoCache." + def setUp(self): super().setUp() self.dirname = tempfile.mkdtemp() @@ -868,14 +944,18 @@ def test_ignores_non_cache_files(self): with open(fname, 'w'): os.utime(fname, None) cache.clear() - self.assertTrue(os.path.exists(fname), - 'Expected cache.clear to ignore non cache files') + self.assertTrue( + os.path.exists(fname), + 'Expected cache.clear to ignore non cache files', + ) os.remove(fname) def test_clear_does_not_remove_cache_dir(self): cache.clear() - self.assertTrue(os.path.exists(self.dirname), - 'Expected cache.clear to keep the cache dir') + self.assertTrue( + os.path.exists(self.dirname), + 'Expected cache.clear to keep the cache dir', + ) def test_cache_write_unpicklable_type(self): # This fails if not using the highest pickling protocol on Python 2. @@ -942,7 +1022,9 @@ def test_pop(self): self.assertEqual(cache.pop(0, default=1), 1) self.assertEqual(cache.pop(1, expire_time=True), (1, None)) self.assertEqual(cache.pop(2, tag=True), (2, None)) - self.assertEqual(cache.pop(3, expire_time=True, tag=True), (3, None, None)) + self.assertEqual( + cache.pop(3, expire_time=True, tag=True), (3, None, None) + ) self.assertEqual(cache.pop(4, retry=False), 4) def test_pickle(self): @@ -975,6 +1057,7 @@ def test_index(self): def test_memoize(self): with self.assertRaises(TypeError): + @cache.memoize # <-- Missing parens! def test(): pass diff --git a/tests/test_recipes.py b/tests/test_recipes.py index b26a239..2d53d49 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -31,11 +31,13 @@ def test_averager(cache): def test_rlock(cache): state = {'num': 0} rlock = dc.RLock(cache, 'demo') + def worker(): state['num'] += 1 with rlock: state['num'] += 1 time.sleep(0.1) + with rlock: thread = threading.Thread(target=worker) thread.start() @@ -48,11 +50,13 @@ def worker(): def test_semaphore(cache): state = {'num': 0} semaphore = dc.BoundedSemaphore(cache, 'demo', value=3) + def worker(): state['num'] += 1 with semaphore: state['num'] += 1 time.sleep(0.1) + semaphore.acquire() semaphore.acquire() with semaphore: @@ -68,11 +72,13 @@ def worker(): def test_memoize_stampede(cache): state = {'num': 0} + @dc.memoize_stampede(cache, 0.1) def worker(num): time.sleep(0.01) state['num'] += 1 return num + start = time.time() while (time.time() - start) < 1: worker(100) diff --git a/tests/utils.py b/tests/utils.py index 47da791..504b930 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -96,16 +96,19 @@ def display(name, timings): len_total += len(values) sum_total += sum(values) - print(template % ( - action, - len(values), - len(timings.get(action + '-miss', [])), - secs(percentile(values, 0.5)), - secs(percentile(values, 0.9)), - secs(percentile(values, 0.99)), - secs(percentile(values, 1.0)), - secs(sum(values)), - )) + print( + template + % ( + action, + len(values), + len(timings.get(action + '-miss', [])), + secs(percentile(values, 0.5)), + secs(percentile(values, 0.9)), + secs(percentile(values, 0.99)), + secs(percentile(values, 1.0)), + secs(sum(values)), + ) + ) totals = ('Total', len_total, '', '', '', '', '', secs(sum_total)) print(template % totals) From 3b87dde40b4d0b2a7f96534110b5103ce7c9e1fc Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:27:24 -0800 Subject: [PATCH 453/550] Make imports consistent with isort --- diskcache/__init__.py | 18 ++++++++++++++---- diskcache/djangocache.py | 1 + diskcache/fanout.py | 2 +- diskcache/persistent.py | 12 ++++++++---- setup.py | 1 + tests/benchmark_kv_store.py | 4 ++-- tests/issue_109.py | 1 + tests/issue_85.py | 3 ++- tests/plot.py | 3 ++- tests/plot_early_recompute.py | 3 ++- tests/stress_test_core.py | 3 ++- tests/stress_test_fanout.py | 3 ++- tests/test_core.py | 4 ++-- tests/test_deque.py | 4 ++-- tests/test_djangocache.py | 1 - tests/test_fanout.py | 4 ++-- tests/test_index.py | 4 ++-- tests/test_recipes.py | 7 ++++--- 18 files changed, 50 insertions(+), 28 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 0300123..befed76 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -7,18 +7,28 @@ """ from .core import ( + DEFAULT_SETTINGS, + ENOVAL, + EVICTION_POLICY, + UNKNOWN, Cache, Disk, EmptyDirWarning, JSONDisk, - UnknownFileWarning, Timeout, + UnknownFileWarning, ) -from .core import DEFAULT_SETTINGS, ENOVAL, EVICTION_POLICY, UNKNOWN from .fanout import FanoutCache from .persistent import Deque, Index -from .recipes import Averager, BoundedSemaphore, Lock, RLock -from .recipes import barrier, memoize_stampede, throttle +from .recipes import ( + Averager, + BoundedSemaphore, + Lock, + RLock, + barrier, + memoize_stampede, + throttle, +) __all__ = [ 'Averager', diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 2f1db07..44f673d 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -1,6 +1,7 @@ "Django-compatible disk and file backed cache." from functools import wraps + from django.core.cache.backends.base import BaseCache try: diff --git a/diskcache/fanout.py b/diskcache/fanout.py index cc40c07..e7987b4 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -9,7 +9,7 @@ import tempfile import time -from .core import ENOVAL, DEFAULT_SETTINGS, Cache, Disk, Timeout +from .core import DEFAULT_SETTINGS, ENOVAL, Cache, Disk, Timeout from .persistent import Deque, Index diff --git a/diskcache/persistent.py b/diskcache/persistent.py index ac5a5b0..5ff1939 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -3,14 +3,18 @@ """ import operator as op - from collections import OrderedDict -from collections.abc import MutableMapping, Sequence -from collections.abc import KeysView, ValuesView, ItemsView +from collections.abc import ( + ItemsView, + KeysView, + MutableMapping, + Sequence, + ValuesView, +) from contextlib import contextmanager from shutil import rmtree -from .core import Cache, ENOVAL +from .core import ENOVAL, Cache def _make_compare(seq_op, doc): diff --git a/setup.py b/setup.py index 2d6daa3..5f3cd88 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ from io import open + from setuptools import setup from setuptools.command.test import test as TestCommand diff --git a/tests/benchmark_kv_store.py b/tests/benchmark_kv_store.py index e214f1a..e141a36 100644 --- a/tests/benchmark_kv_store.py +++ b/tests/benchmark_kv_store.py @@ -4,10 +4,10 @@ """ -import diskcache - from IPython import get_ipython +import diskcache + ipython = get_ipython() assert ipython is not None, 'No IPython! Run with $ ipython ...' diff --git a/tests/issue_109.py b/tests/issue_109.py index a5355b1..c10a81f 100644 --- a/tests/issue_109.py +++ b/tests/issue_109.py @@ -3,6 +3,7 @@ """ import time + import diskcache as dc diff --git a/tests/issue_85.py b/tests/issue_85.py index db32046..b325df0 100644 --- a/tests/issue_85.py +++ b/tests/issue_85.py @@ -6,7 +6,6 @@ """ import collections -import django import os import random import shutil @@ -14,6 +13,8 @@ import threading import time +import django + def remove_cache_dir(): print('REMOVING CACHE DIRECTORY') diff --git a/tests/plot.py b/tests/plot.py index 2f83f14..670bf49 100644 --- a/tests/plot.py +++ b/tests/plot.py @@ -7,10 +7,11 @@ import argparse import collections as co -import matplotlib.pyplot as plt import re import sys +import matplotlib.pyplot as plt + def parse_timing(timing, limit): "Parse timing." diff --git a/tests/plot_early_recompute.py b/tests/plot_early_recompute.py index da46e3e..7096ea0 100644 --- a/tests/plot_early_recompute.py +++ b/tests/plot_early_recompute.py @@ -2,13 +2,14 @@ """ -import diskcache as dc import functools as ft import multiprocessing.pool import shutil import threading import time +import diskcache as dc + def make_timer(times): """Make a decorator which accumulates (start, end) in `times` for function diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index a36ae4e..6c48d5c 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -1,7 +1,6 @@ "Stress test diskcache.core.Cache." import collections as co -from diskcache import Cache, UnknownFileWarning, EmptyDirWarning, Timeout import multiprocessing as mp import os import pickle @@ -13,6 +12,8 @@ import time import warnings +from diskcache import Cache, EmptyDirWarning, Timeout, UnknownFileWarning + from .utils import display OPERATIONS = int(1e4) diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index fc2de39..1f29987 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -1,7 +1,6 @@ "Stress test diskcache.core.Cache." import collections as co -from diskcache import FanoutCache, UnknownFileWarning, EmptyDirWarning import multiprocessing as mp import os import pickle @@ -13,6 +12,8 @@ import time import warnings +from diskcache import EmptyDirWarning, FanoutCache, UnknownFileWarning + from .utils import display OPERATIONS = int(1e4) diff --git a/tests/test_core.py b/tests/test_core.py index cb4e34f..6fe50c1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8,7 +8,6 @@ import os import os.path as op import pickle -import pytest import random import shutil import sqlite3 @@ -19,9 +18,10 @@ import time import unittest import warnings - from unittest import mock +import pytest + import diskcache as dc pytestmark = pytest.mark.filterwarnings('ignore', category=dc.EmptyDirWarning) diff --git a/tests/test_deque.py b/tests/test_deque.py index 1ca966d..bfb9e7d 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -2,12 +2,12 @@ import functools as ft import pickle -import pytest import shutil import tempfile - from unittest import mock +import pytest + import diskcache as dc from diskcache.core import ENOVAL diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 5cb0047..2c216fe 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -14,7 +14,6 @@ import time import unittest import warnings - from unittest import mock from django.conf import settings diff --git a/tests/test_fanout.py b/tests/test_fanout.py index aa3c833..effe47d 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -8,7 +8,6 @@ import os import os.path as op import pickle -import pytest import random import shutil import sqlite3 @@ -18,9 +17,10 @@ import threading import time import warnings - from unittest import mock +import pytest + import diskcache as dc warnings.simplefilter('error') diff --git a/tests/test_index.py b/tests/test_index.py index 575558c..8852e99 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -2,13 +2,13 @@ import functools as ft import pickle -import pytest import shutil import sys import tempfile - from unittest import mock +import pytest + import diskcache as dc diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 2d53d49..efaa47c 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -1,13 +1,14 @@ "Test diskcache.recipes." -import diskcache as dc -import pytest import shutil import threading import time - from unittest import mock +import pytest + +import diskcache as dc + @pytest.fixture def cache(): From c40b57facd5d9d167a645cb55a4b69257ef20299 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:50:33 -0800 Subject: [PATCH 454/550] Increase max attributes to 8 --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 3314897..8458ed4 100644 --- a/.pylintrc +++ b/.pylintrc @@ -541,7 +541,7 @@ valid-metaclass-classmethod-first-arg=cls max-args=8 # Maximum number of attributes for a class (see R0902). -max-attributes=7 +max-attributes=8 # Maximum number of boolean expressions in an if statement. max-bool-expr=5 From 58227d18dbdd885fd27ba74a9a34e74fb72222d3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:50:46 -0800 Subject: [PATCH 455/550] Tell mypy to ignore django --- mypy.ini | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..053b283 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,4 @@ +[mypy] + +[mypy-django.*] +ignore_missing_imports = True From db76f111e43ebacc392443b861565bfed14e87b7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:50:58 -0800 Subject: [PATCH 456/550] Remove useless `dataset` target --- README.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/README.rst b/README.rst index 847b2cf..ba2bf3e 100644 --- a/README.rst +++ b/README.rst @@ -314,7 +314,6 @@ Object Relational Mappings (ORM) .. _`Django ORM`: https://docs.djangoproject.com/en/dev/topics/db/ .. _`SQLAlchemy`: https://www.sqlalchemy.org/ .. _`Peewee`: http://docs.peewee-orm.com/ -.. _`dataset`: https://dataset.readthedocs.io/ .. _`SQLObject`: http://sqlobject.org/ .. _`Pony ORM`: https://ponyorm.com/ From b2c0ae421ccdf332698431f1dd29eb6e9936d375 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:51:57 -0800 Subject: [PATCH 457/550] Ignore type errors when setting class attributes --- diskcache/fanout.py | 2 +- diskcache/persistent.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index e7987b4..6a13d6e 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -664,4 +664,4 @@ def index(self, name): return index -FanoutCache.memoize = Cache.memoize +FanoutCache.memoize = Cache.memoize # type: ignore diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 5ff1939..44f9cc7 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -591,7 +591,7 @@ def rotate(self, steps=1): else: self.append(value) - __hash__ = None + __hash__ = None # type: ignore @contextmanager def transact(self): @@ -1033,7 +1033,7 @@ def items(self): """ return ItemsView(self) - __hash__ = None + __hash__ = None # type: ignore def __getstate__(self): return self.directory From 585f47f1084581e8d2fa0aed8ce0dd8a2e0a9162 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:52:58 -0800 Subject: [PATCH 458/550] Add django to deps for sphinx and pylint --- tox.ini | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 6bbe69c..356a576 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,9 @@ commands=doc8 docs allowlist_externals=make changedir=docs commands=make html -deps=sphinx +deps= + django==2.2.* + sphinx [testenv:flake8] commands=flake8 {toxinidir}/setup.py {toxinidir}/diskcache {toxinidir}/tests @@ -48,7 +50,9 @@ deps=mypy [testenv:pylint] commands=pylint {toxinidir}/diskcache -deps=pylint +deps= + django==2.2.* + pylint [testenv:rstcheck] commands=rstcheck {toxinidir}/README.rst From 945ee10ca54871032196a0b468ce17d8e0711dcb Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:53:14 -0800 Subject: [PATCH 459/550] Tell doc8 to ignore docs/_build dir --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 356a576..de47de5 100644 --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,7 @@ deps=blue [testenv:doc8] deps=doc8 -commands=doc8 docs +commands=doc8 docs --ignore-path docs/_build [testenv:docs] allowlist_externals=make From 4bbe73fb251830a2b2e7d78c21a67f2ab9b18804 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:53:36 -0800 Subject: [PATCH 460/550] Update flake8 configs --- tox.ini | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index de47de5..d5f43c0 100644 --- a/tox.ini +++ b/tox.ini @@ -91,7 +91,6 @@ addopts= # ignore=D000 [flake8] -# ignore= -# E124 -# E303 -# W503 +exclude=tests/test_djangocache.py +extend-ignore=E203 +max-line-length=120 From 99f0ca2439f6c98ded51d555658b7d538f09010c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 11:53:46 -0800 Subject: [PATCH 461/550] Flake8 fixes (mostly removing useless module imports) --- tests/benchmark_core.py | 9 ++++----- tests/benchmark_djangocache.py | 10 ++++------ tests/issue_85.py | 2 +- tests/plot.py | 2 +- tests/plot_early_recompute.py | 2 +- tests/settings_benchmark.py | 4 ++-- tests/stress_test_core.py | 7 +++---- tests/stress_test_deque.py | 1 - tests/stress_test_deque_mp.py | 1 - tests/stress_test_fanout.py | 4 +--- tests/test_core.py | 9 ++------- tests/test_deque.py | 5 ----- tests/test_doctest.py | 2 -- tests/test_fanout.py | 9 ++------- tests/test_index.py | 3 --- tests/test_recipes.py | 1 - tests/utils.py | 19 ------------------- 17 files changed, 21 insertions(+), 69 deletions(-) diff --git a/tests/benchmark_core.py b/tests/benchmark_core.py index a048b79..282ce2a 100644 --- a/tests/benchmark_core.py +++ b/tests/benchmark_core.py @@ -12,7 +12,6 @@ import pickle import random import shutil -import sys import time import warnings @@ -30,7 +29,7 @@ # Disk Cache Benchmarks ############################################################################### -import diskcache +import diskcache # noqa caches.append( ( @@ -123,13 +122,13 @@ def worker(num, kind, args, kwargs): start = time.time() result = obj.set(key, value) end = time.time() - miss = result == False + miss = result is False action = 'set' else: start = time.time() result = obj.delete(key) end = time.time() - miss = result == False + miss = result is False action = 'delete' if count > WARMUP: @@ -154,7 +153,7 @@ def dispatch(): try: obj.close() - except: + except Exception: pass processes = [ diff --git a/tests/benchmark_djangocache.py b/tests/benchmark_djangocache.py index 0de0424..9dbbcd7 100644 --- a/tests/benchmark_djangocache.py +++ b/tests/benchmark_djangocache.py @@ -12,9 +12,7 @@ import pickle import random import shutil -import sys import time -import warnings from utils import display @@ -59,13 +57,13 @@ def worker(num, name): start = time.time() result = obj.set(key, value) end = time.time() - miss = result == False + miss = result is False action = 'set' else: start = time.time() result = obj.delete(key) end = time.time() - miss = result == False + miss = result is False action = 'delete' if count > WARMUP: @@ -91,14 +89,14 @@ def prepare(name): try: obj.close() - except: + except Exception: pass def dispatch(): setup() - from django.core.cache import caches + from django.core.cache import caches # noqa for name in ['locmem', 'memcached', 'redis', 'diskcache', 'filebased']: shutil.rmtree('tmp', ignore_errors=True) diff --git a/tests/issue_85.py b/tests/issue_85.py index b325df0..723406b 100644 --- a/tests/issue_85.py +++ b/tests/issue_85.py @@ -109,7 +109,7 @@ def run(statements): shard._sql(statement) if index == 0: values.append(('BEGIN', ident)) - except sqlite3.OperationalError as exc: + except sqlite3.OperationalError: values.append(('ERROR', ident)) diff --git a/tests/plot.py b/tests/plot.py index 670bf49..2138659 100644 --- a/tests/plot.py +++ b/tests/plot.py @@ -48,7 +48,7 @@ def parse_data(infile): if blocks.match(line): try: name = title.match(lines[index + 1]).group(1) - except: + except Exception: index += 1 continue diff --git a/tests/plot_early_recompute.py b/tests/plot_early_recompute.py index 7096ea0..e58f580 100644 --- a/tests/plot_early_recompute.py +++ b/tests/plot_early_recompute.py @@ -22,7 +22,7 @@ def timer(func): @ft.wraps(func) def wrapper(*args, **kwargs): start = time.time() - result = func(*args, **kwargs) + func(*args, **kwargs) pair = start, time.time() with lock: times.append(pair) diff --git a/tests/settings_benchmark.py b/tests/settings_benchmark.py index 1724cef..c734e68 100644 --- a/tests/settings_benchmark.py +++ b/tests/settings_benchmark.py @@ -1,9 +1,9 @@ -from .settings import * +from .settings import * # noqa CACHES = { 'default': { 'BACKEND': 'diskcache.DjangoCache', - 'LOCATION': CACHE_DIR, + 'LOCATION': CACHE_DIR, # noqa }, 'memcached': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index 6c48d5c..6fd0991 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -7,7 +7,6 @@ import queue import random import shutil -import sys import threading import time import warnings @@ -196,7 +195,7 @@ def dispatch(num, eviction_policy, processes, threads): for thread_queue in thread_queues: thread_queue.put(None) - start = time.time() + # start = time.time() for thread in subthreads: thread.start() @@ -204,7 +203,7 @@ def dispatch(num, eviction_policy, processes, threads): for thread in subthreads: thread.join() - stop = time.time() + # stop = time.time() timings = co.defaultdict(list) @@ -396,7 +395,7 @@ def stress_test_mp(): parser.add_argument( '-v', '--eviction-policy', - type=unicode, + type=str, default=u'least-recently-stored', ) diff --git a/tests/stress_test_deque.py b/tests/stress_test_deque.py index 7b3ac2f..845b2c2 100644 --- a/tests/stress_test_deque.py +++ b/tests/stress_test_deque.py @@ -2,7 +2,6 @@ import collections as co import functools as ft -import itertools as it import random import diskcache as dc diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py index db7b5c4..091ff0e 100644 --- a/tests/stress_test_deque_mp.py +++ b/tests/stress_test_deque_mp.py @@ -1,6 +1,5 @@ """Stress test diskcache.persistent.Deque.""" -import functools as ft import itertools as it import multiprocessing as mp import os diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index 1f29987..58708c9 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -1,13 +1,11 @@ "Stress test diskcache.core.Cache." -import collections as co import multiprocessing as mp import os import pickle import queue import random import shutil -import sys import threading import time import warnings @@ -388,7 +386,7 @@ def stress_test_mp(): parser.add_argument( '-v', '--eviction-policy', - type=unicode, + type=str, default=u'least-recently-stored', ) diff --git a/tests/test_core.py b/tests/test_core.py index 6fe50c1..bcc79e1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,22 +1,17 @@ "Test diskcache.core.Cache." -import collections as co import errno -import functools as ft import hashlib import io import os import os.path as op import pickle -import random import shutil import sqlite3 import subprocess as sp -import sys import tempfile import threading import time -import unittest import warnings from unittest import mock @@ -130,7 +125,7 @@ def test_init_makedirs(): with pytest.raises(EnvironmentError): try: with mock.patch('os.makedirs', makedirs): - cache = dc.Cache(cache_dir) + dc.Cache(cache_dir) except EnvironmentError: shutil.rmtree(cache_dir, ignore_errors=True) raise @@ -244,7 +239,7 @@ def test_read(cache): def test_read_keyerror(cache): with pytest.raises(KeyError): - with cache.read(0) as reader: + with cache.read(0): pass diff --git a/tests/test_deque.py b/tests/test_deque.py index bfb9e7d..8113dfe 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -1,6 +1,5 @@ "Test diskcache.persistent.Deque." -import functools as ft import pickle import shutil import tempfile @@ -278,7 +277,3 @@ def test_rotate_indexerror_negative(deque): with mock.patch.object(deque, '_cache', cache): deque.rotate(-1) - - -def test_repr(deque): - assert repr(deque).startswith('Deque(') diff --git a/tests/test_doctest.py b/tests/test_doctest.py index 70fa61c..822d8db 100644 --- a/tests/test_doctest.py +++ b/tests/test_doctest.py @@ -1,6 +1,4 @@ import doctest -import shutil -import sys import diskcache.core import diskcache.djangocache diff --git a/tests/test_fanout.py b/tests/test_fanout.py index effe47d..8918af3 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -1,18 +1,13 @@ "Test diskcache.fanout.FanoutCache." import collections as co -import errno -import functools as ft import hashlib import io import os import os.path as op import pickle -import random import shutil -import sqlite3 import subprocess as sp -import sys import tempfile import threading import time @@ -67,7 +62,7 @@ def test_set_get_delete(cache): for value in range(100): assert cache.delete(value) - assert cache.delete(100) == False + assert cache.delete(100) is False cache.check() @@ -353,7 +348,7 @@ def test_read(cache): def test_read_keyerror(cache): with pytest.raises(KeyError): - with cache.read(0) as reader: + with cache.read(0): pass diff --git a/tests/test_index.py b/tests/test_index.py index 8852e99..27639f7 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,11 +1,8 @@ "Test diskcache.persistent.Index." -import functools as ft import pickle import shutil -import sys import tempfile -from unittest import mock import pytest diff --git a/tests/test_recipes.py b/tests/test_recipes.py index efaa47c..8d13f2b 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -3,7 +3,6 @@ import shutil import threading import time -from unittest import mock import pytest diff --git a/tests/utils.py b/tests/utils.py index 504b930..5b41ce9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -18,7 +18,6 @@ def percentile(sequence, percent): def secs(value): units = ['s ', 'ms', 'us', 'ns'] - pos = 0 if value is None: return ' 0.000ns' @@ -60,24 +59,6 @@ def unmount_ramdisk(dev_path, path): run('rm', '-r', path) -def retry(sql, query): - pause = 0.001 - error = sqlite3.OperationalError - - for _ in range(int(LIMITS[u'timeout'] / pause)): - try: - sql(query).fetchone() - except sqlite3.OperationalError as exc: - error = exc - time.sleep(pause) - else: - break - else: - raise error - - del error - - def display(name, timings): cols = ('Action', 'Count', 'Miss', 'Median', 'P90', 'P99', 'Max', 'Total') template = ' '.join(['%9s'] * len(cols)) From 4b0fc2342d87f59ca1a024d3f74332dd53e3cb9e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 12:16:28 -0800 Subject: [PATCH 462/550] Update pylint and fix code --- .pylintrc | 90 ++++++++++++++++++++++++++------------------ diskcache/core.py | 6 +-- diskcache/fanout.py | 2 +- diskcache/recipes.py | 8 ++-- 4 files changed, 61 insertions(+), 45 deletions(-) diff --git a/.pylintrc b/.pylintrc index 8458ed4..158fe34 100644 --- a/.pylintrc +++ b/.pylintrc @@ -5,6 +5,9 @@ # run arbitrary code. extension-pkg-whitelist= +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS @@ -26,16 +29,13 @@ jobs=1 # complex, nested conditions. limit-inference-results=100 -# List of plugins (as comma separated values of python modules names) to load, +# List of plugins (as comma separated values of python module names) to load, # usually to register additional checkers. load-plugins= # Pickle collected data for later comparisons. persistent=yes -# Specify a configuration file. -#rcfile= - # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. suggestion-mode=yes @@ -139,12 +139,10 @@ disable=print-statement, deprecated-sys-function, exception-escape, comprehension-escape, - no-else-return, no-member, - useless-object-inheritance, + no-else-return, + duplicate-code, inconsistent-return-statements, - ungrouped-imports, - not-callable, # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -155,11 +153,11 @@ enable=c-extension-no-member [REPORTS] -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string @@ -181,7 +179,7 @@ score=yes [REFACTORING] # Maximum number of nested blocks for function / method body -max-nested-blocks=6 +max-nested-blocks=5 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then @@ -192,8 +190,8 @@ never-returning-functions=sys.exit [LOGGING] -# Format style used to check logging format string. `old` means using % -# formatting, while `new` is for `{}` formatting. +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. logging-format-style=old # Logging modules to check that the string format arguments are in logging @@ -206,18 +204,18 @@ logging-modules=logging # Limits count of emitted suggestions for spelling mistakes. max-spelling-suggestions=4 -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package.. +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= -# A path to a file that contains private dictionary; one word per line. +# A path to a file that contains the private dictionary; one word per line. spelling-private-dict-file= -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. spelling-store-unknown-words=no @@ -228,6 +226,9 @@ notes=FIXME, XXX, TODO +# Regular expression of note tags to take in consideration. +#notes-rgx= + [TYPECHECK] @@ -264,7 +265,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It +# and thus existing member attributes cannot be deduced by static analysis). It # supports qualified module names, as well as Unix pattern matching. ignored-modules= @@ -280,6 +281,9 @@ missing-member-hint-distance=1 # showing a hint for a missing member. missing-member-max-choices=1 +# List of decorators that change the signature of a decorated function. +signature-mutators= + [VARIABLES] @@ -330,14 +334,7 @@ indent-string=' ' max-line-length=100 # Maximum number of lines in a module. -max-module-lines=2500 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator +max-module-lines=3000 # Allow the body of a class to be on the same line as the declaration if body # contains single statement. @@ -357,10 +354,10 @@ ignore-comments=yes ignore-docstrings=yes # Ignore imports when computing similarities. -ignore-imports=no +ignore-imports=yes # Minimum lines number of a similarity. -min-similarity-lines=9 +min-similarity-lines=4 [BASIC] @@ -387,6 +384,10 @@ bad-names=foo, tutu, tata +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + # Naming style matching correct class attribute names. class-attribute-naming-style=any @@ -427,6 +428,10 @@ good-names=i, Run, _ +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + # Include a hint for the correct naming format with invalid-name. include-naming-hint=no @@ -474,14 +479,21 @@ variable-naming-style=snake_case [STRING] -# This flag controls whether the implicit-str-concat-in-sequence should -# generate a warning on implicit string concatenation in sequences defined over -# several lines. +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. check-str-concat-over-line-jumps=no [IMPORTS] +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no @@ -512,13 +524,17 @@ known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, - setUp + setUp, + __post_init__ # List of member names, which should be excluded from the protected access # warning. @@ -543,7 +559,7 @@ max-args=8 # Maximum number of attributes for a class (see R0902). max-attributes=8 -# Maximum number of boolean expressions in an if statement. +# Maximum number of boolean expressions in an if statement (see R0916). max-bool-expr=5 # Maximum number of branch for function / method body. diff --git a/diskcache/core.py b/diskcache/core.py index 419c64f..da4d884 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -109,7 +109,7 @@ def __repr__(self): } -class Disk(object): +class Disk: "Cache key and value serialization for SQLite database and files." def __init__(self, directory, min_file_size=0, pickle_protocol=0): @@ -418,7 +418,7 @@ def args_to_key(base, args, kwargs, typed): return key -class Cache(object): +class Cache: "Disk and file backed cache." def __init__(self, directory=None, timeout=60, disk=Disk, **settings): @@ -2138,7 +2138,7 @@ def cull(self, retry=False): select_policy = EVICTION_POLICY[self.eviction_policy]['cull'] if select_policy is None: - return + return 0 select_filename = select_policy.format(fields='filename', now=now) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 6a13d6e..99384a0 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -13,7 +13,7 @@ from .persistent import Deque, Index -class FanoutCache(object): +class FanoutCache: "Cache that shards keys and values." def __init__( diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 3acddd8..8f7bd32 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -12,7 +12,7 @@ from .core import ENOVAL, args_to_key, full_name -class Averager(object): +class Averager: """Recipe for calculating a running average. Sometimes known as "online statistics," the running average maintains the @@ -63,7 +63,7 @@ def pop(self): return None if count == 0 else total / count -class Lock(object): +class Lock: """Recipe for cross-process and cross-thread lock. >>> import diskcache @@ -111,7 +111,7 @@ def __exit__(self, *exc_info): self.release() -class RLock(object): +class RLock: """Recipe for cross-process and cross-thread re-entrant lock. >>> import diskcache @@ -179,7 +179,7 @@ def __exit__(self, *exc_info): self.release() -class BoundedSemaphore(object): +class BoundedSemaphore: """Recipe for cross-process and cross-thread bounded semaphore. >>> import diskcache From c654aeda6a969eeadc5fd81e1de5faab24a832a4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 12:31:18 -0800 Subject: [PATCH 463/550] Update Sphinx and re-gen conf.py --- docs/Makefile | 222 +++-------------------------------------- docs/conf.py | 271 +++++--------------------------------------------- docs/make.bat | 246 ++------------------------------------------- 3 files changed, 46 insertions(+), 693 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index e3bd50b..d4bb2cb 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,216 +1,20 @@ -# Makefile for Sphinx documentation +# Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . BUILDDIR = _build -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help +# Put it first so that "make" without argument is like "make help". help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DiskCache.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DiskCache.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/DiskCache" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DiskCache" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." +.PHONY: help Makefile -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py index 2556188..402d3ad 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,31 +1,33 @@ -# -*- coding: utf-8 -*- +# Configuration file for the Sphinx documentation builder. # -# DiskCache documentation build configuration file, created by -# sphinx-quickstart on Wed Feb 10 20:20:15 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html -import sys -import os +# -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. + +import os +import sys sys.path.insert(0, os.path.abspath('..')) + import diskcache -from diskcache import __version__ -# -- General configuration ------------------------------------------------ -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# -- Project information ----------------------------------------------------- + +project = 'DiskCache' +copyright = '2021, Grant Jenks' +author = 'Grant Jenks' + +# The full version, including alpha/beta/rc tags +release = diskcache.__version__ + + +# -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -38,77 +40,13 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'DiskCache' -copyright = u'2020, Grant Jenks' -author = u'Grant Jenks' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = __version__ -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] -# -- Options for HTML output ---------------------------------------------- +# -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. @@ -130,43 +68,11 @@ 'github_type': 'star', } -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - # Custom sidebar templates, maps document names to template names. html_sidebars = { '**': [ @@ -178,134 +84,5 @@ ] } -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'DiskCacheDoc' - def setup(app): - app.add_stylesheet('custom.css') - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'DiskCache.tex', u'DiskCache Documentation', - u'Grant Jenks', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'diskcache', u'DiskCache Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'DiskCache', u'DiskCache Documentation', - author, 'DiskCache', 'Disk and file backed cache.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False + app.add_css_file('custom.css') diff --git a/docs/make.bat b/docs/make.bat index e1a063b..2119f51 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,62 +1,18 @@ @ECHO OFF +pushd %~dp0 + REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) +set SOURCEDIR=. set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) if "%1" == "" goto help -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul +%SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx @@ -69,195 +25,11 @@ if errorlevel 9009 ( exit /b 1 ) -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\DiskCache.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\DiskCache.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end +popd From d1cf8c528ae21fc8d2dff7afe91299df7e5cd1f7 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 12:32:45 -0800 Subject: [PATCH 464/550] Update copyright year --- LICENSE | 2 +- README.rst | 4 ++-- diskcache/__init__.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index d31985f..ca80a22 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2016-2020 Grant Jenks +Copyright 2016-2021 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/README.rst b/README.rst index ba2bf3e..969ea10 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ DiskCache: Disk Backed Cache `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. -The cloud-based computing of 2020 puts a premium on memory. Gigabytes of empty +The cloud-based computing of 2021 puts a premium on memory. Gigabytes of empty space is left on disks as processes vie for memory. Among these processes is Memcached (and sometimes Redis) which is used as a cache. Wouldn't it be nice to leverage empty disk space for caching? @@ -387,7 +387,7 @@ Reference License ------- -Copyright 2016-2020 Grant Jenks +Copyright 2016-2021 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/diskcache/__init__.py b/diskcache/__init__.py index befed76..c050a17 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -66,4 +66,4 @@ __build__ = 0x050100 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2016-2020 Grant Jenks' +__copyright__ = 'Copyright 2016-2021 Grant Jenks' From 6df650543aa3834af84346aefd4be85b74d993af Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 12:34:12 -0800 Subject: [PATCH 465/550] Update readme badges and CI notes --- README.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 969ea10..b7e1ee2 100644 --- a/README.rst +++ b/README.rst @@ -77,16 +77,16 @@ Features - Thread-safe and process-safe - Supports multiple eviction policies (LRU and LFU included) - Keys support "tag" metadata and eviction -- Developed on Python 3.8 -- Tested on CPython 3.5, 3.6, 3.7, 3.8 +- Developed on Python 3.9 +- Tested on CPython 3.6, 3.7, 3.8, 3.9 - Tested on Linux, Mac OS X, and Windows -- Tested using Travis CI and AppVeyor CI +- Tested using GitHub Actions -.. image:: https://api.travis-ci.org/grantjenks/python-diskcache.svg?branch=master - :target: http://www.grantjenks.com/docs/diskcache/ +.. image:: https://github.com/grantjenks/python-diskcache/workflows/integration/badge.svg + :target: https://github.com/grantjenks/python-diskcache/actions?query=workflow%3Aintegration -.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-diskcache?branch=master&svg=true - :target: http://www.grantjenks.com/docs/diskcache/ +.. image:: https://github.com/grantjenks/python-diskcache/workflows/release/badge.svg + :target: https://github.com/grantjenks/python-diskcache/actions?query=workflow%3Arelease Quickstart ---------- From 72127020640a383149b643933e89c7ebc577cc5f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 13:11:21 -0800 Subject: [PATCH 466/550] Pin jedi for ipython --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a9023a1..2ab91c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ django_redis doc8 flake8 ipython +jedi==0.17.* # Remove after IPython bug fixed. pickleDB pylibmc pylint From e49358a20ee359614a39d94047b27b9cfaf4ab1a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 13:33:58 -0800 Subject: [PATCH 467/550] Skip help() examples when running doctest --- README.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index b7e1ee2..acf6803 100644 --- a/README.rst +++ b/README.rst @@ -99,7 +99,7 @@ You can access documentation in the interpreter with Python's built-in help function:: >>> import diskcache - >>> help(diskcache) + >>> help(diskcache) # doctest: +SKIP The core of `DiskCache`_ is three data types intended for caching. `Cache`_ objects manage a SQLite database and filesystem directory to store key and @@ -107,26 +107,26 @@ value pairs. `FanoutCache`_ provides a sharding layer to utilize multiple caches and `DjangoCache`_ integrates that with `Django`_:: >>> from diskcache import Cache, FanoutCache, DjangoCache - >>> help(Cache) - >>> help(FanoutCache) - >>> help(DjangoCache) + >>> help(Cache) # doctest: +SKIP + >>> help(FanoutCache) # doctest: +SKIP + >>> help(DjangoCache) # doctest: +SKIP Built atop the caching data types, are `Deque`_ and `Index`_ which work as a cross-process, persistent replacements for Python's ``collections.deque`` and ``dict``. These implement the sequence and mapping container base classes:: >>> from diskcache import Deque, Index - >>> help(Deque) - >>> help(Index) + >>> help(Deque) # doctest: +SKIP + >>> help(Index) # doctest: +SKIP Finally, a number of `recipes`_ for cross-process synchronization are provided using an underlying cache. Features like memoization with cache stampede prevention, cross-process locking, and cross-process throttling are available:: >>> from diskcache import memoize_stampede, Lock, throttle - >>> help(memoize_stampede) - >>> help(Lock) - >>> help(throttle) + >>> help(memoize_stampede) # doctest: +SKIP + >>> help(Lock) # doctest: +SKIP + >>> help(throttle) # doctest: +SKIP Python's docstrings are a quick way to get started but not intended as a replacement for the `DiskCache Tutorial`_ and `DiskCache API Reference`_. From 0d41722a030695f4d988ac21383513c52f2aa073 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 13:34:44 -0800 Subject: [PATCH 468/550] Fix configs for pytest --- tox.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tox.ini b/tox.ini index d5f43c0..acf7b22 100644 --- a/tox.ini +++ b/tox.ini @@ -7,10 +7,12 @@ commands=pytest deps= django==2.2.* pytest + pytest-cov pytest-django pytest-xdist setenv= DJANGO_SETTINGS_MODULE=tests.settings + PYTHONPATH={toxinidir} [testenv:blue] commands=blue {toxinidir}/setup.py {toxinidir}/diskcache {toxinidir}/tests @@ -81,6 +83,8 @@ addopts= --cov-report=term-missing --cov=diskcache --doctest-glob="*.rst" + --ignore docs/case-study-web-crawler.rst + --ignore docs/sf-python-2017-meetup-talk.rst --ignore tests/benchmark_core.py --ignore tests/benchmark_djangocache.py --ignore tests/benchmark_glob.py From a5d1c73e38fbc917c5503e2e8bcd9a2f834efc3f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 13:39:33 -0800 Subject: [PATCH 469/550] Add branch coverage and decrease coverage minimum to 96 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index acf7b22..04b7382 100644 --- a/tox.ini +++ b/tox.ini @@ -79,7 +79,7 @@ line_length = 79 addopts= -n auto --cov-branch - --cov-fail-under=100 + --cov-fail-under=96 --cov-report=term-missing --cov=diskcache --doctest-glob="*.rst" From fc38bd97009bd9ce352e8bf01950a2a22b5604b5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 13:43:31 -0800 Subject: [PATCH 470/550] Ignore more .coverage files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c496721..67157b8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ # test files/directories /.cache/ -.coverage +.coverage* .pytest_cache/ /.tox/ From 6b6944750acdaf80c6ce28eb8e7a35320ea3a3c0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 13:57:25 -0800 Subject: [PATCH 471/550] Bump version to 5.2.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index c050a17..d1021cd 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -62,8 +62,8 @@ pass __title__ = 'diskcache' -__version__ = '5.1.0' -__build__ = 0x050100 +__version__ = '5.2.0' +__build__ = 0x050200 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2021 Grant Jenks' From 619eb6904810440ef952ea425421315a7353ba12 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 18:46:28 -0800 Subject: [PATCH 472/550] Install libmemcached-dev for release action --- .github/workflows/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57c68e5..2a07787 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,11 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Install libmemcached-dev + run: | + sudo apt-get update + sudo apt-get install libmemcached-dev + - name: Set up Python uses: actions/setup-python@v2 with: From e1d7c4aaa6729178ca3216f4c8a75b835f963022 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Fri, 22 Jan 2021 18:47:55 -0800 Subject: [PATCH 473/550] Bump version to 5.2.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index d1021cd..428eb30 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -62,8 +62,8 @@ pass __title__ = 'diskcache' -__version__ = '5.2.0' -__build__ = 0x050200 +__version__ = '5.2.1' +__build__ = 0x050201 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2021 Grant Jenks' From 12c1db268d03699de86cf114facd2a5091bfc257 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Thu, 28 Jan 2021 11:11:33 +0200 Subject: [PATCH 474/550] Add Python 3.9 support trove classifier. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5f3cd88..7f0561c 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ def run_tests(self): 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', ), ) From 660e53d3afe5789babcf461e4cf7764e90dcf15a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sat, 30 Jan 2021 12:20:10 -0800 Subject: [PATCH 475/550] Run integration on pull requests --- .github/workflows/integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 7dfc198..0a44f89 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -1,6 +1,6 @@ name: integration -on: [push] +on: [push, pull_request] jobs: From 2ba659ece1cb7a2b99430cc805c5631f79ec9e8a Mon Sep 17 00:00:00 2001 From: Joakim Nordling Date: Sun, 28 Mar 2021 06:59:44 +0300 Subject: [PATCH 476/550] Fix typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index acf6803..c4aa8e9 100644 --- a/README.rst +++ b/README.rst @@ -185,7 +185,7 @@ other projects are shown in the tables below. access. Keys are arbitrary strings, values arbitrary pickle-able objects. * `pickleDB`_ is a lightweight and simple key-value store. It is built upon Python's simplejson module and was inspired by Redis. It is licensed with the - BSD three-caluse license. + BSD three-clause license. .. _`dbm`: https://docs.python.org/3/library/dbm.html .. _`shelve`: https://docs.python.org/3/library/shelve.html From 12400ec12eb32be37c41a4940b545797aae036a6 Mon Sep 17 00:00:00 2001 From: Abhilash Raj Date: Fri, 21 May 2021 14:44:18 -0700 Subject: [PATCH 477/550] Fix the URL to Django documentation for cache. --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 8f8dc0f..58220b2 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -535,7 +535,7 @@ they are guaranteed to be stored in files. The full path is available on the file handle in the `name` attribute. Remember to also include the `Content-Type` header if known. -.. _`Django documentation on caching`: https://docs.djangoproject.com/en/1.9/topics/cache/#the-low-level-cache-api +.. _`Django documentation on caching`: https://docs.djangoproject.com/en/3.2/topics/cache/#the-low-level-cache-api Deque ----- From b460e7409d8d299e208500d640d69e55c1faccec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Gmach?= Date: Mon, 7 Jun 2021 15:11:34 +0200 Subject: [PATCH 478/550] remove leftovers from Travis and AppVeyor Both were removed in favor of GitHub actions. --- docs/conf.py | 1 - tests/stress_test_deque_mp.py | 6 ------ tests/stress_test_index_mp.py | 6 ------ 3 files changed, 13 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 402d3ad..d725198 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,6 @@ 'logo': 'gj-logo.png', 'logo_name': True, 'logo_text_align': 'center', - 'travis_button': True, 'analytics_id': 'UA-19364636-2', 'show_powered_by': False, 'show_related': True, diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py index 091ff0e..d6bb56a 100644 --- a/tests/stress_test_deque_mp.py +++ b/tests/stress_test_deque_mp.py @@ -107,12 +107,6 @@ def stress(seed, deque): def test(status=False): - if os.environ.get('TRAVIS') == 'true': - return - - if os.environ.get('APPVEYOR') == 'True': - return - random.seed(SEED) deque = dc.Deque(range(SIZE)) processes = [] diff --git a/tests/stress_test_index_mp.py b/tests/stress_test_index_mp.py index f8718f0..2d290ec 100644 --- a/tests/stress_test_index_mp.py +++ b/tests/stress_test_index_mp.py @@ -94,12 +94,6 @@ def stress(seed, index): def test(status=False): - if os.environ.get('TRAVIS') == 'true': - return - - if os.environ.get('APPVEYOR') == 'True': - return - random.seed(SEED) index = dc.Index(enumerate(range(KEYS))) processes = [] From d20ddb3b2273dee41e73aa72ba182f4331ccba83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Gmach?= Date: Tue, 8 Jun 2021 11:05:03 +0200 Subject: [PATCH 479/550] remove unused imports --- tests/stress_test_deque_mp.py | 1 - tests/stress_test_index_mp.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/stress_test_deque_mp.py b/tests/stress_test_deque_mp.py index d6bb56a..f3b8a48 100644 --- a/tests/stress_test_deque_mp.py +++ b/tests/stress_test_deque_mp.py @@ -2,7 +2,6 @@ import itertools as it import multiprocessing as mp -import os import random import time diff --git a/tests/stress_test_index_mp.py b/tests/stress_test_index_mp.py index 2d290ec..06ed102 100644 --- a/tests/stress_test_index_mp.py +++ b/tests/stress_test_index_mp.py @@ -2,7 +2,6 @@ import itertools as it import multiprocessing as mp -import os import random import time From d9b9d06614a41beff9ebce9b1bc8038d5540394e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 8 Jun 2021 21:42:54 -0700 Subject: [PATCH 480/550] Ignore pylint's consider-using-with in Disk.fetch --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index da4d884..4d0ae05 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -261,7 +261,7 @@ def fetch(self, mode, filename, value, read): :return: corresponding Python value """ - # pylint: disable=no-self-use,unidiomatic-typecheck + # pylint: disable=no-self-use,unidiomatic-typecheck,consider-using-with if mode == MODE_RAW: return bytes(value) if type(value) is sqlite3.Binary else value elif mode == MODE_BINARY: From fac9ad3ffdc289336b7c280e50ca26712f65f8ea Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 30 Aug 2021 22:07:25 -0700 Subject: [PATCH 481/550] Simplify ENOENT handling around fetch() and remove() --- diskcache/core.py | 53 ++++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 4d0ae05..1c915b1 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -26,14 +26,6 @@ def full_name(func): return func.__module__ + '.' + func.__qualname__ -try: - WindowsError -except NameError: - - class WindowsError(Exception): - "Windows error place-holder on platforms without support." - - class Constant(tuple): "Pretty display of immutable constant." @@ -328,13 +320,10 @@ def remove(self, filename): try: os.remove(full_path) - except WindowsError: + except OSError: + # OSError may occur if two caches attempt to delete the same + # file at the same time. pass - except OSError as error: - if error.errno != errno.ENOENT: - # ENOENT may occur if two caches attempt to delete the same - # file at the same time. - raise class JSONDisk(Disk): @@ -1201,13 +1190,10 @@ def get( try: value = self._disk.fetch(mode, filename, db_value, read) except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - if self.statistics: - sql(cache_miss) - return default - else: - raise + # Key was deleted before we could retrieve result. + if self.statistics: + sql(cache_miss) + return default if self.statistics: sql(cache_hit) @@ -1324,11 +1310,8 @@ def pop( try: value = self._disk.fetch(mode, filename, db_value, False) except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - return default - else: - raise + # Key was deleted before we could retrieve result. + return default finally: if filename is not None: self._disk.remove(filename) @@ -1595,10 +1578,8 @@ def pull( try: value = self._disk.fetch(mode, name, db_value, False) except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - continue - raise + # Key was deleted before we could retrieve result. + continue finally: if name is not None: self._disk.remove(name) @@ -1711,10 +1692,8 @@ def peek( try: value = self._disk.fetch(mode, name, db_value, False) except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - continue - raise + # Key was deleted before we could retrieve result. + continue finally: if name is not None: self._disk.remove(name) @@ -1794,10 +1773,8 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): try: value = self._disk.fetch(mode, name, db_value, False) except IOError as error: - if error.errno == errno.ENOENT: - # Key was deleted before we could retrieve result. - continue - raise + # Key was deleted before we could retrieve result. + continue break if expire_time and tag: From ceff81cde5ecacaf7ff79b50bd90250e988a5db0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 30 Aug 2021 22:11:33 -0700 Subject: [PATCH 482/550] Add doc about IOError --- diskcache/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/core.py b/diskcache/core.py index 1c915b1..efa175b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -251,6 +251,7 @@ def fetch(self, mode, filename, value, read): :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=no-self-use,unidiomatic-typecheck,consider-using-with From aefb2feda735b1f602eee290cac3a1baa95c8c8c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 30 Aug 2021 22:12:36 -0700 Subject: [PATCH 483/550] Add notes about changes to store() and remove() --- diskcache/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index efa175b..2cbcdad 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -187,6 +187,7 @@ def store(self, value, read, key=UNKNOWN): :return: (size, mode, filename, value) tuple for Cache table """ + # TODO: Retry mkdirs!!! # pylint: disable=unidiomatic-typecheck type_value = type(value) min_file_size = self.min_file_size @@ -317,6 +318,7 @@ def remove(self, filename): :param str filename: relative path to file """ + # TODO: Delete dir if empty!!! full_path = op.join(self._directory, filename) try: From 49c5979190bd33b134ab8acfe5b5c95823d567e2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 31 Aug 2021 22:33:14 -0700 Subject: [PATCH 484/550] Update remove to cleanup parent dirs --- diskcache/core.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 2cbcdad..13a1b01 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -309,24 +309,26 @@ def filename(self, key=UNKNOWN, value=UNKNOWN): full_path = op.join(self._directory, filename) return filename, full_path - def remove(self, filename): - """Remove a file given by `filename`. + def remove(self, file_path): + """Remove a file given by `file_path`. - This method is cross-thread and cross-process safe. If an "error no - entry" occurs, it is suppressed. + This method is cross-thread and cross-process safe. If an OSError + occurs, it is suppressed. - :param str filename: relative path to file + :param str file_path: relative path to file """ - # TODO: Delete dir if empty!!! - full_path = op.join(self._directory, filename) + full_path = op.join(self._directory, file_path) + full_dir, _ = op.split(full_path) - try: + # 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) - except OSError: - # OSError may occur if two caches attempt to delete the same - # file at the same time. - pass + + with cl.suppress(OSError): + os.removedirs(full_dir) class JSONDisk(Disk): From 1acab3b7b0809180b7c5b0863751e18662662cc3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 31 Aug 2021 22:33:38 -0700 Subject: [PATCH 485/550] Remove logic from filename() for creating directories --- diskcache/core.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 13a1b01..ac266e3 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -297,14 +297,6 @@ def filename(self, key=UNKNOWN, value=UNKNOWN): hex_name = codecs.encode(os.urandom(16), 'hex').decode('utf-8') sub_dir = op.join(hex_name[:2], hex_name[2:4]) name = hex_name[4:] + '.val' - directory = op.join(self._directory, sub_dir) - - try: - os.makedirs(directory) - except OSError as error: - if error.errno != errno.EEXIST: - raise - filename = op.join(sub_dir, name) full_path = op.join(self._directory, filename) return filename, full_path From b86aa9e0ba840821f2b5bcbc064a1283fd4d3e1e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 31 Aug 2021 22:34:03 -0700 Subject: [PATCH 486/550] Modify store() to create the subdirs when writing the file (1 of 4) --- diskcache/core.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index ac266e3..9040fb4 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -206,9 +206,26 @@ def store(self, value, read, key=UNKNOWN): return 0, MODE_RAW, None, sqlite3.Binary(value) else: filename, full_path = self.filename(key, value) + full_dir, _ = op.split(full_path) - with open(full_path, 'xb') as writer: - writer.write(value) + for count in range(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, 'xb') + except OSError: + if count == 10: + # Give up after 10 tries to open the file. + raise + continue + + with writer: + writer.write(value) + + break return len(value), MODE_BINARY, filename, None elif type_value is str: From 879a65a1932377d017cc187661fd880346459b0d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:02:21 -0700 Subject: [PATCH 487/550] Refactor file writing logic to retry makedirs --- diskcache/core.py | 66 +++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 9040fb4..332276a 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -187,7 +187,6 @@ def store(self, value, read, key=UNKNOWN): :return: (size, mode, filename, value) tuple for Cache table """ - # TODO: Retry mkdirs!!! # pylint: disable=unidiomatic-typecheck type_value = type(value) min_file_size = self.min_file_size @@ -206,46 +205,18 @@ def store(self, value, read, key=UNKNOWN): return 0, MODE_RAW, None, sqlite3.Binary(value) else: filename, full_path = self.filename(key, value) - full_dir, _ = op.split(full_path) - - for count in range(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, 'xb') - except OSError: - if count == 10: - # Give up after 10 tries to open the file. - raise - continue - - with writer: - writer.write(value) - - break - + self._write(full_path, io.BytesIO(value), 'xb') return len(value), MODE_BINARY, filename, None elif type_value is str: filename, full_path = self.filename(key, value) - - with open(full_path, 'x', encoding='UTF-8') as writer: - writer.write(value) - + self._write(full_path, io.StringIO(value), 'x', 'UTF-8') size = op.getsize(full_path) return size, MODE_TEXT, filename, None elif read: - size = 0 reader = ft.partial(value.read, 2 ** 22) filename, full_path = self.filename(key, value) - - with open(full_path, 'xb') as writer: - for chunk in iter(reader, b''): - size += len(chunk) - writer.write(chunk) - + iterator = iter(reader, b'') + size = self._write(full_path, iterator, 'xb') return size, MODE_BINARY, filename, None else: result = pickle.dumps(value, protocol=self.pickle_protocol) @@ -254,11 +225,34 @@ def store(self, value, read, key=UNKNOWN): return 0, MODE_PICKLE, None, sqlite3.Binary(result) else: filename, full_path = self.filename(key, value) + self._write(full_path, io.BytesIO(result), 'xb') + return len(result), MODE_PICKLE, filename, None + + def _write(self, full_path, iterator, mode, encoding=None): + full_dir, _ = op.split(full_path) - with open(full_path, 'xb') as writer: - writer.write(result) + for count in range(1, 11): + with cl.suppress(OSError): + os.makedirs(full_dir) - return len(result), MODE_PICKLE, filename, None + 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 + + break def fetch(self, mode, filename, value, read): """Convert fields `mode`, `filename`, and `value` from Cache table to From 226a5cfd2ec68a726a1f6d535d6bc5c055cf226f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:39:29 -0700 Subject: [PATCH 488/550] Add test for Lock.locked() --- tests/test_recipes.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 8d13f2b..88612cf 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -28,6 +28,26 @@ def test_averager(cache): assert nums.pop() == 9.5 +def test_lock(cache): + state = {'num': 0} + lock = dc.Lock(cache, 'demo') + + def worker(): + state['num'] += 1 + with lock: + assert lock.locked() + state['num'] += 1 + time.sleep(0.1) + + with lock: + thread = threading.Thread(target=worker) + thread.start() + time.sleep(0.1) + assert state['num'] == 1 + thread.join() + assert state['num'] == 2 + + def test_rlock(cache): state = {'num': 0} rlock = dc.RLock(cache, 'demo') From 9d5c0b7ebf43bea02eae968f75a9104a699fba46 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:39:47 -0700 Subject: [PATCH 489/550] Test re-entrancy of "rlock" --- tests/test_recipes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 88612cf..3f330a6 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -55,8 +55,9 @@ def test_rlock(cache): def worker(): state['num'] += 1 with rlock: - state['num'] += 1 - time.sleep(0.1) + with rlock: + state['num'] += 1 + time.sleep(0.1) with rlock: thread = threading.Thread(target=worker) From 14094b31006d2951b64a8b87f2a07bd57efa6525 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:40:23 -0700 Subject: [PATCH 490/550] Delete EACCESS error tests --- tests/test_core.py | 121 --------------------------------------------- 1 file changed, 121 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index bcc79e1..a113443 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -333,26 +333,6 @@ def test_get_expired_slow_path(cache): assert cache.get(0) is None -def test_get_ioerror_slow_path(cache): - cache.reset('eviction_policy', 'least-recently-used') - cache.set(0, 0) - - disk = mock.Mock() - put = mock.Mock() - fetch = mock.Mock() - - disk.put = put - put.side_effect = [(0, True)] - disk.fetch = fetch - io_error = IOError() - io_error.errno = errno.EACCES - fetch.side_effect = io_error - - with mock.patch.object(cache, '_disk', disk): - with pytest.raises(IOError): - cache.get(0) - - def test_pop(cache): assert cache.incr('alpha') == 1 assert cache.pop('alpha') == 1 @@ -396,25 +376,6 @@ def test_pop_ioerror(cache): assert cache.pop(0) is None -def test_pop_ioerror_eacces(cache): - assert cache.set(0, 0) - - disk = mock.Mock() - put = mock.Mock() - fetch = mock.Mock() - - disk.put = put - put.side_effect = [(0, True)] - disk.fetch = fetch - io_error = IOError() - io_error.errno = errno.EACCES - fetch.side_effect = io_error - - with mock.patch.object(cache, '_disk', disk): - with pytest.raises(IOError): - cache.pop(0) - - def test_delete(cache): cache[0] = 0 assert cache.delete(0) @@ -591,29 +552,6 @@ def test_least_frequently_used(cache): assert len(cache.check()) == 0 -def test_filename_error(cache): - func = mock.Mock(side_effect=OSError(errno.EACCES)) - - with mock.patch('os.makedirs', func): - with pytest.raises(OSError): - cache._disk.filename() - - -def test_remove_error(cache): - func = mock.Mock(side_effect=OSError(errno.EACCES)) - - try: - with mock.patch('os.remove', func): - cache._disk.remove('ab/cd/efg.val') - except OSError: - pass - else: - if os.name == 'nt': - pass # File delete errors ignored on Windows. - else: - raise Exception('test_remove_error failed') - - def test_check(cache): blob = b'a' * 2 ** 20 keys = (0, 1, 1234, 56.78, u'hello', b'world', None) @@ -1028,44 +966,6 @@ def test_peek_ioerror(cache): assert value == 0 -def test_pull_ioerror_eacces(cache): - assert cache.push(0) == 500000000000000 - - disk = mock.Mock() - put = mock.Mock() - fetch = mock.Mock() - - disk.put = put - put.side_effect = [(0, True)] - disk.fetch = fetch - io_error = IOError() - io_error.errno = errno.EACCES - fetch.side_effect = io_error - - with mock.patch.object(cache, '_disk', disk): - with pytest.raises(IOError): - cache.pull() - - -def test_peek_ioerror_eacces(cache): - assert cache.push(0) == 500000000000000 - - disk = mock.Mock() - put = mock.Mock() - fetch = mock.Mock() - - disk.put = put - put.side_effect = [(0, True)] - disk.fetch = fetch - io_error = IOError() - io_error.errno = errno.EACCES - fetch.side_effect = io_error - - with mock.patch.object(cache, '_disk', disk): - with pytest.raises(IOError): - cache.peek() - - def test_peekitem_extras(cache): with pytest.raises(KeyError): cache.peekitem() @@ -1117,27 +1017,6 @@ def test_peekitem_ioerror(cache): assert value == 2 -def test_peekitem_ioerror_eacces(cache): - assert cache.set('a', 0) - assert cache.set('b', 1) - assert cache.set('c', 2) - - disk = mock.Mock() - put = mock.Mock() - fetch = mock.Mock() - - disk.put = put - put.side_effect = [(0, True)] - disk.fetch = fetch - io_error = IOError() - io_error.errno = errno.EACCES - fetch.side_effect = io_error - - with mock.patch.object(cache, '_disk', disk): - with pytest.raises(IOError): - cache.peekitem() - - def test_iterkeys(cache): assert list(cache.iterkeys()) == [] From 2f18867705a536d8a6f54404856f029923204f08 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:40:36 -0700 Subject: [PATCH 491/550] Test Cache.memoize() with typed kwargs --- tests/test_core.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index a113443..518f99e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1338,3 +1338,10 @@ def fibrec(num): assert hits2 == (hits1 + count) assert misses2 == misses1 + + +def test_memoize_kwargs(cache): + @cache.memoize(typed=True) + def foo(*args, **kwargs): + return args, kwargs + assert foo(1, 2, 3, a=4, b=5) == ((1, 2, 3), {'a': 4, 'b': 5}) From 094b873e1a780004d6b07dd3ebbb216898d803e4 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:40:53 -0700 Subject: [PATCH 492/550] Test JSONDisk.get by iterating cache --- tests/test_core.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 518f99e..efe3e24 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -89,6 +89,9 @@ def test_custom_disk(): for value in values: assert cache[value] == value + for key, value in zip(cache, values): + assert key == value + shutil.rmtree(cache.directory, ignore_errors=True) From b92f2fd3abaf9c30a921ee7d287f573f2d6546a6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:41:07 -0700 Subject: [PATCH 493/550] Increase coverage to 97% --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 04b7382..926a2b3 100644 --- a/tox.ini +++ b/tox.ini @@ -79,7 +79,7 @@ line_length = 79 addopts= -n auto --cov-branch - --cov-fail-under=96 + --cov-fail-under=97 --cov-report=term-missing --cov=diskcache --doctest-glob="*.rst" From f9503321a78ecffabac670bdd147bb6026af78fa Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:46:16 -0700 Subject: [PATCH 494/550] Add test for cleaning up dirs --- tests/test_core.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index efe3e24..cb44da6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1348,3 +1348,16 @@ def test_memoize_kwargs(cache): def foo(*args, **kwargs): return args, kwargs assert foo(1, 2, 3, a=4, b=5) == ((1, 2, 3), {'a': 4, 'b': 5}) + + +def test_cleanup_dirs(cache): + value = b'\0' * 2**20 + start_count = len(os.listdir(cache.directory)) + for i in range(10): + cache[i] = value + set_count = len(os.listdir(cache.directory)) + assert set_count > start_count + for i in range(10): + del cache[i] + del_count = len(os.listdir(cache.directory)) + assert start_count == del_count From 587f00d97724ef9bd2f66fcfa4935fecfc1f86a3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 6 Sep 2021 22:47:21 -0700 Subject: [PATCH 495/550] Add TODO for testing Disk._write --- tests/test_core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index cb44da6..2d09f94 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1361,3 +1361,11 @@ def test_cleanup_dirs(cache): del cache[i] del_count = len(os.listdir(cache.directory)) assert start_count == del_count + + +# TODO: Add tests for Disk._write +# diskcache/core.py +## Disk._write +# - 234->exit +# - 242-246 +# - 255 From 28aa595a662754e88a0d2665b4a9c5eaeadd93fd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 20:25:34 -0700 Subject: [PATCH 496/550] Add tests for Disk._write --- diskcache/core.py | 2 -- tests/test_core.py | 11 +++++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 332276a..b836e84 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -252,8 +252,6 @@ def _write(self, full_path, iterator, mode, encoding=None): writer.write(chunk) return size - break - def fetch(self, mode, filename, value, read): """Convert fields `mode`, `filename`, and `value` from Cache table to value. diff --git a/tests/test_core.py b/tests/test_core.py index 2d09f94..41cfe51 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1363,9 +1363,8 @@ def test_cleanup_dirs(cache): assert start_count == del_count -# TODO: Add tests for Disk._write -# diskcache/core.py -## Disk._write -# - 234->exit -# - 242-246 -# - 255 +def test_disk_write_os_error(cache): + func = mock.Mock(side_effect=[OSError] * 10) + with mock.patch('diskcache.core.open', func): + with pytest.raises(OSError): + cache[0] = '\0' * 2**20 From a2e461ad93f7fa99a0d861db614cea2f7e521a6c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 20:38:35 -0700 Subject: [PATCH 497/550] Add a pragma "no cover" statements and increase threshold to 98 --- diskcache/__init__.py | 2 +- diskcache/djangocache.py | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 428eb30..b5a6218 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -57,7 +57,7 @@ from .djangocache import DjangoCache # noqa __all__.append('DjangoCache') -except Exception: # pylint: disable=broad-except +except Exception: # pylint: disable=broad-except # pragma: no cover # Django not installed or not setup so ignore. pass diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 44f673d..bf9f4d4 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -6,7 +6,7 @@ try: from django.core.cache.backends.base import DEFAULT_TIMEOUT -except ImportError: +except ImportError: # pragma: no cover # For older versions of Django simply use 300 seconds. DEFAULT_TIMEOUT = 300 diff --git a/tox.ini b/tox.ini index 926a2b3..104a084 100644 --- a/tox.ini +++ b/tox.ini @@ -79,7 +79,7 @@ line_length = 79 addopts= -n auto --cov-branch - --cov-fail-under=97 + --cov-fail-under=98 --cov-report=term-missing --cov=diskcache --doctest-glob="*.rst" From ab1484daf149039a468ed087b5073198bc4f21c1 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 20:42:06 -0700 Subject: [PATCH 498/550] Blue fixes (mostly docstring triple quotes) --- diskcache/__init__.py | 1 - diskcache/cli.py | 2 +- diskcache/core.py | 29 ++++++++++++++--------------- diskcache/djangocache.py | 14 +++++++------- diskcache/fanout.py | 12 ++++++------ diskcache/persistent.py | 13 ++++++------- diskcache/recipes.py | 29 ++++++++++++++--------------- tests/benchmark_core.py | 1 - tests/benchmark_djangocache.py | 2 -- tests/benchmark_glob.py | 2 +- tests/benchmark_incr.py | 5 ++--- tests/benchmark_kv_store.py | 1 - tests/issue_109.py | 1 - tests/issue_85.py | 1 - tests/plot.py | 9 ++++----- tests/plot_early_recompute.py | 5 ++--- tests/stress_test_core.py | 10 +++++----- tests/stress_test_fanout.py | 10 +++++----- tests/test_core.py | 7 ++++--- tests/test_deque.py | 2 +- tests/test_djangocache.py | 4 ++-- tests/test_fanout.py | 2 +- tests/test_index.py | 2 +- tests/test_recipes.py | 2 +- tests/utils.py | 6 +++--- 25 files changed, 80 insertions(+), 92 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index b5a6218..934a3a7 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -3,7 +3,6 @@ ======================= The :doc:`tutorial` provides a helpful walkthrough of most methods. - """ from .core import ( diff --git a/diskcache/cli.py b/diskcache/cli.py index 44bffeb..6a39f60 100644 --- a/diskcache/cli.py +++ b/diskcache/cli.py @@ -1 +1 @@ -"Command line interface to disk cache." +"""Command line interface to disk cache.""" diff --git a/diskcache/core.py b/diskcache/core.py index b836e84..4cbf67f 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1,5 +1,4 @@ """Core disk and file backed cache API. - """ import codecs @@ -22,12 +21,12 @@ def full_name(func): - "Return full name of `func` by adding the module and function name." + """Return full name of `func` by adding the module and function name.""" return func.__module__ + '.' + func.__qualname__ class Constant(tuple): - "Pretty display of immutable constant." + """Pretty display of immutable constant.""" def __new__(cls, name): return tuple.__new__(cls, (name,)) @@ -102,7 +101,7 @@ def __repr__(self): class Disk: - "Cache key and value serialization for SQLite database and files." + """Cache key and value serialization for SQLite database and files.""" def __init__(self, directory, min_file_size=0, pickle_protocol=0): """Initialize disk instance. @@ -333,7 +332,7 @@ def remove(self, file_path): class JSONDisk(Disk): - "Cache key and value using JSON serialization with zlib compression." + """Cache key and value using JSON serialization with zlib compression.""" def __init__(self, directory, compress_level=1, **kwargs): """Initialize JSON disk instance. @@ -374,15 +373,15 @@ def fetch(self, mode, filename, value, read): class Timeout(Exception): - "Database timeout expired." + """Database timeout expired.""" class UnknownFileWarning(UserWarning): - "Warning used by Cache.check for unknown files." + """Warning used by Cache.check for unknown files.""" class EmptyDirWarning(UserWarning): - "Warning used by Cache.check for empty directories." + """Warning used by Cache.check for empty directories.""" def args_to_key(base, args, kwargs, typed): @@ -414,7 +413,7 @@ def args_to_key(base, args, kwargs, typed): class Cache: - "Disk and file backed cache." + """Disk and file backed cache.""" def __init__(self, directory=None, timeout=60, disk=Disk, **settings): """Initialize cache instance. @@ -1859,12 +1858,12 @@ def memoize(self, name=None, typed=False, expire=None, tag=None): raise TypeError('name cannot be callable') def decorator(func): - "Decorator created by memoize() for callable `func`." + """Decorator created by memoize() for callable `func`.""" base = (full_name(func),) if name is None else (name,) @ft.wraps(func) def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." + """Wrapper for callable to cache arguments and return values.""" key = wrapper.__cache_key__(*args, **kwargs) result = self.get(key, default=ENOVAL, retry=True) @@ -1876,7 +1875,7 @@ def wrapper(*args, **kwargs): return result def __cache_key__(*args, **kwargs): - "Make key for cache given function arguments." + """Make key for cache given function arguments.""" return args_to_key(base, args, kwargs, typed) wrapper.__cache_key__ = __cache_key__ @@ -2291,13 +2290,13 @@ def _iter(self, ascending=True): yield _disk_get(key, raw) def __iter__(self): - "Iterate keys in cache including expired items." + """Iterate keys in cache including expired items.""" iterator = self._iter() next(iterator) return iterator def __reversed__(self): - "Reverse iterate keys in cache including expired items." + """Reverse iterate keys in cache including expired items.""" iterator = self._iter(ascending=False) next(iterator) return iterator @@ -2355,7 +2354,7 @@ def __exit__(self, *exception): self.close() def __len__(self): - "Count of items in cache including expired items." + """Count of items in cache including expired items.""" return self.reset('count') def __getstate__(self): diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index bf9f4d4..449f3a0 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -1,4 +1,4 @@ -"Django-compatible disk and file backed cache." +"""Django-compatible disk and file backed cache.""" from functools import wraps @@ -15,7 +15,7 @@ class DjangoCache(BaseCache): - "Django-compatible disk and file backed cache." + """Django-compatible disk and file backed cache.""" def __init__(self, directory, params): """Initialize DjangoCache instance. @@ -344,11 +344,11 @@ def cull(self): return self._cache.cull() def clear(self): - "Remove *all* values from the cache at once." + """Remove *all* values from the cache at once.""" return self._cache.clear() def close(self, **kwargs): - "Close the cache connection." + """Close the cache connection.""" # pylint: disable=unused-argument self._cache.close() @@ -415,12 +415,12 @@ def memoize( raise TypeError('name cannot be callable') def decorator(func): - "Decorator created by memoize() for callable `func`." + """Decorator created by memoize() for callable `func`.""" base = (full_name(func),) if name is None else (name,) @wraps(func) def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." + """Wrapper for callable to cache arguments and return values.""" key = wrapper.__cache_key__(*args, **kwargs) result = self.get(key, ENOVAL, version, retry=True) @@ -444,7 +444,7 @@ def wrapper(*args, **kwargs): return result def __cache_key__(*args, **kwargs): - "Make key for cache given function arguments." + """Make key for cache given function arguments.""" return args_to_key(base, args, kwargs, typed) wrapper.__cache_key__ = __cache_key__ diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 99384a0..dc5240c 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -1,4 +1,4 @@ -"Fanout cache automatically shards keys and values." +"""Fanout cache automatically shards keys and values.""" import contextlib as cl import functools @@ -14,7 +14,7 @@ class FanoutCache: - "Cache that shards keys and values." + """Cache that shards keys and values.""" def __init__( self, directory=None, shards=8, timeout=0.010, disk=Disk, **settings @@ -512,7 +512,7 @@ def volume(self): return sum(shard.volume() for shard in self._shards) def close(self): - "Close database connection." + """Close database connection.""" for shard in self._shards: shard.close() self._caches.clear() @@ -532,17 +532,17 @@ def __setstate__(self, state): self.__init__(*state) def __iter__(self): - "Iterate keys in cache including expired items." + """Iterate keys in cache including expired items.""" iterators = (iter(shard) for shard in self._shards) return it.chain.from_iterable(iterators) def __reversed__(self): - "Reverse iterate keys in cache including expired items." + """Reverse iterate keys in cache including expired items.""" iterators = (reversed(shard) for shard in reversed(self._shards)) return it.chain.from_iterable(iterators) def __len__(self): - "Count of items in cache including expired items." + """Count of items in cache including expired items.""" return sum(len(shard) for shard in self._shards) def reset(self, key, value=ENOVAL): diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 44f9cc7..89c3899 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -1,5 +1,4 @@ """Persistent Data Types - """ import operator as op @@ -18,10 +17,10 @@ def _make_compare(seq_op, doc): - "Make compare method with Sequence semantics." + """Make compare method with Sequence semantics.""" def compare(self, that): - "Compare method for deque and sequence." + """Compare method for deque and sequence.""" if not isinstance(that, Sequence): return NotImplemented @@ -117,12 +116,12 @@ def fromcache(cls, cache, iterable=()): @property def cache(self): - "Cache used by deque." + """Cache used by deque.""" return self._cache @property def directory(self): - "Directory path where deque is stored." + """Directory path where deque is stored.""" return self._cache.directory def _index(self, index, func): @@ -699,12 +698,12 @@ def fromcache(cls, cache, *args, **kwargs): @property def cache(self): - "Cache used by index." + """Cache used by index.""" return self._cache @property def directory(self): - "Directory path where items are stored." + """Directory path where items are stored.""" return self._cache.directory def __getitem__(self, key): diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 8f7bd32..0c02dd7 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -1,5 +1,4 @@ """Disk Cache Recipes - """ import functools @@ -40,7 +39,7 @@ def __init__(self, cache, key, expire=None, tag=None): self._tag = tag def add(self, value): - "Add `value` to average." + """Add `value` to average.""" with self._cache.transact(retry=True): total, count = self._cache.get(self._key, default=(0.0, 0)) total += value @@ -53,12 +52,12 @@ def add(self, value): ) def get(self): - "Get current average or return `None` if count equals zero." + """Get current average or return `None` if count equals zero.""" total, count = self._cache.get(self._key, default=(0.0, 0), retry=True) return None if count == 0 else total / count def pop(self): - "Return current average and delete key." + """Return current average and delete key.""" total, count = self._cache.pop(self._key, default=(0.0, 0), retry=True) return None if count == 0 else total / count @@ -83,7 +82,7 @@ def __init__(self, cache, key, expire=None, tag=None): self._tag = tag def acquire(self): - "Acquire lock using spin-lock algorithm." + """Acquire lock using spin-lock algorithm.""" while True: added = self._cache.add( self._key, @@ -97,11 +96,11 @@ def acquire(self): time.sleep(0.001) def release(self): - "Release lock by deleting key." + """Release lock by deleting key.""" self._cache.delete(self._key, retry=True) def locked(self): - "Return true if the lock is acquired." + """Return true if the lock is acquired.""" return self._key in self._cache def __enter__(self): @@ -137,7 +136,7 @@ def __init__(self, cache, key, expire=None, tag=None): self._tag = tag def acquire(self): - "Acquire lock by incrementing count using spin-lock algorithm." + """Acquire lock by incrementing count using spin-lock algorithm.""" pid = os.getpid() tid = threading.get_ident() pid_tid = '{}-{}'.format(pid, tid) @@ -156,7 +155,7 @@ def acquire(self): time.sleep(0.001) def release(self): - "Release lock by decrementing count." + """Release lock by decrementing count.""" pid = os.getpid() tid = threading.get_ident() pid_tid = '{}-{}'.format(pid, tid) @@ -206,7 +205,7 @@ def __init__(self, cache, key, value=1, expire=None, tag=None): self._tag = tag def acquire(self): - "Acquire semaphore by decrementing value using spin-lock algorithm." + """Acquire semaphore by decrementing value using spin-lock algorithm.""" while True: with self._cache.transact(retry=True): value = self._cache.get(self._key, default=self._value) @@ -221,7 +220,7 @@ def acquire(self): time.sleep(0.001) def release(self): - "Release semaphore by incrementing value." + """Release semaphore by incrementing value.""" with self._cache.transact(retry=True): value = self._cache.get(self._key, default=self._value) assert self._value > value, 'cannot release un-acquired semaphore' @@ -396,11 +395,11 @@ def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1): """ # Caution: Nearly identical code exists in Cache.memoize def decorator(func): - "Decorator created by memoize call for callable." + """Decorator created by memoize call for callable.""" base = (full_name(func),) if name is None else (name,) def timer(*args, **kwargs): - "Time execution of `func` and return result and time delta." + """Time execution of `func` and return result and time delta.""" start = time.time() result = func(*args, **kwargs) delta = time.time() - start @@ -408,7 +407,7 @@ def timer(*args, **kwargs): @functools.wraps(func) def wrapper(*args, **kwargs): - "Wrapper for callable to cache arguments and return values." + """Wrapper for callable to cache arguments and return values.""" key = wrapper.__cache_key__(*args, **kwargs) pair, expire_time = cache.get( key, @@ -459,7 +458,7 @@ def recompute(): return pair[0] def __cache_key__(*args, **kwargs): - "Make key for cache given function arguments." + """Make key for cache given function arguments.""" return args_to_key(base, args, kwargs, typed) wrapper.__cache_key__ = __cache_key__ diff --git a/tests/benchmark_core.py b/tests/benchmark_core.py index 282ce2a..7d64595 100644 --- a/tests/benchmark_core.py +++ b/tests/benchmark_core.py @@ -3,7 +3,6 @@ $ export PYTHONPATH=/Users/grantj/repos/python-diskcache $ python tests/benchmark_core.py -p 1 > tests/timings_core_p1.txt $ python tests/benchmark_core.py -p 8 > tests/timings_core_p8.txt - """ import collections as co diff --git a/tests/benchmark_djangocache.py b/tests/benchmark_djangocache.py index 9dbbcd7..61a80bf 100644 --- a/tests/benchmark_djangocache.py +++ b/tests/benchmark_djangocache.py @@ -2,8 +2,6 @@ $ export PYTHONPATH=/Users/grantj/repos/python-diskcache $ python tests/benchmark_djangocache.py > tests/timings_djangocache.txt - - """ import collections as co diff --git a/tests/benchmark_glob.py b/tests/benchmark_glob.py index 9c23104..7f0bf7c 100644 --- a/tests/benchmark_glob.py +++ b/tests/benchmark_glob.py @@ -1,4 +1,4 @@ -"Benchmark glob.glob1 as used by django.core.cache.backends.filebased." +"""Benchmark glob.glob1 as used by django.core.cache.backends.filebased.""" import os import os.path as op diff --git a/tests/benchmark_incr.py b/tests/benchmark_incr.py index 9c8e2fa..4f758aa 100644 --- a/tests/benchmark_incr.py +++ b/tests/benchmark_incr.py @@ -1,5 +1,4 @@ """Benchmark cache.incr method. - """ import json @@ -16,7 +15,7 @@ def worker(num): - "Rapidly increment key and time operation." + """Rapidly increment key and time operation.""" time.sleep(0.1) # Let other workers start. cache = dc.Cache('tmp') @@ -33,7 +32,7 @@ def worker(num): def main(): - "Run workers and print percentile results." + """Run workers and print percentile results.""" shutil.rmtree('tmp', ignore_errors=True) processes = [ diff --git a/tests/benchmark_kv_store.py b/tests/benchmark_kv_store.py index e141a36..7015470 100644 --- a/tests/benchmark_kv_store.py +++ b/tests/benchmark_kv_store.py @@ -1,7 +1,6 @@ """Benchmarking Key-Value Stores $ python -m IPython tests/benchmark_kv_store.py - """ from IPython import get_ipython diff --git a/tests/issue_109.py b/tests/issue_109.py index c10a81f..a649c58 100644 --- a/tests/issue_109.py +++ b/tests/issue_109.py @@ -1,5 +1,4 @@ """Benchmark for Issue #109 - """ import time diff --git a/tests/issue_85.py b/tests/issue_85.py index 723406b..cb8789b 100644 --- a/tests/issue_85.py +++ b/tests/issue_85.py @@ -2,7 +2,6 @@ $ export PYTHONPATH=`pwd` $ python tests/issue_85.py - """ import collections diff --git a/tests/plot.py b/tests/plot.py index 2138659..fcac0bc 100644 --- a/tests/plot.py +++ b/tests/plot.py @@ -2,7 +2,6 @@ $ export PYTHONPATH=/Users/grantj/repos/python-diskcache $ python tests/plot.py --show tests/timings_core_p1.txt - """ import argparse @@ -14,7 +13,7 @@ def parse_timing(timing, limit): - "Parse timing." + """Parse timing.""" if timing.endswith('ms'): value = float(timing[:-2]) * 1e-3 elif timing.endswith('us'): @@ -26,12 +25,12 @@ def parse_timing(timing, limit): def parse_row(row, line): - "Parse row." + """Parse row.""" return [val.strip() for val in row.match(line).groups()] def parse_data(infile): - "Parse data from `infile`." + """Parse data from `infile`.""" blocks = re.compile(' '.join(['=' * 9] * 8)) dashes = re.compile('^-{79}$') title = re.compile('^Timings for (.*)$') @@ -83,7 +82,7 @@ def parse_data(infile): def make_plot(data, action, save=False, show=False, limit=0.005): - "Make plot." + """Make plot.""" fig, ax = plt.subplots(figsize=(8, 10)) colors = ['#ff7f00', '#377eb8', '#4daf4a', '#984ea3', '#e41a1c'] width = 0.15 diff --git a/tests/plot_early_recompute.py b/tests/plot_early_recompute.py index e58f580..1508c45 100644 --- a/tests/plot_early_recompute.py +++ b/tests/plot_early_recompute.py @@ -1,5 +1,4 @@ """Early Recomputation Measurements - """ import functools as ft @@ -61,14 +60,14 @@ def repeat(num): def frange(start, stop, step=1e-3): - "Generator for floating point values from `start` to `stop` by `step`." + """Generator for floating point values from `start` to `stop` by `step`.""" while start < stop: yield start start += step def plot(option, filename, cache_times, worker_times): - "Plot concurrent workers and latency." + """Plot concurrent workers and latency.""" import matplotlib.pyplot as plt fig, (workers, latency) = plt.subplots(2, sharex=True) diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index 6fd0991..c30fa3f 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -1,4 +1,4 @@ -"Stress test diskcache.core.Cache." +"""Stress test diskcache.core.Cache.""" import collections as co import multiprocessing as mp @@ -292,22 +292,22 @@ def stress_test( def stress_test_lru(): - "Stress test least-recently-used eviction policy." + """Stress test least-recently-used eviction policy.""" stress_test(eviction_policy=u'least-recently-used') def stress_test_lfu(): - "Stress test least-frequently-used eviction policy." + """Stress test least-frequently-used eviction policy.""" stress_test(eviction_policy=u'least-frequently-used') def stress_test_none(): - "Stress test 'none' eviction policy." + """Stress test 'none' eviction policy.""" stress_test(eviction_policy=u'none') def stress_test_mp(): - "Stress test multiple threads and processes." + """Stress test multiple threads and processes.""" stress_test(processes=4, threads=4) diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index 58708c9..d3b67e3 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -1,4 +1,4 @@ -"Stress test diskcache.core.Cache." +"""Stress test diskcache.core.Cache.""" import multiprocessing as mp import os @@ -283,22 +283,22 @@ def stress_test( def stress_test_lru(): - "Stress test least-recently-used eviction policy." + """Stress test least-recently-used eviction policy.""" stress_test(eviction_policy=u'least-recently-used') def stress_test_lfu(): - "Stress test least-frequently-used eviction policy." + """Stress test least-frequently-used eviction policy.""" stress_test(eviction_policy=u'least-frequently-used') def stress_test_none(): - "Stress test 'none' eviction policy." + """Stress test 'none' eviction policy.""" stress_test(eviction_policy=u'none') def stress_test_mp(): - "Stress test multiple threads and processes." + """Stress test multiple threads and processes.""" stress_test(processes=4, threads=4) diff --git a/tests/test_core.py b/tests/test_core.py index 41cfe51..c1e7a4a 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,4 +1,4 @@ -"Test diskcache.core.Cache." +"""Test diskcache.core.Cache.""" import errno import hashlib @@ -1347,11 +1347,12 @@ def test_memoize_kwargs(cache): @cache.memoize(typed=True) def foo(*args, **kwargs): return args, kwargs + assert foo(1, 2, 3, a=4, b=5) == ((1, 2, 3), {'a': 4, 'b': 5}) def test_cleanup_dirs(cache): - value = b'\0' * 2**20 + value = b'\0' * 2 ** 20 start_count = len(os.listdir(cache.directory)) for i in range(10): cache[i] = value @@ -1367,4 +1368,4 @@ def test_disk_write_os_error(cache): func = mock.Mock(side_effect=[OSError] * 10) with mock.patch('diskcache.core.open', func): with pytest.raises(OSError): - cache[0] = '\0' * 2**20 + cache[0] = '\0' * 2 ** 20 diff --git a/tests/test_deque.py b/tests/test_deque.py index 8113dfe..add7714 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -1,4 +1,4 @@ -"Test diskcache.persistent.Deque." +"""Test diskcache.persistent.Deque.""" import pickle import shutil diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 2c216fe..cdaf101 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -100,7 +100,7 @@ class UnpicklableType(object): def custom_key_func(key, key_prefix, version): - "A customized cache key function" + """A customized cache key function""" return 'CUSTOM-' + '-'.join([key_prefix, str(version), key]) @@ -921,7 +921,7 @@ def __getstate__(self): ) ) class DiskCacheTests(BaseCacheTests, TestCase): - "Specific test cases for diskcache.DjangoCache." + """Specific test cases for diskcache.DjangoCache.""" def setUp(self): super().setUp() diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 8918af3..f212fac 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -1,4 +1,4 @@ -"Test diskcache.fanout.FanoutCache." +"""Test diskcache.fanout.FanoutCache.""" import collections as co import hashlib diff --git a/tests/test_index.py b/tests/test_index.py index 27639f7..742daf3 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,4 +1,4 @@ -"Test diskcache.persistent.Index." +"""Test diskcache.persistent.Index.""" import pickle import shutil diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 3f330a6..ae74459 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -1,4 +1,4 @@ -"Test diskcache.recipes." +"""Test diskcache.recipes.""" import shutil import threading diff --git a/tests/utils.py b/tests/utils.py index 5b41ce9..38e5d33 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -32,7 +32,7 @@ def secs(value): def run(*args): - "Run command, print output, and return output." + """Run command, print output, and return output.""" print('utils$', *args) result = sp.check_output(args) print(result) @@ -40,7 +40,7 @@ def run(*args): def mount_ramdisk(size, path): - "Mount RAM disk at `path` with `size` in bytes." + """Mount RAM disk at `path` with `size` in bytes.""" sectors = size / 512 os.makedirs(path) @@ -53,7 +53,7 @@ def mount_ramdisk(size, path): def unmount_ramdisk(dev_path, path): - "Unmount RAM disk with `dev_path` and `path`." + """Unmount RAM disk with `dev_path` and `path`.""" run('umount', path) run('diskutil', 'eject', dev_path) run('rm', '-r', path) From 72bbd73d29184e095df248efd48c31eeeb8e992c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 20:58:43 -0700 Subject: [PATCH 499/550] Pylint fixes --- diskcache/core.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 4cbf67f..2e946ad 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -46,25 +46,25 @@ def __repr__(self): MODE_PICKLE = 4 DEFAULT_SETTINGS = { - u'statistics': 0, # False - u'tag_index': 0, # False - u'eviction_policy': u'least-recently-stored', - u'size_limit': 2 ** 30, # 1gb - u'cull_limit': 10, - u'sqlite_auto_vacuum': 1, # FULL - u'sqlite_cache_size': 2 ** 13, # 8,192 pages - u'sqlite_journal_mode': u'wal', - u'sqlite_mmap_size': 2 ** 26, # 64mb - u'sqlite_synchronous': 1, # NORMAL - u'disk_min_file_size': 2 ** 15, # 32kb - u'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, + 'statistics': 0, # False + 'tag_index': 0, # False + 'eviction_policy': 'least-recently-stored', + 'size_limit': 2 ** 30, # 1gb + 'cull_limit': 10, + 'sqlite_auto_vacuum': 1, # FULL + 'sqlite_cache_size': 2 ** 13, # 8,192 pages + 'sqlite_journal_mode': 'wal', + 'sqlite_mmap_size': 2 ** 26, # 64mb + 'sqlite_synchronous': 1, # NORMAL + 'disk_min_file_size': 2 ** 15, # 32kb + 'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, } METADATA = { - u'count': 0, - u'size': 0, - u'hits': 0, - u'misses': 0, + 'count': 0, + 'size': 0, + 'hits': 0, + 'misses': 0, } EVICTION_POLICY = { @@ -1194,7 +1194,7 @@ def get( try: value = self._disk.fetch(mode, filename, db_value, read) - except IOError as error: + except IOError: # Key was deleted before we could retrieve result. if self.statistics: sql(cache_miss) @@ -1314,7 +1314,7 @@ def pop( try: value = self._disk.fetch(mode, filename, db_value, False) - except IOError as error: + except IOError: # Key was deleted before we could retrieve result. return default finally: @@ -1582,7 +1582,7 @@ def pull( try: value = self._disk.fetch(mode, name, db_value, False) - except IOError as error: + except IOError: # Key was deleted before we could retrieve result. continue finally: @@ -1696,7 +1696,7 @@ def peek( try: value = self._disk.fetch(mode, name, db_value, False) - except IOError as error: + except IOError: # Key was deleted before we could retrieve result. continue finally: @@ -1777,7 +1777,7 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): try: value = self._disk.fetch(mode, name, db_value, False) - except IOError as error: + except IOError: # Key was deleted before we could retrieve result. continue break @@ -1911,7 +1911,7 @@ def check(self, fix=False, retry=False): rows = sql('PRAGMA integrity_check').fetchall() - if len(rows) != 1 or rows[0][0] != u'ok': + if len(rows) != 1 or rows[0][0] != 'ok': for (message,) in rows: warnings.warn(message) From 3e87128d4154acf90bd64c6630b1200108dc1ed2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 21:04:34 -0700 Subject: [PATCH 500/550] Disable no-self-use in Disk._write --- diskcache/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/core.py b/diskcache/core.py index 2e946ad..251c5a2 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -228,6 +228,7 @@ def store(self, value, read, key=UNKNOWN): return len(result), MODE_PICKLE, filename, None def _write(self, full_path, iterator, mode, encoding=None): + # pylint: disable=no-self-use full_dir, _ = op.split(full_path) for count in range(1, 11): From fbc537a7138330652d0d42aa09caa5531746b302 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 21:51:30 -0700 Subject: [PATCH 501/550] Add `ignore` to memoize() --- diskcache/core.py | 10 +++++++--- diskcache/djangocache.py | 4 +++- diskcache/persistent.py | 5 +++-- diskcache/recipes.py | 5 +++-- tests/test_core.py | 13 +++++++++++++ 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 251c5a2..4035293 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -385,19 +385,22 @@ class EmptyDirWarning(UserWarning): """Warning used by Cache.check for empty directories.""" -def args_to_key(base, args, kwargs, typed): +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 not in ignore) key = base + args if kwargs: + kwargs = {key: val for key, val in kwargs.items() if key not in ignore} key += (ENOVAL,) sorted_items = sorted(kwargs.items()) @@ -1792,7 +1795,7 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): else: return key, value - def memoize(self, name=None, typed=False, expire=None, tag=None): + def memoize(self, name=None, typed=False, expire=None, tag=None, ignore=()): """Memoizing cache decorator. Decorator to wrap callable with memoizing function using cache. @@ -1851,6 +1854,7 @@ def memoize(self, name=None, typed=False, expire=None, tag=None): :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 """ @@ -1877,7 +1881,7 @@ def wrapper(*args, **kwargs): def __cache_key__(*args, **kwargs): """Make key for cache given function arguments.""" - return args_to_key(base, args, kwargs, typed) + return args_to_key(base, args, kwargs, typed, ignore) wrapper.__cache_key__ = __cache_key__ return wrapper diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 449f3a0..347a613 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -373,6 +373,7 @@ def memoize( version=None, typed=False, tag=None, + ignore=(), ): """Memoizing cache decorator. @@ -407,6 +408,7 @@ def memoize( :param int version: key version number (default None, cache parameter) :param bool typed: cache different types separately (default False) :param str tag: text to associate with arguments (default None) + :param set ignore: positional or keyword args to ignore (default ()) :return: callable decorator """ @@ -445,7 +447,7 @@ def wrapper(*args, **kwargs): def __cache_key__(*args, **kwargs): """Make key for cache given function arguments.""" - return args_to_key(base, args, kwargs, typed) + return args_to_key(base, args, kwargs, typed, ignore) wrapper.__cache_key__ = __cache_key__ return wrapper diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 89c3899..9b5939b 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -1096,7 +1096,7 @@ def __ne__(self, other): """ return not self == other - def memoize(self, name=None, typed=False): + def memoize(self, name=None, typed=False, ignore=()): """Memoizing cache decorator. Decorator to wrap callable with memoizing function using cache. @@ -1147,10 +1147,11 @@ def memoize(self, name=None, typed=False): :param str name: name given for callable (default None, automatic) :param bool typed: cache different types separately (default False) + :param set ignore: positional or keyword args to ignore (default ()) :return: callable decorator """ - return self._cache.memoize(name, typed) + return self._cache.memoize(name, typed, ignore) @contextmanager def transact(self): diff --git a/diskcache/recipes.py b/diskcache/recipes.py index 0c02dd7..b345560 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -337,7 +337,7 @@ def wrapper(*args, **kwargs): return decorator -def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1): +def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1, ignore=()): """Memoizing cache decorator with cache stampede protection. Cache stampedes are a type of system overload that can occur when parallel @@ -390,6 +390,7 @@ def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1): :param str name: name given for callable (default None, automatic) :param bool typed: cache different types separately (default False) :param str tag: text to associate with arguments (default None) + :param set ignore: positional or keyword args to ignore (default ()) :return: callable decorator """ @@ -459,7 +460,7 @@ def recompute(): def __cache_key__(*args, **kwargs): """Make key for cache given function arguments.""" - return args_to_key(base, args, kwargs, typed) + return args_to_key(base, args, kwargs, typed, ignore) wrapper.__cache_key__ = __cache_key__ return wrapper diff --git a/tests/test_core.py b/tests/test_core.py index c1e7a4a..0a1fca6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1369,3 +1369,16 @@ def test_disk_write_os_error(cache): with mock.patch('diskcache.core.open', func): with pytest.raises(OSError): cache[0] = '\0' * 2 ** 20 + + +def test_memoize_ignore(cache): + + @cache.memoize(ignore={1, 'arg1'}) + def test(*args, **kwargs): + return args, kwargs + + cache.stats(enable=True) + assert test('a', 'b', 'c', arg0='d', arg1='e', arg2='f') + assert test('a', 'w', 'c', arg0='d', arg1='x', arg2='f') + assert test('a', 'y', 'c', arg0='d', arg1='z', arg2='f') + assert cache.stats() == (2, 1) From bd800aa069ad6bc15aa3f6fec84ae92d2f644de5 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 21:52:05 -0700 Subject: [PATCH 502/550] Fixes for blue --- diskcache/core.py | 4 +++- diskcache/recipes.py | 4 +++- tests/test_core.py | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index 4035293..d7f707b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1795,7 +1795,9 @@ def peekitem(self, last=True, expire_time=False, tag=False, retry=False): else: return key, value - def memoize(self, name=None, typed=False, expire=None, tag=None, ignore=()): + def memoize( + self, name=None, typed=False, expire=None, tag=None, ignore=() + ): """Memoizing cache decorator. Decorator to wrap callable with memoizing function using cache. diff --git a/diskcache/recipes.py b/diskcache/recipes.py index b345560..b5af6dd 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -337,7 +337,9 @@ def wrapper(*args, **kwargs): return decorator -def memoize_stampede(cache, expire, name=None, typed=False, tag=None, beta=1, ignore=()): +def memoize_stampede( + cache, expire, name=None, typed=False, tag=None, beta=1, ignore=() +): """Memoizing cache decorator with cache stampede protection. Cache stampedes are a type of system overload that can occur when parallel diff --git a/tests/test_core.py b/tests/test_core.py index 0a1fca6..b3d3d66 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1372,7 +1372,6 @@ def test_disk_write_os_error(cache): def test_memoize_ignore(cache): - @cache.memoize(ignore={1, 'arg1'}) def test(*args, **kwargs): return args, kwargs From 606d8f2b12e8022e6d5b509d7c41439c4242043d Mon Sep 17 00:00:00 2001 From: Abhinav Omprakash <55880260+AbhinavOmprakash@users.noreply.github.com> Date: Fri, 11 Jun 2021 09:54:54 +0530 Subject: [PATCH 503/550] Fixes #201 added github repo to project_urls --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7f0561c..b29a463 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,12 @@ def run_tests(self): long_description=readme, author='Grant Jenks', author_email='contact@grantjenks.com', - url='http://www.grantjenks.com/docs/diskcache/', + url='http://www.grantjenks.com/docs/diskcache/', + project_urls = { + 'Documentation':'http://www.grantjenks.com/docs/diskcache/', + 'Source':'https://github.com/grantjenks/python-diskcache', + 'Tracker':'https://github.com/grantjenks/python-diskcache/issues', + 'Funding':'https://gumroad.com/l/diskcache',} license='Apache 2.0', packages=['diskcache'], tests_require=['tox'], From c22d3ee59ee28bd58aec59182d375b1eccd191f2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 22:03:57 -0700 Subject: [PATCH 504/550] Fixup formatting for project urls --- setup.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index b29a463..b49d9c8 100644 --- a/setup.py +++ b/setup.py @@ -29,12 +29,13 @@ def run_tests(self): long_description=readme, author='Grant Jenks', author_email='contact@grantjenks.com', - url='http://www.grantjenks.com/docs/diskcache/', - project_urls = { - 'Documentation':'http://www.grantjenks.com/docs/diskcache/', - 'Source':'https://github.com/grantjenks/python-diskcache', - 'Tracker':'https://github.com/grantjenks/python-diskcache/issues', - 'Funding':'https://gumroad.com/l/diskcache',} + url='http://www.grantjenks.com/docs/diskcache/', + project_urls={ + 'Documentation': 'http://www.grantjenks.com/docs/diskcache/', + 'Funding': 'https://gum.co/diskcache', + 'Source': 'https://github.com/grantjenks/python-diskcache', + 'Tracker': 'https://github.com/grantjenks/python-diskcache/issues', + }, license='Apache 2.0', packages=['diskcache'], tests_require=['tox'], From d55a50ee083784afa9c85e14e41c4a2d132f3111 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 22:15:50 -0700 Subject: [PATCH 505/550] Stop using ENOVAL in args_to_key() --- diskcache/core.py | 3 +-- tests/test_core.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index d7f707b..fb343be 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -397,11 +397,10 @@ def args_to_key(base, args, kwargs, typed, ignore): """ args = tuple(arg for index, arg in enumerate(args) if index not in ignore) - key = base + args + key = base + args + (None,) if kwargs: kwargs = {key: val for key, val in kwargs.items() if key not in ignore} - key += (ENOVAL,) sorted_items = sorted(kwargs.items()) for item in sorted_items: diff --git a/tests/test_core.py b/tests/test_core.py index b3d3d66..55ca962 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -92,6 +92,8 @@ def test_custom_disk(): for key, value in zip(cache, values): assert key == value + test_memoize_iter(cache) + shutil.rmtree(cache.directory, ignore_errors=True) @@ -1381,3 +1383,17 @@ def test(*args, **kwargs): assert test('a', 'w', 'c', arg0='d', arg1='x', arg2='f') assert test('a', 'y', 'c', arg0='d', arg1='z', arg2='f') assert cache.stats() == (2, 1) + + +def test_memoize_iter(cache): + @cache.memoize() + def test(*args, **kwargs): + return sum(args) + sum(kwargs.values()) + + cache.clear() + assert test(1, 2, 3) + assert test(a=1, b=2, c=3) + assert test(-1, 0, 1, a=1, b=2, c=3) + assert len(cache) == 3 + for key in cache: + assert cache[key] == 6 From b10b3866f4d64ea197e3fb40c0ebe25aa32900d6 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 22:29:21 -0700 Subject: [PATCH 506/550] Add caveat about inconsistent pickles --- docs/tutorial.rst | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 58220b2..3191af6 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -863,8 +863,17 @@ protocol`_ is not used. Neither the `__hash__` nor `__eq__` methods are used for lookups. Instead lookups depend on the serialization method defined by :class:`Disk ` objects. For strings, bytes, integers, and floats, equality matches Python's definition. But large integers and all other -types will be converted to bytes using pickling and the bytes representation -will define equality. +types will be converted to bytes and the bytes representation will define +equality. + +The default :class:`diskcache.Disk` serialization uses pickling for both keys +and values. Unfortunately, pickling produces inconsistencies sometimes when +applied to container data types like tuples. Two equal tuples may serialize to +different bytes objects using pickle. The likelihood of differences is reduced +by using `pickletools.optimize` but still inconsistencies occur (`#54`_). The +inconsistent serialized pickle values is particularly problematic when applied +to the key in the cache. Consider using an alternative Disk type, like +:class:`JSONDisk `, for consistent serialization of keys. SQLite is used to synchronize database access between threads and processes and as such inherits all SQLite caveats. Most notably SQLite is `not recommended`_ @@ -898,6 +907,7 @@ does not account the size of directories themselves or other filesystem metadata. If directory count or size is a concern then consider implementing an alternative :class:`Disk `. +.. _`#54`: https://github.com/grantjenks/python-diskcache/issues/54 .. _`hash protocol`: https://docs.python.org/library/functions.html#hash .. _`not recommended`: https://www.sqlite.org/faq.html#q5 .. _`performs poorly`: https://www.pythonanywhere.com/forums/topic/1847/ From 3ad6c0e4365ef93e60a75aee75740e6551e279f9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 13 Sep 2021 22:40:26 -0700 Subject: [PATCH 507/550] Bug Fix: Use "ignore" keyword argument with Index.memoize() --- diskcache/persistent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 9b5939b..c3d570b 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -1151,7 +1151,7 @@ def memoize(self, name=None, typed=False, ignore=()): :return: callable decorator """ - return self._cache.memoize(name, typed, ignore) + return self._cache.memoize(name, typed, ignore=ignore) @contextmanager def transact(self): From 4de3c0ed99dd2bcebffad8e6e0cb7a2885210a97 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Tue, 14 Sep 2021 14:07:00 -0700 Subject: [PATCH 508/550] Drop old Ubuntu from integration testing --- .github/workflows/integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 0a44f89..b1143a4 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -31,7 +31,7 @@ jobs: strategy: max-parallel: 8 matrix: - os: [ubuntu-latest, macos-latest, windows-latest, ubuntu-16.04] + os: [ubuntu-latest, macos-latest, windows-latest] python-version: [3.6, 3.7, 3.8, 3.9] steps: From 6ed012a655673c54468beb7ff00038443ed2595e Mon Sep 17 00:00:00 2001 From: artiom Date: Wed, 29 Sep 2021 10:57:36 +0100 Subject: [PATCH 509/550] docs: fix typo --- docs/tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 3191af6..1963635 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -570,7 +570,7 @@ access and editing at both front and back sides. :class:`Deque cross-thread and cross-process communication. :class:`Deque ` objects are also useful in scenarios where contents should remain persistent or limitations prohibit holding all items in memory at the same time. The deque -uses a fixed amout of memory regardless of the size or number of items stored +uses a fixed amount of memory regardless of the size or number of items stored inside it. Index @@ -603,7 +603,7 @@ interface. :class:`Index ` objects inherit all the benefits of cross-thread and cross-process communication. :class:`Index ` objects are also useful in scenarios where contents should remain persistent or limitations prohibit holding all items in memory at the same time. The index -uses a fixed amout of memory regardless of the size or number of items stored +uses a fixed amount of memory regardless of the size or number of items stored inside it. .. _tutorial-transactions: From 20f1d93a6e3852bac4289cb94e27585d9c23330c Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 29 Sep 2021 08:11:04 -0700 Subject: [PATCH 510/550] Disable consider-using-f-string --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index 158fe34..6baa978 100644 --- a/.pylintrc +++ b/.pylintrc @@ -143,6 +143,7 @@ disable=print-statement, no-else-return, duplicate-code, inconsistent-return-statements, + consider-using-f-string, # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From 7bfbce63ba127d601ddfbf2838539e34cad87616 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 29 Nov 2021 22:58:48 -0800 Subject: [PATCH 511/550] Support for Python 3.10 in testing (#238) * Add support for Python 3.10 * Update copyright to 2022 * Bump version to 5.3.0 * Add Python 3.10 to the README --- .github/workflows/integration.yml | 4 ++-- .github/workflows/release.yml | 2 +- LICENSE | 2 +- README.rst | 6 +++--- diskcache/__init__.py | 6 +++--- docs/conf.py | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index b1143a4..07a5650 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: '3.10' - name: Install dependencies run: | pip install --upgrade pip @@ -32,7 +32,7 @@ jobs: max-parallel: 8 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] steps: - name: Set up Python ${{ matrix.python-version }} x64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a07787..1f89c14 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: '3.10' - name: Install dependencies run: | diff --git a/LICENSE b/LICENSE index ca80a22..bb4cfb7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2016-2021 Grant Jenks +Copyright 2016-2022 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/README.rst b/README.rst index c4aa8e9..eb06a6a 100644 --- a/README.rst +++ b/README.rst @@ -77,8 +77,8 @@ Features - Thread-safe and process-safe - Supports multiple eviction policies (LRU and LFU included) - Keys support "tag" metadata and eviction -- Developed on Python 3.9 -- Tested on CPython 3.6, 3.7, 3.8, 3.9 +- Developed on Python 3.10 +- Tested on CPython 3.6, 3.7, 3.8, 3.9, 3.10 - Tested on Linux, Mac OS X, and Windows - Tested using GitHub Actions @@ -387,7 +387,7 @@ Reference License ------- -Copyright 2016-2021 Grant Jenks +Copyright 2016-2022 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 934a3a7..c361ca9 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.2.1' -__build__ = 0x050201 +__version__ = '5.3.0' +__build__ = 0x050300 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2016-2021 Grant Jenks' +__copyright__ = 'Copyright 2016-2022 Grant Jenks' diff --git a/docs/conf.py b/docs/conf.py index d725198..92ce1b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ # -- Project information ----------------------------------------------------- project = 'DiskCache' -copyright = '2021, Grant Jenks' +copyright = '2022, Grant Jenks' author = 'Grant Jenks' # The full version, including alpha/beta/rc tags From 78a5cc690e0aa53ad3537a72b2f52f3c161da1ae Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 30 Dec 2021 17:35:44 -0800 Subject: [PATCH 512/550] Update tests for Django 3.2 --- tests/test_djangocache.py | 266 +++++++++++++++++++++++++------------- 1 file changed, 176 insertions(+), 90 deletions(-) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index cdaf101..36e1b01 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -1,5 +1,5 @@ # Most of this file was copied from: -# https://raw.githubusercontent.com/django/django/stable/2.2.x/tests/cache/tests.py +# https://raw.githubusercontent.com/django/django/stable/3.2.x/tests/cache/tests.py # Unit tests for cache framework # Uses whatever cache backend is set in the test settings file. @@ -9,12 +9,14 @@ import pickle import re import shutil +import sys import tempfile import threading import time import unittest import warnings -from unittest import mock +from pathlib import Path +from unittest import mock, skipIf from django.conf import settings from django.core import management, signals @@ -88,6 +90,10 @@ def __getstate__(self): raise pickle.PickleError() +def empty_response(request): + return HttpResponse() + + KEY_ERRORS_WITH_MEMCACHED_MSG = ( 'Cache key contains characters that will cause errors if used with ' 'memcached: %r' @@ -138,6 +144,14 @@ class BaseCacheTests: # A common set of tests to apply to all cache backends factory = RequestFactory() + # RemovedInDjango41Warning: python-memcached doesn't support .get() with + # default. + supports_get_with_default = True + + # Some clients raise custom exceptions when .incr() or .decr() are called + # with a non-integer value. + incr_decr_type_error = TypeError + def tearDown(self): cache.clear() @@ -146,11 +160,15 @@ def test_simple(self): cache.set('key', 'value') self.assertEqual(cache.get('key'), 'value') + def test_default_used_when_none_is_set(self): + """If None is cached, get() returns it instead of the default.""" + cache.set('key_default_none', None) + self.assertIsNone(cache.get('key_default_none', default='default')) + def test_add(self): # A key can be added to a cache - cache.add('addkey1', 'value') - result = cache.add('addkey1', 'newvalue') - self.assertFalse(result) + self.assertIs(cache.add('addkey1', 'value'), True) + self.assertIs(cache.add('addkey1', 'newvalue'), False) self.assertEqual(cache.get('addkey1'), 'value') def test_prefix(self): @@ -158,7 +176,7 @@ def test_prefix(self): cache.set('somekey', 'value') # should not be set in the prefixed cache - self.assertFalse(caches['prefix'].has_key('somekey')) + self.assertIs(caches['prefix'].has_key('somekey'), False) caches['prefix'].set('somekey', 'value2') @@ -180,28 +198,43 @@ def test_get_many(self): self.assertEqual( cache.get_many(iter(['a', 'b', 'e'])), {'a': 'a', 'b': 'b'} ) + cache.set_many({'x': None, 'y': 1}) + self.assertEqual(cache.get_many(['x', 'y']), {'x': None, 'y': 1}) def test_delete(self): # Cache keys can be deleted cache.set_many({'key1': 'spam', 'key2': 'eggs'}) self.assertEqual(cache.get('key1'), 'spam') - cache.delete('key1') + self.assertIs(cache.delete('key1'), True) self.assertIsNone(cache.get('key1')) self.assertEqual(cache.get('key2'), 'eggs') + def test_delete_nonexistent(self): + self.assertIs(cache.delete('nonexistent_key'), False) + def test_has_key(self): # The cache can be inspected for cache keys cache.set('hello1', 'goodbye1') - self.assertTrue(cache.has_key('hello1')) - self.assertFalse(cache.has_key('goodbye1')) + self.assertIs(cache.has_key('hello1'), True) + self.assertIs(cache.has_key('goodbye1'), False) cache.set('no_expiry', 'here', None) - self.assertTrue(cache.has_key('no_expiry')) + self.assertIs(cache.has_key('no_expiry'), True) + cache.set('null', None) + self.assertIs( + cache.has_key('null'), + True if self.supports_get_with_default else False, + ) def test_in(self): # The in operator can be used to inspect cache contents cache.set('hello2', 'goodbye2') self.assertIn('hello2', cache) self.assertNotIn('goodbye2', cache) + cache.set('null', None) + if self.supports_get_with_default: + self.assertIn('null', cache) + else: + self.assertNotIn('null', cache) def test_incr(self): # Cache values can be incremented @@ -213,6 +246,9 @@ def test_incr(self): self.assertEqual(cache.incr('answer', -10), 42) with self.assertRaises(ValueError): cache.incr('does_not_exist') + cache.set('null', None) + with self.assertRaises(self.incr_decr_type_error): + cache.incr('null') def test_decr(self): # Cache values can be decremented @@ -224,6 +260,9 @@ def test_decr(self): self.assertEqual(cache.decr('answer', -10), 42) with self.assertRaises(ValueError): cache.decr('does_not_exist') + cache.set('null', None) + with self.assertRaises(self.incr_decr_type_error): + cache.decr('null') def test_close(self): self.assertTrue(hasattr(cache, 'close')) @@ -295,24 +334,23 @@ def test_expiration(self): time.sleep(2) self.assertIsNone(cache.get('expire1')) - cache.add('expire2', 'newvalue') + self.assertIs(cache.add('expire2', 'newvalue'), True) self.assertEqual(cache.get('expire2'), 'newvalue') - self.assertFalse(cache.has_key('expire3')) + self.assertIs(cache.has_key('expire3'), False) def test_touch(self): # cache.touch() updates the timeout. cache.set('expire1', 'very quickly', timeout=1) self.assertIs(cache.touch('expire1', timeout=4), True) time.sleep(2) - self.assertTrue(cache.has_key('expire1')) + self.assertIs(cache.has_key('expire1'), True) time.sleep(3) - self.assertFalse(cache.has_key('expire1')) - + self.assertIs(cache.has_key('expire1'), False) # cache.touch() works without the timeout argument. cache.set('expire1', 'very quickly', timeout=1) self.assertIs(cache.touch('expire1'), True) time.sleep(2) - self.assertTrue(cache.has_key('expire1')) + self.assertIs(cache.has_key('expire1'), True) self.assertIs(cache.touch('nonexistent'), False) @@ -333,13 +371,13 @@ def test_unicode(self): # Test `add` for (key, value) in stuff.items(): with self.subTest(key=key): - cache.delete(key) - cache.add(key, value) + self.assertIs(cache.delete(key), True) + self.assertIs(cache.add(key, value), True) self.assertEqual(cache.get(key), value) # Test `set_many` for (key, value) in stuff.items(): - cache.delete(key) + self.assertIs(cache.delete(key), True) cache.set_many(stuff) for (key, value) in stuff.items(): with self.subTest(key=key): @@ -359,7 +397,7 @@ def test_binary_string(self): self.assertEqual(value, decompress(compressed_result).decode()) # Test add - cache.add('binary1-add', compressed_value) + self.assertIs(cache.add('binary1-add', compressed_value), True) compressed_result = cache.get('binary1-add') self.assertEqual(compressed_value, compressed_result) self.assertEqual(value, decompress(compressed_result).decode()) @@ -405,14 +443,14 @@ def test_clear(self): def test_long_timeout(self): """ - Followe memcached's convention where a timeout greater than 30 days is + Follow memcached's convention where a timeout greater than 30 days is treated as an absolute expiration timestamp instead of a relative offset (#12399). """ cache.set('key1', 'eggs', 60 * 60 * 24 * 30 + 1) # 30 days + 1 second self.assertEqual(cache.get('key1'), 'eggs') - cache.add('key2', 'ham', 60 * 60 * 24 * 30 + 1) + self.assertIs(cache.add('key2', 'ham', 60 * 60 * 24 * 30 + 1), True) self.assertEqual(cache.get('key2'), 'ham') cache.set_many( @@ -429,10 +467,9 @@ def test_forever_timeout(self): cache.set('key1', 'eggs', None) self.assertEqual(cache.get('key1'), 'eggs') - cache.add('key2', 'ham', None) + self.assertIs(cache.add('key2', 'ham', None), True) self.assertEqual(cache.get('key2'), 'ham') - added = cache.add('key1', 'new eggs', None) - self.assertIs(added, False) + self.assertIs(cache.add('key1', 'new eggs', None), False) self.assertEqual(cache.get('key1'), 'eggs') cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, None) @@ -440,7 +477,7 @@ def test_forever_timeout(self): self.assertEqual(cache.get('key4'), 'lobster bisque') cache.set('key5', 'belgian fries', timeout=1) - cache.touch('key5', timeout=None) + self.assertIs(cache.touch('key5', timeout=None), True) time.sleep(2) self.assertEqual(cache.get('key5'), 'belgian fries') @@ -451,7 +488,7 @@ def test_zero_timeout(self): cache.set('key1', 'eggs', 0) self.assertIsNone(cache.get('key1')) - cache.add('key2', 'ham', 0) + self.assertIs(cache.add('key2', 'ham', 0), True) self.assertIsNone(cache.get('key2')) cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 0) @@ -459,7 +496,7 @@ def test_zero_timeout(self): self.assertIsNone(cache.get('key4')) cache.set('key5', 'belgian fries', timeout=5) - cache.touch('key5', timeout=0) + self.assertIs(cache.touch('key5', timeout=0), True) self.assertIsNone(cache.get('key5')) def test_float_timeout(self): @@ -467,7 +504,12 @@ def test_float_timeout(self): cache.set('key1', 'spam', 100.2) self.assertEqual(cache.get('key1'), 'spam') - def _perform_cull_test(self, cull_cache, initial_count, final_count): + def _perform_cull_test(self, cull_cache_name, initial_count, final_count): + try: + cull_cache = caches[cull_cache_name] + except InvalidCacheBackendError: + self.skipTest("Culling isn't implemented.") + # Create initial cache key entries. This will overflow the cache, # causing a cull. for i in range(1, initial_count): @@ -480,10 +522,24 @@ def _perform_cull_test(self, cull_cache, initial_count, final_count): self.assertEqual(count, final_count) def test_cull(self): - self._perform_cull_test(caches['cull'], 50, 29) + self._perform_cull_test('cull', 50, 29) def test_zero_cull(self): - self._perform_cull_test(caches['zero_cull'], 50, 19) + self._perform_cull_test('zero_cull', 50, 19) + + def test_cull_delete_when_store_empty(self): + try: + cull_cache = caches['cull'] + except InvalidCacheBackendError: + self.skipTest("Culling isn't implemented.") + old_max_entries = cull_cache._max_entries + # Force _cull to delete on first cached record. + cull_cache._max_entries = -1 + try: + cull_cache.set('force_cull_delete', 'value', 1000) + self.assertIs(cull_cache.has_key('force_cull_delete'), True) + finally: + cull_cache._max_entries = old_max_entries def _perform_invalid_key_test(self, key, expected_warning): """ @@ -500,10 +556,24 @@ def func(key, *args): old_func = cache.key_func cache.key_func = func + tests = [ + ('add', [key, 1]), + ('get', [key]), + ('set', [key, 1]), + ('incr', [key]), + ('decr', [key]), + ('touch', [key]), + ('delete', [key]), + ('get_many', [[key, 'b']]), + ('set_many', [{key: 1, 'b': 2}]), + ('delete_many', [{key: 1, 'b': 2}]), + ] try: - with self.assertWarns(CacheKeyWarning) as cm: - cache.set(key, 'value') - self.assertEqual(str(cm.warning), expected_warning) + for operation, args in tests: + with self.subTest(operation=operation): + with self.assertWarns(CacheKeyWarning) as cm: + getattr(cache, operation)(*args) + self.assertEqual(str(cm.warning), expected_warning) finally: cache.key_func = old_func @@ -567,41 +637,41 @@ def test_cache_versioning_get_set(self): def test_cache_versioning_add(self): # add, default version = 1, but manually override version = 2 - cache.add('answer1', 42, version=2) + self.assertIs(cache.add('answer1', 42, version=2), True) self.assertIsNone(cache.get('answer1', version=1)) self.assertEqual(cache.get('answer1', version=2), 42) - cache.add('answer1', 37, version=2) + self.assertIs(cache.add('answer1', 37, version=2), False) self.assertIsNone(cache.get('answer1', version=1)) self.assertEqual(cache.get('answer1', version=2), 42) - cache.add('answer1', 37, version=1) + self.assertIs(cache.add('answer1', 37, version=1), True) self.assertEqual(cache.get('answer1', version=1), 37) self.assertEqual(cache.get('answer1', version=2), 42) # v2 add, using default version = 2 - caches['v2'].add('answer2', 42) + self.assertIs(caches['v2'].add('answer2', 42), True) self.assertIsNone(cache.get('answer2', version=1)) self.assertEqual(cache.get('answer2', version=2), 42) - caches['v2'].add('answer2', 37) + self.assertIs(caches['v2'].add('answer2', 37), False) self.assertIsNone(cache.get('answer2', version=1)) self.assertEqual(cache.get('answer2', version=2), 42) - caches['v2'].add('answer2', 37, version=1) + self.assertIs(caches['v2'].add('answer2', 37, version=1), True) self.assertEqual(cache.get('answer2', version=1), 37) self.assertEqual(cache.get('answer2', version=2), 42) # v2 add, default version = 2, but manually override version = 1 - caches['v2'].add('answer3', 42, version=1) + self.assertIs(caches['v2'].add('answer3', 42, version=1), True) self.assertEqual(cache.get('answer3', version=1), 42) self.assertIsNone(cache.get('answer3', version=2)) - caches['v2'].add('answer3', 37, version=1) + self.assertIs(caches['v2'].add('answer3', 37, version=1), False) self.assertEqual(cache.get('answer3', version=1), 42) self.assertIsNone(cache.get('answer3', version=2)) - caches['v2'].add('answer3', 37) + self.assertIs(caches['v2'].add('answer3', 37), True) self.assertEqual(cache.get('answer3', version=1), 42) self.assertEqual(cache.get('answer3', version=2), 37) @@ -609,73 +679,73 @@ def test_cache_versioning_has_key(self): cache.set('answer1', 42) # has_key - self.assertTrue(cache.has_key('answer1')) - self.assertTrue(cache.has_key('answer1', version=1)) - self.assertFalse(cache.has_key('answer1', version=2)) + self.assertIs(cache.has_key('answer1'), True) + self.assertIs(cache.has_key('answer1', version=1), True) + self.assertIs(cache.has_key('answer1', version=2), False) - self.assertFalse(caches['v2'].has_key('answer1')) - self.assertTrue(caches['v2'].has_key('answer1', version=1)) - self.assertFalse(caches['v2'].has_key('answer1', version=2)) + self.assertIs(caches['v2'].has_key('answer1'), False) + self.assertIs(caches['v2'].has_key('answer1', version=1), True) + self.assertIs(caches['v2'].has_key('answer1', version=2), False) def test_cache_versioning_delete(self): cache.set('answer1', 37, version=1) cache.set('answer1', 42, version=2) - cache.delete('answer1') + self.assertIs(cache.delete('answer1'), True) self.assertIsNone(cache.get('answer1', version=1)) self.assertEqual(cache.get('answer1', version=2), 42) cache.set('answer2', 37, version=1) cache.set('answer2', 42, version=2) - cache.delete('answer2', version=2) + self.assertIs(cache.delete('answer2', version=2), True) self.assertEqual(cache.get('answer2', version=1), 37) self.assertIsNone(cache.get('answer2', version=2)) cache.set('answer3', 37, version=1) cache.set('answer3', 42, version=2) - caches['v2'].delete('answer3') + self.assertIs(caches['v2'].delete('answer3'), True) self.assertEqual(cache.get('answer3', version=1), 37) self.assertIsNone(cache.get('answer3', version=2)) cache.set('answer4', 37, version=1) cache.set('answer4', 42, version=2) - caches['v2'].delete('answer4', version=1) + self.assertIs(caches['v2'].delete('answer4', version=1), True) self.assertIsNone(cache.get('answer4', version=1)) self.assertEqual(cache.get('answer4', version=2), 42) def test_cache_versioning_incr_decr(self): cache.set('answer1', 37, version=1) cache.set('answer1', 42, version=2) - cache.incr('answer1') + self.assertEqual(cache.incr('answer1'), 38) self.assertEqual(cache.get('answer1', version=1), 38) self.assertEqual(cache.get('answer1', version=2), 42) - cache.decr('answer1') + self.assertEqual(cache.decr('answer1'), 37) self.assertEqual(cache.get('answer1', version=1), 37) self.assertEqual(cache.get('answer1', version=2), 42) cache.set('answer2', 37, version=1) cache.set('answer2', 42, version=2) - cache.incr('answer2', version=2) + self.assertEqual(cache.incr('answer2', version=2), 43) self.assertEqual(cache.get('answer2', version=1), 37) self.assertEqual(cache.get('answer2', version=2), 43) - cache.decr('answer2', version=2) + self.assertEqual(cache.decr('answer2', version=2), 42) self.assertEqual(cache.get('answer2', version=1), 37) self.assertEqual(cache.get('answer2', version=2), 42) cache.set('answer3', 37, version=1) cache.set('answer3', 42, version=2) - caches['v2'].incr('answer3') + self.assertEqual(caches['v2'].incr('answer3'), 43) self.assertEqual(cache.get('answer3', version=1), 37) self.assertEqual(cache.get('answer3', version=2), 43) - caches['v2'].decr('answer3') + self.assertEqual(caches['v2'].decr('answer3'), 42) self.assertEqual(cache.get('answer3', version=1), 37) self.assertEqual(cache.get('answer3', version=2), 42) cache.set('answer4', 37, version=1) cache.set('answer4', 42, version=2) - caches['v2'].incr('answer4', version=1) + self.assertEqual(caches['v2'].incr('answer4', version=1), 38) self.assertEqual(cache.get('answer4', version=1), 38) self.assertEqual(cache.get('answer4', version=2), 42) - caches['v2'].decr('answer4', version=1) + self.assertEqual(caches['v2'].decr('answer4', version=1), 37) self.assertEqual(cache.get('answer4', version=1), 37) self.assertEqual(cache.get('answer4', version=2), 42) @@ -790,6 +860,13 @@ def test_incr_version(self): with self.assertRaises(ValueError): cache.incr_version('does_not_exist') + cache.set('null', None) + if self.supports_get_with_default: + self.assertEqual(cache.incr_version('null'), 2) + else: + with self.assertRaises(self.incr_decr_type_error): + cache.incr_version('null') + def test_decr_version(self): cache.set('answer', 42, version=2) self.assertIsNone(cache.get('answer')) @@ -814,6 +891,13 @@ def test_decr_version(self): with self.assertRaises(ValueError): cache.decr_version('does_not_exist', version=2) + cache.set('null', None, version=2) + if self.supports_get_with_default: + self.assertEqual(cache.decr_version('null', version=2), 1) + else: + with self.assertRaises(self.incr_decr_type_error): + cache.decr_version('null', version=2) + def test_custom_key_func(self): # Two caches with different key functions aren't visible to each other cache.set('answer1', 42) @@ -827,30 +911,33 @@ def test_custom_key_func(self): self.assertEqual(caches['custom_key2'].get('answer2'), 42) def test_cache_write_unpicklable_object(self): - update_middleware = UpdateCacheMiddleware() - update_middleware.cache = cache - - fetch_middleware = FetchFromCacheMiddleware() + fetch_middleware = FetchFromCacheMiddleware(empty_response) fetch_middleware.cache = cache request = self.factory.get('/cache/test') request._cache_update_cache = True - get_cache_data = FetchFromCacheMiddleware().process_request(request) + get_cache_data = FetchFromCacheMiddleware( + empty_response + ).process_request(request) self.assertIsNone(get_cache_data) - response = HttpResponse() content = 'Testing cookie serialization.' - response.content = content - response.set_cookie('foo', 'bar') - update_middleware.process_response(request, response) + def get_response(req): + response = HttpResponse(content) + response.set_cookie('foo', 'bar') + return response + + update_middleware = UpdateCacheMiddleware(get_response) + update_middleware.cache = cache + response = update_middleware(request) get_cache_data = fetch_middleware.process_request(request) self.assertIsNotNone(get_cache_data) self.assertEqual(get_cache_data.content, content.encode()) self.assertEqual(get_cache_data.cookies, response.cookies) - update_middleware.process_response(request, get_cache_data) + UpdateCacheMiddleware(lambda req: get_cache_data)(request) get_cache_data = fetch_middleware.process_request(request) self.assertIsNotNone(get_cache_data) self.assertEqual(get_cache_data.content, content.encode()) @@ -869,7 +956,12 @@ def test_get_or_set(self): self.assertIsNone(cache.get('projector')) self.assertEqual(cache.get_or_set('projector', 42), 42) self.assertEqual(cache.get('projector'), 42) - self.assertEqual(cache.get_or_set('null', None), None) + self.assertIsNone(cache.get_or_set('null', None)) + if self.supports_get_with_default: + # Previous get_or_set() stores None in the cache. + self.assertIsNone(cache.get('null', 'default')) + else: + self.assertEqual(cache.get('null', 'default'), 'default') def test_get_or_set_callable(self): def my_callable(): @@ -878,14 +970,16 @@ def my_callable(): self.assertEqual(cache.get_or_set('mykey', my_callable), 'value') self.assertEqual(cache.get_or_set('mykey', my_callable()), 'value') - def test_get_or_set_callable_returning_none(self): - self.assertIsNone(cache.get_or_set('mykey', lambda: None)) - # Previous get_or_set() doesn't store None in the cache. - self.assertEqual(cache.get('mykey', 'default'), 'default') + self.assertIsNone(cache.get_or_set('null', lambda: None)) + if self.supports_get_with_default: + # Previous get_or_set() stores None in the cache. + self.assertIsNone(cache.get('null', 'default')) + else: + self.assertEqual(cache.get('null', 'default'), 'default') def test_get_or_set_version(self): msg = "get_or_set() missing 1 required positional argument: 'default'" - cache.get_or_set('brian', 1979, version=2) + self.assertEqual(cache.get_or_set('brian', 1979, version=2), 1979) with self.assertRaisesMessage(TypeError, msg): cache.get_or_set('brian') with self.assertRaisesMessage(TypeError, msg): @@ -949,6 +1043,11 @@ def test_ignores_non_cache_files(self): ) os.remove(fname) + def test_creates_cache_dir_if_nonexistent(self): + os.rmdir(self.dirname) + cache.set('foo', 'bar') + self.assertTrue(os.path.exists(self.dirname)) + def test_clear_does_not_remove_cache_dir(self): cache.clear() self.assertTrue( @@ -1026,19 +1125,6 @@ def test_pop(self): ) self.assertEqual(cache.pop(4, retry=False), 4) - def test_pickle(self): - letters = 'abcde' - cache.clear() - - for num, val in enumerate(letters): - cache.set(val, num) - - data = pickle.dumps(cache) - other = pickle.loads(data) - - for key in letters: - self.assertEqual(other.get(key), cache.get(key)) - def test_cache(self): subcache = cache.cache('test') directory = os.path.join(cache.directory, 'cache', 'test') From f3836f9f1938ede1eaf32d474be0311d0c7a3183 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 30 Dec 2021 17:35:55 -0800 Subject: [PATCH 513/550] Fix DjangoCache.delete to return True/False --- diskcache/djangocache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 347a613..8bf85ce 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -220,7 +220,7 @@ def delete(self, key, version=None, retry=True): """ # pylint: disable=arguments-differ key = self.make_key(key, version=version) - self._cache.delete(key, retry) + return self._cache.delete(key, retry) def incr(self, key, delta=1, version=None, default=None, retry=True): """Increment value by delta for item with key. From 8da6634877178ff3704d8468aa5a95b9baf4f9ac Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 30 Dec 2021 17:37:38 -0800 Subject: [PATCH 514/550] Bump Django testing to 3.2 --- requirements.txt | 2 +- tox.ini | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2ab91c7..efb2160 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -e . blue coverage -django==2.2.* +django==3.2.* django_redis doc8 flake8 diff --git a/tox.ini b/tox.ini index 104a084..650c8f0 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ skip_missing_interpreters=True [testenv] commands=pytest deps= - django==2.2.* + django==3.2.* pytest pytest-cov pytest-django @@ -31,7 +31,7 @@ allowlist_externals=make changedir=docs commands=make html deps= - django==2.2.* + django==3.2.* sphinx [testenv:flake8] @@ -53,7 +53,7 @@ deps=mypy [testenv:pylint] commands=pylint {toxinidir}/diskcache deps= - django==2.2.* + django==3.2.* pylint [testenv:rstcheck] From 221f3d38cea69e33ba9a83cde0e2e202f3d3d70e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 30 Dec 2021 17:45:26 -0800 Subject: [PATCH 515/550] Remove unused imports --- tests/test_djangocache.py | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 36e1b01..6fae5be 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -3,64 +3,30 @@ # Unit tests for cache framework # Uses whatever cache backend is set in the test settings file. -import copy -import io import os import pickle -import re import shutil -import sys import tempfile -import threading import time -import unittest -import warnings -from pathlib import Path -from unittest import mock, skipIf +from unittest import mock from django.conf import settings -from django.core import management, signals from django.core.cache import ( - DEFAULT_CACHE_ALIAS, CacheKeyWarning, - InvalidCacheKey, cache, caches, ) -from django.core.cache.utils import make_template_fragment_key -from django.db import close_old_connections, connection, connections -from django.http import ( - HttpRequest, - HttpResponse, - HttpResponseNotModified, - StreamingHttpResponse, -) +from django.http import HttpResponse from django.middleware.cache import ( - CacheMiddleware, FetchFromCacheMiddleware, UpdateCacheMiddleware, ) -from django.middleware.csrf import CsrfViewMiddleware -from django.template import engines -from django.template.context_processors import csrf -from django.template.response import TemplateResponse from django.test import ( RequestFactory, - SimpleTestCase, TestCase, - TransactionTestCase, override_settings, ) from django.test.signals import setting_changed -from django.utils import timezone, translation -from django.utils.cache import ( - get_cache_key, - learn_cache_key, - patch_cache_control, - patch_vary_headers, -) -from django.utils.encoding import force_text -from django.views.decorators.cache import cache_page ################################################################################ # Setup Django for models import. From 5ac77969c88df42eece83be2e4ce85d2e277109d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 30 Dec 2021 17:48:08 -0800 Subject: [PATCH 516/550] Run isort --- tests/test_djangocache.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 6fae5be..5f83b81 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -11,21 +11,13 @@ from unittest import mock from django.conf import settings -from django.core.cache import ( - CacheKeyWarning, - cache, - caches, -) +from django.core.cache import CacheKeyWarning, cache, caches from django.http import HttpResponse from django.middleware.cache import ( FetchFromCacheMiddleware, UpdateCacheMiddleware, ) -from django.test import ( - RequestFactory, - TestCase, - override_settings, -) +from django.test import RequestFactory, TestCase, override_settings from django.test.signals import setting_changed ################################################################################ From 1cb1425b1ba24f26fb1e37349c4c2658c2a46d8f Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Thu, 30 Dec 2021 17:58:24 -0800 Subject: [PATCH 517/550] Bump version to 5.4.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index c361ca9..2355128 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.3.0' -__build__ = 0x050300 +__version__ = '5.4.0' +__build__ = 0x050400 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2022 Grant Jenks' From c9844bba9de039ab64858073d8864f7e40172c9e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 20 Feb 2022 12:47:41 -0800 Subject: [PATCH 518/550] Put commands above deps for doc8 testenv --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 650c8f0..65da17c 100644 --- a/tox.ini +++ b/tox.ini @@ -23,8 +23,8 @@ commands=blue --check {toxinidir}/setup.py {toxinidir}/diskcache {toxinidir}/tes deps=blue [testenv:doc8] -deps=doc8 commands=doc8 docs --ignore-path docs/_build +deps=doc8 [testenv:docs] allowlist_externals=make From 2d2cc8b39db3e0c0785dd304bde7902219d85ffe Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 20 Feb 2022 13:42:14 -0800 Subject: [PATCH 519/550] Update rsync command for uploading docs --- tox.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 65da17c..36ed2b8 100644 --- a/tox.ini +++ b/tox.ini @@ -64,8 +64,9 @@ deps=rstcheck allowlist_externals=rsync changedir=docs commands= - rsync -azP --stats --delete _build/html/ \ - grantjenks.com:/srv/www/www.grantjenks.com/public/docs/diskcache/ + rsync --rsync-path 'sudo -u herokuish rsync' -azP --stats --delete \ + _build/html/ \ + grantjenks:/srv/www/grantjenks.com/public/docs/diskcache/ [isort] multi_line_output = 3 From c1774469b8d4c4906fe24f7b5afd637795af48d9 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 22:12:28 -0700 Subject: [PATCH 520/550] Remove unused import --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index b49d9c8..841dfb9 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -from io import open - from setuptools import setup from setuptools.command.test import test as TestCommand From f3fcdffee88af7923740b9864d533524a79d1222 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 22:27:48 -0700 Subject: [PATCH 521/550] Update Cache(...) params when allocating --- diskcache/fanout.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index dc5240c..8fe51d9 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -573,9 +573,11 @@ def reset(self, key, value=ENOVAL): break return result - def cache(self, name): + def cache(self, name, timeout=60, disk=None, **settings): """Return Cache with given `name` in subdirectory. + If disk is none (default), uses the fanout cache disk. + >>> fanout_cache = FanoutCache() >>> cache = fanout_cache.cache('test') >>> cache.set('abc', 123) @@ -588,6 +590,9 @@ def cache(self, name): True :param str name: subdirectory name for Cache + :param float timeout: SQLite connection timeout + :param disk: Disk type or subclass for serialization + :param settings: any of DEFAULT_SETTINGS :return: Cache with given name """ @@ -598,7 +603,12 @@ def cache(self, name): except KeyError: parts = name.split('/') directory = op.join(self._directory, 'cache', *parts) - temp = Cache(directory=directory, disk=self._disk) + temp = Cache( + directory=directory, + timeout=timeout, + disk=self._disk if disk is None else Disk, + **settings, + ) _caches[name] = temp return temp @@ -626,7 +636,11 @@ def deque(self, name): except KeyError: parts = name.split('/') directory = op.join(self._directory, 'deque', *parts) - cache = Cache(directory=directory, disk=self._disk) + cache = Cache( + directory=directory, + disk=self._disk, + eviction_policy='none', + ) deque = Deque.fromcache(cache) _deques[name] = deque return deque @@ -658,7 +672,11 @@ def index(self, name): except KeyError: parts = name.split('/') directory = op.join(self._directory, 'index', *parts) - cache = Cache(directory=directory, disk=self._disk) + cache = Cache( + directory=directory, + disk=self._disk, + eviction_policy='none', + ) index = Index.fromcache(cache) _indexes[name] = index return index From ee7a248e5c09e6fb9145b2e4a1777a345114b71d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 22:37:39 -0700 Subject: [PATCH 522/550] Add docs about the eviction policy to recipes --- diskcache/recipes.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/diskcache/recipes.py b/diskcache/recipes.py index b5af6dd..babb68f 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -17,6 +17,9 @@ class Averager: Sometimes known as "online statistics," the running average maintains the total and count. The average can then be calculated at any time. + Assumes the key will not be evicted. Set the eviction policy to 'none' on + the cache to guarantee the key is not evicted. + >>> import diskcache >>> cache = diskcache.FanoutCache() >>> ave = Averager(cache, 'latency') @@ -65,6 +68,9 @@ def pop(self): class Lock: """Recipe for cross-process and cross-thread lock. + Assumes the key will not be evicted. Set the eviction policy to 'none' on + the cache to guarantee the key is not evicted. + >>> import diskcache >>> cache = diskcache.Cache() >>> lock = Lock(cache, 'report-123') @@ -113,6 +119,9 @@ def __exit__(self, *exc_info): class RLock: """Recipe for cross-process and cross-thread re-entrant lock. + Assumes the key will not be evicted. Set the eviction policy to 'none' on + the cache to guarantee the key is not evicted. + >>> import diskcache >>> cache = diskcache.Cache() >>> rlock = RLock(cache, 'user-123') @@ -181,6 +190,9 @@ def __exit__(self, *exc_info): class BoundedSemaphore: """Recipe for cross-process and cross-thread bounded semaphore. + Assumes the key will not be evicted. Set the eviction policy to 'none' on + the cache to guarantee the key is not evicted. + >>> import diskcache >>> cache = diskcache.Cache() >>> semaphore = BoundedSemaphore(cache, 'max-cons', value=2) @@ -251,6 +263,9 @@ def throttle( ): """Decorator to throttle calls to function. + Assumes keys will not be evicted. Set the eviction policy to 'none' on the + cache to guarantee the keys are not evicted. + >>> import diskcache, time >>> cache = diskcache.Cache() >>> count = 0 @@ -305,6 +320,9 @@ def barrier(cache, lock_factory, name=None, expire=None, tag=None): Supports different kinds of locks: Lock, RLock, BoundedSemaphore. + Assumes keys will not be evicted. Set the eviction policy to 'none' on the + cache to guarantee the keys are not evicted. + >>> import diskcache, time >>> cache = diskcache.Cache() >>> @barrier(cache, Lock) From fb2fa2c401bb88ecbbc58009d02cbe65f2fc594a Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 22:52:52 -0700 Subject: [PATCH 523/550] Test on Django 4.2 LTS --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 36ed2b8..0ddcc8f 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ skip_missing_interpreters=True [testenv] commands=pytest deps= - django==3.2.* + django==4.2.* pytest pytest-cov pytest-django @@ -31,7 +31,7 @@ allowlist_externals=make changedir=docs commands=make html deps= - django==3.2.* + django==4.2.* sphinx [testenv:flake8] @@ -53,7 +53,7 @@ deps=mypy [testenv:pylint] commands=pylint {toxinidir}/diskcache deps= - django==3.2.* + django==4.2.* pylint [testenv:rstcheck] From 0a9783353ff7dc9f874154ef98b23de27e4aba8d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 22:53:17 -0700 Subject: [PATCH 524/550] Update year to 2023 --- README.rst | 4 ++-- diskcache/__init__.py | 2 +- docs/conf.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index eb06a6a..04abdc0 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ DiskCache: Disk Backed Cache `DiskCache`_ is an Apache2 licensed disk and file backed cache library, written in pure-Python, and compatible with Django. -The cloud-based computing of 2021 puts a premium on memory. Gigabytes of empty +The cloud-based computing of 2023 puts a premium on memory. Gigabytes of empty space is left on disks as processes vie for memory. Among these processes is Memcached (and sometimes Redis) which is used as a cache. Wouldn't it be nice to leverage empty disk space for caching? @@ -387,7 +387,7 @@ Reference License ------- -Copyright 2016-2022 Grant Jenks +Copyright 2016-2023 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 2355128..f7aa771 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -65,4 +65,4 @@ __build__ = 0x050400 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2016-2022 Grant Jenks' +__copyright__ = 'Copyright 2016-2023 Grant Jenks' diff --git a/docs/conf.py b/docs/conf.py index 92ce1b9..92bf3ec 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ # -- Project information ----------------------------------------------------- project = 'DiskCache' -copyright = '2022, Grant Jenks' +copyright = '2023, Grant Jenks' author = 'Grant Jenks' # The full version, including alpha/beta/rc tags From 712cc1827b29fb8f6a76803d32f87a5fb57f3c6e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 22:56:16 -0700 Subject: [PATCH 525/550] Bump python testing to 3.11 --- .github/workflows/integration.yml | 4 ++-- .github/workflows/release.yml | 2 +- tox.ini | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 07a5650..2d83ad3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.10' + python-version: '3.11' - name: Install dependencies run: | pip install --upgrade pip @@ -32,7 +32,7 @@ jobs: max-parallel: 8 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] + python-version: [3.7, 3.8, 3.9, '3.10', 3.11] steps: - name: Set up Python ${{ matrix.python-version }} x64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f89c14..676593c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.10' + python-version: '3.11' - name: Install dependencies run: | diff --git a/tox.ini b/tox.ini index 0ddcc8f..3735ebc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=bluecheck,doc8,docs,isortcheck,flake8,mypy,pylint,rstcheck,py36,py37,py38,py39 +envlist=bluecheck,doc8,docs,isortcheck,flake8,mypy,pylint,rstcheck,py37,py38,py39,py310,py311 skip_missing_interpreters=True [testenv] From d7ae0990b240e8ea22c1a4060f58900edd339a18 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:07:29 -0700 Subject: [PATCH 526/550] i blue it --- diskcache/core.py | 10 ++--- diskcache/fanout.py | 2 +- tests/benchmark_glob.py | 2 +- tests/settings.py | 2 +- tests/stress_test_core.py | 30 ++++++------- tests/stress_test_fanout.py | 30 ++++++------- tests/test_core.py | 84 ++++++++++++++++++------------------- tests/test_djangocache.py | 2 +- tests/test_fanout.py | 16 +++---- 9 files changed, 85 insertions(+), 93 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index fb343be..05a0854 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -49,14 +49,14 @@ def __repr__(self): 'statistics': 0, # False 'tag_index': 0, # False 'eviction_policy': 'least-recently-stored', - 'size_limit': 2 ** 30, # 1gb + 'size_limit': 2**30, # 1gb 'cull_limit': 10, 'sqlite_auto_vacuum': 1, # FULL - 'sqlite_cache_size': 2 ** 13, # 8,192 pages + 'sqlite_cache_size': 2**13, # 8,192 pages 'sqlite_journal_mode': 'wal', - 'sqlite_mmap_size': 2 ** 26, # 64mb + 'sqlite_mmap_size': 2**26, # 64mb 'sqlite_synchronous': 1, # NORMAL - 'disk_min_file_size': 2 ** 15, # 32kb + 'disk_min_file_size': 2**15, # 32kb 'disk_pickle_protocol': pickle.HIGHEST_PROTOCOL, } @@ -212,7 +212,7 @@ def store(self, value, read, key=UNKNOWN): size = op.getsize(full_path) return size, MODE_TEXT, filename, None elif read: - reader = ft.partial(value.read, 2 ** 22) + reader = ft.partial(value.read, 2**22) filename, full_path = self.filename(key, value) iterator = iter(reader, b'') size = self._write(full_path, iterator, 'xb') diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 8fe51d9..5283490 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -45,7 +45,7 @@ def __init__( timeout=timeout, disk=disk, size_limit=size_limit, - **settings + **settings, ) for num in range(shards) ) diff --git a/tests/benchmark_glob.py b/tests/benchmark_glob.py index 7f0bf7c..7da5fd3 100644 --- a/tests/benchmark_glob.py +++ b/tests/benchmark_glob.py @@ -22,7 +22,7 @@ print(template % ('Count', 'Time')) print(' '.join(['=' * size] * len(cols))) -for count in [10 ** exp for exp in range(6)]: +for count in [10**exp for exp in range(6)]: for value in range(count): with open(op.join('tmp', '%s.tmp' % value), 'wb') as writer: pass diff --git a/tests/settings.py b/tests/settings.py index 1a2f569..04aee85 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -25,7 +25,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [u'testserver'] +ALLOWED_HOSTS = ['testserver'] # Application definition diff --git a/tests/stress_test_core.py b/tests/stress_test_core.py index c30fa3f..2b2578b 100644 --- a/tests/stress_test_core.py +++ b/tests/stress_test_core.py @@ -33,16 +33,14 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) - word = u''.join( - random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) - ) + word = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz', word_size)) size = random.randint(1, int(200 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) - word = u''.join( - random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + word = ''.join( + random.sample('abcdefghijklmnopqrstuvwxyz', word_size) ).encode('utf-8') size = random.randint(1, int(200 / 13)) return word * size @@ -77,18 +75,16 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) - word = u''.join( - random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) - ) - size = random.randint(1, int(2 ** 16 / 13)) + word = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz', word_size)) + size = random.randint(1, int(2**16 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) - word = u''.join( - random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + word = ''.join( + random.sample('abcdefghijklmnopqrstuvwxyz', word_size) ).encode('utf-8') - size = random.randint(1, int(2 ** 16 / 13)) + size = random.randint(1, int(2**16 / 13)) return word * size def make_float(): @@ -233,7 +229,7 @@ def percentile(sequence, percent): def stress_test( create=True, delete=True, - eviction_policy=u'least-recently-stored', + eviction_policy='least-recently-stored', processes=1, threads=1, ): @@ -293,17 +289,17 @@ def stress_test( def stress_test_lru(): """Stress test least-recently-used eviction policy.""" - stress_test(eviction_policy=u'least-recently-used') + stress_test(eviction_policy='least-recently-used') def stress_test_lfu(): """Stress test least-frequently-used eviction policy.""" - stress_test(eviction_policy=u'least-frequently-used') + stress_test(eviction_policy='least-frequently-used') def stress_test_none(): """Stress test 'none' eviction policy.""" - stress_test(eviction_policy=u'none') + stress_test(eviction_policy='none') def stress_test_mp(): @@ -396,7 +392,7 @@ def stress_test_mp(): '-v', '--eviction-policy', type=str, - default=u'least-recently-stored', + default='least-recently-stored', ) args = parser.parse_args() diff --git a/tests/stress_test_fanout.py b/tests/stress_test_fanout.py index d3b67e3..e78dda5 100644 --- a/tests/stress_test_fanout.py +++ b/tests/stress_test_fanout.py @@ -32,16 +32,14 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) - word = u''.join( - random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) - ) + word = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz', word_size)) size = random.randint(1, int(200 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) - word = u''.join( - random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + word = ''.join( + random.sample('abcdefghijklmnopqrstuvwxyz', word_size) ).encode('utf-8') size = random.randint(1, int(200 / 13)) return word * size @@ -76,18 +74,16 @@ def make_long(): def make_unicode(): word_size = random.randint(1, 26) - word = u''.join( - random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) - ) - size = random.randint(1, int(2 ** 16 / 13)) + word = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz', word_size)) + size = random.randint(1, int(2**16 / 13)) return word * size def make_bytes(): word_size = random.randint(1, 26) - word = u''.join( - random.sample(u'abcdefghijklmnopqrstuvwxyz', word_size) + word = ''.join( + random.sample('abcdefghijklmnopqrstuvwxyz', word_size) ).encode('utf-8') - size = random.randint(1, int(2 ** 16 / 13)) + size = random.randint(1, int(2**16 / 13)) return word * size def make_float(): @@ -224,7 +220,7 @@ def percentile(sequence, percent): def stress_test( create=True, delete=True, - eviction_policy=u'least-recently-stored', + eviction_policy='least-recently-stored', processes=1, threads=1, ): @@ -284,17 +280,17 @@ def stress_test( def stress_test_lru(): """Stress test least-recently-used eviction policy.""" - stress_test(eviction_policy=u'least-recently-used') + stress_test(eviction_policy='least-recently-used') def stress_test_lfu(): """Stress test least-frequently-used eviction policy.""" - stress_test(eviction_policy=u'least-frequently-used') + stress_test(eviction_policy='least-frequently-used') def stress_test_none(): """Stress test 'none' eviction policy.""" - stress_test(eviction_policy=u'none') + stress_test(eviction_policy='none') def stress_test_mp(): @@ -387,7 +383,7 @@ def stress_test_mp(): '-v', '--eviction-policy', type=str, - default=u'least-recently-stored', + default='least-recently-stored', ) args = parser.parse_args() diff --git a/tests/test_core.py b/tests/test_core.py index 55ca962..356d104 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -38,11 +38,11 @@ def test_init(cache): def test_init_disk(): - with dc.Cache(disk_pickle_protocol=1, disk_min_file_size=2 ** 20) as cache: + with dc.Cache(disk_pickle_protocol=1, disk_min_file_size=2**20) as cache: key = (None, 0, 'abc') cache[key] = 0 cache.check() - assert cache.disk_min_file_size == 2 ** 20 + assert cache.disk_min_file_size == 2**20 assert cache.disk_pickle_protocol == 1 shutil.rmtree(cache.directory, ignore_errors=True) @@ -59,15 +59,15 @@ def test_disk_reset(): assert cache._disk.min_file_size == 0 assert cache._disk.pickle_protocol == 0 - cache.reset('disk_min_file_size', 2 ** 10) + cache.reset('disk_min_file_size', 2**10) cache.reset('disk_pickle_protocol', 2) cache[1] = value cache.check() - assert cache.disk_min_file_size == 2 ** 10 + assert cache.disk_min_file_size == 2**10 assert cache.disk_pickle_protocol == 2 - assert cache._disk.min_file_size == 2 ** 10 + assert cache._disk.min_file_size == 2**10 assert cache._disk.pickle_protocol == 2 shutil.rmtree(cache.directory, ignore_errors=True) @@ -150,7 +150,7 @@ def test_pragma_error(cache): cursor.fetchall = fetchall fetchall.side_effect = [sqlite3.OperationalError] * 60000 - size = 2 ** 28 + size = 2**28 with mock.patch('time.sleep', lambda num: 0): with mock.patch.object(cache, '_local', local): @@ -177,15 +177,15 @@ def __getattr__(self, name): def test_getsetdel(cache): values = [ (None, False), - ((None,) * 2 ** 20, False), + ((None,) * 2**20, False), (1234, False), - (2 ** 512, False), + (2**512, False), (56.78, False), - (u'hello', False), - (u'hello' * 2 ** 20, False), + ('hello', False), + ('hello' * 2**20, False), (b'world', False), - (b'world' * 2 ** 20, False), - (io.BytesIO(b'world' * 2 ** 20), True), + (b'world' * 2**20, False), + (io.BytesIO(b'world' * 2**20), True), ] for key, (value, file_like) in enumerate(values): @@ -229,7 +229,7 @@ def test_get_keyerror4(cache): func = mock.Mock(side_effect=IOError(errno.ENOENT, '')) cache.reset('statistics', True) - cache[0] = b'abcd' * 2 ** 20 + cache[0] = b'abcd' * 2**20 with mock.patch('diskcache.core.open', func): with pytest.raises((IOError, KeyError, OSError)): @@ -237,7 +237,7 @@ def test_get_keyerror4(cache): def test_read(cache): - cache.set(0, b'abcd' * 2 ** 20) + cache.set(0, b'abcd' * 2**20) with cache.read(0) as reader: assert reader is not None @@ -249,7 +249,7 @@ def test_read_keyerror(cache): def test_set_twice(cache): - large_value = b'abcd' * 2 ** 20 + large_value = b'abcd' * 2**20 cache[0] = 0 cache[0] = 1 @@ -283,7 +283,7 @@ def test_set_timeout(cache): with pytest.raises(dc.Timeout): try: with mock.patch.object(cache, '_local', local): - cache.set('a', 'b' * 2 ** 20) + cache.set('a', 'b' * 2**20) finally: cache.check() @@ -299,11 +299,11 @@ def test_get(cache): assert cache.get(2, {}) == {} assert cache.get(0, expire_time=True, tag=True) == (None, None, None) - assert cache.set(0, 0, expire=None, tag=u'number') + assert cache.set(0, 0, expire=None, tag='number') assert cache.get(0, expire_time=True) == (0, None) - assert cache.get(0, tag=True) == (0, u'number') - assert cache.get(0, expire_time=True, tag=True) == (0, None, u'number') + assert cache.get(0, tag=True) == (0, 'number') + assert cache.get(0, expire_time=True, tag=True) == (0, None, 'number') def test_get_expired_fast_path(cache): @@ -359,8 +359,8 @@ def test_pop(cache): assert cache.set('delta', 210) assert cache.pop('delta', expire_time=True) == (210, None) - assert cache.set('epsilon', '0' * 2 ** 20) - assert cache.pop('epsilon') == '0' * 2 ** 20 + assert cache.set('epsilon', '0' * 2**20) + assert cache.pop('epsilon') == '0' * 2**20 def test_pop_ioerror(cache): @@ -426,11 +426,11 @@ def test_stats(cache): def test_path(cache): - cache[0] = u'abc' - large_value = b'abc' * 2 ** 20 + cache[0] = 'abc' + large_value = b'abc' * 2**20 cache[1] = large_value - assert cache.get(0, read=True) == u'abc' + assert cache.get(0, read=True) == 'abc' with cache.get(1, read=True) as reader: assert reader.name is not None @@ -465,7 +465,7 @@ def test_expire_rows(cache): def test_least_recently_stored(cache): - cache.reset('eviction_policy', u'least-recently-stored') + cache.reset('eviction_policy', 'least-recently-stored') cache.reset('size_limit', int(10.1e6)) cache.reset('cull_limit', 2) @@ -500,7 +500,7 @@ def test_least_recently_stored(cache): def test_least_recently_used(cache): - cache.reset('eviction_policy', u'least-recently-used') + cache.reset('eviction_policy', 'least-recently-used') cache.reset('size_limit', int(10.1e6)) cache.reset('cull_limit', 5) @@ -530,7 +530,7 @@ def test_least_recently_used(cache): def test_least_frequently_used(cache): - cache.reset('eviction_policy', u'least-frequently-used') + cache.reset('eviction_policy', 'least-frequently-used') cache.reset('size_limit', int(10.1e6)) cache.reset('cull_limit', 5) @@ -558,8 +558,8 @@ def test_least_frequently_used(cache): def test_check(cache): - blob = b'a' * 2 ** 20 - keys = (0, 1, 1234, 56.78, u'hello', b'world', None) + blob = b'a' * 2**20 + keys = (0, 1, 1234, 56.78, 'hello', b'world', None) for key in keys: cache[key] = blob @@ -662,12 +662,12 @@ def test_clear_timeout(cache): def test_tag(cache): - assert cache.set(0, None, tag=u'zero') + assert cache.set(0, None, tag='zero') assert cache.set(1, None, tag=1234) assert cache.set(2, None, tag=5.67) assert cache.set(3, None, tag=b'three') - assert cache.get(0, tag=True) == (None, u'zero') + assert cache.get(0, tag=True) == (None, 'zero') assert cache.get(1, tag=True) == (None, 1234) assert cache.get(2, tag=True) == (None, 5.67) assert cache.get(3, tag=True) == (None, b'three') @@ -675,11 +675,11 @@ def test_tag(cache): def test_with(cache): with dc.Cache(cache.directory) as tmp: - tmp[u'a'] = 0 - tmp[u'b'] = 1 + tmp['a'] = 0 + tmp['b'] = 1 - assert cache[u'a'] == 0 - assert cache[u'b'] == 1 + assert cache['a'] == 0 + assert cache['b'] == 1 def test_contains(cache): @@ -708,7 +708,7 @@ def test_add(cache): def test_add_large_value(cache): - value = b'abcd' * 2 ** 20 + value = b'abcd' * 2**20 assert cache.add(b'test-key', value) assert cache.get(b'test-key') == value assert not cache.add(b'test-key', value * 2) @@ -919,7 +919,7 @@ def test_push_peek_expire(cache): def test_push_pull_large_value(cache): - value = b'test' * (2 ** 20) + value = b'test' * (2**20) cache.push(value) assert cache.pull() == (500000000000000, value) assert len(cache) == 0 @@ -927,7 +927,7 @@ def test_push_pull_large_value(cache): def test_push_peek_large_value(cache): - value = b'test' * (2 ** 20) + value = b'test' * (2**20) cache.push(value) assert cache.peek() == (500000000000000, value) assert len(cache) == 1 @@ -1144,8 +1144,8 @@ def test_cull_timeout(cache): def test_key_roundtrip(cache): - key_part_0 = u'part0' - key_part_1 = u'part1' + key_part_0 = 'part0' + key_part_1 = 'part1' to_test = [ (key_part_0, key_part_1), [key_part_0, key_part_1], @@ -1354,7 +1354,7 @@ def foo(*args, **kwargs): def test_cleanup_dirs(cache): - value = b'\0' * 2 ** 20 + value = b'\0' * 2**20 start_count = len(os.listdir(cache.directory)) for i in range(10): cache[i] = value @@ -1370,7 +1370,7 @@ def test_disk_write_os_error(cache): func = mock.Mock(side_effect=[OSError] * 10) with mock.patch('diskcache.core.open', func): with pytest.raises(OSError): - cache[0] = '\0' * 2 ** 20 + cache[0] = '\0' * 2**20 def test_memoize_ignore(cache): diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index 5f83b81..b5cc2a8 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -1033,7 +1033,7 @@ def test_directory(self): self.assertTrue('tmp' in cache.directory) def test_read(self): - value = b'abcd' * 2 ** 20 + value = b'abcd' * 2**20 result = cache.set(b'test-key', value) self.assertTrue(result) diff --git a/tests/test_fanout.py b/tests/test_fanout.py index f212fac..deea03f 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -34,7 +34,7 @@ def test_init(cache): del default_settings['size_limit'] for key, value in default_settings.items(): assert getattr(cache, key) == value - assert cache.size_limit == 2 ** 27 + assert cache.size_limit == 2**27 cache.check() @@ -229,15 +229,15 @@ def test_incr_concurrent(): def test_getsetdel(cache): values = [ (None, False), - ((None,) * 2 ** 10, False), + ((None,) * 2**10, False), (1234, False), - (2 ** 512, False), + (2**512, False), (56.78, False), - (u'hello', False), - (u'hello' * 2 ** 10, False), + ('hello', False), + ('hello' * 2**10, False), (b'world', False), - (b'world' * 2 ** 10, False), - (io.BytesIO(b'world' * 2 ** 10), True), + (b'world' * 2**10, False), + (io.BytesIO(b'world' * 2**10), True), ] for key, (value, file_like) in enumerate(values): @@ -341,7 +341,7 @@ def test_tag_index(cache): def test_read(cache): - cache.set(0, b'abcd' * 2 ** 20) + cache.set(0, b'abcd' * 2**20) with cache.read(0) as reader: assert reader is not None From a53283d554fbe7bd678067f24d6f45d57de3f83b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:25:16 -0700 Subject: [PATCH 527/550] Update requirements --- requirements-dev.txt | 23 +++++++++++++++++++++++ requirements.txt | 22 ---------------------- 2 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 requirements-dev.txt diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..6149361 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,23 @@ +-e . +blue +coverage +django==4.2.* +django_redis +doc8 +flake8 +ipython +jedi +pickleDB +pylibmc +pylint +pytest +pytest-cov +pytest-django +pytest-env +pytest-xdist +rstcheck +sphinx +sqlitedict +tox +twine +wheel diff --git a/requirements.txt b/requirements.txt index efb2160..d6e1198 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,23 +1 @@ -e . -blue -coverage -django==3.2.* -django_redis -doc8 -flake8 -ipython -jedi==0.17.* # Remove after IPython bug fixed. -pickleDB -pylibmc -pylint -pytest -pytest-cov -pytest-django -pytest-env -pytest-xdist -rstcheck -sphinx -sqlitedict -tox -twine -wheel From b22a7d58c3dbf3f71fa4f9156ccbd4892ebf3fe2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:25:35 -0700 Subject: [PATCH 528/550] Update pylint --- .pylintrc | 829 ++++++++++++++++++++++++---------------------- diskcache/core.py | 6 +- 2 files changed, 439 insertions(+), 396 deletions(-) diff --git a/.pylintrc b/.pylintrc index 6baa978..dc1490a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,27 +1,77 @@ -[MASTER] +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) extension-pkg-whitelist= -# Specify a score threshold to be exceeded before program exits with error. -fail-under=10.0 +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= -# Add files or directories to the blacklist. They should be base names, not -# paths. +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. ignore=CVS -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. jobs=1 # Control the amount of potential inferred values when inferring a single @@ -36,6 +86,19 @@ load-plugins= # Pickle collected data for later comparisons. persistent=yes +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.11 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. suggestion-mode=yes @@ -44,321 +107,8 @@ suggestion-mode=yes # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, - no-member, - no-else-return, - duplicate-code, - inconsistent-return-statements, - consider-using-f-string, - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'error', 'warning', 'refactor', and 'convention' -# which contain the number of messages in each category, as well as 'statement' -# which is the total number of statements analyzed. This score is used by the -# global evaluation report (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it work, -# install the python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -#notes-rgx= - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis). It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=3000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - -# Minimum lines number of a similarity. -min-similarity-lines=4 +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= [BASIC] @@ -367,13 +117,15 @@ min-similarity-lines=4 argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- -# naming-style. +# naming-style. If left empty, argument names will be checked with the set +# naming style. #argument-rgx= # Naming style matching correct attribute names. attr-naming-style=snake_case # Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming # style. #attr-rgx= @@ -393,20 +145,30 @@ bad-names-rgxs= class-attribute-naming-style=any # Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. #class-attribute-rgx= +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + # Naming style matching correct class names. class-naming-style=PascalCase # Regular expression matching correct class names. Overrides class-naming- -# style. +# style. If left empty, class names will be checked with the set naming style. #class-rgx= # Naming style matching correct constant names. const-naming-style=UPPER_CASE # Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming # style. #const-rgx= @@ -418,7 +180,8 @@ docstring-min-length=-1 function-naming-style=snake_case # Regular expression matching correct function names. Overrides function- -# naming-style. +# naming-style. If left empty, function names will be checked with the set +# naming style. #function-rgx= # Good variable names which should always be accepted, separated by a comma. @@ -440,21 +203,22 @@ include-naming-hint=no inlinevar-naming-style=any # Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. #inlinevar-rgx= # Naming style matching correct method names. method-naming-style=snake_case # Regular expression matching correct method names. Overrides method-naming- -# style. +# style. If left empty, method names will be checked with the set naming style. #method-rgx= # Naming style matching correct module names. module-naming-style=snake_case # Regular expression matching correct module names. Overrides module-naming- -# style. +# style. If left empty, module names will be checked with the set naming style. #module-rgx= # Colon-delimited sets of names that determine each other's naming style when @@ -470,90 +234,56 @@ no-docstring-rgx=^_ # These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + # Naming style matching correct variable names. variable-naming-style=snake_case # Regular expression matching correct variable names. Overrides variable- -# naming-style. +# naming-style. If left empty, variable names will be checked with the set +# naming style. #variable-rgx= -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=no +[CLASSES] -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=no - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level= - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[CLASSES] +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, setUp, + asyncSetUp, __post_init__ # List of member names, which should be excluded from the protected access # warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls +valid-metaclass-classmethod-first-arg=mcs [DESIGN] +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + # Maximum number of arguments for function / method. max-args=8 @@ -587,7 +317,320 @@ min-public-methods=2 [EXCEPTIONS] -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=2500 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + consider-using-f-string, + no-member, + no-else-return, + no-else-raise, + inconsistent-return-statements + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=20 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work.. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/diskcache/core.py b/diskcache/core.py index 05a0854..9f3a597 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -170,7 +170,7 @@ def get(self, key, raw): :return: corresponding Python key """ - # pylint: disable=no-self-use,unidiomatic-typecheck + # pylint: disable=unidiomatic-typecheck if raw: return bytes(key) if type(key) is sqlite3.Binary else key else: @@ -228,7 +228,6 @@ def store(self, value, read, key=UNKNOWN): return len(result), MODE_PICKLE, filename, None def _write(self, full_path, iterator, mode, encoding=None): - # pylint: disable=no-self-use full_dir, _ = op.split(full_path) for count in range(1, 11): @@ -264,7 +263,7 @@ def fetch(self, mode, filename, value, read): :raises: IOError if the value cannot be read """ - # pylint: disable=no-self-use,unidiomatic-typecheck,consider-using-with + # 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: @@ -1378,6 +1377,7 @@ def delete(self, key, retry=False): :raises Timeout: if database timeout occurs """ + # pylint: disable=unnecessary-dunder-call try: return self.__delitem__(key, retry=retry) except KeyError: From 471aa5e551aed186dd34d5bc7d345be835efd6f3 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:26:38 -0700 Subject: [PATCH 529/550] Drop Python 3.7 from testing --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3735ebc..e7217a7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=bluecheck,doc8,docs,isortcheck,flake8,mypy,pylint,rstcheck,py37,py38,py39,py310,py311 +envlist=bluecheck,doc8,docs,isortcheck,flake8,mypy,pylint,rstcheck,py38,py39,py310,py311 skip_missing_interpreters=True [testenv] From c14345f105f14eba45986b09ec96d87b8997c9cc Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:42:38 -0700 Subject: [PATCH 530/550] Update tests for Django 4.2 --- tests/test_djangocache.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_djangocache.py b/tests/test_djangocache.py index b5cc2a8..734ba1b 100644 --- a/tests/test_djangocache.py +++ b/tests/test_djangocache.py @@ -870,7 +870,6 @@ def test_custom_key_func(self): def test_cache_write_unpicklable_object(self): fetch_middleware = FetchFromCacheMiddleware(empty_response) - fetch_middleware.cache = cache request = self.factory.get('/cache/test') request._cache_update_cache = True @@ -887,7 +886,6 @@ def get_response(req): return response update_middleware = UpdateCacheMiddleware(get_response) - update_middleware.cache = cache response = update_middleware(request) get_cache_data = fetch_middleware.process_request(request) From 0294d58cd0cb72dd6affbbc46b3ff05d96d015cc Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:43:15 -0700 Subject: [PATCH 531/550] Bump version to v5.5.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index f7aa771..8d7e28c 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.4.0' -__build__ = 0x050400 +__version__ = '5.5.0' +__build__ = 0x050500 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2023 Grant Jenks' From 6cd6888a16be1d531a19ca91d5e0daa6edac9718 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:45:14 -0700 Subject: [PATCH 532/550] Drop 3.7 from CI --- .github/workflows/integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 2d83ad3..07aceec 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -32,7 +32,7 @@ jobs: max-parallel: 8 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.7, 3.8, 3.9, '3.10', 3.11] + python-version: [3.8, 3.9, '3.10', 3.11] steps: - name: Set up Python ${{ matrix.python-version }} x64 From bbac13b54f9ac0a5c45b7cfbd242869b41042cac Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:52:57 -0700 Subject: [PATCH 533/550] Install dev requirements for wheel package --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 676593c..21b6e5b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install -r requirements.txt + pip install -r requirements-dev.txt - name: Create source dist run: python setup.py sdist From 0f5d8ed63406e26de96701f98aa585b4fb26f6dd Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 10 Apr 2023 23:53:20 -0700 Subject: [PATCH 534/550] Bump version to 5.5.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 8d7e28c..95dafb3 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.5.0' -__build__ = 0x050500 +__version__ = '5.5.1' +__build__ = 0x050501 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2023 Grant Jenks' From fe5ee43ac5df1847556f280c696ab921f0910be2 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 16 Apr 2023 21:42:13 -0700 Subject: [PATCH 535/550] Close the cache explicitly before deleting the reference --- diskcache/persistent.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index c3d570b..cce3736 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -533,12 +533,13 @@ def reverse(self): # GrantJ 2019-03-22 Consider using an algorithm that swaps the values # at two keys. Like self._cache.swap(key1, key2, retry=True) The swap # method would exchange the values at two given keys. Then, using a - # forward iterator and a reverse iterator, the reversis method could + # forward iterator and a reverse iterator, the reverse method could # avoid making copies of the values. temp = Deque(iterable=reversed(self)) self.clear() self.extend(temp) directory = temp.directory + temp.close() del temp rmtree(directory) From 9380c784d9e2954611b2ea309f1c657602085f25 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Sun, 16 Apr 2023 23:02:24 -0700 Subject: [PATCH 536/550] Oops, close the cache, not the deque --- diskcache/persistent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index cce3736..01cf4e3 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -539,7 +539,7 @@ def reverse(self): self.clear() self.extend(temp) directory = temp.directory - temp.close() + temp._cache.close() del temp rmtree(directory) From f5a17ff0959a4cc7147d45a4cc8d8eef8d7416b0 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Apr 2023 19:00:34 -0700 Subject: [PATCH 537/550] Shutup pylint --- diskcache/persistent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/diskcache/persistent.py b/diskcache/persistent.py index 01cf4e3..c3f22b5 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -530,6 +530,7 @@ def reverse(self): ['c', 'b', 'a'] """ + # pylint: disable=protected-access # GrantJ 2019-03-22 Consider using an algorithm that swaps the values # at two keys. Like self._cache.swap(key1, key2, retry=True) The swap # method would exchange the values at two given keys. Then, using a From ef94856d2447fa9662bc62557f9b96ba6f131e15 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Apr 2023 19:01:25 -0700 Subject: [PATCH 538/550] Bump version to 5.5.2 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 95dafb3..134c88a 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.5.1' -__build__ = 0x050501 +__version__ = '5.5.2' +__build__ = 0x050502 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2023 Grant Jenks' From 74e554c5d9340765f6fd6f7891da49a420e92b70 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Apr 2023 19:05:01 -0700 Subject: [PATCH 539/550] Bump versions of checkout and setup-python --- .github/workflows/integration.yml | 8 ++++---- .github/workflows/release.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 07aceec..b596fc6 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -12,9 +12,9 @@ jobs: check: [bluecheck, doc8, docs, flake8, isortcheck, mypy, pylint, rstcheck] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies @@ -36,12 +36,12 @@ jobs: steps: - name: Set up Python ${{ matrix.python-version }} x64 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: x64 - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install tox run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21b6e5b..efe73c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install libmemcached-dev run: | @@ -19,7 +19,7 @@ jobs: sudo apt-get install libmemcached-dev - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.11' From 35dbeabd283b242e9afd33713a5cea5cd260f51d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Apr 2023 20:38:04 -0700 Subject: [PATCH 540/550] Add maxlen parameter to diskcache.Deque (#191) * Add maxlen parameter to diskcache.Deque --- diskcache/djangocache.py | 5 ++- diskcache/fanout.py | 5 ++- diskcache/persistent.py | 91 +++++++++++++++++++++++++++++++--------- docs/tutorial.rst | 3 ++ tests/test_deque.py | 27 +++++++++++- 5 files changed, 107 insertions(+), 24 deletions(-) diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 8bf85ce..5dc8ce2 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -44,14 +44,15 @@ def cache(self, name): """ return self._cache.cache(name) - def deque(self, name): + def deque(self, name, maxlen=None): """Return Deque with given `name` in subdirectory. :param str name: subdirectory name for Deque + :param maxlen: max length (default None, no max) :return: Deque with given name """ - return self._cache.deque(name) + return self._cache.deque(name, maxlen=maxlen) def index(self, name): """Return Index with given `name` in subdirectory. diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 5283490..50005fc 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -612,7 +612,7 @@ def cache(self, name, timeout=60, disk=None, **settings): _caches[name] = temp return temp - def deque(self, name): + def deque(self, name, maxlen=None): """Return Deque with given `name` in subdirectory. >>> cache = FanoutCache() @@ -626,6 +626,7 @@ def deque(self, name): 1 :param str name: subdirectory name for Deque + :param maxlen: max length (default None, no max) :return: Deque with given name """ @@ -641,7 +642,7 @@ def deque(self, name): disk=self._disk, eviction_policy='none', ) - deque = Deque.fromcache(cache) + deque = Deque.fromcache(cache, maxlen=maxlen) _deques[name] = deque return deque diff --git a/diskcache/persistent.py b/diskcache/persistent.py index c3f22b5..522bb74 100644 --- a/diskcache/persistent.py +++ b/diskcache/persistent.py @@ -75,7 +75,7 @@ class Deque(Sequence): """ - def __init__(self, iterable=(), directory=None): + def __init__(self, iterable=(), directory=None, maxlen=None): """Initialize deque instance. If directory is None then temporary directory created. The directory @@ -86,10 +86,11 @@ def __init__(self, iterable=(), directory=None): """ self._cache = Cache(directory, eviction_policy='none') - self.extend(iterable) + self._maxlen = float('inf') if maxlen is None else maxlen + self._extend(iterable) @classmethod - def fromcache(cls, cache, iterable=()): + def fromcache(cls, cache, iterable=(), maxlen=None): """Initialize deque using `cache`. >>> cache = Cache() @@ -111,7 +112,8 @@ def fromcache(cls, cache, iterable=()): # pylint: disable=no-member,protected-access self = cls.__new__(cls) self._cache = cache - self.extend(iterable) + self._maxlen = float('inf') if maxlen is None else maxlen + self._extend(iterable) return self @property @@ -124,6 +126,31 @@ def directory(self): """Directory path where deque is stored.""" return self._cache.directory + @property + def maxlen(self): + """Max length of the deque.""" + return self._maxlen + + @maxlen.setter + def maxlen(self, value): + """Set max length of the deque. + + Pops items from left while length greater than max. + + >>> deque = Deque() + >>> deque.extendleft('abcde') + >>> deque.maxlen = 3 + >>> list(deque) + ['c', 'd', 'e'] + + :param value: max length + + """ + self._maxlen = value + with self._cache.transact(retry=True): + while len(self._cache) > self._maxlen: + self._popleft() + def _index(self, index, func): len_self = len(self) @@ -244,7 +271,7 @@ def __iadd__(self, iterable): :return: deque with added items """ - self.extend(iterable) + self._extend(iterable) return self def __iter__(self): @@ -292,10 +319,11 @@ def __reversed__(self): pass def __getstate__(self): - return self.directory + return self.directory, self.maxlen def __setstate__(self, state): - self.__init__(directory=state) + directory, maxlen = state + self.__init__(directory=directory, maxlen=maxlen) def append(self, value): """Add `value` to back of deque. @@ -310,7 +338,12 @@ def append(self, value): :param value: value to add to back of deque """ - self._cache.push(value, retry=True) + with self._cache.transact(retry=True): + self._cache.push(value, retry=True) + if len(self._cache) > self._maxlen: + self._popleft() + + _append = append def appendleft(self, value): """Add `value` to front of deque. @@ -325,7 +358,12 @@ def appendleft(self, value): :param value: value to add to front of deque """ - self._cache.push(value, side='front', retry=True) + with self._cache.transact(retry=True): + self._cache.push(value, side='front', retry=True) + if len(self._cache) > self._maxlen: + self._pop() + + _appendleft = appendleft def clear(self): """Remove all elements from deque. @@ -340,6 +378,13 @@ def clear(self): """ self._cache.clear(retry=True) + _clear = clear + + def copy(self): + """Copy deque with same directory and max length.""" + TypeSelf = type(self) + return TypeSelf(directory=self.directory, maxlen=self.maxlen) + def count(self, value): """Return number of occurrences of `value` in deque. @@ -365,7 +410,9 @@ def extend(self, iterable): """ for value in iterable: - self.append(value) + self._append(value) + + _extend = extend def extendleft(self, iterable): """Extend front side of deque with value from `iterable`. @@ -379,7 +426,7 @@ def extendleft(self, iterable): """ for value in iterable: - self.appendleft(value) + self._appendleft(value) def peek(self): """Peek at value at back of deque. @@ -459,6 +506,8 @@ def pop(self): raise IndexError('pop from an empty deque') return value + _pop = pop + def popleft(self): """Remove and return value at front of deque. @@ -483,6 +532,8 @@ def popleft(self): raise IndexError('pop from an empty deque') return value + _popleft = popleft + def remove(self, value): """Remove first occurrence of `value` in deque. @@ -537,8 +588,8 @@ def reverse(self): # forward iterator and a reverse iterator, the reverse method could # avoid making copies of the values. temp = Deque(iterable=reversed(self)) - self.clear() - self.extend(temp) + self._clear() + self._extend(temp) directory = temp.directory temp._cache.close() del temp @@ -575,22 +626,22 @@ def rotate(self, steps=1): for _ in range(steps): try: - value = self.pop() + value = self._pop() except IndexError: return else: - self.appendleft(value) + self._appendleft(value) else: steps *= -1 steps %= len_self for _ in range(steps): try: - value = self.popleft() + value = self._popleft() except IndexError: return else: - self.append(value) + self._append(value) __hash__ = None # type: ignore @@ -669,7 +720,9 @@ def __init__(self, *args, **kwargs): args = args[1:] directory = None self._cache = Cache(directory, eviction_policy='none') - self.update(*args, **kwargs) + self._update(*args, **kwargs) + + _update = MutableMapping.update @classmethod def fromcache(cls, cache, *args, **kwargs): @@ -695,7 +748,7 @@ def fromcache(cls, cache, *args, **kwargs): # pylint: disable=no-member,protected-access self = cls.__new__(cls) self._cache = cache - self.update(*args, **kwargs) + self._update(*args, **kwargs) return self @property diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 1963635..69277d3 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -565,6 +565,9 @@ access and editing at both front and back sides. :class:`Deque 4 >>> other.popleft() 'foo' + >>> thing = Deque('abcde', maxlen=3) + >>> list(thing) + ['c', 'd', 'e'] :class:`Deque ` objects provide an efficient and safe means of cross-thread and cross-process communication. :class:`Deque ` diff --git a/tests/test_deque.py b/tests/test_deque.py index add7714..71c69e2 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -77,6 +77,20 @@ def test_getsetdel(deque): assert len(deque) == 0 +def test_append(deque): + deque.maxlen = 3 + for item in 'abcde': + deque.append(item) + assert deque == 'cde' + + +def test_appendleft(deque): + deque.maxlen = 3 + for item in 'abcde': + deque.appendleft(item) + assert deque == 'edc' + + def test_index_positive(deque): cache = mock.MagicMock() cache.__len__.return_value = 3 @@ -131,9 +145,12 @@ def test_state(deque): sequence = list('abcde') deque.extend(sequence) assert deque == sequence + deque.maxlen = 3 + assert list(deque) == sequence[-3:] state = pickle.dumps(deque) values = pickle.loads(state) - assert values == sequence + assert values == sequence[-3:] + assert values.maxlen == 3 def test_compare(deque): @@ -161,6 +178,14 @@ def test_repr(): assert repr(deque) == 'Deque(directory=%r)' % directory +def test_copy(deque): + sequence = list('abcde') + deque.extend(sequence) + temp = deque.copy() + assert deque == sequence + assert temp == sequence + + def test_count(deque): deque += 'abbcccddddeeeee' From 4beffe892a6c4352098a79614de40649d7e9f88e Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Apr 2023 20:40:55 -0700 Subject: [PATCH 541/550] Bump version to 5.6.0 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 134c88a..8647b9a 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.5.2' -__build__ = 0x050502 +__version__ = '5.6.0' +__build__ = 0x050600 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2023 Grant Jenks' From cffbcec2b198e3a296ec294bd43da37fc559645b Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Apr 2023 22:30:26 -0700 Subject: [PATCH 542/550] Fix docs re: JSONDisk --- docs/tutorial.rst | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 69277d3..2eb454d 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -821,30 +821,28 @@ example below uses compressed JSON, available for convenience as .. code-block:: python - import json, zlib - class JSONDisk(diskcache.Disk): def __init__(self, directory, compress_level=1, **kwargs): self.compress_level = compress_level - super(JSONDisk, self).__init__(directory, **kwargs) + super().__init__(directory, **kwargs) def put(self, key): json_bytes = json.dumps(key).encode('utf-8') data = zlib.compress(json_bytes, self.compress_level) - return super(JSONDisk, self).put(data) + return super().put(data) def get(self, key, raw): - data = super(JSONDisk, self).get(key, raw) + data = super().get(key, raw) return json.loads(zlib.decompress(data).decode('utf-8')) - def store(self, value, read): + def store(self, value, read, key=UNKNOWN): if not read: json_bytes = json.dumps(value).encode('utf-8') value = zlib.compress(json_bytes, self.compress_level) - return super(JSONDisk, self).store(value, read) + return super().store(value, read, key=key) def fetch(self, mode, filename, value, read): - data = super(JSONDisk, self).fetch(mode, filename, value, read) + data = super().fetch(mode, filename, value, read) if not read: data = json.loads(zlib.decompress(data).decode('utf-8')) return data From f81160f22af9e8af0e07e179808280188146a020 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Apr 2023 22:46:44 -0700 Subject: [PATCH 543/550] Support pathlib.Path as directory argument --- diskcache/core.py | 1 + diskcache/fanout.py | 1 + tests/test_core.py | 8 ++++++++ tests/test_fanout.py | 8 ++++++++ 4 files changed, 18 insertions(+) diff --git a/diskcache/core.py b/diskcache/core.py index 9f3a597..af65454 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -433,6 +433,7 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): if directory is None: directory = tempfile.mkdtemp(prefix='diskcache-') + directory = str(directory) directory = op.expanduser(directory) directory = op.expandvars(directory) diff --git a/diskcache/fanout.py b/diskcache/fanout.py index 50005fc..9822ee4 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -30,6 +30,7 @@ def __init__( """ if directory is None: directory = tempfile.mkdtemp(prefix='diskcache-') + directory = str(directory) directory = op.expanduser(directory) directory = op.expandvars(directory) diff --git a/tests/test_core.py b/tests/test_core.py index 356d104..788afef 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5,6 +5,7 @@ import io import os import os.path as op +import pathlib import pickle import shutil import sqlite3 @@ -37,6 +38,13 @@ def test_init(cache): cache.close() +def test_init_path(cache): + path = pathlib.Path(cache.directory) + other = dc.Cache(path) + other.close() + assert cache.directory == other.directory + + def test_init_disk(): with dc.Cache(disk_pickle_protocol=1, disk_min_file_size=2**20) as cache: key = (None, 0, 'abc') diff --git a/tests/test_fanout.py b/tests/test_fanout.py index deea03f..af221b6 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -5,6 +5,7 @@ import io import os import os.path as op +import pathlib import pickle import shutil import subprocess as sp @@ -44,6 +45,13 @@ def test_init(cache): cache.check() +def test_init_path(cache): + path = pathlib.Path(cache.directory) + other = dc.FanoutCache(path) + other.close() + assert cache.directory == other.directory + + def test_set_get_delete(cache): for value in range(100): cache.set(value, value) From 4d3068625a3edcd2f5a1f6f104ef621f1f7ea395 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Mon, 17 Apr 2023 22:47:22 -0700 Subject: [PATCH 544/550] Bump version to 5.6.1 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 8647b9a..1931a0d 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.6.0' -__build__ = 0x050600 +__version__ = '5.6.1' +__build__ = 0x050601 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2023 Grant Jenks' From 17a5f42facc312dae6e98b7b53345e2ed02be21d Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 30 Aug 2023 22:56:38 -0700 Subject: [PATCH 545/550] Bug fix: Fix peek when value is so large that a file is used (#288) Error caused by copy/paste from pull(). --- diskcache/core.py | 3 --- tests/test_deque.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/diskcache/core.py b/diskcache/core.py index af65454..c7c8486 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -1703,9 +1703,6 @@ def peek( except IOError: # Key was deleted before we could retrieve result. continue - finally: - if name is not None: - self._disk.remove(name) break if expire_time and tag: diff --git a/tests/test_deque.py b/tests/test_deque.py index 71c69e2..f997a86 100644 --- a/tests/test_deque.py +++ b/tests/test_deque.py @@ -302,3 +302,13 @@ def test_rotate_indexerror_negative(deque): with mock.patch.object(deque, '_cache', cache): deque.rotate(-1) + + +def test_peek(deque): + value = b'x' * 100_000 + deque.append(value) + assert len(deque) == 1 + assert deque.peek() == value + assert len(deque) == 1 + assert deque.peek() == value + assert len(deque) == 1 From 63a5f6068b77fe9c02c8f310758fa1f05ae1ae04 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 30 Aug 2023 22:58:11 -0700 Subject: [PATCH 546/550] Bump version to 5.6.2 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 1931a0d..719640f 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.6.1' -__build__ = 0x050601 +__version__ = '5.6.2' +__build__ = 0x050602 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2023 Grant Jenks' From 23d10dce8f4be9c00df4786d508964b3b7d72b27 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 30 Aug 2023 23:09:54 -0700 Subject: [PATCH 547/550] Update release.yml to use pypa/gh-action-pypi-publish --- .github/workflows/release.yml | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index efe73c6..33b3a8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,35 +9,22 @@ jobs: upload: runs-on: ubuntu-latest + permissions: + id-token: write steps: - uses: actions/checkout@v3 - - name: Install libmemcached-dev - run: | - sudo apt-get update - sudo apt-get install libmemcached-dev - - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.11' - - name: Install dependencies - run: | - pip install --upgrade pip - pip install -r requirements-dev.txt - - - name: Create source dist - run: python setup.py sdist + - name: Install build + run: pip install build - - name: Create wheel dist - run: python setup.py bdist_wheel + - name: Create build + run: python -m build - - name: Upload with twine - env: - TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} - TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} - run: | - ls -l dist/* - twine upload dist/* + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 From 323787f507a6456c56cce213156a78b17073fe00 Mon Sep 17 00:00:00 2001 From: Grant Jenks Date: Wed, 30 Aug 2023 23:10:27 -0700 Subject: [PATCH 548/550] Bump version to 5.6.3 --- diskcache/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 719640f..7757d66 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -61,8 +61,8 @@ pass __title__ = 'diskcache' -__version__ = '5.6.2' -__build__ = 0x050602 +__version__ = '5.6.3' +__build__ = 0x050603 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2023 Grant Jenks' From 9cd3816333fa34cb30d6cc2a7f227b6b1cdb793c Mon Sep 17 00:00:00 2001 From: ddorian Date: Tue, 27 Feb 2024 00:30:46 +0100 Subject: [PATCH 549/550] Change `Cache_expire_time` to a partial index because we don't need to query rows efficiently `where expire_time IS NULL` (#305) --- diskcache/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index c7c8486..ad9ad4c 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -531,7 +531,7 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): sql( 'CREATE INDEX IF NOT EXISTS Cache_expire_time ON' - ' Cache (expire_time)' + ' Cache (expire_time) WHERE expire_time IS NOT NULL' ) query = EVICTION_POLICY[self.eviction_policy]['init'] From ebfa37cd99d7ef716ec452ad8af4b4276a8e2233 Mon Sep 17 00:00:00 2001 From: ddorian Date: Sun, 3 Mar 2024 02:19:29 +0100 Subject: [PATCH 550/550] Change `Cache_tag_rowid` to a partial index because we don't need to query rows efficiently `where tag IS NULL` (#307) * Change `Cache_tag_rowid` to a partial index because we don't need to query rows efficiently `where tag IS NULL` * Fix formatting --- diskcache/core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index ad9ad4c..7a3d23b 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -2028,7 +2028,10 @@ def create_tag_index(self): """ sql = self._sql - sql('CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON Cache(tag, rowid)') + sql( + 'CREATE INDEX IF NOT EXISTS Cache_tag_rowid ON Cache(tag, rowid) ' + 'WHERE tag IS NOT NULL' + ) self.reset('tag_index', 1) def drop_tag_index(self):