Skip to content

Commit 08ad09c

Browse files
authored
Guard iterations (#100)
* Fix #99: guard iterations * Update CHANGES
1 parent e7f2044 commit 08ad09c

4 files changed

Lines changed: 61 additions & 2 deletions

File tree

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
3.1.0 (2017-xx-xx)
2+
------------------
3+
4+
* Fix #99: raise `RuntimeError` on dict iterations if the dict was changed
5+
6+
17
3.0.0 (2017-06-21)
28
------------------
39

multidict/_multidict.pyx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,16 +638,20 @@ cdef class _ItemsIter:
638638
cdef _Impl _impl
639639
cdef int _current
640640
cdef int _len
641+
cdef unsigned long long _version
641642

642643
def __cinit__(self, _Impl impl):
643644
self._impl = impl
644645
self._current = 0
645-
self._len = len(self._impl._items)
646+
self._version = impl._version
647+
self._len = len(impl._items)
646648

647649
def __iter__(self):
648650
return self
649651

650652
def __next__(self):
653+
if self._version != self._impl._version:
654+
raise RuntimeError("Dictionary changed during iteration")
651655
if self._current == self._len:
652656
raise StopIteration
653657
item = <_Pair>self._impl._items[self._current]
@@ -700,16 +704,20 @@ cdef class _ValuesIter:
700704
cdef _Impl _impl
701705
cdef int _current
702706
cdef int _len
707+
cdef unsigned long long _version
703708

704709
def __cinit__(self, _Impl impl):
705710
self._impl = impl
706711
self._current = 0
707-
self._len = len(self._impl._items)
712+
self._len = len(impl._items)
713+
self._version = impl._version
708714

709715
def __iter__(self):
710716
return self
711717

712718
def __next__(self):
719+
if self._version != self._impl._version:
720+
raise RuntimeError("Dictionary changed during iteration")
713721
if self._current == self._len:
714722
raise StopIteration
715723
item = <_Pair>self._impl._items[self._current]
@@ -747,16 +755,20 @@ cdef class _KeysIter:
747755
cdef _Impl _impl
748756
cdef int _current
749757
cdef int _len
758+
cdef unsigned long long _version
750759

751760
def __cinit__(self, _Impl impl):
752761
self._impl = impl
753762
self._current = 0
754763
self._len = len(self._impl._items)
764+
self._version = impl._version
755765

756766
def __iter__(self):
757767
return self
758768

759769
def __next__(self):
770+
if self._version != self._impl._version:
771+
raise RuntimeError("Dictionary changed during iteration")
760772
if self._current == self._len:
761773
raise StopIteration
762774
item = <_Pair>self._impl._items[self._current]

multidict/_multidict_py.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ class _ViewBase:
375375

376376
def __init__(self, impl):
377377
self._impl = impl
378+
self._version = impl._version
378379

379380
def __len__(self):
380381
return len(self._impl._items)
@@ -392,6 +393,8 @@ def __contains__(self, item):
392393

393394
def __iter__(self):
394395
for i, k, v in self._impl._items:
396+
if self._version != self._impl._version:
397+
raise RuntimeError("Dictionary changed during iteration")
395398
yield k, v
396399

397400
def __repr__(self):
@@ -412,6 +415,8 @@ def __contains__(self, value):
412415

413416
def __iter__(self):
414417
for item in self._impl._items:
418+
if self._version != self._impl._version:
419+
raise RuntimeError("Dictionary changed during iteration")
415420
yield item[2]
416421

417422
def __repr__(self):
@@ -432,6 +437,8 @@ def __contains__(self, key):
432437

433438
def __iter__(self):
434439
for item in self._impl._items:
440+
if self._version != self._impl._version:
441+
raise RuntimeError("Dictionary changed during iteration")
435442
yield item[1]
436443

437444
def __repr__(self):

tests/test_guard.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import pytest
2+
3+
from multidict._multidict import MultiDict
4+
from multidict._multidict_py import MultiDict as PyMultiDict
5+
6+
7+
@pytest.fixture(params=[MultiDict, PyMultiDict],
8+
ids=['MultiDict', 'PyMultiDict'])
9+
def cls(request):
10+
return request.param
11+
12+
13+
def test_guard_items(cls):
14+
md = cls({'a': 'b'})
15+
it = iter(md.items())
16+
md['a'] = 'c'
17+
with pytest.raises(RuntimeError):
18+
next(it)
19+
20+
21+
def test_guard_keys(cls):
22+
md = cls({'a': 'b'})
23+
it = iter(md.keys())
24+
md['a'] = 'c'
25+
with pytest.raises(RuntimeError):
26+
next(it)
27+
28+
29+
def test_guard_values(cls):
30+
md = cls({'a': 'b'})
31+
it = iter(md.values())
32+
md['a'] = 'c'
33+
with pytest.raises(RuntimeError):
34+
next(it)

0 commit comments

Comments
 (0)