Fix getattr on namespaces, closes #2

This commit is contained in:
Lucidiot 2019-10-05 14:36:24 +02:00
parent ba8dbc7740
commit ebf519bd9b
Signed by: lucidiot
GPG Key ID: 3358C1CA6906FB8D
2 changed files with 22 additions and 42 deletions

View File

@ -3,10 +3,7 @@ This module provides helpers for working with collections (as in abstract
base classes defined in the ``collections.abc`` standard Python module: base classes defined in the ``collections.abc`` standard Python module:
iterables, iterators, mappings, etc.). iterables, iterators, mappings, etc.).
""" """
from typing import ( from typing import Mapping, Dict, Sequence, Set, Iterable, ByteString, Any
Mapping, MutableMapping, Sequence, Set,
Iterable, Iterator, ByteString, Any,
)
def namespacify(value: Any) -> Any: def namespacify(value: Any) -> Any:
@ -42,12 +39,11 @@ def namespacify(value: Any) -> Any:
return value return value
class Namespace(MutableMapping): class Namespace(Dict):
""" """
A class that maps items of a mapping to attributes. Takes the same A subclass of ``dict`` which allows accessing items using attributes.
arguments as a classic ``dict``. Attributes and items are kept in sync; Attributes and items are kept in sync; deleting an item deletes the
deleting an item deletes the attribute, and updating an item updates the attribute, and updating an item updates the attribute, and vice-versa.
attribute, and vice-versa.
Getting an attribute will call :meth:`namespacify` on the returned value, Getting an attribute will call :meth:`namespacify` on the returned value,
to allow easier manipulation of complex structures such as JSON documents: to allow easier manipulation of complex structures such as JSON documents:
@ -60,45 +56,29 @@ class Namespace(MutableMapping):
42 42
""" """
def __init__(self, *args: Iterable, **kwargs: Any):
"""
:param \\*args: Mappings or iterables yielding
``(key, value)`` two-tuples.
:param \\**kwargs: Key-value pairs set as attributes.
"""
for iterable in (*args, kwargs):
if isinstance(iterable, Mapping):
iterable = iterable.items()
for k, v in iterable:
setattr(self, k, v)
def __getitem__(self, name: Any) -> Any: def __getitem__(self, name: Any) -> Any:
return namespacify(super().__getitem__(name))
def __getattr__(self, name: str) -> Any:
try: try:
attr = getattr(self, name) return self[name]
except AttributeError: except KeyError:
raise KeyError(repr(name)) raise AttributeError(repr(name))
return namespacify(attr)
def __setitem__(self, name: Any, value: Any) -> None: def __setattr__(self, name: str, value: Any) -> None:
setattr(self, name, value) self[name] = value
def __delitem__(self, name: Any) -> None: def __delattr__(self, name: str) -> None:
try: try:
delattr(self, name) del self[name]
except AttributeError: except KeyError as e:
raise KeyError(repr(name)) raise AttributeError(str(e))
def __iter__(self) -> Iterator:
return iter(self.__dict__)
def __len__(self) -> int:
return len(self.__dict__)
def __repr__(self) -> str: def __repr__(self) -> str:
return '{}({!r})'.format(self.__class__.__name__, self.__dict__) return '{}({})'.format(self.__class__.__name__, super().__repr__())
def __str__(self) -> str: def __str__(self) -> str:
return str(self.__dict__) return str(dict(self))
def _repr_pretty_(self, p: Any, cycle: bool) -> None: def _repr_pretty_(self, p: Any, cycle: bool) -> None:
# IPython's pretty printing # IPython's pretty printing
@ -106,11 +86,11 @@ class Namespace(MutableMapping):
p.text('{}(...)'.format(self.__class__.__name__)) p.text('{}(...)'.format(self.__class__.__name__))
else: else:
p.text('{}('.format(self.__class__.__name__)) p.text('{}('.format(self.__class__.__name__))
p.pretty(self.__dict__) p.pretty(dict(self))
p.text(')') p.text(')')
def copy(self) -> 'Namespace': def copy(self) -> 'Namespace':
""" """
Create a shallow copy of this namespace. Create a shallow copy of this namespace.
""" """
return self.__class__(self.__dict__) return self.__class__(self)

View File

@ -55,7 +55,7 @@ class TestNamespace(TestCase):
def test_init(self) -> None: def test_init(self) -> None:
ns: Namespace = Namespace() ns: Namespace = Namespace()
self.assertDictEqual(dict(ns), {}) self.assertDictEqual(dict(ns), {})
ns = Namespace({'b': 3}, {'a': 4}, a=1, b=2) ns = Namespace({'b': 3}, a=1, b=2)
self.assertDictEqual(dict(ns), {'a': 1, 'b': 2}) self.assertDictEqual(dict(ns), {'a': 1, 'b': 2})
def test_getitem(self) -> None: def test_getitem(self) -> None: