Skip to content

Commit

Permalink
Added DuplicateOrderedDict
Browse files Browse the repository at this point in the history
  • Loading branch information
SleepProgger authored and rossengeorgiev committed Apr 1, 2016
1 parent 15c63f2 commit 161c28b
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 0 deletions.
151 changes: 151 additions & 0 deletions tests/test_duplicate_ordered_dict.py
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")

4 changes: 4 additions & 0 deletions vdf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
Module for deserializing/serializing to and from VDF
"""
__version__ = "1.10"
import collections
__author__ = "Rossen Georgiev"

from vdf.vdfDict import VDFDict

import re
import sys
import struct
Expand All @@ -26,6 +29,7 @@ def strip_bom(line):

def strip_bom(line):
return line.lstrip(BOMS if isinstance(line, str) else BOMS_UNICODE)



def parse(fp, mapper=dict):
Expand Down
142 changes: 142 additions & 0 deletions vdf/vdfDict.py
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]



0 comments on commit 161c28b

Please sign in to comment.