Package attrbox

Attribute-based data structures.

Otto the Otter
Otto the Otter

Build PyPI Supported Python Versions

Why?

I have common use cases where I want to improve python's dict and list:

  • AttrDict: attribute-based dict with better merge and deep value access
  • AttrList: list that broadcasts operations to its members
  • Environment: reading environment files
  • Configuration: loading command-line arguments and configuration files
  • JSend: sending JSON responses

Install

python -m pip install attrbox

AttrDict

AttrDict features:

  • Attribute Syntax for dict similar to accessing properties in JavaScript: thing.prop means thing["prop"] for get / set / delete.

  • No KeyError: if a key is missing, just return None (like dict.get()).

  • Deep Indexing: use a list of keys and int to get and set deeply nested values. This is similar to lodash.get except that only the array-like syntax is supported and you must use actual int to index across list objects.

  • Deep Merge: combine two dict objects by extending deeply-nested keys where possible. This is different than the new dict union operator (PEP 584).

from attrbox import AttrDict

items = AttrDict(a=1, b=[{"c": {"d": 5}}], e={"f": {"g": 7}})
items.a
# => 1
items.x is None
# => True
items.x = 10
items['x']
# => 10
items.get(["b", 0, "c", "d"])
# => 5
items <<= {"e": {"f": {"g": 20, "h": [30, 40]}}}
items.e.f.g
# => 20
items[['e', 'f', 'h', 1]]
# => 40

Read more about AttrDict

AttrList

AttrList provides member broadcast: performing operations on the list performs the operation on all the items in the list. I typically use this to achieve the Composite design pattern.

from attrbox import AttrDict, AttrList

numbers = AttrList([complex(1, 2), complex(3, 4), complex(5, 6)])
numbers.real
# => [1.0, 3.0, 5.0]

words = AttrList(["Apple", "Bat", "Cat"])
words.lower()
# => ['apple', 'bat', 'cat']

items = AttrList([AttrDict(a=1, b=2), AttrDict(a=5)])
items.a
# => [1, 5]
items.b
# => [2, None]

Read more about AttrList

Environment

attrbox.env is similar to python-dotenv, but uses the AttrDict ability to do deep indexing to allow for things like dotted variable names. Typically, you'll use it by calling load_env() which will find the nearest .env file and load it into os.environ.

Read more about attrbox.env

Configuration

attrbox supports loading configuration files from .json, .toml, and .attrbox.env files. By default, load_config() looks for a key imports and will recursively import those files (relative to the current file) before loading the rest of the current file (data is merged using AttrDict). This allows you to create templates or smaller configurations that build up to a larger configuration.

For CLI applications, parse_docopt() let's you use the power of docopt with the flexibility of AttrDict. By default, --config and <config> arguments will load the file using the load_config()

"""Usage: prog.py [--help] [--version] [-c CONFIG] --file FILE

Options:
  --help                show this message and exit
  --version             show the version number and exit
  -c, --config CONFIG   load this configuration file (supported: .toml, .json, .env)
  --file FILE           the file to process
"""

def main():
    args = parse_docopt(__doc__, version=__version__)
    args.file # has the value of --file

if __name__ == "__main__":
    main()

Building on top of docopt we strip off leading dashes and convert them to underscores so that we can access the arguments as AttrDict attributes.

Read more about attrbox.config

JSend

JSend is an approximate implementation of the JSend specification that makes it easy to create standard JSON responses. The main difference is that I added an ok attribute to make it easy to tell if there was a problem (fail or error).

from attrbox import JSend

def some_function(arg1):
    result = JSend() # default is "success"

    if not is_good(arg1):
        # fail = controlled problem
        return result.fail(message="You gone messed up.")

    try:
        result.success(data=process(arg1))
    except Exception:
        # error = uncontrolled problem
        return result.error(message="We have a problem.")

    return result

Because the JSend object is an AttrDict, it acts like a dict in every other respect (e.g., it is JSON-serializable).

Read more about JSend

License

MIT License

Expand source code
"""Attribute-based data structures.

.. include:: ../../README.md
   :start-line: 2
"""

# pkg
from .attrdict import __pdoc__ as doc1
from .attrdict import AttrDict
from .attrlist import __pdoc__ as doc2
from .attrlist import AttrList

from .jsend import JSend

from .env import load_env
from .config import load_config
from .config import parse_docopt


__version__ = "0.1.5"
__all__ = (
    "__version__",
    "AttrDict",
    "AttrList",
    "JSend",
    "load_env",
    "load_config",
    "parse_docopt",
)

# Update pdoc at all levels.
__pdoc__ = {}
for doc in [doc1, doc2]:
    __pdoc__.update(doc)

Sub-modules

attrbox.attrdict

dict with attribute access, deep selectors, and merging.

attrbox.attrlist

list that broadcasts attribute access to its elements.

attrbox.config

Configuration loading and parsing.

attrbox.env

Load configuration from environment files …

attrbox.fn

Generally useful functions.

attrbox.jsend

Standard JSON responses.

Functions

def load_env(path: Union[pathlib.Path, str, ForwardRef(None)] = None) ‑> Dict[str, str]

Load an environment file.

We recursively search for a .attrbox.env file from the path given or the current working directory, if omitted.

Args

path : PathStr, optional
starting path. If None, start from the current working directory. Defaults to None.

Raises

FileNotFoundError
If not .attrbox.env file is found.

Returns

Dict[str, str]
configuration values

Examples

>>> load_env() # our .env doesn't have any values
{}

If no .attrbox.env can be found, a FileNotFoundError is raised:

>>> load_env("/")
Traceback (most recent call last):
    ...
FileNotFoundError: Cannot find .env file to load.
Expand source code
def load_env(path: Optional[PathStr] = None) -> Dict[str, str]:
    """Load an environment file.

    We recursively search for a `.env` file from the path given or the current
    working directory, if omitted.

    Args:
        path (PathStr, optional): starting path. If `None`, start from the
            current working directory. Defaults to `None`.

    Raises:
        FileNotFoundError: If not `.env` file is found.

    Returns:
        Dict[str, str]: configuration values

    Examples:
        >>> load_env() # our .env doesn't have any values
        {}

        If no `.env` can be found, a `FileNotFoundError` is raised:
        >>> load_env("/")
        Traceback (most recent call last):
            ...
        FileNotFoundError: Cannot find .env file to load.
    """
    path = find_env(path)
    if not path or not path.exists():
        raise FileNotFoundError("Cannot find .env file to load.")
    return loads(path.read_text(encoding="utf-8"), update_env=True, dotted_keys=True)
def load_config(path: pathlib.Path, /, *, load_imports: bool = True, loaders: Optional[Mapping[str, Callable[[str], Any]]] = None, done: Optional[List[pathlib.Path]] = None) ‑> Dict[str, Any]

Load a configuration file from path using configuration loaders.

Args

path : Path
file to load.
load_imports : bool, optional
If True, recursively load any files located at the imports key. Defaults to True.
loaders : Mapping[str, LoaderFunc], optional
mapping of file suffixes to to loader functions. If None, uses the global LOADERS. Defaults to None.
done : List[Path], optional
If provided, a list of paths to ignore when doing recursive loading. Defaults to None.

Returns

Dict[str, Any]
keys/values from the configuration file

Examples

>>> root = Path(__file__).parent.parent.parent
>>> expected = {'section': {'key': 'value1', "env": "loaded",
... "json": "loaded", "toml": "loaded"}}
>>> load_config(root / "test/config_1.toml") == expected
True
Expand source code
def load_config(
    path: Path,
    /,
    *,
    load_imports: bool = True,
    loaders: Optional[Mapping[str, LoaderFunc]] = None,
    done: Optional[List[Path]] = None,
) -> Dict[str, Any]:
    """Load a configuration file from `path` using configuration `loaders`.

    Args:
        path (Path): file to load.

        load_imports (bool, optional): If `True`, recursively load any files
            located at the `imports` key. Defaults to `True`.

        loaders (Mapping[str, LoaderFunc], optional): mapping of file suffixes
            to to loader functions. If `None`, uses the global `LOADERS`.
            Defaults to `None`.

        done (List[Path], optional): If provided, a list of paths to ignore when
            doing recursive loading. Defaults to `None`.

    Returns:
        Dict[str, Any]: keys/values from the configuration file

    Examples:
        >>> root = Path(__file__).parent.parent.parent
        >>> expected = {'section': {'key': 'value1', "env": "loaded",
        ... "json": "loaded", "toml": "loaded"}}
        >>> load_config(root / "test/config_1.toml") == expected
        True
    """
    result = AttrDict()
    path = path.resolve()
    done = done or []

    loader = (loaders or LOADERS)[path.suffix]
    data = loader(path.read_text())
    if load_imports and "imports" in data:
        imports = [(path.parent / p).resolve() for p in data.pop("imports")]
        for file in imports:
            if file in done:
                continue

            result <<= load_config(
                file,
                load_imports=True,
                loaders=loaders,
                done=done + imports,
            )
    result <<= data
    return result
def parse_docopt(doc: str, /, argv: Optional[Sequence[str]] = None, *, version: str = '1.0.0', options_first: bool = False, read_config: bool = True) ‑> AttrDict

Parse docopt args and load config.

Args

doc : str
docstring with description of command
argv : Sequence[str], optional
arguments to parse against the doc. If None, will default to sys.argv[1:]. Defaults to None.
version : str, optional
program version. Defaults to "1.0.0".
options_first : bool
If True, options must come before positional arguments. Defaults to False.
read_config : bool
If True, a <config> argument or --config option will be automatically loaded before args are parsed. Defaults to True.

Returns

AttrDict[str, Any]
mapping of options to values

Examples

>>> usage = "Usage: test.py [--debug]"
>>> parse_docopt(usage, argv=["--debug"])
{'debug': True}
>>> root = Path(__file__).parent.parent.parent
>>> path = str(root / "test/config_1.toml")
>>> usage = "Usage: test.py <config> [--section.key=VAL]"
>>> argv = [path, "--section.key=overwrite"]
>>> expected = {"section": {
...     "key": "overwrite", # overwritten by argv
...     "env": "loaded", "json": "loaded", "toml": "loaded"
... }, "config": path}
>>> parse_docopt(usage, argv=argv) == expected
True
Expand source code
def parse_docopt(
    doc: str,
    /,
    argv: Optional[Sequence[str]] = None,
    *,
    version: str = "1.0.0",
    options_first: bool = False,
    read_config: bool = True,
) -> AttrDict:
    """Parse docopt args and load config.

    Args:
        doc (str): docstring with description of command

        argv (Sequence[str], optional): arguments to parse against the
            doc. If `None`, will default to `sys.argv[1:]`. Defaults to `None`.

        version (str, optional): program version. Defaults to `"1.0.0"`.

        options_first (bool): If `True`, options must come before positional
            arguments. Defaults to `False`.

        read_config (bool): If `True`, a `<config>` argument or `--config` option
            will be automatically loaded before args are parsed. Defaults to `True`.

    Returns:
        AttrDict[str, Any]: mapping of options to values

    Examples:
        >>> usage = "Usage: test.py [--debug]"
        >>> parse_docopt(usage, argv=["--debug"])
        {'debug': True}

        >>> root = Path(__file__).parent.parent.parent
        >>> path = str(root / "test/config_1.toml")
        >>> usage = "Usage: test.py <config> [--section.key=VAL]"
        >>> argv = [path, "--section.key=overwrite"]
        >>> expected = {"section": {
        ...     "key": "overwrite", # overwritten by argv
        ...     "env": "loaded", "json": "loaded", "toml": "loaded"
        ... }, "config": path}
        >>> parse_docopt(usage, argv=argv) == expected
        True
    """
    result = AttrDict()
    args = {
        optvar(k, shadow_builtins=True): v
        for k, v in docopt(
            cleandoc(doc),
            argv=argv,
            help=True,
            version=version,
            options_first=options_first,
        ).items()
    }

    if read_config and "config" in args:
        result <<= load_config(Path(args["config"]))

    for key, val in args.items():
        key = optvar(key, shadow_builtins=True)
        result[key.split(".")] = val
    return result

Classes

class AttrDict (*args, **kwargs)

Like a dict, but with attribute syntax.

NOTE: We do not throw AttributeError or KeyError because accessing a non-existent key or attribute returns None.

Examples

>>> AttrDict({"a": 1}).a
1
Expand source code
class AttrDict(Dict[str, Any]):
    """Like a `dict`, but with attribute syntax.

    NOTE: We do not throw `AttributeError` or `KeyError` because accessing
    a non-existent key or attribute returns `None`.

    Examples:
        >>> AttrDict({"a": 1}).a
        1
    """

    def copy(self: Self) -> Self:
        """Return a shallow copy.

        Examples:
            >>> items = AttrDict(a=1)
            >>> clone = items.copy()
            >>> items == clone # same contents
            True
            >>> items is not clone # different pointers
            True
            >>> clone.a = 5
            >>> items != clone
            True
        """
        return self.__class__(super().copy())

    def __contains__(self, key: Any) -> bool:
        """Return `True` if `key` is a key.

        Args:
            key (Any): typically a `AnyIndex`. If it's a string,
                the check proceeds as usual. If it's a `Sequence`, the
                checks are performed using `.get()`.

        Returns:
            bool: `True` if the `key` is a valid key, `False` otherwise.

        Examples:
            Normal checking works as expected:
            >>> items = AttrDict(a=1, b=2)
            >>> 'a' in items
            True
            >>> 'x' in items
            False

            Nested checks are also possible:
            >>> items = AttrDict(a=[{"b": 2}, {"b": 3}])
            >>> ['a', 0, 'b'] in items
            True
            >>> ['a', 1, 'x'] in items
            False
        """
        return self.get(key, NOT_FOUND) is not NOT_FOUND

    def __getattr__(self, name: str) -> Optional[Any]:
        """Return the value of the attribute or `None`.

        Args:
            name (str): attribute name

        Returns:
            Any: attribute value or `None` if the attribute is missing

        Examples:
            Typically, attributes are the same as key/values:
            >>> item = AttrDict(a=1)
            >>> item.a
            1
            >>> item['a']
            1

            However, instance attributes supersede key/values:
            >>> item = AttrDict(b=1)
            >>> object.__setattr__(item, 'b', 2)
            >>> item.b
            2
            >>> item['b']
            1

            Missing attributes return `None`:
            >>> item = AttrDict(a=1)
            >>> item.b is None
            True
        """
        # NOTE: This method is only called when the attribute cannot be found.
        return self[name]

    def __setattr__(self, name: str, value: Any) -> None:
        """Set the value of an attribute.

        Args:
            name (str): attribute name
            value (Any): value to set

        Examples:
            Setting an attribute is usually the same as a key/value:
            >>> item = AttrDict()
            >>> item.a = 1
            >>> item.a
            1
            >>> item['a']
            1

            However, instance attributes supersede key/values:
            >>> item = AttrDict()
            >>> object.__setattr__(item, 'b', 2)
            >>> item.b
            2
            >>> item.b = 3  # instance attribute updated
            >>> item.b
            3
            >>> item['b'] is None
            True
        """
        try:
            super().__getattribute__(name)  # is real?
            super().__setattr__(name, value)
        except AttributeError:  # use key/value
            self[name] = value

    def __delattr__(self, name: str) -> None:
        """Delete an attribute.

        Args:
            name (str): attribute name

        Examples:
            Deleting an attribute usually deletes the underlying key/value:
            >>> item = AttrDict(a=1)
            >>> del item.a
            >>> item
            {}

            However, instance attributes supersede key/values:
            >>> item = AttrDict(b=1)
            >>> object.__setattr__(item, 'b', 2)
            >>> item.b  # real attribute supersedes key/value
            2
            >>> del item.b  # deletes real attribute
            >>> object.__getattribute__(item, 'b') # instance attribute gone
            Traceback (most recent call last):
             ...
            AttributeError: 'AttrDict' object has no attribute 'b'
            >>> item
            {'b': 1}
        """
        try:
            super().__getattribute__(name)  # is real?
            super().__delattr__(name)
        except AttributeError:  # use key/value
            del self[name]

    def __getitem__(self, key: AnyIndex) -> Optional[Any]:
        """Return the value of the key.

        Args:
            key (AnyIndex): key name or Sequence of the path to a key

        Returns:
            Any: value of the key or `None` if it cannot be found

        Examples:
            >>> item = AttrDict(a=1)
            >>> item['a']
            1
            >>> item['b'] is None
            True
        """
        return self.get(key)

    def __setitem__(self, key: AnyIndex, value: Any) -> None:
        """Set the value of a key.

        Args:
            key (AnyIndex): key name
            value (Any): key value

        Examples:
            >>> item = AttrDict(a=1)
            >>> item['a'] = 5
            >>> item['a']
            5

            >>> item[['a', 'b']] = 10
            >>> item.a.b
            10
        """
        self.set(key, value)

    def __delitem__(self, key: str) -> None:
        """Delete a key.

        Args:
            key (str): key name

        Examples:
            >>> item = AttrDict(a=1)
            >>> del item['a']
            >>> item
            {}

            Deleting missing keys has no effect:
            >>> item = AttrDict(a=1)
            >>> del item['b']
            >>> item
            {'a': 1}
        """
        try:
            super().__delitem__(key)
        except KeyError:
            pass

    def get(self, path: AnyIndex, default: Optional[Any] = None, /) -> Optional[Any]:
        """Return the value at `path` or `default` if it cannot be found.

        Args:
            path (AnyIndex): path to the value
            default (Any, optional): value to return if `name` is not found.
                Defaults to `None`.

        Returns:
            Optional[Any]: key value or `default` if the key is not found

        Examples:
            Normal `.get` functions work:
            >>> items = AttrDict(a=dict(b=[{"c": 3}, {"c": -10}]), d=4)
            >>> items.get('d') == 4
            True
            >>> items.get('e') is None
            True
            >>> items.get('e', 5) == 5
            True

            But you can also indexing into nested `list` and `dict`:
            >>> items.get(['a', 'b', 1, 'c']) == -10
            True

            Bad indexing will just return the default:
            >>> items.get(['e']) is None # key doesn't exist
            True
            >>> items.get(['a', 'b', 5]) is None  # index unreachable
            True
            >>> items.get(['d', 'e']) is None # int is not indexable
            True
        """
        if isinstance(path, str):
            return super().get(path, default)
        return get_path(self, path, default)

    def set(self: Self, path: AnyIndex, value: Optional[Any] = None, /) -> Self:
        """Set key at `path` to `value`.

        Args:
            path (AnyIndex): path to the key to set.
            value (Any, optional): value to set. Defaults to `None`.

        Returns:
            Self: for chaining

        Examples:
            >>> items = AttrDict({"a": 1})
            >>> items.set("b", 2)
            {'a': 1, 'b': 2}

            You can set values deeper:
            >>> items.set(["b", "c", "d"], 5)
            {'a': 1, 'b': {'c': {'d': 5}}}

            You can also use a `tuple` (or other Sequence):
            >>> items.set(("b", "c", "d"), 10)
            {'a': 1, 'b': {'c': {'d': 10}}}

            An empty `Sequence` performs no action:
            >>> items.set((), 20)
            {'a': 1, 'b': {'c': {'d': 10}}}
        """
        if isinstance(path, str):
            super().__setitem__(path, value)
            return self

        set_path(self, path, value, self.__class__)
        return self

    def __lshift__(self: Self, other: Mapping[str, Any]) -> Self:
        """Merge `other` into `self`.

        NOTE: Any nested dictionaries will be converted to `AttrDict` objects.

        Args:
            other (Mapping[str, Any]): other dictionary to merge

        Returns:
            AttrDict: merged dictionary

        Examples:
            >>> item = AttrDict(a=1, b=2)
            >>> item <<= {"b": 3}
            >>> item.b
            3

            >>> item << {"b": 2, "c": {"d": 4}} << {"c": {"d": {"e": 5}}}
            {'a': 1, 'b': 2, 'c': {'d': {'e': 5}}}
            >>> item.c.d.e
            5
        """
        dict_merge(self, other, cls_dict=self.__class__)
        return self

Ancestors

  • builtins.dict
  • typing.Generic

Subclasses

Methods

def copy(self: Self) ‑> ~Self

Return a shallow copy.

Examples

>>> items = AttrDict(a=1)
>>> clone = items.copy()
>>> items == clone # same contents
True
>>> items is not clone # different pointers
True
>>> clone.a = 5
>>> items != clone
True
Expand source code
def copy(self: Self) -> Self:
    """Return a shallow copy.

    Examples:
        >>> items = AttrDict(a=1)
        >>> clone = items.copy()
        >>> items == clone # same contents
        True
        >>> items is not clone # different pointers
        True
        >>> clone.a = 5
        >>> items != clone
        True
    """
    return self.__class__(super().copy())
def __contains__(self, key: Any) ‑> bool

Return True if key is a key.

Args

key : Any
typically a AnyIndex. If it's a string, the check proceeds as usual. If it's a Sequence, the checks are performed using .get().

Returns

bool
True if the key is a valid key, False otherwise.

Examples

Normal checking works as expected:

>>> items = AttrDict(a=1, b=2)
>>> 'a' in items
True
>>> 'x' in items
False

Nested checks are also possible:

>>> items = AttrDict(a=[{"b": 2}, {"b": 3}])
>>> ['a', 0, 'b'] in items
True
>>> ['a', 1, 'x'] in items
False
Expand source code
def __contains__(self, key: Any) -> bool:
    """Return `True` if `key` is a key.

    Args:
        key (Any): typically a `AnyIndex`. If it's a string,
            the check proceeds as usual. If it's a `Sequence`, the
            checks are performed using `.get()`.

    Returns:
        bool: `True` if the `key` is a valid key, `False` otherwise.

    Examples:
        Normal checking works as expected:
        >>> items = AttrDict(a=1, b=2)
        >>> 'a' in items
        True
        >>> 'x' in items
        False

        Nested checks are also possible:
        >>> items = AttrDict(a=[{"b": 2}, {"b": 3}])
        >>> ['a', 0, 'b'] in items
        True
        >>> ['a', 1, 'x'] in items
        False
    """
    return self.get(key, NOT_FOUND) is not NOT_FOUND
def __getattr__(self, name: str) ‑> Optional[Any]

Return the value of the attribute or None.

Args

name : str
attribute name

Returns

Any
attribute value or None if the attribute is missing

Examples

Typically, attributes are the same as key/values:

>>> item = AttrDict(a=1)
>>> item.a
1
>>> item['a']
1

However, instance attributes supersede key/values:

>>> item = AttrDict(b=1)
>>> object.__setattr__(item, 'b', 2)
>>> item.b
2
>>> item['b']
1

Missing attributes return None:

>>> item = AttrDict(a=1)
>>> item.b is None
True
Expand source code
def __getattr__(self, name: str) -> Optional[Any]:
    """Return the value of the attribute or `None`.

    Args:
        name (str): attribute name

    Returns:
        Any: attribute value or `None` if the attribute is missing

    Examples:
        Typically, attributes are the same as key/values:
        >>> item = AttrDict(a=1)
        >>> item.a
        1
        >>> item['a']
        1

        However, instance attributes supersede key/values:
        >>> item = AttrDict(b=1)
        >>> object.__setattr__(item, 'b', 2)
        >>> item.b
        2
        >>> item['b']
        1

        Missing attributes return `None`:
        >>> item = AttrDict(a=1)
        >>> item.b is None
        True
    """
    # NOTE: This method is only called when the attribute cannot be found.
    return self[name]
def __setattr__(self, name: str, value: Any) ‑> None

Set the value of an attribute.

Args

name : str
attribute name
value : Any
value to set

Examples

Setting an attribute is usually the same as a key/value:

>>> item = AttrDict()
>>> item.a = 1
>>> item.a
1
>>> item['a']
1

However, instance attributes supersede key/values:

>>> item = AttrDict()
>>> object.__setattr__(item, 'b', 2)
>>> item.b
2
>>> item.b = 3  # instance attribute updated
>>> item.b
3
>>> item['b'] is None
True
Expand source code
def __setattr__(self, name: str, value: Any) -> None:
    """Set the value of an attribute.

    Args:
        name (str): attribute name
        value (Any): value to set

    Examples:
        Setting an attribute is usually the same as a key/value:
        >>> item = AttrDict()
        >>> item.a = 1
        >>> item.a
        1
        >>> item['a']
        1

        However, instance attributes supersede key/values:
        >>> item = AttrDict()
        >>> object.__setattr__(item, 'b', 2)
        >>> item.b
        2
        >>> item.b = 3  # instance attribute updated
        >>> item.b
        3
        >>> item['b'] is None
        True
    """
    try:
        super().__getattribute__(name)  # is real?
        super().__setattr__(name, value)
    except AttributeError:  # use key/value
        self[name] = value
def __delattr__(self, name: str) ‑> None

Delete an attribute.

Args

name : str
attribute name

Examples

Deleting an attribute usually deletes the underlying key/value:

>>> item = AttrDict(a=1)
>>> del item.a
>>> item
{}

However, instance attributes supersede key/values:

>>> item = AttrDict(b=1)
>>> object.__setattr__(item, 'b', 2)
>>> item.b  # real attribute supersedes key/value
2
>>> del item.b  # deletes real attribute
>>> object.__getattribute__(item, 'b') # instance attribute gone
Traceback (most recent call last):
 ...
AttributeError: 'AttrDict' object has no attribute 'b'
>>> item
{'b': 1}
Expand source code
def __delattr__(self, name: str) -> None:
    """Delete an attribute.

    Args:
        name (str): attribute name

    Examples:
        Deleting an attribute usually deletes the underlying key/value:
        >>> item = AttrDict(a=1)
        >>> del item.a
        >>> item
        {}

        However, instance attributes supersede key/values:
        >>> item = AttrDict(b=1)
        >>> object.__setattr__(item, 'b', 2)
        >>> item.b  # real attribute supersedes key/value
        2
        >>> del item.b  # deletes real attribute
        >>> object.__getattribute__(item, 'b') # instance attribute gone
        Traceback (most recent call last):
         ...
        AttributeError: 'AttrDict' object has no attribute 'b'
        >>> item
        {'b': 1}
    """
    try:
        super().__getattribute__(name)  # is real?
        super().__delattr__(name)
    except AttributeError:  # use key/value
        del self[name]
def __getitem__(self, key: AnyIndex) ‑> Optional[Any]

Return the value of the key.

Args

key : AnyIndex
key name or Sequence of the path to a key

Returns

Any
value of the key or None if it cannot be found

Examples

>>> item = AttrDict(a=1)
>>> item['a']
1
>>> item['b'] is None
True
Expand source code
def __getitem__(self, key: AnyIndex) -> Optional[Any]:
    """Return the value of the key.

    Args:
        key (AnyIndex): key name or Sequence of the path to a key

    Returns:
        Any: value of the key or `None` if it cannot be found

    Examples:
        >>> item = AttrDict(a=1)
        >>> item['a']
        1
        >>> item['b'] is None
        True
    """
    return self.get(key)
def __setitem__(self, key: AnyIndex, value: Any) ‑> None

Set the value of a key.

Args

key : AnyIndex
key name
value : Any
key value

Examples

>>> item = AttrDict(a=1)
>>> item['a'] = 5
>>> item['a']
5
>>> item[['a', 'b']] = 10
>>> item.a.b
10
Expand source code
def __setitem__(self, key: AnyIndex, value: Any) -> None:
    """Set the value of a key.

    Args:
        key (AnyIndex): key name
        value (Any): key value

    Examples:
        >>> item = AttrDict(a=1)
        >>> item['a'] = 5
        >>> item['a']
        5

        >>> item[['a', 'b']] = 10
        >>> item.a.b
        10
    """
    self.set(key, value)
def __delitem__(self, key: str) ‑> None

Delete a key.

Args

key : str
key name

Examples

>>> item = AttrDict(a=1)
>>> del item['a']
>>> item
{}

Deleting missing keys has no effect:

>>> item = AttrDict(a=1)
>>> del item['b']
>>> item
{'a': 1}
Expand source code
def __delitem__(self, key: str) -> None:
    """Delete a key.

    Args:
        key (str): key name

    Examples:
        >>> item = AttrDict(a=1)
        >>> del item['a']
        >>> item
        {}

        Deleting missing keys has no effect:
        >>> item = AttrDict(a=1)
        >>> del item['b']
        >>> item
        {'a': 1}
    """
    try:
        super().__delitem__(key)
    except KeyError:
        pass
def get(self, path: AnyIndex, default: Optional[Any] = None, /) ‑> Optional[Any]

Return the value at path or default if it cannot be found.

Args

path : AnyIndex
path to the value
default : Any, optional
value to return if name is not found. Defaults to None.

Returns

Optional[Any]
key value or default if the key is not found

Examples

Normal .get functions work:

>>> items = AttrDict(a=dict(b=[{"c": 3}, {"c": -10}]), d=4)
>>> items.get('d') == 4
True
>>> items.get('e') is None
True
>>> items.get('e', 5) == 5
True

But you can also indexing into nested list and dict:

>>> items.get(['a', 'b', 1, 'c']) == -10
True

Bad indexing will just return the default:

>>> items.get(['e']) is None # key doesn't exist
True
>>> items.get(['a', 'b', 5]) is None  # index unreachable
True
>>> items.get(['d', 'e']) is None # int is not indexable
True
Expand source code
def get(self, path: AnyIndex, default: Optional[Any] = None, /) -> Optional[Any]:
    """Return the value at `path` or `default` if it cannot be found.

    Args:
        path (AnyIndex): path to the value
        default (Any, optional): value to return if `name` is not found.
            Defaults to `None`.

    Returns:
        Optional[Any]: key value or `default` if the key is not found

    Examples:
        Normal `.get` functions work:
        >>> items = AttrDict(a=dict(b=[{"c": 3}, {"c": -10}]), d=4)
        >>> items.get('d') == 4
        True
        >>> items.get('e') is None
        True
        >>> items.get('e', 5) == 5
        True

        But you can also indexing into nested `list` and `dict`:
        >>> items.get(['a', 'b', 1, 'c']) == -10
        True

        Bad indexing will just return the default:
        >>> items.get(['e']) is None # key doesn't exist
        True
        >>> items.get(['a', 'b', 5]) is None  # index unreachable
        True
        >>> items.get(['d', 'e']) is None # int is not indexable
        True
    """
    if isinstance(path, str):
        return super().get(path, default)
    return get_path(self, path, default)
def set(self: Self, path: AnyIndex, value: Optional[Any] = None, /) ‑> ~Self

Set key at path to value.

Args

path : AnyIndex
path to the key to set.
value : Any, optional
value to set. Defaults to None.

Returns

Self
for chaining

Examples

>>> items = AttrDict({"a": 1})
>>> items.set("b", 2)
{'a': 1, 'b': 2}

You can set values deeper:

>>> items.set(["b", "c", "d"], 5)
{'a': 1, 'b': {'c': {'d': 5}}}

You can also use a tuple (or other Sequence):

>>> items.set(("b", "c", "d"), 10)
{'a': 1, 'b': {'c': {'d': 10}}}

An empty Sequence performs no action:

>>> items.set((), 20)
{'a': 1, 'b': {'c': {'d': 10}}}
Expand source code
def set(self: Self, path: AnyIndex, value: Optional[Any] = None, /) -> Self:
    """Set key at `path` to `value`.

    Args:
        path (AnyIndex): path to the key to set.
        value (Any, optional): value to set. Defaults to `None`.

    Returns:
        Self: for chaining

    Examples:
        >>> items = AttrDict({"a": 1})
        >>> items.set("b", 2)
        {'a': 1, 'b': 2}

        You can set values deeper:
        >>> items.set(["b", "c", "d"], 5)
        {'a': 1, 'b': {'c': {'d': 5}}}

        You can also use a `tuple` (or other Sequence):
        >>> items.set(("b", "c", "d"), 10)
        {'a': 1, 'b': {'c': {'d': 10}}}

        An empty `Sequence` performs no action:
        >>> items.set((), 20)
        {'a': 1, 'b': {'c': {'d': 10}}}
    """
    if isinstance(path, str):
        super().__setitem__(path, value)
        return self

    set_path(self, path, value, self.__class__)
    return self
def __lshift__(self: Self, other: Mapping[str, Any]) ‑> ~Self

Merge other into self.

NOTE: Any nested dictionaries will be converted to AttrDict objects.

Args

other : Mapping[str, Any]
other dictionary to merge

Returns

AttrDict
merged dictionary

Examples

>>> item = AttrDict(a=1, b=2)
>>> item <<= {"b": 3}
>>> item.b
3
>>> item << {"b": 2, "c": {"d": 4}} << {"c": {"d": {"e": 5}}}
{'a': 1, 'b': 2, 'c': {'d': {'e': 5}}}
>>> item.c.d.e
5
Expand source code
def __lshift__(self: Self, other: Mapping[str, Any]) -> Self:
    """Merge `other` into `self`.

    NOTE: Any nested dictionaries will be converted to `AttrDict` objects.

    Args:
        other (Mapping[str, Any]): other dictionary to merge

    Returns:
        AttrDict: merged dictionary

    Examples:
        >>> item = AttrDict(a=1, b=2)
        >>> item <<= {"b": 3}
        >>> item.b
        3

        >>> item << {"b": 2, "c": {"d": 4}} << {"c": {"d": {"e": 5}}}
        {'a': 1, 'b': 2, 'c': {'d': {'e': 5}}}
        >>> item.c.d.e
        5
    """
    dict_merge(self, other, cls_dict=self.__class__)
    return self
class AttrList (*args, **kwargs)

Collection that broadcasts attribute, index, and function calls to its members.

Attribute access (.) is broadcast to all members. An exception is an attribute that exists on the list instance itself.

>>> nums = AttrList([complex(1, 2), complex(3, 4), complex(5, 6)])
>>> nums.real
[1.0, 3.0, 5.0]

Array access ([]) with int and slice indexes works as usual by returning a portion of the list. string indexes, however, are broadcast to each member.

>>> items = AttrList(["Apple", "Bat", "Cat"])
>>> items[0]
'Apple'
>>> items["0"]
['A', 'B', 'C']

Calling the list (()) broadcasts the call to all members. Usually, this is combined with attribute access:

>>> items = AttrList(["Apple", "Bat", "Cat"])
>>> items.lower()
['apple', 'bat', 'cat']
Expand source code
class AttrList(List[Any]):
    """Collection that broadcasts attribute, index, and function calls to its members.

    Attribute access (`.`) is broadcast to all members. An exception
    is an attribute that exists on the list instance itself.

    >>> nums = AttrList([complex(1, 2), complex(3, 4), complex(5, 6)])
    >>> nums.real
    [1.0, 3.0, 5.0]

    Array access (`[]`) with `int` and `slice` indexes works as usual by returning
    a portion of the list. `string` indexes, however, are broadcast to each member.

    >>> items = AttrList(["Apple", "Bat", "Cat"])
    >>> items[0]
    'Apple'
    >>> items["0"]
    ['A', 'B', 'C']

    Calling the list (`()`) broadcasts the call to all members. Usually, this is
    combined with attribute access:

    >>> items = AttrList(["Apple", "Bat", "Cat"])
    >>> items.lower()
    ['apple', 'bat', 'cat']
    """

    def __getattr__(self, name: str) -> AttrList:
        """Return an attribute from all members.

        Args:
            name (str): attribute name

        Returns:
            (list[any]): value of attribute for each member or `None` if missing

        Examples:
            >>> from . import AttrDict
            >>> items = AttrList([AttrDict(a=1), AttrDict(a=2), AttrDict(a=3, b=4)])
            >>> items.a
            [1, 2, 3]
            >>> items.b
            [None, None, 4]

            Note that instance attributes supersede member attributes:
            >>> object.__setattr__(items, "b", 5)
            >>> items.b
            5
        """
        # NOTE: This method is only called when the attribute cannot be found.
        # We delegate this call to every member.
        result = self.__class__()
        for member in self:
            # if isinstance(member, AttrDict):  # check if name is defined
            #     result.append(member[name] if name in member else None)
            # else:
            result.append(getattr(member, name, None))
        return result

    def __setattr__(self, name: str, value: Any) -> None:
        """Set an attribute on all members (or the list itself).

        Args:
            name (str): attribute name
            value (any): attribute value

        Examples:
            >>> from . import AttrDict
            >>> items = AttrList([AttrDict(a=1), AttrDict(b=2)])
            >>> items.a = 5
            >>> items
            [{'a': 5}, {'b': 2, 'a': 5}]

            Note that instance attributes supersede member attributes:
            >>> object.__setattr__(items, "b", 5)
            >>> items.b = 7
            >>> items.b
            7
        """
        try:
            super().__getattribute__(name)  # is real?
            super().__setattr__(name, value)
        except AttributeError:  # use members
            for member in self:
                setattr(member, name, value)

    def __delattr__(self, name: str) -> None:
        """Delete an attribute from all members (or the list itself).

        Args:
            name (str): attribute name

        Examples:
            >>> from . import AttrDict
            >>> items = AttrList([AttrDict(a=1, b=2), AttrDict(a=2, c=3), dict(d=4)])
            >>> del items.a
            >>> items
            [{'b': 2}, {'c': 3}, {'d': 4}]

            Deleting an instance attribute works:
            >>> object.__setattr__(items, 'b', 5)
            >>> items.b = 7
            >>> items.b
            7
            >>> del items.b # deletes instance attribute
            >>> items
            [{'b': 2}, {'c': 3}, {'d': 4}]
        """
        try:
            super().__getattribute__(name)  # is real?
            super().__delattr__(name)
        except AttributeError:  # use members
            for member in self:
                try:
                    delattr(member, name)
                except AttributeError:
                    pass

    def __getitem__(self: Self, index: AttrListKey) -> Union[Self, Any]:
        """Return an item from all members (or the list itself).

        Args:
            index (int|slice|str): item index

        Returns:
            (any): the item at the index in each member (`str` index) or the
            member at the given index (`int` or `slice`)

        Examples:
            A string index is applied to all members:
            >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
            >>> items["-1"]
            [3, 6, 9]

            Normal indexing works as expected:
            >>> items = AttrList([1, 2, 3])
            >>> items[1]
            2
            >>> items[0:2]
            [1, 2]

            Weird indexing throws a `TypeError`:
            >>> items = AttrList([1, 2, 3])
            >>> items[{"a": 1}]
            Traceback (most recent call last):
             ...
            TypeError: list indices must be integers or slices, not dict
        """
        result = self
        if isinstance(index, str):
            index = str2index(index)
            result = self.__class__(item[index] for item in self)
        elif isinstance(index, slice):
            result = self.__class__(super().__getitem__(index))
        else:
            result = super().__getitem__(index)
        return result

    def __setitem__(self, index: AttrListKey, value: Any) -> None:
        """Set an item in all members (or the list itself).

        Args:
            index (int|slice|str): item index
            value (any): item value

        Examples:
            A string index is applied to all members:
            >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
            >>> items["0"] = 100
            >>> items
            [[100, 2, 3], [100, 5, 6], [100, 8, 9]]

            Immutable members are untouched:
            >>> items = AttrList(["Cat", [1, 2, 3]])
            >>> items["0"] = "A"
            >>> items
            ['Cat', ['A', 2, 3]]

            Normal indexing works as expected:
            >>> items = AttrList([1, 2, 3])
            >>> items[1] = 5
            >>> items
            [1, 5, 3]

            Weird indexing throws a `TypeError`:
            >>> items = AttrList([1, 2, 3])
            >>> items[{"a": 1}] = 100
            Traceback (most recent call last):
             ...
            TypeError: list indices must be integers or slices, not dict
        """
        if isinstance(index, str):
            index = str2index(index)
            for member in self:
                if hasattr(member, "__setitem__"):
                    member.__setitem__(index, value)
        else:
            super().__setitem__(index, value)

    def __delitem__(self, index: AttrListKey) -> None:
        """Delete an item from all members (or the list itself).

        Args:
            index (int|slice|str): item index

        Examples:
            A string index is applied to all members:
            >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
            >>> del items["0"]
            >>> items
            [[2, 3], [5, 6], [8, 9]]

            Immutable members are untouched:
            >>> items = AttrList(["Cat", [1, 2, 3]])
            >>> del items["-1"]
            >>> items
            ['Cat', [1, 2]]

            Normal indexing works as expected:
            >>> items = AttrList([1, 2, 3])
            >>> del items[1]
            >>> items
            [1, 3]

            Weird indexing throws a `TypeError`:
            >>> items = AttrList([1, 2, 3])
            >>> del items[{"a": 1}]
            Traceback (most recent call last):
             ...
            TypeError: list indices must be integers or slices, not dict
        """
        if isinstance(index, str):
            index = str2index(index)
            for member in self:
                if hasattr(member, "__delitem__"):
                    member.__delitem__(index)
        else:
            super().__delitem__(index)

    def __call__(self: Self, *args: Any, **kwargs: Any) -> Self:
        """Return a new list with the result of calling all callables in the list.

        Args:
            args (*any): arguments to the callable
            kwargs (**any): keyword arguments to the callable

        Returns:
            (Self): new list with results of the callable

        Examples:
            >>> items = AttrList(["a", "b", "c"])
            >>> items.upper()
            ['A', 'B', 'C']

            >>> items = AttrList([lambda x: x + 2, lambda y: y + 5, "Z"])
            >>> items(1)
            [3, 6, 'Z']
        """
        return self.__class__(
            item(*args, **kwargs) if callable(item) else item for item in self
        )

Ancestors

  • builtins.list
  • typing.Generic

Methods

def __getattr__(self, name: str) ‑> AttrList

Return an attribute from all members.

Args

name : str
attribute name

Returns

(list[any]): value of attribute for each member or None if missing

Examples

>>> from . import AttrDict
>>> items = AttrList([AttrDict(a=1), AttrDict(a=2), AttrDict(a=3, b=4)])
>>> items.a
[1, 2, 3]
>>> items.b
[None, None, 4]

Note that instance attributes supersede member attributes:

>>> object.__setattr__(items, "b", 5)
>>> items.b
5
Expand source code
def __getattr__(self, name: str) -> AttrList:
    """Return an attribute from all members.

    Args:
        name (str): attribute name

    Returns:
        (list[any]): value of attribute for each member or `None` if missing

    Examples:
        >>> from . import AttrDict
        >>> items = AttrList([AttrDict(a=1), AttrDict(a=2), AttrDict(a=3, b=4)])
        >>> items.a
        [1, 2, 3]
        >>> items.b
        [None, None, 4]

        Note that instance attributes supersede member attributes:
        >>> object.__setattr__(items, "b", 5)
        >>> items.b
        5
    """
    # NOTE: This method is only called when the attribute cannot be found.
    # We delegate this call to every member.
    result = self.__class__()
    for member in self:
        # if isinstance(member, AttrDict):  # check if name is defined
        #     result.append(member[name] if name in member else None)
        # else:
        result.append(getattr(member, name, None))
    return result
def __setattr__(self, name: str, value: Any) ‑> None

Set an attribute on all members (or the list itself).

Args

name : str
attribute name
value : any
attribute value

Examples

>>> from . import AttrDict
>>> items = AttrList([AttrDict(a=1), AttrDict(b=2)])
>>> items.a = 5
>>> items
[{'a': 5}, {'b': 2, 'a': 5}]

Note that instance attributes supersede member attributes:

>>> object.__setattr__(items, "b", 5)
>>> items.b = 7
>>> items.b
7
Expand source code
def __setattr__(self, name: str, value: Any) -> None:
    """Set an attribute on all members (or the list itself).

    Args:
        name (str): attribute name
        value (any): attribute value

    Examples:
        >>> from . import AttrDict
        >>> items = AttrList([AttrDict(a=1), AttrDict(b=2)])
        >>> items.a = 5
        >>> items
        [{'a': 5}, {'b': 2, 'a': 5}]

        Note that instance attributes supersede member attributes:
        >>> object.__setattr__(items, "b", 5)
        >>> items.b = 7
        >>> items.b
        7
    """
    try:
        super().__getattribute__(name)  # is real?
        super().__setattr__(name, value)
    except AttributeError:  # use members
        for member in self:
            setattr(member, name, value)
def __delattr__(self, name: str) ‑> None

Delete an attribute from all members (or the list itself).

Args

name : str
attribute name

Examples

>>> from . import AttrDict
>>> items = AttrList([AttrDict(a=1, b=2), AttrDict(a=2, c=3), dict(d=4)])
>>> del items.a
>>> items
[{'b': 2}, {'c': 3}, {'d': 4}]

Deleting an instance attribute works:

>>> object.__setattr__(items, 'b', 5)
>>> items.b = 7
>>> items.b
7
>>> del items.b # deletes instance attribute
>>> items
[{'b': 2}, {'c': 3}, {'d': 4}]
Expand source code
def __delattr__(self, name: str) -> None:
    """Delete an attribute from all members (or the list itself).

    Args:
        name (str): attribute name

    Examples:
        >>> from . import AttrDict
        >>> items = AttrList([AttrDict(a=1, b=2), AttrDict(a=2, c=3), dict(d=4)])
        >>> del items.a
        >>> items
        [{'b': 2}, {'c': 3}, {'d': 4}]

        Deleting an instance attribute works:
        >>> object.__setattr__(items, 'b', 5)
        >>> items.b = 7
        >>> items.b
        7
        >>> del items.b # deletes instance attribute
        >>> items
        [{'b': 2}, {'c': 3}, {'d': 4}]
    """
    try:
        super().__getattribute__(name)  # is real?
        super().__delattr__(name)
    except AttributeError:  # use members
        for member in self:
            try:
                delattr(member, name)
            except AttributeError:
                pass
def __getitem__(self: Self, index: AttrListKey) ‑> Union[~Self, Any]

Return an item from all members (or the list itself).

Args

index (int|slice|str): item index

Returns

(any): the item at the index in each member (str index) or the member at the given index (int or slice)

Examples

A string index is applied to all members:

>>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> items["-1"]
[3, 6, 9]

Normal indexing works as expected:

>>> items = AttrList([1, 2, 3])
>>> items[1]
2
>>> items[0:2]
[1, 2]

Weird indexing throws a TypeError:

>>> items = AttrList([1, 2, 3])
>>> items[{"a": 1}]
Traceback (most recent call last):
 ...
TypeError: list indices must be integers or slices, not dict
Expand source code
def __getitem__(self: Self, index: AttrListKey) -> Union[Self, Any]:
    """Return an item from all members (or the list itself).

    Args:
        index (int|slice|str): item index

    Returns:
        (any): the item at the index in each member (`str` index) or the
        member at the given index (`int` or `slice`)

    Examples:
        A string index is applied to all members:
        >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
        >>> items["-1"]
        [3, 6, 9]

        Normal indexing works as expected:
        >>> items = AttrList([1, 2, 3])
        >>> items[1]
        2
        >>> items[0:2]
        [1, 2]

        Weird indexing throws a `TypeError`:
        >>> items = AttrList([1, 2, 3])
        >>> items[{"a": 1}]
        Traceback (most recent call last):
         ...
        TypeError: list indices must be integers or slices, not dict
    """
    result = self
    if isinstance(index, str):
        index = str2index(index)
        result = self.__class__(item[index] for item in self)
    elif isinstance(index, slice):
        result = self.__class__(super().__getitem__(index))
    else:
        result = super().__getitem__(index)
    return result
def __setitem__(self, index: AttrListKey, value: Any) ‑> None

Set an item in all members (or the list itself).

Args

index (int|slice|str): item index
value : any
item value

Examples

A string index is applied to all members:

>>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> items["0"] = 100
>>> items
[[100, 2, 3], [100, 5, 6], [100, 8, 9]]

Immutable members are untouched:

>>> items = AttrList(["Cat", [1, 2, 3]])
>>> items["0"] = "A"
>>> items
['Cat', ['A', 2, 3]]

Normal indexing works as expected:

>>> items = AttrList([1, 2, 3])
>>> items[1] = 5
>>> items
[1, 5, 3]

Weird indexing throws a TypeError:

>>> items = AttrList([1, 2, 3])
>>> items[{"a": 1}] = 100
Traceback (most recent call last):
 ...
TypeError: list indices must be integers or slices, not dict
Expand source code
def __setitem__(self, index: AttrListKey, value: Any) -> None:
    """Set an item in all members (or the list itself).

    Args:
        index (int|slice|str): item index
        value (any): item value

    Examples:
        A string index is applied to all members:
        >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
        >>> items["0"] = 100
        >>> items
        [[100, 2, 3], [100, 5, 6], [100, 8, 9]]

        Immutable members are untouched:
        >>> items = AttrList(["Cat", [1, 2, 3]])
        >>> items["0"] = "A"
        >>> items
        ['Cat', ['A', 2, 3]]

        Normal indexing works as expected:
        >>> items = AttrList([1, 2, 3])
        >>> items[1] = 5
        >>> items
        [1, 5, 3]

        Weird indexing throws a `TypeError`:
        >>> items = AttrList([1, 2, 3])
        >>> items[{"a": 1}] = 100
        Traceback (most recent call last):
         ...
        TypeError: list indices must be integers or slices, not dict
    """
    if isinstance(index, str):
        index = str2index(index)
        for member in self:
            if hasattr(member, "__setitem__"):
                member.__setitem__(index, value)
    else:
        super().__setitem__(index, value)
def __delitem__(self, index: AttrListKey) ‑> None

Delete an item from all members (or the list itself).

Args

index (int|slice|str): item index

Examples

A string index is applied to all members:

>>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> del items["0"]
>>> items
[[2, 3], [5, 6], [8, 9]]

Immutable members are untouched:

>>> items = AttrList(["Cat", [1, 2, 3]])
>>> del items["-1"]
>>> items
['Cat', [1, 2]]

Normal indexing works as expected:

>>> items = AttrList([1, 2, 3])
>>> del items[1]
>>> items
[1, 3]

Weird indexing throws a TypeError:

>>> items = AttrList([1, 2, 3])
>>> del items[{"a": 1}]
Traceback (most recent call last):
 ...
TypeError: list indices must be integers or slices, not dict
Expand source code
def __delitem__(self, index: AttrListKey) -> None:
    """Delete an item from all members (or the list itself).

    Args:
        index (int|slice|str): item index

    Examples:
        A string index is applied to all members:
        >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
        >>> del items["0"]
        >>> items
        [[2, 3], [5, 6], [8, 9]]

        Immutable members are untouched:
        >>> items = AttrList(["Cat", [1, 2, 3]])
        >>> del items["-1"]
        >>> items
        ['Cat', [1, 2]]

        Normal indexing works as expected:
        >>> items = AttrList([1, 2, 3])
        >>> del items[1]
        >>> items
        [1, 3]

        Weird indexing throws a `TypeError`:
        >>> items = AttrList([1, 2, 3])
        >>> del items[{"a": 1}]
        Traceback (most recent call last):
         ...
        TypeError: list indices must be integers or slices, not dict
    """
    if isinstance(index, str):
        index = str2index(index)
        for member in self:
            if hasattr(member, "__delitem__"):
                member.__delitem__(index)
    else:
        super().__delitem__(index)
def __call__(self: Self, *args: Any, **kwargs: Any) ‑> ~Self

Return a new list with the result of calling all callables in the list.

Args

args (any): arguments to the callable kwargs (*any): keyword arguments to the callable

Returns

(Self): new list with results of the callable

Examples

>>> items = AttrList(["a", "b", "c"])
>>> items.upper()
['A', 'B', 'C']
>>> items = AttrList([lambda x: x + 2, lambda y: y + 5, "Z"])
>>> items(1)
[3, 6, 'Z']
Expand source code
def __call__(self: Self, *args: Any, **kwargs: Any) -> Self:
    """Return a new list with the result of calling all callables in the list.

    Args:
        args (*any): arguments to the callable
        kwargs (**any): keyword arguments to the callable

    Returns:
        (Self): new list with results of the callable

    Examples:
        >>> items = AttrList(["a", "b", "c"])
        >>> items.upper()
        ['A', 'B', 'C']

        >>> items = AttrList([lambda x: x + 2, lambda y: y + 5, "Z"])
        >>> items(1)
        [3, 6, 'Z']
    """
    return self.__class__(
        item(*args, **kwargs) if callable(item) else item for item in self
    )
class JSend (*args: Any, **kwargs: Dict[str, Any])

Service response object.

This object loosely conforms to the JSend specification.

Construct a JSend object.

Examples

>>> result = JSend()
>>> result.ok
True
>>> result.status == 'success'
True
>>> result.data is None
True
>>> result == {'ok': True, 'status': 'success', 'data': None}
True
Expand source code
class JSend(AttrDict):
    """Service response object.

    This object loosely conforms to the
    [JSend specification](https://labs.omniti.com/labs/jsend).
    """

    def __init__(self, *args: Any, **kwargs: Dict[str, Any]):
        """Construct a JSend object.

        Examples:
            >>> result = JSend()
            >>> result.ok
            True
            >>> result.status == 'success'
            True
            >>> result.data is None
            True
            >>> result == {'ok': True, 'status': 'success', 'data': None}
            True
        """
        self.update(ok=True, status=STATUS_SUCCESS, data=None)
        super().__init__(*args, **kwargs)

    def fail(self, message: Msg = None) -> JSend:
        """Indicate a controlled failure.

        Args:
            message (str): human-readable explanation of the failure

        Returns:
            JSend: self for chaining

        Examples:
            >>> result = JSend()
            >>> msg = 'Missing a phone number.'
            >>> result.fail(msg) is result
            True
            >>> result.ok is False
            True
            >>> result.status == 'fail'
            True
            >>> result.message == msg
            True
        """
        self.update(ok=False, status=STATUS_FAIL, message=message)
        return self

    def error(self, message: Msg = None, code: Optional[Any] = None) -> JSend:
        """Indicate an uncontrolled error.

        Args:
            message (str): human-readable explanation of the error
            code (Any, optional): technical indication of the error

        Returns:
            JSend: self for chaining

        Examples:
            >>> result = JSend()
            >>> msg = 'No such file [file.text].'
            >>> code = 13
            >>> result.error(msg, code) is result
            True
            >>> result.ok is False
            True
            >>> result.status == 'error'
            True
            >>> result.message == msg
            True
            >>> result.code == code
            True
        """
        self.update(ok=False, status=STATUS_ERROR, message=message, code=code)
        return self

    def success(self, data: Optional[Any] = None) -> JSend:
        """Indicate a successful response.

        Args:
            data (Any, optional): response payload

        Returns:
            JSend: self for chaining

        Examples:
            >>> data = "Works"
            >>> result = JSend()
            >>> result.success(data) is result
            True
            >>> result.data == data
            True
        """
        self.update(ok=True, status=STATUS_SUCCESS, data=data)
        return self

Ancestors

Methods

def fail(self, message: Msg = None) ‑> JSend

Indicate a controlled failure.

Args

message : str
human-readable explanation of the failure

Returns

JSend
self for chaining

Examples

>>> result = JSend()
>>> msg = 'Missing a phone number.'
>>> result.fail(msg) is result
True
>>> result.ok is False
True
>>> result.status == 'fail'
True
>>> result.message == msg
True
Expand source code
def fail(self, message: Msg = None) -> JSend:
    """Indicate a controlled failure.

    Args:
        message (str): human-readable explanation of the failure

    Returns:
        JSend: self for chaining

    Examples:
        >>> result = JSend()
        >>> msg = 'Missing a phone number.'
        >>> result.fail(msg) is result
        True
        >>> result.ok is False
        True
        >>> result.status == 'fail'
        True
        >>> result.message == msg
        True
    """
    self.update(ok=False, status=STATUS_FAIL, message=message)
    return self
def error(self, message: Msg = None, code: Optional[Any] = None) ‑> JSend

Indicate an uncontrolled error.

Args

message : str
human-readable explanation of the error
code : Any, optional
technical indication of the error

Returns

JSend
self for chaining

Examples

>>> result = JSend()
>>> msg = 'No such file [file.text].'
>>> code = 13
>>> result.error(msg, code) is result
True
>>> result.ok is False
True
>>> result.status == 'error'
True
>>> result.message == msg
True
>>> result.code == code
True
Expand source code
def error(self, message: Msg = None, code: Optional[Any] = None) -> JSend:
    """Indicate an uncontrolled error.

    Args:
        message (str): human-readable explanation of the error
        code (Any, optional): technical indication of the error

    Returns:
        JSend: self for chaining

    Examples:
        >>> result = JSend()
        >>> msg = 'No such file [file.text].'
        >>> code = 13
        >>> result.error(msg, code) is result
        True
        >>> result.ok is False
        True
        >>> result.status == 'error'
        True
        >>> result.message == msg
        True
        >>> result.code == code
        True
    """
    self.update(ok=False, status=STATUS_ERROR, message=message, code=code)
    return self
def success(self, data: Optional[Any] = None) ‑> JSend

Indicate a successful response.

Args

data : Any, optional
response payload

Returns

JSend
self for chaining

Examples

>>> data = "Works"
>>> result = JSend()
>>> result.success(data) is result
True
>>> result.data == data
True
Expand source code
def success(self, data: Optional[Any] = None) -> JSend:
    """Indicate a successful response.

    Args:
        data (Any, optional): response payload

    Returns:
        JSend: self for chaining

    Examples:
        >>> data = "Works"
        >>> result = JSend()
        >>> result.success(data) is result
        True
        >>> result.data == data
        True
    """
    self.update(ok=True, status=STATUS_SUCCESS, data=data)
    return self
def copy(self: Self) ‑> ~Self

Inherited from: AttrDict.copy

Return a shallow copy …

def __contains__(self, key: Any) ‑> bool

Inherited from: AttrDict.__contains__

Return True if key is a key …

def __getattr__(self, name: str) ‑> Optional[Any]

Inherited from: AttrDict.__getattr__

Return the value of the attribute or None

def __setattr__(self, name: str, value: Any) ‑> None

Inherited from: AttrDict.__setattr__

Set the value of an attribute …

def __delattr__(self, name: str) ‑> None

Inherited from: AttrDict.__delattr__

Delete an attribute …

def __getitem__(self, key: AnyIndex) ‑> Optional[Any]

Inherited from: AttrDict.__getitem__

Return the value of the key …

def __setitem__(self, key: AnyIndex, value: Any) ‑> None

Inherited from: AttrDict.__setitem__

Set the value of a key …

def __delitem__(self, key: str) ‑> None

Inherited from: AttrDict.__delitem__

Delete a key …

def get(self, path: AnyIndex, default: Optional[Any] = None, /) ‑> Optional[Any]

Inherited from: AttrDict.get

Return the value at path or default if it cannot be found …

def set(self: Self, path: AnyIndex, value: Optional[Any] = None, /) ‑> ~Self

Inherited from: AttrDict.set

Set key at path to value

def __lshift__(self: Self, other: Mapping[str, Any]) ‑> ~Self

Inherited from: AttrDict.__lshift__

Merge other into self