Fix getattr on namespaces, closes #2
This commit is contained in:
parent
ba8dbc7740
commit
ebf519bd9b
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue