Skip to content

Commit

Permalink
map_tree supports postorder transformation
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Gaultney committed Sep 29, 2020
1 parent cb33805 commit c3e3015
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[settings]
profile=black
line_length=100
7 changes: 6 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ repos:
- id: prettier
exclude: .*\.html

- repo: https://github.com/PyCQA/isort
rev: 5.4.2 # Use the revision sha / tag you want to point at
hooks:
- id: isort

- repo: https://github.com/ambv/black
rev: stable
rev: 19.10b0
hooks:
- id: black
language_version: python3.7
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 1.5.0

`map_tree` now supports postorder transformations via keyword argument.

### 1.4.0

- Improved DynamoDB Item-related Exceptions for `GetItem`,
Expand Down
25 changes: 21 additions & 4 deletions tests/xoto3/utils/tree_map_test.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from decimal import Decimal

import pytest

from xoto3.utils.dec import decimal_to_number
from xoto3.utils.tree_map import (
compose,
make_path_only_transform,
make_path_stop_transform,
map_tree,
type_dispatched_transform,
make_path_stop_transform,
make_path_only_transform,
compose,
)
from xoto3.utils.dec import decimal_to_number


def test_map_tree_idents():
Expand Down Expand Up @@ -76,3 +78,18 @@ def test_compose():
d = dict(p=[1.2, 3.4, 8.8])

assert map_tree(tx, d) == dict(p=["1i", "3i", "8i"])


def test_postorder_transformation():
def coerce_to_int(o):
return int(o)

inp = dict(a=2.3, b=4.4)
with pytest.raises(TypeError):
map_tree(coerce_to_int, inp)

assert dict(a=2, b=4) == map_tree(coerce_to_int, inp, postorder=True)

assert dict(p=7, g=[2, 3, 4]) == map_tree(
coerce_to_int, dict(p=7.4, g=[2.01, 3.01, 4.01]), postorder=True
)
2 changes: 1 addition & 1 deletion xoto3/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""xoto3"""
__version__ = "1.4.0"
__version__ = "1.5.0"
__author__ = "Peter Gaultney"
__author_email__ = "[email protected]"
38 changes: 25 additions & 13 deletions xoto3/utils/tree_map.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Tuple, Hashable, Callable, Any, Mapping, Set, Union, cast
from functools import singledispatch, wraps
import inspect

from functools import singledispatch, wraps
from typing import Any, Callable, Hashable, Mapping, Set, Tuple, Union, cast

SimpleTransform = Callable[[Any], Any]

Expand Down Expand Up @@ -35,7 +34,7 @@ def coerce_transform(transform: TreeTransform) -> PathTransform:
)


def map_tree(transform: TreeTransform, obj: Any) -> Any:
def map_tree(transform: TreeTransform, obj: Any, *, postorder: bool = False) -> Any:
"""Maps a tree made of Python general-purpose builtin containers.
The tree property of the object is important - technically you may
Expand All @@ -50,6 +49,10 @@ def map_tree(transform: TreeTransform, obj: Any) -> Any:
tuples, and sets, rendering a new corresponding builtin instance
for each.
If you want a post-order call to your transform, pass the keyword
argument postorder=True. This will disable the use of the stop
return.
Only applies the first recursive transform that matches the type
of the provided object.
Expand Down Expand Up @@ -86,23 +89,32 @@ def map_tree(transform: TreeTransform, obj: Any) -> Any:
paths only are provided.
"""
return _map_tree(coerce_transform(transform), obj)
return _map_tree(coerce_transform(transform), obj, postorder=postorder)


def _map_tree(transform: PathTransform, obj: Any, *, path: KeyPath = ()) -> Any:
obj, stop = transform(obj, path)
if stop:
return obj
def _map_tree(
transform: PathTransform, obj: Any, *, path: KeyPath = (), postorder: bool = False
) -> Any:
if not postorder:
obj, stop = transform(obj, path)
if stop:
return obj

# then apply the first builtin-type-matching recursive transform.
if isinstance(obj, Mapping):
return {k: _map_tree(transform, v, path=path + (k,)) for k, v in obj.items()}
return {
k: _map_tree(transform, v, path=path + (k,), postorder=postorder)
for k, v in obj.items()
}
if isinstance(obj, Set):
return {_map_tree(transform, member, path=path) for member in obj}
return {_map_tree(transform, member, path=path, postorder=postorder) for member in obj}
if isinstance(obj, list):
return [_map_tree(transform, item, path=path) for item in obj]
return [_map_tree(transform, item, path=path, postorder=postorder) for item in obj]
if isinstance(obj, tuple):
return tuple((_map_tree(transform, item, path=path) for item in obj))
return tuple((_map_tree(transform, item, path=path, postorder=postorder) for item in obj))

if postorder:
obj, _stop = transform(obj, path)

return obj

Expand Down

0 comments on commit c3e3015

Please sign in to comment.