Adding upstream version 1.0.0.

Signed-off-by: Mathias Behrle <mathiasb@m9s.biz>
This commit is contained in:
Mathias Behrle 2015-04-10 01:07:54 +02:00
commit edd243494a
No known key found for this signature in database
GPG Key ID: D6D09BE48405BBF6
18 changed files with 1352 additions and 0 deletions

14
AUTHORS.rst Normal file
View File

@ -0,0 +1,14 @@
=======
Credits
=======
Development Lead
----------------
* Daniel Greenfeld <pydanny@gmail.com>
Contributors
------------
* Tin Tvrtković <tinchester@gmail.com>
* @bcho <bcho@vtmer.com>

111
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,111 @@
============
Contributing
============
Contributions are welcome, and they are greatly appreciated! Every
little bit helps, and credit will always be given.
You can contribute in many ways:
Types of Contributions
----------------------
Report Bugs
~~~~~~~~~~~
Report bugs at https://github.com/pydanny/cached-property/issues.
If you are reporting a bug, please include:
* Your operating system name and version.
* Any details about your local setup that might be helpful in troubleshooting.
* Detailed steps to reproduce the bug.
Fix Bugs
~~~~~~~~
Look through the GitHub issues for bugs. Anything tagged with "bug"
is open to whoever wants to implement it.
Implement Features
~~~~~~~~~~~~~~~~~~
Look through the GitHub issues for features. Anything tagged with "feature"
is open to whoever wants to implement it.
Write Documentation
~~~~~~~~~~~~~~~~~~~
cached-property could always use more documentation, whether as part of the
official cached-property docs, in docstrings, or even on the web in blog posts,
articles, and such.
Submit Feedback
~~~~~~~~~~~~~~~
The best way to send feedback is to file an issue at https://github.com/pydanny/cached-property/issues.
If you are proposing a feature:
* Explain in detail how it would work.
* Keep the scope as narrow as possible, to make it easier to implement.
* Remember that this is a volunteer-driven project, and that contributions
are welcome :)
Get Started!
------------
Ready to contribute? Here's how to set up `cached-property` for local development.
1. Fork the `cached-property` repo on GitHub.
2. Clone your fork locally::
$ git clone git@github.com:your_name_here/cached-property.git
3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
$ mkvirtualenv cached-property
$ cd cached-property/
$ python setup.py develop
4. Create a branch for local development::
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox::
$ flake8 cached-property tests
$ python setup.py test
$ tox
To get flake8 and tox, just pip install them into your virtualenv.
6. Commit your changes and push your branch to GitHub::
$ git add .
$ git commit -m "Your detailed description of your changes."
$ git push origin name-of-your-bugfix-or-feature
7. Submit a pull request through the GitHub website.
Pull Request Guidelines
-----------------------
Before you submit a pull request, check that it meets these guidelines:
1. The pull request should include tests.
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in README.rst.
3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check
https://travis-ci.org/pydanny/cached-property/pull_requests
and make sure that the tests pass for all supported Python versions.
Tips
----
To run a subset of tests::
$ python -m unittest tests.test_cached-property

43
HISTORY.rst Normal file
View File

@ -0,0 +1,43 @@
.. :changelog:
History
-------
1.0.0 (2014-02-13)
++++++++++++++++++
* Added timed to expire feature to ``cached_property`` decorator.
* Changed ``del monopoly.boardwalk`` to ``del monopoly['boardwalk'] in order to support the new TTL feature.
0.1.5 (2014-05-20)
++++++++++++++++++
* Added threading support with new ``threaded_cached_property`` decorator
* Documented cache invalidation
* Updated credits
* Sourced the bottle implementation
0.1.4 (2014-05-17)
++++++++++++++++++
* Fix the dang-blarged py_modules argument.
0.1.3 (2014-05-17)
++++++++++++++++++
* Removed import of package into ``setup.py``
0.1.2 (2014-05-17)
++++++++++++++++++
* Documentation fixes. Not opening up a RTFD instance for this because it's so simple to use.
0.1.1 (2014-05-17)
++++++++++++++++++
* setup.py fix. Whoops!
0.1.0 (2014-05-17)
++++++++++++++++++
* First release on PyPI.

12
LICENSE Normal file
View File

@ -0,0 +1,12 @@
Copyright (c) 2015, Daniel Greenfeld
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of cached-property nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

11
MANIFEST.in Normal file
View File

@ -0,0 +1,11 @@
include AUTHORS.rst
include CONTRIBUTING.rst
include HISTORY.rst
include LICENSE
include README.rst
recursive-include tests *
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-include docs *.rst conf.py Makefile make.bat

264
PKG-INFO Normal file
View File

@ -0,0 +1,264 @@
Metadata-Version: 1.1
Name: cached-property
Version: 1.0.0
Summary: A cached-property for decorating methods in classes.
Home-page: https://github.com/pydanny/cached-property
Author: Daniel Greenfeld
Author-email: pydanny@gmail.com
License: BSD
Description: ===============================
cached-property
===============================
.. image:: https://badge.fury.io/py/cached-property.png
:target: http://badge.fury.io/py/cached-property
.. image:: https://travis-ci.org/pydanny/cached-property.png?branch=master
:target: https://travis-ci.org/pydanny/cached-property
.. image:: https://pypip.in/d/cached-property/badge.png
:target: https://pypi.python.org/pypi/cached-property
A cached-property for decorating methods in classes.
Why?
-----
* Makes caching of time or computational expensive properties quick and easy.
* Because I got tired of copy/pasting this code from non-web project to non-web project.
* I needed something really simple that worked in Python 2 and 3.
How to use it
--------------
Let's define a class with an expensive property. Every time you stay there the
price goes up by $50!
.. code-block:: python
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@property
def boardwalk(self):
# In reality, this might represent a database call or time
# intensive task like calling a third-party API.
self.boardwalk_price += 50
return self.boardwalk_price
Now run it:
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
600
Let's convert the boardwalk property into a ``cached_property``.
.. code-block:: python
from cached_property import cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@cached_property
def boardwalk(self):
# Again, this is a silly example. Don't worry about it, this is
# just an example for clarity.
self.boardwalk_price += 50
return self.boardwalk_price
Now when we run it the price stays at $550.
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
Why doesn't the value of ``monopoly.boardwalk`` change? Because it's a **cached property**!
Invalidating the Cache
----------------------
Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> # invalidate the cache
>>> del monopoly['boardwalk']
>>> # request the boardwalk property again
>>> monopoly.boardwalk
600
>>> monopoly.boardwalk
600
Timing out the cache
--------------------
Sometimes you want the price of things to reset after a time.
.. code-block:: python
import random
from cached_property import cached_property
class Monopoly(object):
@cached_property(ttl=5) # cache invalidates after 10 seconds
def dice(self):
# I dare the reader to implement a game using this method of 'rolling dice'.
return random.randint(2,12)
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.dice
10
>>> monopoly.dice
10
>>> from time import sleep
>>> sleep(6) # Sleeps long enough to expire the cache
>>> monopoly.dice
3
>>> monopoly.dice
3
Working with Threads
---------------------
What if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which
unfortunately causes problems with the standard ``cached_property``. In this case, switch to using the
``threaded_cached_property``:
.. code-block:: python
import threading
from cached_property import threaded_cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
self.lock = threading.Lock()
@threaded_cached_property
def boardwalk(self):
"""threaded_cached_property is really nice for when no one waits
for other people to finish their turn and rudely start rolling
dice and moving their pieces."""
sleep(1)
# Need to guard this since += isn't atomic.
with self.lock:
self.boardwalk_price += 50
return self.boardwalk_price
Now use it:
.. code-block:: python
>>> from threading import Thread
>>> from monopoly import Monopoly
>>> monopoly = Monopoly()
>>> threads = []
>>> for x in range(10):
>>> thread = Thread(target=lambda: monopoly.boardwalk)
>>> thread.start()
>>> threads.append(thread)
>>> for thread in threads:
>>> thread.join()
>>> self.assertEqual(m.boardwalk, 550)
Credits
--------
* Pip, Django, Werkzueg, Bottle, Pyramid, and Zope for having their own implementations. This package uses an implementation that matches the Bottle version.
* Reinout Van Rees for pointing out the `cached_property` decorator to me.
* My awesome wife `@audreyr`_ who created `cookiecutter`_, which meant rolling this out took me just 15 minutes.
* @tinche for pointing out the threading issue and providing a solution.
* @bcho for providing the time-to-expire feature
.. _`@audreyr`: https://github.com/audreyr
.. _`cookiecutter`: https://github.com/audreyr/cookiecutter
History
-------
1.0.0 (2014-02-13)
++++++++++++++++++
* Added timed to expire feature to ``cached_property`` decorator.
* Changed ``del monopoly.boardwalk`` to ``del monopoly['boardwalk'] in order to support the new TTL feature.
0.1.5 (2014-05-20)
++++++++++++++++++
* Added threading support with new ``threaded_cached_property`` decorator
* Documented cache invalidation
* Updated credits
* Sourced the bottle implementation
0.1.4 (2014-05-17)
++++++++++++++++++
* Fix the dang-blarged py_modules argument.
0.1.3 (2014-05-17)
++++++++++++++++++
* Removed import of package into ``setup.py``
0.1.2 (2014-05-17)
++++++++++++++++++
* Documentation fixes. Not opening up a RTFD instance for this because it's so simple to use.
0.1.1 (2014-05-17)
++++++++++++++++++
* setup.py fix. Whoops!
0.1.0 (2014-05-17)
++++++++++++++++++
* First release on PyPI.
Keywords: cached-property
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4

198
README.rst Normal file
View File

@ -0,0 +1,198 @@
===============================
cached-property
===============================
.. image:: https://badge.fury.io/py/cached-property.png
:target: http://badge.fury.io/py/cached-property
.. image:: https://travis-ci.org/pydanny/cached-property.png?branch=master
:target: https://travis-ci.org/pydanny/cached-property
.. image:: https://pypip.in/d/cached-property/badge.png
:target: https://pypi.python.org/pypi/cached-property
A cached-property for decorating methods in classes.
Why?
-----
* Makes caching of time or computational expensive properties quick and easy.
* Because I got tired of copy/pasting this code from non-web project to non-web project.
* I needed something really simple that worked in Python 2 and 3.
How to use it
--------------
Let's define a class with an expensive property. Every time you stay there the
price goes up by $50!
.. code-block:: python
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@property
def boardwalk(self):
# In reality, this might represent a database call or time
# intensive task like calling a third-party API.
self.boardwalk_price += 50
return self.boardwalk_price
Now run it:
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
600
Let's convert the boardwalk property into a ``cached_property``.
.. code-block:: python
from cached_property import cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@cached_property
def boardwalk(self):
# Again, this is a silly example. Don't worry about it, this is
# just an example for clarity.
self.boardwalk_price += 50
return self.boardwalk_price
Now when we run it the price stays at $550.
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
Why doesn't the value of ``monopoly.boardwalk`` change? Because it's a **cached property**!
Invalidating the Cache
----------------------
Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> # invalidate the cache
>>> del monopoly['boardwalk']
>>> # request the boardwalk property again
>>> monopoly.boardwalk
600
>>> monopoly.boardwalk
600
Timing out the cache
--------------------
Sometimes you want the price of things to reset after a time.
.. code-block:: python
import random
from cached_property import cached_property
class Monopoly(object):
@cached_property(ttl=5) # cache invalidates after 10 seconds
def dice(self):
# I dare the reader to implement a game using this method of 'rolling dice'.
return random.randint(2,12)
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.dice
10
>>> monopoly.dice
10
>>> from time import sleep
>>> sleep(6) # Sleeps long enough to expire the cache
>>> monopoly.dice
3
>>> monopoly.dice
3
Working with Threads
---------------------
What if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which
unfortunately causes problems with the standard ``cached_property``. In this case, switch to using the
``threaded_cached_property``:
.. code-block:: python
import threading
from cached_property import threaded_cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
self.lock = threading.Lock()
@threaded_cached_property
def boardwalk(self):
"""threaded_cached_property is really nice for when no one waits
for other people to finish their turn and rudely start rolling
dice and moving their pieces."""
sleep(1)
# Need to guard this since += isn't atomic.
with self.lock:
self.boardwalk_price += 50
return self.boardwalk_price
Now use it:
.. code-block:: python
>>> from threading import Thread
>>> from monopoly import Monopoly
>>> monopoly = Monopoly()
>>> threads = []
>>> for x in range(10):
>>> thread = Thread(target=lambda: monopoly.boardwalk)
>>> thread.start()
>>> threads.append(thread)
>>> for thread in threads:
>>> thread.join()
>>> self.assertEqual(m.boardwalk, 550)
Credits
--------
* Pip, Django, Werkzueg, Bottle, Pyramid, and Zope for having their own implementations. This package uses an implementation that matches the Bottle version.
* Reinout Van Rees for pointing out the `cached_property` decorator to me.
* My awesome wife `@audreyr`_ who created `cookiecutter`_, which meant rolling this out took me just 15 minutes.
* @tinche for pointing out the threading issue and providing a solution.
* @bcho for providing the time-to-expire feature
.. _`@audreyr`: https://github.com/audreyr
.. _`cookiecutter`: https://github.com/audreyr/cookiecutter

View File

@ -0,0 +1,264 @@
Metadata-Version: 1.1
Name: cached-property
Version: 1.0.0
Summary: A cached-property for decorating methods in classes.
Home-page: https://github.com/pydanny/cached-property
Author: Daniel Greenfeld
Author-email: pydanny@gmail.com
License: BSD
Description: ===============================
cached-property
===============================
.. image:: https://badge.fury.io/py/cached-property.png
:target: http://badge.fury.io/py/cached-property
.. image:: https://travis-ci.org/pydanny/cached-property.png?branch=master
:target: https://travis-ci.org/pydanny/cached-property
.. image:: https://pypip.in/d/cached-property/badge.png
:target: https://pypi.python.org/pypi/cached-property
A cached-property for decorating methods in classes.
Why?
-----
* Makes caching of time or computational expensive properties quick and easy.
* Because I got tired of copy/pasting this code from non-web project to non-web project.
* I needed something really simple that worked in Python 2 and 3.
How to use it
--------------
Let's define a class with an expensive property. Every time you stay there the
price goes up by $50!
.. code-block:: python
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@property
def boardwalk(self):
# In reality, this might represent a database call or time
# intensive task like calling a third-party API.
self.boardwalk_price += 50
return self.boardwalk_price
Now run it:
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
600
Let's convert the boardwalk property into a ``cached_property``.
.. code-block:: python
from cached_property import cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@cached_property
def boardwalk(self):
# Again, this is a silly example. Don't worry about it, this is
# just an example for clarity.
self.boardwalk_price += 50
return self.boardwalk_price
Now when we run it the price stays at $550.
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
Why doesn't the value of ``monopoly.boardwalk`` change? Because it's a **cached property**!
Invalidating the Cache
----------------------
Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> # invalidate the cache
>>> del monopoly['boardwalk']
>>> # request the boardwalk property again
>>> monopoly.boardwalk
600
>>> monopoly.boardwalk
600
Timing out the cache
--------------------
Sometimes you want the price of things to reset after a time.
.. code-block:: python
import random
from cached_property import cached_property
class Monopoly(object):
@cached_property(ttl=5) # cache invalidates after 10 seconds
def dice(self):
# I dare the reader to implement a game using this method of 'rolling dice'.
return random.randint(2,12)
.. code-block:: python
>>> monopoly = Monopoly()
>>> monopoly.dice
10
>>> monopoly.dice
10
>>> from time import sleep
>>> sleep(6) # Sleeps long enough to expire the cache
>>> monopoly.dice
3
>>> monopoly.dice
3
Working with Threads
---------------------
What if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which
unfortunately causes problems with the standard ``cached_property``. In this case, switch to using the
``threaded_cached_property``:
.. code-block:: python
import threading
from cached_property import threaded_cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
self.lock = threading.Lock()
@threaded_cached_property
def boardwalk(self):
"""threaded_cached_property is really nice for when no one waits
for other people to finish their turn and rudely start rolling
dice and moving their pieces."""
sleep(1)
# Need to guard this since += isn't atomic.
with self.lock:
self.boardwalk_price += 50
return self.boardwalk_price
Now use it:
.. code-block:: python
>>> from threading import Thread
>>> from monopoly import Monopoly
>>> monopoly = Monopoly()
>>> threads = []
>>> for x in range(10):
>>> thread = Thread(target=lambda: monopoly.boardwalk)
>>> thread.start()
>>> threads.append(thread)
>>> for thread in threads:
>>> thread.join()
>>> self.assertEqual(m.boardwalk, 550)
Credits
--------
* Pip, Django, Werkzueg, Bottle, Pyramid, and Zope for having their own implementations. This package uses an implementation that matches the Bottle version.
* Reinout Van Rees for pointing out the `cached_property` decorator to me.
* My awesome wife `@audreyr`_ who created `cookiecutter`_, which meant rolling this out took me just 15 minutes.
* @tinche for pointing out the threading issue and providing a solution.
* @bcho for providing the time-to-expire feature
.. _`@audreyr`: https://github.com/audreyr
.. _`cookiecutter`: https://github.com/audreyr/cookiecutter
History
-------
1.0.0 (2014-02-13)
++++++++++++++++++
* Added timed to expire feature to ``cached_property`` decorator.
* Changed ``del monopoly.boardwalk`` to ``del monopoly['boardwalk'] in order to support the new TTL feature.
0.1.5 (2014-05-20)
++++++++++++++++++
* Added threading support with new ``threaded_cached_property`` decorator
* Documented cache invalidation
* Updated credits
* Sourced the bottle implementation
0.1.4 (2014-05-17)
++++++++++++++++++
* Fix the dang-blarged py_modules argument.
0.1.3 (2014-05-17)
++++++++++++++++++
* Removed import of package into ``setup.py``
0.1.2 (2014-05-17)
++++++++++++++++++
* Documentation fixes. Not opening up a RTFD instance for this because it's so simple to use.
0.1.1 (2014-05-17)
++++++++++++++++++
* setup.py fix. Whoops!
0.1.0 (2014-05-17)
++++++++++++++++++
* First release on PyPI.
Keywords: cached-property
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4

View File

@ -0,0 +1,17 @@
AUTHORS.rst
CONTRIBUTING.rst
HISTORY.rst
LICENSE
MANIFEST.in
README.rst
cached_property.py
setup.cfg
setup.py
cached_property.egg-info/PKG-INFO
cached_property.egg-info/SOURCES.txt
cached_property.egg-info/dependency_links.txt
cached_property.egg-info/not-zip-safe
cached_property.egg-info/top_level.txt
tests/__init__.py
tests/test_cached_property.py
tests/test_threaded_cached_property.py

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
cached_property

79
cached_property.py Normal file
View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
__author__ = 'Daniel Greenfeld'
__email__ = 'pydanny@gmail.com'
__version__ = '1.0.0'
__license__ = 'BSD'
from time import time
import threading
class cached_property(object):
""" A property that is only computed once per instance and then replaces
itself with an ordinary attribute. Deleting the attribute resets the
property.
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
""" # noqa
def __init__(self, ttl=None):
ttl_or_func = ttl
self.ttl = None
if callable(ttl_or_func):
self.prepare_func(ttl_or_func)
else:
self.ttl = ttl_or_func
def prepare_func(self, func, doc=None):
'''Prepare to cache object method.'''
self.func = func
self.__doc__ = doc or func.__doc__
self.__name__ = func.__name__
self.__module__ = func.__module__
def __call__(self, func, doc=None):
self.prepare_func(func, doc)
return self
def __get__(self, obj, cls):
if obj is None:
return self
now = time()
try:
value, last_update = obj._cache[self.__name__]
if self.ttl and self.ttl > 0 and now - last_update > self.ttl:
raise AttributeError
except (KeyError, AttributeError):
value = self.func(obj)
try:
cache = obj._cache
except AttributeError:
cache = obj._cache = {}
cache[self.__name__] = (value, now)
return value
def __delattr__(self, name):
print(name)
class threaded_cached_property(cached_property):
""" A cached_property version for use in environments where multiple
threads might concurrently try to access the property.
"""
def __init__(self, ttl=None):
super(threaded_cached_property, self).__init__(ttl)
self.lock = threading.RLock()
def __get__(self, obj, cls):
with self.lock:
# Double check if the value was computed before the lock was
# acquired.
prop_name = self.__name__
if hasattr(obj, '_cache') and prop_name in obj._cache:
return obj._cache[prop_name][0]
# If not, do the calculation and release the lock.
return super(threaded_cached_property, self).__get__(obj, cls)

8
setup.cfg Normal file
View File

@ -0,0 +1,8 @@
[wheel]
universal = 1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0

48
setup.py Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
__version__ = '1.0.0'
readme = open('README.rst').read()
history = open('HISTORY.rst').read().replace('.. :changelog:', '')
if sys.argv[-1] == 'publish':
os.system('python setup.py sdist bdist_wheel upload')
os.system("git tag -a %s -m 'version %s'" % (__version__, __version__))
os.system("git push --tags")
sys.exit()
setup(
name='cached-property',
version=__version__,
description='A cached-property for decorating methods in classes.',
long_description=readme + '\n\n' + history,
author='Daniel Greenfeld',
author_email='pydanny@gmail.com',
url='https://github.com/pydanny/cached-property',
py_modules=['cached_property'],
include_package_data=True,
license="BSD",
zip_safe=False,
keywords='cached-property',
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
"Programming Language :: Python :: 2",
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
],
)

1
tests/__init__.py Executable file
View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

163
tests/test_cached_property.py Executable file
View File

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
"""
tests.py
----------------------------------
Tests for `cached-property` module.
"""
from time import sleep
from threading import Lock, Thread
import unittest
from freezegun import freeze_time
from cached_property import cached_property
class TestCachedProperty(unittest.TestCase):
def test_cached_property(self):
class Check(object):
def __init__(self):
self.total1 = 0
self.total2 = 0
@property
def add_control(self):
self.total1 += 1
return self.total1
@cached_property
def add_cached(self):
self.total2 += 1
return self.total2
c = Check()
# The control shows that we can continue to add 1.
self.assertEqual(c.add_control, 1)
self.assertEqual(c.add_control, 2)
# The cached version demonstrates how nothing new is added
self.assertEqual(c.add_cached, 1)
self.assertEqual(c.add_cached, 1)
# Cannot expire the cache.
with freeze_time("9999-01-01"):
self.assertEqual(c.add_cached, 1)
# It's customary for descriptors to return themselves if accessed
# though the class, rather than through an instance.
self.assertTrue(isinstance(Check.add_cached, cached_property))
def test_reset_cached_property(self):
class Check(object):
def __init__(self):
self.total = 0
@cached_property
def add_cached(self):
self.total += 1
return self.total
c = Check()
# Run standard cache assertion
self.assertEqual(c.add_cached, 1)
self.assertEqual(c.add_cached, 1)
# Reset the cache.
del c._cache['add_cached']
self.assertEqual(c.add_cached, 2)
self.assertEqual(c.add_cached, 2)
def test_none_cached_property(self):
class Check(object):
def __init__(self):
self.total = None
@cached_property
def add_cached(self):
return self.total
c = Check()
# Run standard cache assertion
self.assertEqual(c.add_cached, None)
class TestThreadingIssues(unittest.TestCase):
def test_threads(self):
""" How well does the standard cached_property implementation work with threads?
Short answer: It doesn't! Use threaded_cached_property instead!
""" # noqa
class Check(object):
def __init__(self):
self.total = 0
self.lock = Lock()
@cached_property
def add_cached(self):
sleep(1)
# Need to guard this since += isn't atomic.
with self.lock:
self.total += 1
return self.total
c = Check()
threads = []
num_threads = 10
for x in range(num_threads):
thread = Thread(target=lambda: c.add_cached)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
# Threads means that caching is bypassed.
self.assertNotEqual(c.add_cached, 1)
# This assertion hinges on the fact the system executing the test can
# spawn and start running num_threads threads within the sleep period
# (defined in the Check class as 1 second). If num_threads were to be
# massively increased (try 10000), the actual value returned would be
# between 1 and num_threads, depending on thread scheduling and
# preemption.
self.assertEqual(c.add_cached, num_threads)
class TestCachedPropertyWithTTL(unittest.TestCase):
def test_ttl_expiry(self):
class Check(object):
def __init__(self):
self.total = 0
@cached_property(ttl=100000)
def add_cached(self):
self.total += 1
return self.total
c = Check()
# Run standard cache assertion
self.assertEqual(c.add_cached, 1)
self.assertEqual(c.add_cached, 1)
# Expire the cache.
with freeze_time("9999-01-01"):
self.assertEqual(c.add_cached, 2)
self.assertEqual(c.add_cached, 2)

View File

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""
test_threaded_cache_property.py
----------------------------------
Tests for `cached-property` module, threaded_cache_property.
"""
from time import sleep
from threading import Thread, Lock
import unittest
from cached_property import threaded_cached_property
class TestCachedProperty(unittest.TestCase):
def test_cached_property(self):
class Check(object):
def __init__(self):
self.total1 = 0
self.total2 = 0
@property
def add_control(self):
self.total1 += 1
return self.total1
@threaded_cached_property
def add_cached(self):
self.total2 += 1
return self.total2
c = Check()
# The control shows that we can continue to add 1.
self.assertEqual(c.add_control, 1)
self.assertEqual(c.add_control, 2)
# The cached version demonstrates how nothing new is added
self.assertEqual(c.add_cached, 1)
self.assertEqual(c.add_cached, 1)
def test_reset_cached_property(self):
class Check(object):
def __init__(self):
self.total = 0
@threaded_cached_property
def add_cached(self):
self.total += 1
return self.total
c = Check()
# Run standard cache assertion
self.assertEqual(c.add_cached, 1)
self.assertEqual(c.add_cached, 1)
# Reset the cache.
del c._cache['add_cached']
self.assertEqual(c.add_cached, 2)
self.assertEqual(c.add_cached, 2)
def test_none_cached_property(self):
class Check(object):
def __init__(self):
self.total = None
@threaded_cached_property
def add_cached(self):
return self.total
c = Check()
# Run standard cache assertion
self.assertEqual(c.add_cached, None)
class TestThreadingIssues(unittest.TestCase):
def test_threads(self):
""" How well does this implementation work with threads?"""
class Check(object):
def __init__(self):
self.total = 0
self.lock = Lock()
@threaded_cached_property
def add_cached(self):
sleep(1)
# Need to guard this since += isn't atomic.
with self.lock:
self.total += 1
return self.total
c = Check()
threads = []
for x in range(10):
thread = Thread(target=lambda: c.add_cached)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
self.assertEqual(c.add_cached, 1)