Skip to content

Commit 35dbeab

Browse files
authored
Add maxlen parameter to diskcache.Deque (grantjenks#191)
* Add maxlen parameter to diskcache.Deque
1 parent 74e554c commit 35dbeab

File tree

5 files changed

+107
-24
lines changed

5 files changed

+107
-24
lines changed

diskcache/djangocache.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,15 @@ def cache(self, name):
4444
"""
4545
return self._cache.cache(name)
4646

47-
def deque(self, name):
47+
def deque(self, name, maxlen=None):
4848
"""Return Deque with given `name` in subdirectory.
4949
5050
:param str name: subdirectory name for Deque
51+
:param maxlen: max length (default None, no max)
5152
:return: Deque with given name
5253
5354
"""
54-
return self._cache.deque(name)
55+
return self._cache.deque(name, maxlen=maxlen)
5556

5657
def index(self, name):
5758
"""Return Index with given `name` in subdirectory.

diskcache/fanout.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ def cache(self, name, timeout=60, disk=None, **settings):
612612
_caches[name] = temp
613613
return temp
614614

615-
def deque(self, name):
615+
def deque(self, name, maxlen=None):
616616
"""Return Deque with given `name` in subdirectory.
617617
618618
>>> cache = FanoutCache()
@@ -626,6 +626,7 @@ def deque(self, name):
626626
1
627627
628628
:param str name: subdirectory name for Deque
629+
:param maxlen: max length (default None, no max)
629630
:return: Deque with given name
630631
631632
"""
@@ -641,7 +642,7 @@ def deque(self, name):
641642
disk=self._disk,
642643
eviction_policy='none',
643644
)
644-
deque = Deque.fromcache(cache)
645+
deque = Deque.fromcache(cache, maxlen=maxlen)
645646
_deques[name] = deque
646647
return deque
647648

diskcache/persistent.py

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class Deque(Sequence):
7575
7676
"""
7777

78-
def __init__(self, iterable=(), directory=None):
78+
def __init__(self, iterable=(), directory=None, maxlen=None):
7979
"""Initialize deque instance.
8080
8181
If directory is None then temporary directory created. The directory
@@ -86,10 +86,11 @@ def __init__(self, iterable=(), directory=None):
8686
8787
"""
8888
self._cache = Cache(directory, eviction_policy='none')
89-
self.extend(iterable)
89+
self._maxlen = float('inf') if maxlen is None else maxlen
90+
self._extend(iterable)
9091

9192
@classmethod
92-
def fromcache(cls, cache, iterable=()):
93+
def fromcache(cls, cache, iterable=(), maxlen=None):
9394
"""Initialize deque using `cache`.
9495
9596
>>> cache = Cache()
@@ -111,7 +112,8 @@ def fromcache(cls, cache, iterable=()):
111112
# pylint: disable=no-member,protected-access
112113
self = cls.__new__(cls)
113114
self._cache = cache
114-
self.extend(iterable)
115+
self._maxlen = float('inf') if maxlen is None else maxlen
116+
self._extend(iterable)
115117
return self
116118

117119
@property
@@ -124,6 +126,31 @@ def directory(self):
124126
"""Directory path where deque is stored."""
125127
return self._cache.directory
126128

129+
@property
130+
def maxlen(self):
131+
"""Max length of the deque."""
132+
return self._maxlen
133+
134+
@maxlen.setter
135+
def maxlen(self, value):
136+
"""Set max length of the deque.
137+
138+
Pops items from left while length greater than max.
139+
140+
>>> deque = Deque()
141+
>>> deque.extendleft('abcde')
142+
>>> deque.maxlen = 3
143+
>>> list(deque)
144+
['c', 'd', 'e']
145+
146+
:param value: max length
147+
148+
"""
149+
self._maxlen = value
150+
with self._cache.transact(retry=True):
151+
while len(self._cache) > self._maxlen:
152+
self._popleft()
153+
127154
def _index(self, index, func):
128155
len_self = len(self)
129156

@@ -244,7 +271,7 @@ def __iadd__(self, iterable):
244271
:return: deque with added items
245272
246273
"""
247-
self.extend(iterable)
274+
self._extend(iterable)
248275
return self
249276

250277
def __iter__(self):
@@ -292,10 +319,11 @@ def __reversed__(self):
292319
pass
293320

294321
def __getstate__(self):
295-
return self.directory
322+
return self.directory, self.maxlen
296323

297324
def __setstate__(self, state):
298-
self.__init__(directory=state)
325+
directory, maxlen = state
326+
self.__init__(directory=directory, maxlen=maxlen)
299327

300328
def append(self, value):
301329
"""Add `value` to back of deque.
@@ -310,7 +338,12 @@ def append(self, value):
310338
:param value: value to add to back of deque
311339
312340
"""
313-
self._cache.push(value, retry=True)
341+
with self._cache.transact(retry=True):
342+
self._cache.push(value, retry=True)
343+
if len(self._cache) > self._maxlen:
344+
self._popleft()
345+
346+
_append = append
314347

315348
def appendleft(self, value):
316349
"""Add `value` to front of deque.
@@ -325,7 +358,12 @@ def appendleft(self, value):
325358
:param value: value to add to front of deque
326359
327360
"""
328-
self._cache.push(value, side='front', retry=True)
361+
with self._cache.transact(retry=True):
362+
self._cache.push(value, side='front', retry=True)
363+
if len(self._cache) > self._maxlen:
364+
self._pop()
365+
366+
_appendleft = appendleft
329367

330368
def clear(self):
331369
"""Remove all elements from deque.
@@ -340,6 +378,13 @@ def clear(self):
340378
"""
341379
self._cache.clear(retry=True)
342380

381+
_clear = clear
382+
383+
def copy(self):
384+
"""Copy deque with same directory and max length."""
385+
TypeSelf = type(self)
386+
return TypeSelf(directory=self.directory, maxlen=self.maxlen)
387+
343388
def count(self, value):
344389
"""Return number of occurrences of `value` in deque.
345390
@@ -365,7 +410,9 @@ def extend(self, iterable):
365410
366411
"""
367412
for value in iterable:
368-
self.append(value)
413+
self._append(value)
414+
415+
_extend = extend
369416

370417
def extendleft(self, iterable):
371418
"""Extend front side of deque with value from `iterable`.
@@ -379,7 +426,7 @@ def extendleft(self, iterable):
379426
380427
"""
381428
for value in iterable:
382-
self.appendleft(value)
429+
self._appendleft(value)
383430

384431
def peek(self):
385432
"""Peek at value at back of deque.
@@ -459,6 +506,8 @@ def pop(self):
459506
raise IndexError('pop from an empty deque')
460507
return value
461508

509+
_pop = pop
510+
462511
def popleft(self):
463512
"""Remove and return value at front of deque.
464513
@@ -483,6 +532,8 @@ def popleft(self):
483532
raise IndexError('pop from an empty deque')
484533
return value
485534

535+
_popleft = popleft
536+
486537
def remove(self, value):
487538
"""Remove first occurrence of `value` in deque.
488539
@@ -537,8 +588,8 @@ def reverse(self):
537588
# forward iterator and a reverse iterator, the reverse method could
538589
# avoid making copies of the values.
539590
temp = Deque(iterable=reversed(self))
540-
self.clear()
541-
self.extend(temp)
591+
self._clear()
592+
self._extend(temp)
542593
directory = temp.directory
543594
temp._cache.close()
544595
del temp
@@ -575,22 +626,22 @@ def rotate(self, steps=1):
575626

576627
for _ in range(steps):
577628
try:
578-
value = self.pop()
629+
value = self._pop()
579630
except IndexError:
580631
return
581632
else:
582-
self.appendleft(value)
633+
self._appendleft(value)
583634
else:
584635
steps *= -1
585636
steps %= len_self
586637

587638
for _ in range(steps):
588639
try:
589-
value = self.popleft()
640+
value = self._popleft()
590641
except IndexError:
591642
return
592643
else:
593-
self.append(value)
644+
self._append(value)
594645

595646
__hash__ = None # type: ignore
596647

@@ -669,7 +720,9 @@ def __init__(self, *args, **kwargs):
669720
args = args[1:]
670721
directory = None
671722
self._cache = Cache(directory, eviction_policy='none')
672-
self.update(*args, **kwargs)
723+
self._update(*args, **kwargs)
724+
725+
_update = MutableMapping.update
673726

674727
@classmethod
675728
def fromcache(cls, cache, *args, **kwargs):
@@ -695,7 +748,7 @@ def fromcache(cls, cache, *args, **kwargs):
695748
# pylint: disable=no-member,protected-access
696749
self = cls.__new__(cls)
697750
self._cache = cache
698-
self.update(*args, **kwargs)
751+
self._update(*args, **kwargs)
699752
return self
700753

701754
@property

docs/tutorial.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,9 @@ access and editing at both front and back sides. :class:`Deque
565565
4
566566
>>> other.popleft()
567567
'foo'
568+
>>> thing = Deque('abcde', maxlen=3)
569+
>>> list(thing)
570+
['c', 'd', 'e']
568571

569572
:class:`Deque <diskcache.Deque>` objects provide an efficient and safe means of
570573
cross-thread and cross-process communication. :class:`Deque <diskcache.Deque>`

tests/test_deque.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ def test_getsetdel(deque):
7777
assert len(deque) == 0
7878

7979

80+
def test_append(deque):
81+
deque.maxlen = 3
82+
for item in 'abcde':
83+
deque.append(item)
84+
assert deque == 'cde'
85+
86+
87+
def test_appendleft(deque):
88+
deque.maxlen = 3
89+
for item in 'abcde':
90+
deque.appendleft(item)
91+
assert deque == 'edc'
92+
93+
8094
def test_index_positive(deque):
8195
cache = mock.MagicMock()
8296
cache.__len__.return_value = 3
@@ -131,9 +145,12 @@ def test_state(deque):
131145
sequence = list('abcde')
132146
deque.extend(sequence)
133147
assert deque == sequence
148+
deque.maxlen = 3
149+
assert list(deque) == sequence[-3:]
134150
state = pickle.dumps(deque)
135151
values = pickle.loads(state)
136-
assert values == sequence
152+
assert values == sequence[-3:]
153+
assert values.maxlen == 3
137154

138155

139156
def test_compare(deque):
@@ -161,6 +178,14 @@ def test_repr():
161178
assert repr(deque) == 'Deque(directory=%r)' % directory
162179

163180

181+
def test_copy(deque):
182+
sequence = list('abcde')
183+
deque.extend(sequence)
184+
temp = deque.copy()
185+
assert deque == sequence
186+
assert temp == sequence
187+
188+
164189
def test_count(deque):
165190
deque += 'abbcccddddeeeee'
166191

0 commit comments

Comments
 (0)