Merging upstream version 1.4.0.
This commit is contained in:
parent
4a2939a58d
commit
76f1e27276
|
@ -5,7 +5,7 @@ Credits
|
|||
Development Lead
|
||||
----------------
|
||||
|
||||
* Daniel Roy Greenfeld <pydanny@gmail.com>
|
||||
* Daniel Roy Greenfeld (@pydanny)
|
||||
* Audrey Roy Greenfeld (@audreyr)
|
||||
|
||||
Contributors
|
||||
|
@ -17,3 +17,4 @@ Contributors
|
|||
* Adam Williamson <awilliam AT redhat DOT com>
|
||||
* Ionel Cristian Mărieș (@ionelmc)
|
||||
* Malyshev Artem (@proofit404)
|
||||
* Volker Braun (@vbraun)
|
||||
|
|
|
@ -99,7 +99,7 @@ Before you submit a pull request, check that it meets these guidelines:
|
|||
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
|
||||
3. The pull request should work for Python 2.7, and 3.3, 3.4, 3.5, 3.6 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.
|
||||
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
History
|
||||
-------
|
||||
|
||||
1.4.0 (2018-02-25)
|
||||
++++++++++++++++++
|
||||
|
||||
* Added asyncio support, thanks to @vbraun
|
||||
* Remove Python 2.6 support, whose end of life was 5 years ago, thanks to @pydanny
|
||||
|
||||
|
||||
1.3.1 (2017-09-21)
|
||||
++++++++++++++++++
|
||||
|
||||
|
|
63
PKG-INFO
63
PKG-INFO
|
@ -1,11 +1,12 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: cached-property
|
||||
Version: 1.3.1
|
||||
Version: 1.4.0
|
||||
Summary: A decorator for caching properties in classes.
|
||||
Home-page: https://github.com/pydanny/cached-property
|
||||
Author: Daniel Greenfeld
|
||||
Author-email: pydanny@gmail.com
|
||||
License: BSD
|
||||
Description-Content-Type: UNKNOWN
|
||||
Description: ===============================
|
||||
cached-property
|
||||
===============================
|
||||
|
@ -153,6 +154,49 @@ Description: ===============================
|
|||
>>> self.assertEqual(m.boardwalk, 550)
|
||||
|
||||
|
||||
Working with async/await (Python 3.5+)
|
||||
--------------------------------------
|
||||
|
||||
The cached property can be async, in which case you have to use await
|
||||
as usual to get the value. Because of the caching, the value is only
|
||||
computed once and then cached:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cached_property import cached_property
|
||||
|
||||
class Monopoly(object):
|
||||
|
||||
def __init__(self):
|
||||
self.boardwalk_price = 500
|
||||
|
||||
@cached_property
|
||||
async def boardwalk(self):
|
||||
self.boardwalk_price += 50
|
||||
return self.boardwalk_price
|
||||
|
||||
Now use it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> async def print_boardwalk():
|
||||
... monopoly = Monopoly()
|
||||
... print(await monopoly.boardwalk)
|
||||
... print(await monopoly.boardwalk)
|
||||
... print(await monopoly.boardwalk)
|
||||
>>> import asyncio
|
||||
>>> asyncio.get_event_loop().run_until_complete(print_boardwalk())
|
||||
550
|
||||
550
|
||||
550
|
||||
|
||||
Note that this does not work with threading either, most asyncio
|
||||
objects are not thread-safe. And if you run separate event loops in
|
||||
each thread, the cached version will most likely have the wrong event
|
||||
loop. To summarize, either use cooperative multitasking (event loop)
|
||||
or threading, but not both at the same time.
|
||||
|
||||
|
||||
Timing out the cache
|
||||
--------------------
|
||||
|
||||
|
@ -207,11 +251,11 @@ Description: ===============================
|
|||
|
||||
This project is maintained by volunteers. Support their efforts by spreading the word about:
|
||||
|
||||
.. image:: https://s3.amazonaws.com/tsacademy/images/tsa-logo-250x60-transparent-01.png
|
||||
:name: Two Scoops Academy
|
||||
.. image:: https://cdn.shopify.com/s/files/1/0304/6901/t/2/assets/logo.png?8399580890922549623
|
||||
:name: Two Scoops Press
|
||||
:align: center
|
||||
:alt: Two Scoops Academy
|
||||
:target: http://www.twoscoops.academy/
|
||||
:alt: Two Scoops Press
|
||||
:target: https://www.twoscoopspress.com
|
||||
|
||||
|
||||
|
||||
|
@ -219,6 +263,13 @@ Description: ===============================
|
|||
History
|
||||
-------
|
||||
|
||||
1.4.0 (2018-02-25)
|
||||
++++++++++++++++++
|
||||
|
||||
* Added asyncio support, thanks to @vbraun
|
||||
* Remove Python 2.6 support, whose end of life was 5 years ago, thanks to @pydanny
|
||||
|
||||
|
||||
1.3.1 (2017-09-21)
|
||||
++++++++++++++++++
|
||||
|
||||
|
@ -294,9 +345,9 @@ 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
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
|
|
51
README.rst
51
README.rst
|
@ -145,6 +145,49 @@ Now use it:
|
|||
>>> self.assertEqual(m.boardwalk, 550)
|
||||
|
||||
|
||||
Working with async/await (Python 3.5+)
|
||||
--------------------------------------
|
||||
|
||||
The cached property can be async, in which case you have to use await
|
||||
as usual to get the value. Because of the caching, the value is only
|
||||
computed once and then cached:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cached_property import cached_property
|
||||
|
||||
class Monopoly(object):
|
||||
|
||||
def __init__(self):
|
||||
self.boardwalk_price = 500
|
||||
|
||||
@cached_property
|
||||
async def boardwalk(self):
|
||||
self.boardwalk_price += 50
|
||||
return self.boardwalk_price
|
||||
|
||||
Now use it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> async def print_boardwalk():
|
||||
... monopoly = Monopoly()
|
||||
... print(await monopoly.boardwalk)
|
||||
... print(await monopoly.boardwalk)
|
||||
... print(await monopoly.boardwalk)
|
||||
>>> import asyncio
|
||||
>>> asyncio.get_event_loop().run_until_complete(print_boardwalk())
|
||||
550
|
||||
550
|
||||
550
|
||||
|
||||
Note that this does not work with threading either, most asyncio
|
||||
objects are not thread-safe. And if you run separate event loops in
|
||||
each thread, the cached version will most likely have the wrong event
|
||||
loop. To summarize, either use cooperative multitasking (event loop)
|
||||
or threading, but not both at the same time.
|
||||
|
||||
|
||||
Timing out the cache
|
||||
--------------------
|
||||
|
||||
|
@ -199,8 +242,8 @@ Support This Project
|
|||
|
||||
This project is maintained by volunteers. Support their efforts by spreading the word about:
|
||||
|
||||
.. image:: https://s3.amazonaws.com/tsacademy/images/tsa-logo-250x60-transparent-01.png
|
||||
:name: Two Scoops Academy
|
||||
.. image:: https://cdn.shopify.com/s/files/1/0304/6901/t/2/assets/logo.png?8399580890922549623
|
||||
:name: Two Scoops Press
|
||||
:align: center
|
||||
:alt: Two Scoops Academy
|
||||
:target: http://www.twoscoops.academy/
|
||||
:alt: Two Scoops Press
|
||||
:target: https://www.twoscoopspress.com
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: cached-property
|
||||
Version: 1.3.1
|
||||
Version: 1.4.0
|
||||
Summary: A decorator for caching properties in classes.
|
||||
Home-page: https://github.com/pydanny/cached-property
|
||||
Author: Daniel Greenfeld
|
||||
Author-email: pydanny@gmail.com
|
||||
License: BSD
|
||||
Description-Content-Type: UNKNOWN
|
||||
Description: ===============================
|
||||
cached-property
|
||||
===============================
|
||||
|
@ -153,6 +154,49 @@ Description: ===============================
|
|||
>>> self.assertEqual(m.boardwalk, 550)
|
||||
|
||||
|
||||
Working with async/await (Python 3.5+)
|
||||
--------------------------------------
|
||||
|
||||
The cached property can be async, in which case you have to use await
|
||||
as usual to get the value. Because of the caching, the value is only
|
||||
computed once and then cached:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cached_property import cached_property
|
||||
|
||||
class Monopoly(object):
|
||||
|
||||
def __init__(self):
|
||||
self.boardwalk_price = 500
|
||||
|
||||
@cached_property
|
||||
async def boardwalk(self):
|
||||
self.boardwalk_price += 50
|
||||
return self.boardwalk_price
|
||||
|
||||
Now use it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> async def print_boardwalk():
|
||||
... monopoly = Monopoly()
|
||||
... print(await monopoly.boardwalk)
|
||||
... print(await monopoly.boardwalk)
|
||||
... print(await monopoly.boardwalk)
|
||||
>>> import asyncio
|
||||
>>> asyncio.get_event_loop().run_until_complete(print_boardwalk())
|
||||
550
|
||||
550
|
||||
550
|
||||
|
||||
Note that this does not work with threading either, most asyncio
|
||||
objects are not thread-safe. And if you run separate event loops in
|
||||
each thread, the cached version will most likely have the wrong event
|
||||
loop. To summarize, either use cooperative multitasking (event loop)
|
||||
or threading, but not both at the same time.
|
||||
|
||||
|
||||
Timing out the cache
|
||||
--------------------
|
||||
|
||||
|
@ -207,11 +251,11 @@ Description: ===============================
|
|||
|
||||
This project is maintained by volunteers. Support their efforts by spreading the word about:
|
||||
|
||||
.. image:: https://s3.amazonaws.com/tsacademy/images/tsa-logo-250x60-transparent-01.png
|
||||
:name: Two Scoops Academy
|
||||
.. image:: https://cdn.shopify.com/s/files/1/0304/6901/t/2/assets/logo.png?8399580890922549623
|
||||
:name: Two Scoops Press
|
||||
:align: center
|
||||
:alt: Two Scoops Academy
|
||||
:target: http://www.twoscoops.academy/
|
||||
:alt: Two Scoops Press
|
||||
:target: https://www.twoscoopspress.com
|
||||
|
||||
|
||||
|
||||
|
@ -219,6 +263,13 @@ Description: ===============================
|
|||
History
|
||||
-------
|
||||
|
||||
1.4.0 (2018-02-25)
|
||||
++++++++++++++++++
|
||||
|
||||
* Added asyncio support, thanks to @vbraun
|
||||
* Remove Python 2.6 support, whose end of life was 5 years ago, thanks to @pydanny
|
||||
|
||||
|
||||
1.3.1 (2017-09-21)
|
||||
++++++++++++++++++
|
||||
|
||||
|
@ -294,9 +345,9 @@ 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
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
|
|
|
@ -13,4 +13,6 @@ 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_async_cached_property.py
|
||||
tests/test_cached_property.py
|
||||
tests/test_coroutine_cached_property.py
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
__author__ = 'Daniel Greenfeld'
|
||||
__email__ = 'pydanny@gmail.com'
|
||||
__version__ = '1.3.1'
|
||||
__version__ = '1.4.0'
|
||||
__license__ = 'BSD'
|
||||
|
||||
from time import time
|
||||
import threading
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
asyncio = None
|
||||
|
||||
|
||||
class cached_property(object):
|
||||
|
@ -23,9 +27,19 @@ class cached_property(object):
|
|||
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):
|
||||
"""
|
||||
|
|
4
setup.py
4
setup.py
|
@ -10,7 +10,7 @@ try:
|
|||
except ImportError:
|
||||
from distutils.core import setup
|
||||
|
||||
__version__ = '1.3.1'
|
||||
__version__ = '1.4.0'
|
||||
|
||||
|
||||
def read(fname):
|
||||
|
@ -45,11 +45,11 @@ setup(
|
|||
'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',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
],
|
||||
)
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import unittest
|
||||
import asyncio
|
||||
from threading import Lock, Thread
|
||||
from freezegun import freeze_time
|
||||
|
||||
import cached_property
|
||||
|
||||
|
||||
def unittest_run_loop(f):
|
||||
def wrapper(*args, **kwargs):
|
||||
coro = asyncio.coroutine(f)
|
||||
future = coro(*args, **kwargs)
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(future)
|
||||
return wrapper
|
||||
|
||||
|
||||
def CheckFactory(cached_property_decorator, threadsafe=False):
|
||||
"""
|
||||
Create dynamically a Check class whose add_cached method is decorated by
|
||||
the cached_property_decorator.
|
||||
"""
|
||||
|
||||
class Check(object):
|
||||
|
||||
def __init__(self):
|
||||
self.control_total = 0
|
||||
self.cached_total = 0
|
||||
self.lock = Lock()
|
||||
|
||||
async def add_control(self):
|
||||
self.control_total += 1
|
||||
return self.control_total
|
||||
|
||||
@cached_property_decorator
|
||||
async def add_cached(self):
|
||||
if threadsafe:
|
||||
time.sleep(1)
|
||||
# Need to guard this since += isn't atomic.
|
||||
with self.lock:
|
||||
self.cached_total += 1
|
||||
else:
|
||||
self.cached_total += 1
|
||||
|
||||
return self.cached_total
|
||||
|
||||
def run_threads(self, num_threads):
|
||||
threads = []
|
||||
for _ in range(num_threads):
|
||||
def call_add_cached():
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(self.add_cached)
|
||||
thread = Thread(target=call_add_cached)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
return Check
|
||||
|
||||
|
||||
class TestCachedProperty(unittest.TestCase):
|
||||
"""Tests for cached_property"""
|
||||
|
||||
cached_property_factory = cached_property.cached_property
|
||||
|
||||
async def assert_control(self, check, expected):
|
||||
"""
|
||||
Assert that both `add_control` and 'control_total` equal `expected`
|
||||
"""
|
||||
self.assertEqual(await check.add_control(), expected)
|
||||
self.assertEqual(check.control_total, expected)
|
||||
|
||||
async def assert_cached(self, check, expected):
|
||||
"""
|
||||
Assert that both `add_cached` and 'cached_total` equal `expected`
|
||||
"""
|
||||
print('assert_cached', check.add_cached)
|
||||
self.assertEqual(await check.add_cached, expected)
|
||||
self.assertEqual(check.cached_total, expected)
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_cached_property(self):
|
||||
Check = CheckFactory(self.cached_property_factory)
|
||||
check = Check()
|
||||
|
||||
# The control shows that we can continue to add 1
|
||||
await self.assert_control(check, 1)
|
||||
await self.assert_control(check, 2)
|
||||
|
||||
# The cached version demonstrates how nothing is added after the first
|
||||
await self.assert_cached(check, 1)
|
||||
await self.assert_cached(check, 1)
|
||||
|
||||
# The cache does not expire
|
||||
with freeze_time("9999-01-01"):
|
||||
await self.assert_cached(check, 1)
|
||||
|
||||
# Typically descriptors return themselves if accessed though the class
|
||||
# rather than through an instance.
|
||||
self.assertTrue(isinstance(Check.add_cached,
|
||||
self.cached_property_factory))
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_reset_cached_property(self):
|
||||
Check = CheckFactory(self.cached_property_factory)
|
||||
check = Check()
|
||||
|
||||
# Run standard cache assertion
|
||||
await self.assert_cached(check, 1)
|
||||
await self.assert_cached(check, 1)
|
||||
|
||||
# Clear the cache
|
||||
del check.add_cached
|
||||
|
||||
# Value is cached again after the next access
|
||||
await self.assert_cached(check, 2)
|
||||
await self.assert_cached(check, 2)
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_none_cached_property(self):
|
||||
class Check(object):
|
||||
|
||||
def __init__(self):
|
||||
self.cached_total = None
|
||||
|
||||
@self.cached_property_factory
|
||||
async def add_cached(self):
|
||||
return self.cached_total
|
||||
|
||||
await self.assert_cached(Check(), None)
|
|
@ -0,0 +1,127 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
The same tests as in :mod:`.test_async_cached_property`, but with the old
|
||||
yield from instead of the new async/await syntax. Used to test Python 3.4
|
||||
compatibility which has asyncio but doesn't have async/await yet.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import asyncio
|
||||
from freezegun import freeze_time
|
||||
|
||||
import cached_property
|
||||
|
||||
|
||||
def unittest_run_loop(f):
|
||||
def wrapper(*args, **kwargs):
|
||||
coro = asyncio.coroutine(f)
|
||||
future = coro(*args, **kwargs)
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(future)
|
||||
return wrapper
|
||||
|
||||
|
||||
def CheckFactory(cached_property_decorator):
|
||||
"""
|
||||
Create dynamically a Check class whose add_cached method is decorated by
|
||||
the cached_property_decorator.
|
||||
"""
|
||||
|
||||
class Check(object):
|
||||
|
||||
def __init__(self):
|
||||
self.control_total = 0
|
||||
self.cached_total = 0
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_control(self):
|
||||
self.control_total += 1
|
||||
return self.control_total
|
||||
|
||||
@cached_property_decorator
|
||||
@asyncio.coroutine
|
||||
def add_cached(self):
|
||||
self.cached_total += 1
|
||||
return self.cached_total
|
||||
|
||||
return Check
|
||||
|
||||
|
||||
class TestCachedProperty(unittest.TestCase):
|
||||
"""Tests for cached_property"""
|
||||
|
||||
cached_property_factory = cached_property.cached_property
|
||||
|
||||
@asyncio.coroutine
|
||||
def assert_control(self, check, expected):
|
||||
"""
|
||||
Assert that both `add_control` and 'control_total` equal `expected`
|
||||
"""
|
||||
value = yield from check.add_control()
|
||||
self.assertEqual(value, expected)
|
||||
self.assertEqual(check.control_total, expected)
|
||||
|
||||
@asyncio.coroutine
|
||||
def assert_cached(self, check, expected):
|
||||
"""
|
||||
Assert that both `add_cached` and 'cached_total` equal `expected`
|
||||
"""
|
||||
print('assert_cached', check.add_cached)
|
||||
value = yield from check.add_cached
|
||||
self.assertEqual(value, expected)
|
||||
self.assertEqual(check.cached_total, expected)
|
||||
|
||||
@unittest_run_loop
|
||||
@asyncio.coroutine
|
||||
def test_cached_property(self):
|
||||
Check = CheckFactory(self.cached_property_factory)
|
||||
check = Check()
|
||||
|
||||
# The control shows that we can continue to add 1
|
||||
yield from self.assert_control(check, 1)
|
||||
yield from self.assert_control(check, 2)
|
||||
|
||||
# The cached version demonstrates how nothing is added after the first
|
||||
yield from self.assert_cached(check, 1)
|
||||
yield from self.assert_cached(check, 1)
|
||||
|
||||
# The cache does not expire
|
||||
with freeze_time("9999-01-01"):
|
||||
yield from self.assert_cached(check, 1)
|
||||
|
||||
# Typically descriptors return themselves if accessed though the class
|
||||
# rather than through an instance.
|
||||
self.assertTrue(isinstance(Check.add_cached,
|
||||
self.cached_property_factory))
|
||||
|
||||
@unittest_run_loop
|
||||
@asyncio.coroutine
|
||||
def test_reset_cached_property(self):
|
||||
Check = CheckFactory(self.cached_property_factory)
|
||||
check = Check()
|
||||
|
||||
# Run standard cache assertion
|
||||
yield from self.assert_cached(check, 1)
|
||||
yield from self.assert_cached(check, 1)
|
||||
|
||||
# Clear the cache
|
||||
del check.add_cached
|
||||
|
||||
# Value is cached again after the next access
|
||||
yield from self.assert_cached(check, 2)
|
||||
yield from self.assert_cached(check, 2)
|
||||
|
||||
@unittest_run_loop
|
||||
@asyncio.coroutine
|
||||
def test_none_cached_property(self):
|
||||
class Check(object):
|
||||
|
||||
def __init__(self):
|
||||
self.cached_total = None
|
||||
|
||||
@self.cached_property_factory
|
||||
@asyncio.coroutine
|
||||
def add_cached(self):
|
||||
return self.cached_total
|
||||
|
||||
yield from self.assert_cached(Check(), None)
|
Loading…
Reference in New Issue