Repository: jbasko/configmanager

Issue:
Get rid of current ConfigManager.items() implementation

Discussion:

Patch:
diff --git a/configmanager/__init__.py b/configmanager/__init__.py
index b0b47c1..e912d5f 100644
--- a/configmanager/__init__.py
+++ b/configmanager/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '0.0.15'
+__version__ = '0.0.16'
 
 from .base import not_set, ConfigItem, ConfigManager
 from .exceptions import UnknownConfigItem, ConfigValueNotSet, UnsupportedOperation
diff --git a/configmanager/base.py b/configmanager/base.py
index 27b2a3a..fbe389e 100644
--- a/configmanager/base.py
+++ b/configmanager/base.py
@@ -115,6 +115,7 @@ class ConfigItem(object):
         """
         The first segment of :attr:`.path`.
         """
+        # TODO Deprecated
         return self.path[0]
 
     @property
@@ -122,6 +123,7 @@ class ConfigItem(object):
         """
         The second segment of :attr:`.path`.
         """
+        # TODO Deprecated
         return self.path[-1]
 
     @property
@@ -129,6 +131,7 @@ class ConfigItem(object):
         """
         A string, :attr:`.path` joined by dots.
         """
+        # TODO Deprecated
         return '.'.join(self.path)
 
     @property
@@ -384,26 +387,6 @@ class ConfigManager(object):
         """
         return self._resolve_config_path(*path) in self._configs
 
-    def items(self, *prefix):
-        """
-        Returns:
-            list: list of :class:`.ConfigItem` instances managed by this manager.
-            
-            If ``prefix`` is specified, only items with matching path prefix are included.
-        
-        Note:
-            This is different from ``ConfigParser.items()`` which returns what we would call
-            here resolved values of items.
-        
-        See Also:
-            :meth:`.ConfigManager.export()`
-        """
-        prefix = resolve_config_prefix(*prefix)
-        if not prefix:
-            return list(self._configs.values())
-        else:
-            return [c for c in self._configs.values() if c.path[:len(prefix)] == prefix[:]]
-
     def find_items(self, *prefix):
         prefix = resolve_config_prefix(*prefix)
         if not prefix:
@@ -433,7 +416,7 @@ class ConfigManager(object):
         """
         pairs = []
 
-        for item in self.items(*prefix):
+        for item in self.find_items(*prefix):
             if item.has_value or item.has_default:
                 item_name_without_prefix = '.'.join(item.path[len(prefix):])
                 pairs.append((item_name_without_prefix, item.value))
diff --git a/configmanager/proxies.py b/configmanager/proxies.py
index 1e2a07b..3fbdf00 100644
--- a/configmanager/proxies.py
+++ b/configmanager/proxies.py
@@ -60,6 +60,9 @@ class PathProxy(object):
     def __eq__(self, other):
         return isinstance(other, self.__class__) and (self._path_ == other._path_) and (self._config_ is other._config_)
 
+    def items(self):
+        return ((k, self[k]) for k in self)
+
 
 class ConfigItemProxy(PathProxy):
 
diff --git a/docs/guide.rst b/docs/guide.rst
index beaa2b1..e76a5ce 100644
--- a/docs/guide.rst
+++ b/docs/guide.rst
@@ -109,13 +109,13 @@ Copying Config Items Between Managers
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 The easiest way to copy all config items from one :ref:`config-manager` to another is
-to use :meth:`.ConfigManager.items()`::
+to use :meth:`.ConfigManager.find_items()`::
 
     config1 = ConfigManager(
          ConfigItem('upload', 'threads', default=1, value=3)
     )
 
-    config2 = ConfigManager(*config1.items())
+    config2 = ConfigManager(*config1.find_items())
 
 If you don't want to keep the values (just the defaults), you can call :meth:`.ConfigManager.reset`:
 
@@ -128,4 +128,4 @@ If you don't want to keep the values (just the defaults), you can call :meth:`.C
 If the second config manager already exists, you can add config items to it with
 :meth:`.ConfigManager.add`::
 
-    map(config2.add, config1.items())
+    map(config2.add, config1.find_items())


Test patch:
diff --git a/tests/test_configmanager.py b/tests/test_configmanager.py
index b0bbe4d..d93ec92 100644
--- a/tests/test_configmanager.py
+++ b/tests/test_configmanager.py
@@ -164,52 +164,6 @@ def test_reset():
     assert m.get('forgettable', 'y') == 'YES'
 
 
-def test_items_returns_all_config_items():
-    m = ConfigManager(
-        ConfigItem('a', 'aa', 'aaa'),
-        ConfigItem('b', 'bb'),
-        ConfigItem('a', 'aa', 'AAA'),
-        ConfigItem('b', 'BB'),
-    )
-
-    assert len(m.items()) == 4
-    assert m.items()[0].path == ('a', 'aa', 'aaa')
-    assert m.items()[-1].path == ('b', 'BB')
-
-
-def test_items_with_prefix_returns_matching_config_items():
-    m = ConfigManager(
-        ConfigItem('a', 'aa', 'aaa'),
-        ConfigItem('a.aa', 'haha'),
-        ConfigItem('b', 'bb'),
-        ConfigItem('a', 'aa', 'AAA'),
-        ConfigItem('b', 'BB'),
-    )
-
-    a_items = m.items('a')
-    assert len(a_items) == 2
-    assert a_items[0].path == ('a', 'aa', 'aaa')
-    assert a_items[1].path == ('a', 'aa', 'AAA')
-
-    a_aa_items1 = m.items('a', 'aa')
-    a_aa_items2 = m.items('a.aa')
-    assert a_aa_items1 != a_aa_items2
-
-    assert len(a_aa_items1) == 2
-    assert a_aa_items1[0].path == ('a', 'aa', 'aaa')
-    assert a_aa_items1[1].path == ('a', 'aa', 'AAA')
-
-    assert len(a_aa_items2) == 1
-    assert a_aa_items2[0].path == ('a.aa', 'haha')
-
-    assert len(m.items('b')) == 2
-    assert len(m.items('b', 'bb')) == 1
-    assert len(m.items('b.bb')) == 0
-
-    no_items = m.items('haha.haha')
-    assert len(no_items) == 0
-
-
 def test_can_add_items_to_default_section_and_set_their_value_without_naming_section():
     m = ConfigManager()
 
@@ -302,7 +256,7 @@ def test_copying_items_between_managers():
         ConfigItem('b', 'bbbb', 'q'),
     )
 
-    n = ConfigManager(*m.items())
+    n = ConfigManager(*m.find_items())
     assert m.export() == n.export()
 
     m.set('a', 'x', 'xaxa')
diff --git a/tests/test_proxies.py b/tests/test_proxies.py
index 6b94e11..7035e8f 100644
--- a/tests/test_proxies.py
+++ b/tests/test_proxies.py
@@ -51,8 +51,8 @@ def test_value_proxy_does_not_provide_section_and_item_access(config):
     with pytest.raises(AttributeError):
         config.v.uploads.get('enabled', True)
 
-    with pytest.raises(AttributeError):
-        config.v.items()
+    # This is different, "items" don't refer to config items here, it's part of Python's dictionary interface.
+    assert config.v.items()
 
 
 def test_value_proxy_is_iterable(config):
@@ -151,8 +151,8 @@ def test_section_proxy_raises_attribute_error_if_section_not_specified(config):
     with pytest.raises(AttributeError):
         config.s.set('enabled', True)
 
-    with pytest.raises(AttributeError):
-        config.s.items()
+    # This is different now, items() is part of Python's dictionary interface, nothing to do with config items.
+    assert config.s.items()
 
     with pytest.raises(AttributeError):
         config.s.has('uploads')
@@ -194,3 +194,17 @@ def test_item_proxy_is_iterable(config):
     assert ('auth', 'server', 'port') in paths
     assert ('uploads', 'something_else') not in paths
 
+
+def test_proxies_support_items_transparently(config):
+    items = dict(config.t.items())
+    assert len(items) == 7
+    assert isinstance(items[('uploads', 'enabled')], ConfigItem)
+
+    sections = dict(config.s.items())
+    assert len(sections) == 5
+    assert isinstance(sections[('auth', 'server')], ConfigSectionProxy)
+
+    values = dict(config.v.items())
+    assert len(values) == 2
+    assert values[('uploads', 'enabled')] is False
+    assert values[('downloads', 'threads')] == 0