Skip to main content

Cache descriptors for Python and Zope

Project description

gocept.cache

Method cache

Memoize with timeout

Memoize with timeout caches methods with a certain timeout:

>>> import math
>>> import gocept.cache.method
>>>
>>> class Point(object):
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @gocept.cache.method.Memoize(0.1)
...     def distance(self, x, y):
...         print('computing distance')
...         return math.sqrt((self.x - x)**2 + (self.y - y)**2)
...
...     @gocept.cache.method.Memoize(0.1, ignore_self=True)
...     def add_one(self, i):
...         if not isinstance(i, int):
...             print("I want an int")
...         else:
...             print('adding one')
...             return i + 1
...
>>> point = Point(1.0, 2.0)

When we first ask for the distance it is computed:

>>> point.distance(2, 2)
computing distance
1.0

The second time the distance is not computed but returned from the cache:

>>> point.distance(2, 2)
1.0

Now, let’s wait 0.1 secondes, the value we set as cache timeout. After that the distance is computed again:

>>> import time
>>> time.sleep(0.5)
>>> point.distance(2, 2)
computing distance
1.0

When we create a new instance, the new instance gets its own cache:

>>> p2 = Point(1.0, 2.0)
>>> p2.distance(2, 2)
computing distance
1.0

It’s also possible to explicitly ignore self. We did this for the add_one method:

>>> point.add_one(3)
adding one
4

The second time it’s not computed as you would expect: >>> point.add_one(3) 4

If we ask p2 now the result is not computed as well:

>>> p2.add_one(3)
4

If we put a non hashable argument into a memoized function it will not be cached:

>>> point.add_one({'a': 1})
I want an int
>>> point.add_one({'a': 1})
I want an int

The decorated method can be inspected and yields the same results as the original:

>>> from inspect import signature
>>> Point.distance.__name__
'distance'
>>> tuple(signature(Point.distance).parameters.keys())
('self', 'x', 'y')

Explicitly exclude caching of a certain value

Especially when talking to external systems, you want to handle errors (i.e. by returning an emtpy result). But normally you do not want to cache this special return value.

This is our database.

>>> class DB(object):
...
...     call_count = 0
...
...     def get_country(self, zip):
...         self.call_count += 1
...         if self.call_count % 2 == 0:
...             raise ValueError('Some strange response')
...         return 'Somecountry'
...

It will throw an exception with every 2nd call:

>>> db = DB()
>>> db.get_country(12345)
'Somecountry'
>>> db.get_country(12345)
Traceback (most recent call last):
...
ValueError: Some strange response
>>> db.get_country(12345)
'Somecountry'
>>> db.get_country(12345)
Traceback (most recent call last):
...
ValueError: Some strange response

Now we use do_not_cache_and_return to specify that we do not want to cache if there was en error.

>>> import gocept.cache.method
>>>
>>> class Country(object):
...
...     db = DB()
...
...     @gocept.cache.method.Memoize(1000)
...     def by_zip(self, zip):
...         try:
...             return self.db.get_country(zip)
...         except ValueError:
...             return gocept.cache.method.do_not_cache_and_return(
...                     'DB is down.')
...
>>> country = Country()

First call will get cached, so we get the correct country with every call:

>>> country.by_zip(12345)
'Somecountry'
>>> country.by_zip(12345)
'Somecountry'
>>> country.by_zip(12345)
'Somecountry'

By using a new zip, the get_country method is called the second time, and there will be an exception, which is not cached:

>>> country.by_zip(54321)
'DB is down.'

Calling it again will call get_country, because special return value is not cached:

>>> country.by_zip(54321)
'Somecountry'

Now we always get the cached value:

>>> country.by_zip(54321)
'Somecountry'

Store memoizations on an attribute

If you want more control over the cache used by gocept.cache.method.Memoize (e. g. you want to associate it with a gocept.cache.property.CacheDataManager to invalidate it on transaction boundaries), you can use the @memoize_on_attribute decorator to retrieve the cache-dictionary from the instance:

>>> class Bar(object):
...     cache = {}
...
...     @gocept.cache.method.memoize_on_attribute('cache', 10)
...     def echo(self, x):
...         print('miss')
...         return x
>>> bar = Bar()
>>> bar.echo(5)
miss
5
>>> bar.echo(5)
5
>>> bar.cache.clear()
>>> bar.echo(5)
miss
5

This decorator should be used on methods, not on plain functions, since it must be able to retrieve the cache-dictionary from the first argument of the function (which is ‘self’ for methods):

>>> @gocept.cache.method.memoize_on_attribute('cache', 10)
... def bar():
...     print('foo')
>>> bar()
Traceback (most recent call last):
TypeError: gocept.cache.method.memoize_on_attribute could not retrieve cache attribute 'cache' for function <function bar at 0x...>
>>> @gocept.cache.method.memoize_on_attribute('cache', 10)
... def baz(x):
...     print('foo')
>>> baz(5)
Traceback (most recent call last):
TypeError: gocept.cache.method.memoize_on_attribute could not retrieve cache attribute 'cache' for function <function baz at 0x...>

Cached Properties

Transaction Bound Cache

The transaction bound cache is invalidated on transaction boundaries.

Create a class and set some data:

>>> import gocept.cache.property
>>> class Foo(object):
...
...     cache = gocept.cache.property.TransactionBoundCache('_v_cache', dict)
...

(NOTE: You probably want to use a “volatile” attribute name for the cache storage, otherwise a read-only access of the cache triggers a write.)

>>> foo = Foo()
>>> foo.cache
{}
>>> foo.cache['A'] = 1
>>> foo.cache
{'A': 1}

If we commit the transaction the cache is empty again:

>>> import transaction
>>> transaction.commit()
>>> foo.cache
{}

The same happens on abort:

>>> foo.cache['A'] = 1
>>> foo.cache
{'A': 1}
>>> transaction.abort()
>>> foo.cache
{}

Changes

6.0 (2025-03-10)

  • Add support for Python 3.11 and 3.12.

  • Drop support for Python 3.7.

5.0 (2021-08-31)

Backwards incompatible changes

  • Change license from ZPL to MIT.

  • Drop support for Python 2.7 and 3.6.

Features

  • Add support for Python 3.10.

Other changes

  • Use Github actions as CI.

4.0 (2020-02-17)

  • Add support for Python 3.7, 3.8 and 3.9a3.

  • Drop support for Python 3.4 and 3.5.

  • Migrate to github.

3.1 (2018-11-20)

  • Add support for Python 3.7.

  • Remove DeprecationWarnings concerning inspect.

3.0 (2017-11-20)

  • Remove use of __file__ in setup.py, to accommodate recent setuptools.

  • Add support for Python 3.6.

  • Drop support for Python 3.3.

2.1 (2016-11-17)

  • Bugfix: .property.CacheDataManager no longer invalidates the cache in tpc_vote() and commit() but in tpc_finish().

  • Raise TransactionJoinError if joining the transaction failed in .property.TransactionBoundCache.

2.0 (2016-03-18)

  • Drop support of Python 2.6.

  • Declare support of PyPy and PyPy3.

1.0 (2015-09-25)

  • Now testing against currently newest versions of dependencies.

  • Drop support of Python 3.2.

  • Declare Support of Python 3.4 and 3.5.

0.6.1 (2013-09-13)

  • Finish Python 3 compatibility

0.6 (2013-09-13)

  • Changes not recored, sorry.

0.6b2 (2013-09-05)

  • Changes not recored, sorry.

0.6b1 (2013-09-05)

  • Python3 compatibility

0.5.2 (2012-06-22)

  • Added gocept.cache.method.do_not_cache_and_return(value) in memoized methods/functions which will return the given value, without caching it.

0.5.1 (2012-03-10)

  • Prevent race condition which caused values of gocept.cache.method.Memoize not to be stored when collect was called during the Memoize call (in multi threaded environments).

  • Pin test versions to ZTK 1.1

0.5 (2011-03-15)

  • Replace dependency on ZODB with a dependency on transaction.

0.4 (2009-06-18)

  • Registered clearing the cache with zope.testing.cleanup.

0.3 (2008-12-19)

  • Added @memoize_on_attribute to retrieve the memoization cache from the instance instead of using gocept.cache.method’s built-in cache.

0.2.2 (2007-12-17)

  • Fixed the bug in TransactionBoundCache where the cache was not invalidated on transaction abort.

0.2.1 (2007-10-17)

  • Fixed a bug in TransactionBoundCache which yielded an error in the log: TypeError: <lambda>() takes exactly 1 argument (2 given)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gocept_cache-6.0.tar.gz (12.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gocept_cache-6.0-py3-none-any.whl (12.5 kB view details)

Uploaded Python 3

File details

Details for the file gocept_cache-6.0.tar.gz.

File metadata

  • Download URL: gocept_cache-6.0.tar.gz
  • Upload date:
  • Size: 12.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for gocept_cache-6.0.tar.gz
Algorithm Hash digest
SHA256 98630c8292eec68d9b24ac1924fa4f22f1071ef274997f188965a0aafd38d8f1
MD5 42a99360653ef5e3c706e5c3f336c6ac
BLAKE2b-256 7c2d2ba3853b9934d9deedef67364d99435e3ccdb1e595ce8631583d164dde84

See more details on using hashes here.

File details

Details for the file gocept_cache-6.0-py3-none-any.whl.

File metadata

  • Download URL: gocept_cache-6.0-py3-none-any.whl
  • Upload date:
  • Size: 12.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for gocept_cache-6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8e33665162875e2e07103a6dd80e46ec21be9d4ab651802d87642feb4b63e7e3
MD5 83f8b447431846455877cc257bdd90e0
BLAKE2b-256 d1f54675f1cf5921ec84503cc80965dc91c3b4cdc984449f64ada80b4e4e7447

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page