Beispiel #1
0
def test_immutable_traverse_plain_object():
    """Test that by traversin the plain object we get that object itself."""

    # Check on boolean
    assert traverse(True) is True

    # Check on integer
    assert traverse(5) == 5

    # Check non traversable class instance
    class NonTraversable:
        def __init__(self):
            self.value = [1, 2]

    t1 = NonTraversable()
    t2 = traverse(t1)

    # If traverse indeed could not traverse thorugh this
    # object and did not copy it but returned the original one
    # then both 't1' and 't2' point to the
    # same object.
    assert t1.value == t2.value

    # Alter the list value in the original object to make sure
    # the second object has the same change.
    t1.value.append(3)
    assert t2.value == [1, 2, 3]
Beispiel #2
0
def test_mutable_traverse_copy_dict():
    """Test that plain traverse produces deep copy of the Dict."""

    duhast_traversed = traverse(duhast)
    assert duhast_traversed == duhast

    # Alter traversed version and verify the original
    # did not change.
    duhast_traversed["person"]["perks"][-1].append("valorian")
    len_traversed = len(duhast_traversed["person"]["perks"][-1])
    len_original = len(duhast["person"]["perks"][-1])
    assert len_traversed == len_original + 1
Beispiel #3
0
def test_immutable_traverse():
    """Test immutable traverse and visit order.

    This test verifies that my passing a non-modifying visitor
    function to the traversal method we are correctly visiting every
    node and do not alter the initial object.
    """
    def _on_kv(*args, **kwargs):
        k, v = args

        if issequence(v):
            v = "<list>"
        if isinstance(v, Mapping):
            v = "<dict>"

        kwargs["tracking_list"].append((k, v))

    visit_order = []

    traverse(duhast, visitor=_on_kv, tracking_list=visit_order)

    assert visit_order == [
        ("person", "<dict>"),
        ("name", "Duhast"),
        ("parental", "Vyacheslavovich"),
        ("songs", "<dict>"),
        ("Du Hast (variations)", "<list>"),
        (None, "<dict>"),
        ("Du Hast - Live", "<dict>"),
        ("Wales", 2007),
        ("London", 2001),
        (None, "Du Hast - Remix"),
        ("perks", "<list>"),
        (None, "awesome"),
        (None, "<list>"),
        (None, "vocal"),
        (None, "brave"),
        ("event", b"Babaika Fest"),
    ]
Beispiel #4
0
def to_dict(
    mapping: Union[Dict[Hashable, Any], Mapping[Hashable, Any]],
    inplace: bool = False,
) -> Dict[Hashable, Any]:
    """Convert mapping structure to plain dict.

    This is useful to convert any subtype of ``dict`` back to its original
    form. For example, Mapz objects overwrite get/set methods of original
    dict to add new features. Applying ``to_dict`` to Mapz object will
    transform it into a simple dict with the same structure.

    Args:
        mapping: Mapping structure to convert.
        inplace (bool): Whether to replace given Dict with converted
            ``dict`` object. Only works if the passed ``mapping`` is
            also a mutable Dict-like object. Defaults to False.

    Returns:
        dict: Plain dictionary object with the same structure. If the
        "inplace" is True, then the given "mapping" object is also
        rebound to newly converted dictionary instead of pointing to
        the initial given structure.

    Examples:
        Transform Mapz object to dict.

        >>> m = Mapz({"a": 1})
        >>> m
        MapZ{'a': 1}
        >>> to_dict(m)
        {'a': 1}
    """

    d = traverse(mapping, mapping_type=dict)

    if isinstance(mapping, Dict) and inplace:
        mapping.clear()
        mapping.update(d)
        d = mapping

    # Explicit cast here because otherwise mypy complains that 'traverse'
    # returns Any, thus 'd' also evaluates to Any, and 'to_dict'
    # returns Dict.
    # Somehow, unable to specify just 'dict' as a return type for this
    # function.
    return cast(Dict[Hashable, Any], d)
Beispiel #5
0
def to_table(
    mapping: Mapping[Hashable, Any],
    headers: Iterable[str] = ("Key", "Value"),
    indentation: str = "  ",
    limit: int = 0,
) -> TableType:
    """Transform dictionary into a printable table structure.

    Resulting table always consists of the two columns. The first column
    contains mapping keys sorted in ascending order from first row to last
    and indented according to the internal structure of the given mapping.

    If a key in the mapping represents another mapping or a list then the
    value across it in the "Value" column will be empty. If a key represents
    anything different, then its value will be cast to string and truncated
    if its length is more than 79 characters. In such case first 76
    characters of the value are taken and three dots ("...") indicating
    truncation are added to them.

    Each next nested level of the mapping is indented using the
    ``indentation`` string provided in the arguments.

    If a certain key contains a list of values, then each value printed in
    the following rows will be accompanied by the dash ("-") in the "Key"
    column. This also applied to the case when key contains a list of
    mappings. In such case the mappings will be printed as in YAML.

    If ``limit`` is > 0, then no more than ``limit`` rows will be converted.
    An additional row indicating truncation will be added as the last one
    (["...", "..."]).

    Args:
        mapping: Mapping or dictionary to transform to table.
        headers: Iterable of headers for the table. Defaults to
            ("Key", "Value").
        indentation (str): String that will be used as an indentation of
            nested keys in the table. Defaults to double-space "  ".
        limit (int): Row limit. Limits the number of rows in the resulting
            table. Defaults to 0 (no limit).

    Returns:
        TableType: A tuple consiting of headers and list of rows.

    Examples:
        Below, a structure with nested values, dictionaries, plain lists,
        and lists of other dictionaries is transformed to a table. Notice
        how list items each get prepended by a dash and any nested key
        is indented.

        >>> m = Mapz({ \
                "databases": {
                    "db1": {
                        "host": "localhost",
                        "port": 5432,
                    },
                },
                "users": [
                    "Duhast",
                    "Valera",
                ],
                "Params": [
                    {"ttl": 120, "flush": True},
                    {frozenset({1, 2}): {1, 2}},
                ],
                "name": "Boris",
            })
        >>> to_table(m)
        (
            ['Key', 'Value'],
            [
                ['Params', ''],
                ['  - flush', 'True'],
                ['    ttl', '120'],
                ['  - frozenset({1, 2})', '{1, 2}'],
                ['databases', ''],
                ['  db1', ''],
                ['    host', 'localhost'],
                ['    port', '5432'],
                ['name', 'Boris'],
                ['users', ''],
                ['  -', 'Duhast'],
                ['  -', 'Valera']
            ]
        )

    """

    def builder(k: Any, v: Any, **kwargs: Any) -> Optional[Tuple[Any, Any]]:
        """Visit each key and value and collect them into row list.

        Args:
            **kwargs: Arguments provided by ``traverse`` function as well as
            by invoking function. Contains "_depth", "_index", and
            "_ancestors" values provided by ``traverse``. Must also contain
            "rows" and "limit" provided by function that invoked traverse.
        """

        # Mutable list of rows.
        rows = kwargs["rows"]
        # Limit of rows.
        limit = kwargs["limit"]
        # How deeply nested are we.
        depth = kwargs["_depth"]
        # Index of the current item (useful for processing lists).
        index = kwargs["_index"]
        # List of acenstors of current nested key/value.
        ancestors = kwargs["_ancestors"]

        # Render keys by default as:
        table_key = indentation * (depth - 1) + str(k)

        if (
            len(ancestors) > 1
            and ismapping(ancestors[-1])  # noqa: W503
            and issequence(ancestors[-2])  # noqa: W503
        ):
            # If node has two or more ancestors, then check if it's a
            # mapping within a list. Because in that case it must be
            # rendered as in YAML:
            # my_list
            #   - key1      value1
            #     key2      value2
            if index:
                table_key = indentation * (depth - 1) + str(k)
            else:
                table_key = indentation * (depth - 2) + "- " + str(k)

        elif ancestors and issequence(ancestors[-1]):
            # Render child items of lists as just a dash with proper indent
            table_key = indentation * (depth - 1) + "-"

        if not limit or limit and len(rows) < limit:

            value = ""
            if not (ismapping(v) or issequence(v)):
                s = " ".join(
                    str(v).replace("\n", " ").replace("\t", " ").split()
                )
                value = s if len(s) < 80 else f"{s[:76]}..."

            # Ignore empty lines with no key and no value.
            # Example: List of Mappings will result in such row.
            if not k and not value:
                return None

            rows.append([table_key, value])

        return None

    rows: List[RowType] = []
    traverse(
        mapping,
        visitor=builder,
        key_order=lambda keys: sorted(keys),
        rows=rows,
        limit=limit,
    )

    if limit and len(rows) >= limit:
        rows.append(["...", "..."])

    return (list(headers), rows)
Beispiel #6
0
def test_mutable_traverse_plain_object():
    """Test that modifying visitor function has no effect on plain objects."""

    assert traverse(True, lambda k, v, **kwargs: 1 + 2) is True