"""
Flexible tree data structures for organizing lists and dicts into sections.
https://github.com/trevorpogue/sections
"""
from typing import Any
from typing import List
from typing import Type
from typing import Union
from .attr_parser import AttrParser
from .dict import Dict
from .meta import Meta
from .node import Node
from .string_parser import StringParser
from .types import GetType
from .types import SectionAttrs
from .types import SectionType
[docs]class Section(Node, Dict, AttrParser, StringParser, dict,
metaclass=Meta):
"""
Objects instantiated by :class:`Section <Section>` are nodes in a sections
tree structure. Each node has useful methods and properties for organizing
lists/dicts into sections and for conveniently accessing/modifying the
sub-list/dicts from each section/subsection.
"""
##########################################################################
# tree-structure-wide attributes for every node #
# class attributes act as tree-structure-wide attributes across all nodes.
# This is possible because each sections() module call returns a unique
# copy of the Section class, giving each individual structure its own class
# Choose whether to use a cache in each node. The cache contains
# quickly-readable references to attribute iterables parsed from manually
# traversing through descendant nodes in a previous read. The caches are
# invalidated when the tree structure or node attribute values change.
# Using the cache can often make structure attribute reading faster by 5x
# and even much more. The downside is that it also increases memory used by
# roughly 5x as well. This is not a concern on a general-purpose computer
# for structures containing less than 1000 nodes or 10,000 nodes, although
# further testing is required to confirm this. After 10,000 nodes, it may
# be recommended to turn the structure class attribute `use_cache` to
# False.
use_cache = True
# See method Section.get_nearest_attr's doctring for a full description of
# gettype and its default value. 'hybrid' returns a list if more than 1
# element is found, else return the non-iterable raw form of the element
default_gettype = 'hybrid'
use_pluralsingular = True
##########################################################################
def __init__(self, **kwds: SectionAttrs) -> None:
"""Set object attr for every attr in kwds and init attr cache."""
if self.use_cache and self.isleaf:
self._cache = {}
for name, value in kwds.items():
self.__setattr__(name, value, _invalidate_cache=False)
@property
def cls(self) -> Type[SectionType]:
"""The unique structure-wide class of each node."""
return self.__class__
@ property
def sections(self) -> SectionType:
"""A synonym for property :meth:`children <Section.children>`."""
return self.children
@ property
def entries(self) -> SectionType:
"""A synonym for property :meth:`leaves <Section.leaves>`."""
return self.leaves
[docs] def __call__(
self,
name: str,
gettype: GetType = 'default'
) -> Union[Any, List[Any]]:
"""
Run :meth:`get_nearest_attr <Section.get_nearest_attr>`. This
returns attribute `name` from self if self contains the attribute in
either the singular or plural form for `name`. Else, try the same
pattern for each of self's children, putting the returned results from
each child into a list. Else, raise AttributeError.
For argument `gettype`, Setting to `'default'` uses the value of
self.default_gettype for gettype (its default is 'hybrid'). Setting to
`'hybrid'` returns a list if more than 1 element is found, else returns
the non-iterable raw form of the element. Setting to `list` returns a
list containing the attribute values. Setting to `iter` returns an
iterable iterating through the attribute values. Setting to `dict`
returns a dict containing pairs of the containing node's name with the
attribute value. Setting to `'full_dict'` is faster than `dict` and
returns a dict containing pairs of a reference to each node and its
attribute value. `'full_dict'` output is visually identical to `dict`
for printing purposes except that it will contain all attributes even
if some source nodes have duplicate names. The only downside to
`'full_dict'` is that the keys cannot be referenced by name like with
`dict`, but all values() are still valid.
:param name: The name of the attribute to find in self or self's
descendants.
:param gettype: Valid values are `'default'`, `'hybrid'` `list`,
`iter`, `dict`, `'full_dict'`. See method's description
body above for explanation of what each value does.
:return: An iterable or non-iterable form of the attribute `name`
formed from self or descendant nodes. Depends on the value
given to `gettype`.
"""
return self.get_nearest_attr(name, gettype=gettype)