On 02/19/2013 01:51 PM, Jan Cholasta wrote:
Hi,

On 5.2.2013 18:02, Petr Viktorin wrote:
CIDict, our case-insensitive dictionary, inherits from dict but did not
reimplement the full dict interface. Calling the missing methods
silently invoked case-sensitive behavior. Our code seems to avoid that,
but it's a bit of a minefield for new development.

Patch 119 adds the missing dict methods (except
view{items,keys,values}(), which now raise errors), and adds tests.

Can you please also add the (obj, **kwargs) and (**kwargs) variants of
__init__ and update?

Added, thanks for the catch.




Patches 117-118 modernize the testsuite a bit (these have been sitting
in my queue for a while, I think now is a good time to submit them):
The first one moves some old tests from the main code tree to tests/.
(The adtrust_install test wasn't run before, this move makes nose notice
it).
The second converts CIDict's unittest-based suite to nose.


Honza



--
Petr³
From da16447f327302227db6d0e663ccdb14066856e9 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <[email protected]>
Date: Tue, 5 Feb 2013 10:24:46 -0500
Subject: [PATCH] Add missing dict methods to CIDict

Make the CIDict interface match standard dict (except view* methods).

Add __contains__, __iter__, clear.
Add keyword and iterable support for __init__, update.
Also add values() and itervalues(). Previously the dict versions were
used; the new ones guarantee that the order matches keys().
Mark view* methods as not implemented.

Document that CIDict.copy() returns a plain dict.
Test the above additions, and fromkeys() which worked but wasn't tested.
---
 ipapython/ipautil.py                 |   66 +++++++++++++++++++++++++---------
 tests/test_ipapython/test_ipautil.py |   64 ++++++++++++++++++++++++++++++++
 2 files changed, 113 insertions(+), 17 deletions(-)

diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index c0ac3a1f76397e79672f1b99c2d46463d8e0af3e..81e85dd25dd73628569c285ad7c67cbd16daf065 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -439,10 +439,13 @@ class CIDict(dict):
     If you extend UserDict, isinstance(foo, dict) returns false.
     """
 
-    def __init__(self, default=None):
+    def __init__(self, default=None, **kwargs):
         super(CIDict, self).__init__()
-        self._keys = {}
-        self.update(default or {})
+        self._keys = {}  # mapping of lowercased keys to proper case
+        if default:
+            self.update(default)
+        if kwargs:
+            self.update(kwargs)
 
     def __getitem__(self, key):
         return super(CIDict, self).__getitem__(key.lower())
@@ -455,11 +458,22 @@ class CIDict(dict):
     def __delitem__(self, key):
         lower_key = key.lower()
         del self._keys[lower_key]
-        return super(CIDict, self).__delitem__(key.lower())
+        return super(CIDict, self).__delitem__(lower_key)
 
-    def update(self, dict):
-        for key in dict.keys():
-            self[key] = dict[key]
+    def update(self, new=None, **kwargs):
+        if new:
+            try:
+                keys = new.keys
+            except AttributeError:
+                self.update(dict(new))
+            else:
+                for key in keys():
+                    self[key] = new[key]
+        for key, value in kwargs.iteritems():
+            self[key] = value
+
+    def __contains__(self, key):
+        return super(CIDict, self).__contains__(key.lower())
 
     def has_key(self, key):
         return super(CIDict, self).has_key(key.lower())
@@ -470,26 +484,31 @@ class CIDict(dict):
         except KeyError:
             return failobj
 
+    def __iter__(self):
+        return self._keys.itervalues()
+
     def keys(self):
-        return self._keys.values()
+        return list(self.iterkeys())
 
     def items(self):
-        result = []
-        for k in self._keys.values():
-            result.append((k, self[k]))
-        return result
+        return list(self.iteritems())
+
+    def values(self):
+        return list(self.itervalues())
 
     def copy(self):
+        """Returns a copy of this CIDict as an ordinary dict"""
         copy = {}
-        for k in self._keys.values():
-            copy[k] = self[k]
-        return copy
+        return dict(self.items())
 
     def iteritems(self):
-        return self.copy().iteritems()
+        return ((k, self[k]) for k in self._keys.itervalues())
 
     def iterkeys(self):
-        return self.copy().iterkeys()
+        return self._keys.itervalues()
+
+    def itervalues(self):
+        return (v for k, v in self.iteritems())
 
     def setdefault(self, key, value=None):
         try:
@@ -515,6 +534,19 @@ class CIDict(dict):
 
         return (key, value)
 
+    def clear(self):
+        self._keys.clear()
+        return super(CIDict, self).clear()
+
+    def viewitems(self):
+        raise NotImplementedError('CIDict.viewitems is not implemented')
+
+    def viewkeys(self):
+        raise NotImplementedError('CIDict.viewkeys is not implemented')
+
+    def viewvvalues(self):
+        raise NotImplementedError('CIDict.viewvvalues is not implemented')
+
 
 class GeneralizedTimeZone(datetime.tzinfo):
     """This class is a basic timezone wrapper for the offset specified
diff --git a/tests/test_ipapython/test_ipautil.py b/tests/test_ipapython/test_ipautil.py
index 95933581f2f83f61008493079ba11fcda59487ef..1eebe026f33d1777ae899429e385eb61733b2720 100644
--- a/tests/test_ipapython/test_ipautil.py
+++ b/tests/test_ipapython/test_ipautil.py
@@ -76,6 +76,18 @@ class TestCIDict(object):
         self.cidict["key2"] = "val2"
         self.cidict["KEY3"] = "VAL3"
 
+    def test_init(self):
+        cidict = ipautil.CIDict()
+        assert dict(cidict.items()) == {}
+        cidict = ipautil.CIDict([('a', 2), ('b', 3), ('C', 4)])
+        assert dict(cidict.items()) == {'a': 2, 'b': 3, 'C': 4}
+        cidict = ipautil.CIDict([('a', 2), ('b', None)], b=3, C=4)
+        assert dict(cidict.items()) == {'a': 2, 'b': 3, 'C': 4}
+        cidict = ipautil.CIDict({'a': 2, 'b': None}, b=3, C=4)
+        assert dict(cidict.items()) == {'a': 2, 'b': 3, 'C': 4}
+        cidict = ipautil.CIDict(a=2, b=3, C=4)
+        assert dict(cidict.items()) == {'a': 2, 'b': 3, 'C': 4}
+
     def test_len(self):
         nose.tools.assert_equal(3, len(self.cidict))
 
@@ -130,14 +142,31 @@ class TestCIDict(object):
         assert self.cidict.has_key("key2")
         assert self.cidict.has_key("key3")
 
+        assert not self.cidict.has_key("Key4")
+
+    def test_contains(self):
+        assert "KEY1" in self.cidict
+        assert "key2" in self.cidict
+        assert "key3" in self.cidict
+
+        assert "Key4" not in self.cidict
+
     def test_items(self):
         items = self.cidict.items()
         nose.tools.assert_equal(3, len(items))
         items_set = set(items)
         assert ("Key1", "val1") in items_set
         assert ("key2", "val2") in items_set
         assert ("KEY3", "VAL3") in items_set
 
+        assert self.cidict.items() == list(self.cidict.iteritems()) == zip(
+            self.cidict.iterkeys(), self.cidict.itervalues())
+
+    def test_iter(self):
+        items = []
+        assert list(self.cidict) == list(self.cidict.keys())
+        assert sorted(self.cidict) == sorted(['Key1', 'key2', 'KEY3'])
+
     def test_iteritems(self):
         items = []
         for (k,v) in self.cidict.iteritems():
@@ -176,14 +205,18 @@ class TestCIDict(object):
         assert "key2" in keys_set
         assert "KEY3" in keys_set
 
+        assert self.cidict.keys() == list(self.cidict.iterkeys())
+
     def test_values(self):
         values = self.cidict.values()
         nose.tools.assert_equal(3, len(values))
         values_set = set(values)
         assert "val1" in values_set
         assert "val2" in values_set
         assert "VAL3" in values_set
 
+        assert self.cidict.values() == list(self.cidict.itervalues())
+
     def test_update(self):
         newdict = { "KEY2": "newval2",
                     "key4": "val4" }
@@ -199,6 +232,23 @@ class TestCIDict(object):
         assert ("KEY3", "VAL3") in items_set
         assert ("key4", "val4") in items_set
 
+    def test_update_dict_and_kwargs(self):
+        self.cidict.update({'a': 'va', 'b': None}, b='vb', key2='v2')
+        assert dict(self.cidict.items()) == {
+            'a': 'va', 'b': 'vb',
+            'Key1': 'val1', 'key2': 'v2', 'KEY3': 'VAL3'}
+
+    def test_update_list_and_kwargs(self):
+        self.cidict.update([('a', 'va'), ('b', None)], b='vb', key2='val2')
+        assert dict(self.cidict.items()) == {
+            'a': 'va', 'b': 'vb',
+            'Key1': 'val1', 'key2': 'val2', 'KEY3': 'VAL3'}
+
+    def test_update_kwargs(self):
+        self.cidict.update(b='vb', key2='val2')
+        assert dict(self.cidict.items()) == {
+            'b': 'vb', 'Key1': 'val1', 'key2': 'val2', 'KEY3': 'VAL3'}
+
     def test_setdefault(self):
         nose.tools.assert_equal("val1", self.cidict.setdefault("KEY1", "default"))
 
@@ -242,6 +292,20 @@ class TestCIDict(object):
         assert item in items
         items.discard(item)
 
+    def test_fromkeys(self):
+        dct = ipautil.CIDict.fromkeys(('A', 'b', 'C'))
+        assert sorted(dct.keys()) == sorted(['A', 'b', 'C'])
+        assert sorted(dct.values()) == [None] * 3
+
+    def test_clear(self):
+        self.cidict.clear()
+        assert self.cidict == {}
+        assert self.cidict.keys() == []
+        assert self.cidict.values() == []
+        assert self.cidict.items() == []
+        assert self.cidict._keys == {}
+
+
 class TestTimeParser(object):
     def test_simple(self):
         timestr = "20070803"
-- 
1.7.7.6

_______________________________________________
Freeipa-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to