Commits

Mike Bayer committed ab1110e

- Made an adjustment to the test suite that was preventing
the redis tests from running on Py3K.
- Fixed bug in DBM backend whereby if an error occurred
during the "write" operation, the file lock, if enabled,
would not be released, thereby deadlocking the app. #15
- The :func:`.util.function_key_generator` used by the
function decorator no longer coerces non-unicode
arguments into a Python unicode object on Python 2.x;
this causes failures on backends such as DBM which
on Python 2.x apparently require bytestrings. The
key_mangler is still needed if actual unicode arguments
are being used by the decorated function, however. #12
- fixed an errant "except KeyError, e" in the DBM backend
that caused failure to run on Py3K, this was part of
inklesspen's checkin r8383f03853ec.
- Added changelog for r8383f03853ec, support gdbm backend
which doesn't include get().
- Added changelog for the other part of r2b7b3689050f which
is that "password" is now accepted by the redis backend
also; added version note.

Comments (0)

Files changed (7)

docs/build/changelog.rst

     :version: 0.4.1
 
     .. change:
+        :tags: bug
+        :tickets: 15
+
+      Fixed bug in DBM backend whereby if an error occurred
+      during the "write" operation, the file lock, if enabled,
+      would not be released, thereby deadlocking the app.
+
+    .. change:
+        :tags: bug
+        :tickets: 12
+
+      The :func:`.util.function_key_generator` used by the
+      function decorator no longer coerces non-unicode
+      arguments into a Python unicode object on Python 2.x;
+      this causes failures on backends such as DBM which
+      on Python 2.x apparently require bytestrings.  The
+      key_mangler is still needed if actual unicode arguments
+      are being used by the decorated function, however.
+
+    .. change:
         :tags: feature
 
       Redis backend now accepts optional "url" argument,
       will be passed to the new ``StrictRedis.from_url()``
       method to determine connection info.  Courtesy
-      inklesspen.
+      Jon Rosebaugh.
+
+    .. change:
+        :tags: feature
+
+      Redis backend now accepts optional "password"
+      argument.  Courtesy Jon Rosebaugh.
+
+    .. change:
+        :tags: feature
+
+      DBM backend has "fallback" when calling dbm.get() to
+      instead use dictionary access + KeyError, in the case
+      that the "gdbm" backend is used which does not include
+      .get().  Courtesy Jon Rosebaugh.
 
 .. changelog::
     :version: 0.4.0

dogpile/cache/backends/file.py

                 # gdbm objects lack a .get method
                 try:
                     value = dbm[key]
-                except KeyError, e:
+                except KeyError:
                     value = NO_VALUE
             if value is not NO_VALUE:
                 value = compat.pickle.loads(value)
     @contextmanager
     def read(self):
         self.acquire_read_lock(True)
-        yield
-        self.release_read_lock()
+        try:
+            yield
+        finally:
+            self.release_read_lock()
 
     @contextmanager
     def write(self):
         self.acquire_write_lock(True)
-        yield
-        self.release_write_lock()
+        try:
+            yield
+        finally:
+            self.release_write_lock()
 
     def acquire_read_lock(self, wait):
         return self._acquire(wait, os.O_RDONLY, fcntl.LOCK_SH)

dogpile/cache/backends/redis.py

 
     :param password: string, default is no password.
 
+     .. versionadded:: 0.4.1
+
     :param port: integer, default is ``6379``.
 
     :param db: integer, default is ``0``.

dogpile/cache/compat.py

 if py3k: # pragma: no cover
     string_types = str,
     text_type = str
+    string_type = str
 
     import configparser
     import io
 else:
     string_types = basestring,
     text_type = unicode
+    string_type = str
 
     import ConfigParser as configparser
     import StringIO as io

dogpile/cache/util.py

                     "function does not accept keyword arguments.")
         if has_self:
             args = args[1:]
-        return namespace + "|" + " ".join(map(compat.text_type, args))
+        return namespace + "|" + " ".join(map(compat.string_type, args))
     return generate_key
 
 def sha1_mangle_key(key):

tests/cache/_fixtures.py

             raise SkipTest("Backend %s not installed" % cls.backend)
         cls._check_backend_available(backend)
 
+    def tearDown(self):
+        if self._region_inst:
+            for key in self._keys:
+                self._region_inst.delete(key)
+            self._keys.clear()
+        elif self._backend_inst:
+            self._backend_inst.delete("some_key")
+
     @classmethod
     def _check_backend_available(cls, backend):
         pass
     _region_inst = None
     _backend_inst = None
 
+    _keys = set()
+
     def _region(self, region_args={}, config_args={}):
         _region_args = self.region_args.copy()
         _region_args.update(**region_args)
         _config_args = self.config_args.copy()
         _config_args.update(config_args)
 
+        def _store_keys(key):
+            if existing_key_mangler:
+                key = existing_key_mangler(key)
+            self._keys.add(key)
+            return key
         self._region_inst = reg = CacheRegion(**_region_args)
+
+        existing_key_mangler = self._region_inst.key_mangler
+        self._region_inst.key_mangler = _store_keys
+
+
         reg.configure(self.backend, **_config_args)
         return reg
 
         return self._backend_inst
 
 class _GenericBackendTest(_GenericBackendFixture, TestCase):
-    def tearDown(self):
-        if self._region_inst:
-            self._region_inst.delete("some key")
-        elif self._backend_inst:
-            self._backend_inst.delete("some_key")
 
     def test_backend_get_nothing(self):
         backend = self._backend()
         # run a basic dogpile concurrency test.
         # note the concurrency of dogpile itself
         # is intensively tested as part of dogpile.
-        reg = self._region(config_args={"expiration_time":.25})
+        reg = self._region(config_args={"expiration_time": .25})
         lock = Lock()
         canary = []
         def creator():
         eq_(reg.get("some key"), NO_VALUE)
 
     def test_region_expire(self):
-        reg = self._region(config_args={"expiration_time":.25})
+        reg = self._region(config_args={"expiration_time": .25})
         counter = itertools.count(1)
         def creator():
             return "some value %d" % next(counter)
         eq_(reg.get_or_create("some key", creator), "some value 2")
         eq_(reg.get("some key"), "some value 2")
 
+    def test_decorated_fn_functionality(self):
+        # test for any quirks in the fn decoration that interact
+        # with the backend.
+
+        reg = self._region()
+
+        counter = itertools.count(1)
+
+        @reg.cache_on_arguments()
+        def my_function(x, y):
+            return next(counter) + x + y
+
+        eq_(my_function(3, 4), 8)
+        eq_(my_function(5, 6), 13)
+        eq_(my_function(3, 4), 8)
+        eq_(my_function(4, 3), 10)
+
+        my_function.invalidate(4, 3)
+        eq_(my_function(4, 3), 11)
+
+    def test_exploding_value_fn(self):
+        reg = self._region()
+        def boom():
+            raise Exception("boom")
+
+        assert_raises_message(
+            Exception,
+            "boom",
+            reg.get_or_create, "some_key", boom
+        )
+
 class _GenericMutexTest(_GenericBackendFixture, TestCase):
     def test_mutex(self):
         backend = self._backend()

tests/cache/test_redis_backend.py

         try:
             client = backend._create_client()
             client.set("x", "y")
-            assert client.get("x") == "y"
+            # on py3k it appears to return b"y"
+            assert client.get("x").decode("ascii") == "y"
             client.delete("x")
         except:
             raise SkipTest(
             'host': '127.0.0.1',
             'port': 6379,
             'db': 0,
-            'distributed_lock':True,
+            'distributed_lock': True,
             }
     }
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.