-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
15c63f2
commit 161c28b
Showing
3 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import unittest | ||
from vdf import VDFDict | ||
|
||
|
||
class DuplicateOrderedDict_test(unittest.TestCase): | ||
map_test = ( | ||
("1", 2), | ||
("4", 3),("4", 3),("4", 2), | ||
("7", 2), | ||
("1", 2), | ||
) | ||
|
||
def test_keys(self): | ||
_dict = VDFDict(self.map_test) | ||
self.assertSequenceEqual( | ||
tuple(_dict.keys()), | ||
tuple(x[0] for x in self.map_test)) | ||
|
||
def test_values(self): | ||
_dict = VDFDict(self.map_test) | ||
self.assertSequenceEqual( | ||
tuple(_dict.values()), | ||
tuple(x[1] for x in self.map_test)) | ||
|
||
def test_items(self): | ||
_dict = VDFDict(self.map_test) | ||
self.assertSequenceEqual( | ||
tuple(_dict.items()), | ||
self.map_test) | ||
|
||
def test_in(self): | ||
a = VDFDict({"1":2, "3":4, "5":6}) | ||
self.assertTrue('1' in a) | ||
self.assertFalse('6' in a) | ||
|
||
def test_direct_access_set(self): | ||
a = {"1":2, "3":4, "5":6} | ||
b = VDFDict() | ||
for k,v in a.items(): | ||
b[k] = v | ||
self.assertDictEqual(a, b) | ||
|
||
def test_direct_access_get(self): | ||
b = dict() | ||
a = VDFDict({"1":2, "3":4, "5":6}) | ||
for k,v in a.items(): | ||
b[k] = v | ||
self.assertDictEqual(a, b) | ||
|
||
def test_duplicate_keys(self): | ||
items = (('key1', 1), ('key1', 2), ('key3', 3), ('key1', 1)) | ||
keys = tuple(x[0] for x in items) | ||
values = tuple(x[1] for x in items) | ||
_dict = VDFDict((('key1', 1), ('key1', 2), ('key3', 3), ('key1', 1))) | ||
self.assertSequenceEqual(tuple(_dict.items()), items) | ||
self.assertSequenceEqual(tuple(_dict.keys()), keys) | ||
self.assertSequenceEqual(tuple(_dict.values()), values) | ||
|
||
def test_update(self): | ||
a = VDFDict((("1",2),("1",2),("5",3),("1",2))) | ||
b = VDFDict() | ||
b.update((("1",2),("1",2))) | ||
b.update((("5",3),("1",2))) | ||
self.assertSequenceEqual(tuple(a.items()), tuple(b.items())) | ||
|
||
def test_update_2(self): | ||
self.assertSequenceEqual( | ||
tuple(VDFDict(self.map_test).items()), | ||
tuple(VDFDict(VDFDict(self.map_test)).items())) | ||
|
||
def test_del(self): | ||
""" Tests del """ | ||
a = VDFDict((("1",2),("1",2),("5",3),("1",2))) | ||
b = VDFDict((("1",2),("1",2),("1",2))) | ||
del a["5"] | ||
self.assertSequenceEqual(tuple(a.items()), tuple(b.items())) | ||
|
||
def test_remove_all(self): | ||
a = VDFDict((("1",2),("1",2),("5",3),("1",2))) | ||
b = VDFDict((("5",3),)) | ||
a.remove_all_by_key("1") | ||
self.assertSequenceEqual(tuple(a.items()), tuple(b.items())) | ||
|
||
def test_clear(self): | ||
a = VDFDict((("1",2),("1",2),("5",3),("1",2))) | ||
a.clear() | ||
self.assertEqual(len(a), 0) | ||
|
||
def test_get_all(self): | ||
a = VDFDict((("1",2),("1",2**31),("5",3),("1",2))) | ||
self.assertSequenceEqual( | ||
tuple(a.get_all_by_key("1")), | ||
(2,2**31,2)) | ||
|
||
def test_get(self): | ||
a = VDFDict({'key': 'foo'}) | ||
self.assertEqual(a.get("key"), a["key"]) | ||
|
||
def test_repr(self): | ||
a = VDFDict(self.map_test) | ||
self.assertEqual( | ||
repr(a), | ||
"VDFDict(%s)" % repr(self.map_test) | ||
) | ||
|
||
|
||
def test_exception_insert(self): | ||
""" Only strings (and tuples) are supported as keys """ | ||
a = VDFDict() | ||
self.assertRaises(TypeError, a.__setitem__, 5, "foo") | ||
|
||
def test_exception_remove_all(self): | ||
""" Only strings are supported as keys """ | ||
a = VDFDict() | ||
self.assertRaises(TypeError, a.remove_all_by_key, 5) | ||
|
||
def test_exception_get_all(self): | ||
""" Only strings are supported as keys """ | ||
a = VDFDict((("1",2),("1",2**31),("5",3),("1",2))) | ||
self.assertRaises(TypeError, a.get_all_by_key, 5) | ||
|
||
def test_exception_del(self): | ||
a = VDFDict((("1",2),("1",2**31),("5",3),("1",2))) | ||
self.assertRaises(KeyError, a.__delitem__, "7") | ||
|
||
def test_exception_update_1(self): | ||
a = VDFDict((("1",2),("1",2**31),("5",3),("1",2))) | ||
self.assertRaises(TypeError, a.update, 7) | ||
|
||
def test_exception_update_2(self): | ||
a = VDFDict((("1",2),("1",2**31),("5",3),("1",2))) | ||
class foo(): | ||
def items(self): | ||
return None | ||
self.assertRaises(TypeError, a.update, foo()) | ||
|
||
def test_exception_update_3(self): | ||
a = VDFDict((("1",2),("1",2**31),("5",3),("1",2))) | ||
self.assertRaises(TypeError, a.update, range(10)) | ||
|
||
def test_exception_update_4(self): | ||
a = VDFDict((("1",2),("1",2**31),("5",3),("1",2))) | ||
class foo(): | ||
def items(self): | ||
return ((1,2,3),(4,)) | ||
self.assertRaises(TypeError, a.update, foo()) | ||
|
||
def test_exception_set_item(self): | ||
a = VDFDict() | ||
self.assertRaises(KeyError, a.__setitem__, (7, "key"), "value") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import sys | ||
from collections import Iterable | ||
|
||
if sys.version_info[0] >= 3: | ||
string_type = str | ||
_iter_helper = lambda x: x | ||
else: | ||
string_type = basestring | ||
_iter_helper = tuple | ||
|
||
|
||
class VDFDict(dict): | ||
def __init__(self, values=None): | ||
""" | ||
A dictionary implmentation which allows duplicate keys and contains the insert order. | ||
``values`` can be used to initialize this `DuplicateOrderedDict` instance.. | ||
Dict like objects and iterables containing iterables of the length 2 are supported. | ||
""" | ||
self.__omap = [] | ||
if not values is None: | ||
self.update(values) | ||
|
||
def __repr__(self): | ||
out = "%s(" % self.__class__.__name__ | ||
out += "%s)" % repr(tuple(self.items())) | ||
return out | ||
|
||
def __len__(self): | ||
return len(self.__omap) | ||
|
||
def __setitem__(self, key, value): | ||
if not isinstance(key, tuple): | ||
idx = 0 | ||
while True: | ||
if (idx, key) not in self: | ||
self.__omap.append((idx, key)) | ||
break | ||
else: | ||
idx += 1 | ||
key = (idx, key) | ||
else: | ||
if key not in self: | ||
raise KeyError("%s doesn\'t exist" % repr(key)) | ||
if not isinstance(key[1], string_type): | ||
raise TypeError("The key need to be a string") | ||
super(VDFDict, self).__setitem__(key, value) | ||
|
||
def __getitem__(self, key): | ||
if not isinstance(key, tuple): | ||
key = (0, key) | ||
return super(VDFDict, self).__getitem__(key) | ||
|
||
def __delitem__(self, key): | ||
if not isinstance(key, tuple): | ||
key = (0, key) | ||
try: | ||
self.__omap.remove(key) | ||
except ValueError: | ||
raise KeyError(key) | ||
return dict.__delitem__(self, key) | ||
|
||
def __iter__(self): | ||
return iter(self.keys()) | ||
|
||
def __contains__(self, item): | ||
if isinstance(item, tuple): | ||
return dict.__contains__(self, item) | ||
return dict.__contains__(self, (0, item)) | ||
|
||
def __eq__(self, other): | ||
""" | ||
This only returns true if the k,v pairs of `other` | ||
are returned in the same order. | ||
""" | ||
if isinstance(other, dict): | ||
other = tuple(other.items()) | ||
return other == tuple(self.items()) | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
|
||
def clear(self): | ||
dict.clear(self) | ||
self.__omap = list() | ||
|
||
def get(self, key, default=None): | ||
if not isinstance(key, tuple): | ||
key = (0, key) | ||
return dict.get(self, key, default) | ||
|
||
def iteritems(self): | ||
return ((key[1], self[key]) for key in self.__omap) | ||
|
||
def items(self): | ||
return _iter_helper(self.iteritems()) | ||
|
||
def iterkeys(self): | ||
return (key[1] for key in self.__omap) | ||
|
||
def keys(self): | ||
return _iter_helper(self.iterkeys()) | ||
|
||
def itervalues(self): | ||
return (self[key] for key in self.__omap) | ||
|
||
def values(self): | ||
return _iter_helper(self.itervalues()) | ||
|
||
def update(self, data=None, **kwargs): | ||
if not data is None: | ||
if hasattr(data, 'items'): | ||
data = data.items() | ||
if not isinstance(data, Iterable): | ||
raise TypeError('Argument or its items method need to provide an iterable.') | ||
for kv in data: | ||
if not hasattr(kv, '__len__') or len(kv) != 2: | ||
raise TypeError('Argument, or its keys method need to provide iterables of the length 2.') | ||
self[kv[0]] = kv[1] | ||
if len(kwargs) > 0: | ||
self.update(kwargs) | ||
|
||
def get_all_by_key(self, key): | ||
""" Returns all values of the given key as a generator """ | ||
if not isinstance(key, string_type): | ||
raise TypeError("Key need to be a string.") | ||
return (self[d] for d in self.__omap if d[1] == key) | ||
|
||
def remove_all_by_key(self, key): | ||
""" Removes all items with the given key """ | ||
if not isinstance(key, string_type): | ||
raise TypeError("Key need to be a string.") | ||
to_del = list() | ||
for d in self.__omap: | ||
if d[1] == key: | ||
to_del.append(d) | ||
for d in to_del: | ||
del self[d] | ||
|
||
|
||
|