From e8bc6e39a799799973be79aabcab0b8f58085c40 Mon Sep 17 00:00:00 2001 From: Kevin Sheppard Date: Thu, 29 Aug 2019 19:12:32 +0100 Subject: [PATCH] ENH: Use property-cached in place of cached-property Use the modernized version that inherits from property --- .travis.yml | 2 +- README.rst | 10 +- appveyor.yml | 2 +- arch/univariate/base.py | 6 +- arch/univariate/mean.py | 2 +- arch/utility/array.py | 12 +- arch/utility/testing.py | 2 +- arch/vendor/__init__.py | 4 +- arch/vendor/cached_property.py | 179 ----------------------------- arch/vendor/property_cached.py | 204 +++++++++++++++++++++++++++++++++ doc/requirements.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 13 files changed, 230 insertions(+), 199 deletions(-) delete mode 100644 arch/vendor/cached_property.py create mode 100644 arch/vendor/property_cached.py diff --git a/.travis.yml b/.travis.yml index 02aafd7ec2..57063875b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,7 +79,7 @@ before_install: - echo conda create --yes --quiet -n arch-test ${PKGS} - conda create --yes --quiet -n arch-test ${PKGS} - source activate arch-test - - pip install cached_property flake8 pytest pytest-xdist pytest-cov coverage coveralls codecov nbformat nbconvert!=5.4 jupyter_client ipython jupyter -q + - pip install property_cached flake8 pytest pytest-xdist pytest-cov coverage coveralls codecov nbformat nbconvert!=5.4 jupyter_client ipython jupyter -q - if [[ "$STATSMODELS_MASTER" == true ]]; then sh ./ci/statsmodels-master.sh; fi; - | if [[ "$DOCBUILD" == true ]]; then diff --git a/README.rst b/README.rst index ffc7aa4b44..fc228edeec 100644 --- a/README.rst +++ b/README.rst @@ -38,13 +38,11 @@ Module Contents - `Bootstrapping <#bootstrap>`__ - `Multiple Comparison Tests <#multiple-comparison>`__ -Python 2.7 Support -~~~~~~~~~~~~~~~~~~ +Python 3 +~~~~~~~~ -Version 4.8 is the final version that officially supports or is tested -on Python 2.7, and is the final version that has Python 2.7 wheels. It -is time to move to Python 3.5+, and to enjoy the substantial improvement -available in recent Python releases. +``arch`` is Python 3 only. Version 4.8 is the final version that +supported Python 2.7. .. _documentation-1: diff --git a/appveyor.yml b/appveyor.yml index 69dab3ed6a..990ee270ac 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,7 +25,7 @@ build_script: - cmd: conda update conda --quiet - cmd: conda install numpy cython pytest pandas scipy patsy statsmodels matplotlib numba nbconvert nbformat pip pyyaml setuptools pyqt pyparsing --quiet - cmd: python -m pip install --upgrade pip - - cmd: pip install pytest-xdist cached_property + - cmd: pip install pytest-xdist property_cached - cmd: python setup.py develop test_script: diff --git a/arch/univariate/base.py b/arch/univariate/base.py index 755fc213a5..3e87980556 100644 --- a/arch/univariate/base.py +++ b/arch/univariate/base.py @@ -27,7 +27,7 @@ data_scale_warning, starting_value_warning) from arch.utility.testing import WaldTestStatistic -from arch.vendor.cached_property import cached_property +from property_cached import cached_property __all__ = ['implicit_constant', 'ARCHModelResult', 'ARCHModel', 'ARCHModelForecast', 'constraint'] @@ -664,13 +664,13 @@ def starting_values(self): elif params.shape[0] > 1: return params[:-1] - @abstractmethod @cached_property + @abstractmethod def num_params(self): """ Number of parameters in the model """ - pass + return 0 @abstractmethod def simulate(self, params, nobs, burn=500, initial_value=None, x=None, diff --git a/arch/univariate/mean.py b/arch/univariate/mean.py index 7560fe007e..56fb21e72a 100644 --- a/arch/univariate/mean.py +++ b/arch/univariate/mean.py @@ -19,7 +19,7 @@ from arch.univariate.volatility import (ARCH, EGARCH, FIGARCH, GARCH, HARCH, ConstantVariance) from arch.utility.array import cutoff_to_index, ensure1d, parse_dataframe -from arch.vendor.cached_property import cached_property +from property_cached import cached_property __all__ = ['HARX', 'ConstantMean', 'ZeroMean', 'ARX', 'arch_model', 'LS'] diff --git a/arch/utility/array.py b/arch/utility/array.py index edd27f6500..e63d12c8be 100644 --- a/arch/utility/array.py +++ b/arch/utility/array.py @@ -8,6 +8,8 @@ from abc import ABCMeta import datetime as dt +from property_cached import cached_property + import numpy as np from pandas import (DataFrame, DatetimeIndex, NaT, Series, Timestamp, to_datetime) @@ -110,8 +112,14 @@ def __new__(mcs, name, bases, clsdict): doc = getattr(getattr(mro_cls, attr), '__doc__') if doc: if isinstance(attribute, property): - clsdict[attr] = property(attribute.fget, attribute.fset, - attribute.fdel, doc) + + if isinstance(attribute, cached_property): + attribute.func.__doc__ = doc + clsdict[attr] = cached_property(attribute.func) + else: + clsdict[attr] = property(attribute.fget, + attribute.fset, + attribute.fdel, doc) else: attribute.__doc__ = doc break diff --git a/arch/utility/testing.py b/arch/utility/testing.py index 962cb0b8c8..f4a7b0082f 100644 --- a/arch/utility/testing.py +++ b/arch/utility/testing.py @@ -4,7 +4,7 @@ from scipy.stats import chi2 -from arch.vendor.cached_property import cached_property +from property_cached import cached_property __all__ = ['WaldTestStatistic'] diff --git a/arch/vendor/__init__.py b/arch/vendor/__init__.py index f2dd5e58eb..afd4a791f9 100644 --- a/arch/vendor/__init__.py +++ b/arch/vendor/__init__.py @@ -1,7 +1,7 @@ try: # Prefer system installed version if available - from cached_property import cached_property + from property_cached import cached_property except ImportError: - from arch.vendor.cached_property import cached_property + from arch.vendor.property_cached import cached_property __all__ = ['cached_property'] diff --git a/arch/vendor/cached_property.py b/arch/vendor/cached_property.py deleted file mode 100644 index b01baab578..0000000000 --- a/arch/vendor/cached_property.py +++ /dev/null @@ -1,179 +0,0 @@ -""" -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. -""" - -# -*- coding: utf-8 -*- - -__author__ = "Daniel Greenfeld" -__email__ = "pydanny@gmail.com" -__version__ = "1.5.1" -__license__ = "BSD" - -import threading -from time import time - -try: - import asyncio -except (ImportError, SyntaxError): - asyncio = None - - -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, func): - self.__doc__ = getattr(func, "__doc__") - self.func = func - - def __get__(self, obj, cls): - if obj is None: - return self - - if asyncio and asyncio.iscoroutinefunction(self.func): - return self._wrap_in_coroutine(obj) - - value = obj.__dict__[self.func.__name__] = self.func(obj) - return value - - def _wrap_in_coroutine(self, obj): - - @asyncio.coroutine - def wrapper(): - future = asyncio.ensure_future(self.func(obj)) - obj.__dict__[self.func.__name__] = future - return future - - return wrapper() - - -class threaded_cached_property(object): - """ - A cached_property version for use in environments where multiple threads - might concurrently try to access the property. - """ - - def __init__(self, func): - self.__doc__ = getattr(func, "__doc__") - self.func = func - self.lock = threading.RLock() - - def __get__(self, obj, cls): - if obj is None: - return self - - obj_dict = obj.__dict__ - name = self.func.__name__ - with self.lock: - try: - # check if the value was computed before the lock was acquired - return obj_dict[name] - - except KeyError: - # if not, do the calculation and release the lock - return obj_dict.setdefault(name, self.func(obj)) - - -class cached_property_with_ttl(object): - """ - A property that is only computed once per instance and then replaces itself - with an ordinary attribute. Setting the ttl to a number expresses how long - the property will last before being timed out. - """ - - def __init__(self, ttl=None): - if callable(ttl): - func = ttl - ttl = None - else: - func = None - self.ttl = ttl - self._prepare_func(func) - - def __call__(self, func): - self._prepare_func(func) - return self - - def __get__(self, obj, cls): - if obj is None: - return self - - now = time() - obj_dict = obj.__dict__ - name = self.__name__ - try: - value, last_updated = obj_dict[name] - except KeyError: - pass - else: - ttl_expired = self.ttl and self.ttl < now - last_updated - if not ttl_expired: - return value - - value = self.func(obj) - obj_dict[name] = (value, now) - return value - - def __delete__(self, obj): - obj.__dict__.pop(self.__name__, None) - - def __set__(self, obj, value): - obj.__dict__[self.__name__] = (value, time()) - - def _prepare_func(self, func): - self.func = func - if func: - self.__doc__ = func.__doc__ - self.__name__ = func.__name__ - self.__module__ = func.__module__ - - -# Aliases to make cached_property_with_ttl easier to use -cached_property_ttl = cached_property_with_ttl -timed_cached_property = cached_property_with_ttl - - -class threaded_cached_property_with_ttl(cached_property_with_ttl): - """ - 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_with_ttl, self).__init__(ttl) - self.lock = threading.RLock() - - def __get__(self, obj, cls): - with self.lock: - return super(threaded_cached_property_with_ttl, self).__get__(obj, cls) - - -# Alias to make threaded_cached_property_with_ttl easier to use -threaded_cached_property_ttl = threaded_cached_property_with_ttl -timed_threaded_cached_property = threaded_cached_property_with_ttl diff --git a/arch/vendor/property_cached.py b/arch/vendor/property_cached.py new file mode 100644 index 0000000000..ff7c1d40cc --- /dev/null +++ b/arch/vendor/property_cached.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +""" +Copyright (c) 2018-2019, Martin Larralde +Copyright (c) 2015-2018, 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 or property-cached 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. +""" + +import functools +import sys +import threading +import weakref +from time import time + +try: + import asyncio +except (ImportError, SyntaxError): + asyncio = None + + +__author__ = "Martin Larralde" +__email__ = "martin.larralde@ens-paris-saclay.fr" +__license__ = "BSD" +__version__ = "1.6.2" + + +class cached_property(property): + """ + 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 + + _sentinel = object() + + if sys.version_info[0] < 3: + + def _update_wrapper(self, func): + self.__doc__ = getattr(func, "__doc__", None) + self.__module__ = getattr(func, "__module__", None) + self.__name__ = getattr(func, "__name__", None) + + else: + + _update_wrapper = functools.update_wrapper + + def __init__(self, func): + self.cache = weakref.WeakKeyDictionary() + self.func = func + self._update_wrapper(func) + + def __get__(self, obj, cls): + if obj is None: + return self + + if asyncio and asyncio.iscoroutinefunction(self.func): + return self._wrap_in_coroutine(obj) + + value = self.cache.get(obj, self._sentinel) + if value is self._sentinel: + value = self.cache[obj] = self.func(obj) + + return value + + def __set_name__(self, owner, name): + self.__name__ = name + + def __set__(self, obj, value): + self.cache[obj] = value + + def __delete__(self, obj): + del self.cache[obj] + + def _wrap_in_coroutine(self, obj): + + @asyncio.coroutine + def wrapper(): + value = self.cache.get(obj, self._sentinel) + if value is self._sentinel: + self.cache[obj] = value = asyncio.ensure_future(self.func(obj)) + return value + + return wrapper() + + +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, func): + super(threaded_cached_property, self).__init__(func) + self.lock = threading.RLock() + + def __get__(self, obj, cls): + if obj is None: + return self + with self.lock: + return super(threaded_cached_property, self).__get__(obj, cls) + + def __set__(self, obj, value): + with self.lock: + super(threaded_cached_property, self).__set__(obj, value) + + def __delete__(self, obj): + with self.lock: + super(threaded_cached_property, self).__delete__(obj) + + +class cached_property_with_ttl(cached_property): + """ + A property that is only computed once per instance and then replaces itself + with an ordinary attribute. Setting the ttl to a number expresses how long + the property will last before being timed out. + """ + + def __init__(self, ttl=None): + if callable(ttl): + func = ttl + ttl = None + else: + func = None + self.ttl = ttl + super(cached_property_with_ttl, self).__init__(func) + + def __call__(self, func): + super(cached_property_with_ttl, self).__init__(func) + return self + + def __get__(self, obj, cls): + if obj is None: + return self + + now = time() + if obj in self.cache: + value, last_updated = self.cache[obj] + if not self.ttl or self.ttl > now - last_updated: + return value + + value, _ = self.cache[obj] = (self.func(obj), now) + return value + + def __set__(self, obj, value): + super(cached_property_with_ttl, self).__set__(obj, (value, time())) + + +# Aliases to make cached_property_with_ttl easier to use +cached_property_ttl = cached_property_with_ttl +timed_cached_property = cached_property_with_ttl + + +class threaded_cached_property_with_ttl( + cached_property_with_ttl, threaded_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_with_ttl, self).__init__(ttl) + self.lock = threading.RLock() + + def __get__(self, obj, cls): + with self.lock: + return super(threaded_cached_property_with_ttl, self).__get__(obj, cls) + + def __set__(self, obj, value): + with self.lock: + return super(threaded_cached_property_with_ttl, self).__set__(obj, value) + + def __delete__(self, obj): + with self.lock: + return super(threaded_cached_property_with_ttl, self).__delete__(obj) + + +# Alias to make threaded_cached_property_with_ttl easier to use +threaded_cached_property_ttl = threaded_cached_property_with_ttl +timed_threaded_cached_property = threaded_cached_property_with_ttl diff --git a/doc/requirements.txt b/doc/requirements.txt index 7cdbaf0129..808b577213 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -12,4 +12,4 @@ numpydoc nbsphinx sphinx_material jupyter -cached_property +property_cached diff --git a/requirements.txt b/requirements.txt index 2fcfb95920..986f944163 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ pytest statsmodels>=0.9 sphinx_material nbsphinx -cached_property +property_cached diff --git a/setup.py b/setup.py index 9fa1e00a27..a1020e8d61 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,7 @@ def build_extensions(self): INSTALL_REQUIREMENTS.update({'scipy': '1.0', 'pandas': '0.22', 'statsmodels': '0.9', - 'cached_property': '1.5.1'}) + 'property_cached': '1.6.2'}) cmdclass['build_ext'] = build_ext