Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Walk type #47

Merged
merged 7 commits into from
Jul 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.1.23
current_version = 0.1.24
commit = True
tag = True

Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
0.1.24 (2019-07-28)
-------------------


No significant changes.


----


0.1.23 (2019-07-28)
-------------------

Expand Down
1 change: 1 addition & 0 deletions changelog.d/47.breaking
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``walk -t`` no longer uses ``eval`` on the left-hand side of the equal sign. That value can only refer to a type by name.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
year = "2019"
author = "Mario contributors"
copyright = "{0}, {1}".format(year, author)
version = release = "0.1.23"
version = release = "0.1.24"

pygments_style = "trac"
templates_path = ["."]
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def read(*names, **kwargs):
# Enable code coverage for C code: we can't use CFLAGS=-coverage in tox.ini, since that may mess with compiling
setup(
name="mario-addons",
version="0.1.23",
version="0.1.24",
description="More commands for Mario.",
long_description="%s\n%s"
% (
Expand Down
2 changes: 1 addition & 1 deletion src/mario_addons/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Addons for mario."""

__version__ = "0.1.23"
__version__ = "0.1.24"
4 changes: 2 additions & 2 deletions src/mario_addons/plugins/addons.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ section = 'Traversals'
inject_values=['t']
short_help = "[EXPERIMENTAL] Walk a tree from the bottom up."
help = """
[EXPERIMENTAL] Walk a tree from the bottom up, transforming objects as you go. Note this command uses ``eval`` on the transformer arguments.
[EXPERIMENTAL] Walk a tree from the bottom up, transforming objects as you go.

For example,

Expand Down Expand Up @@ -90,7 +90,7 @@ For example,
[[command.options]]
name = "-t"
multiple=true
help="Transformers for types. Pass a type-function pair. Each instance of the type will be converted with the function. For example, use ``-tdict=collections.OrderedDict`` to convert all ``dict`` objects to ``OrderedDict``. ``eval`` is used on both sides of the ``=``."
help="Transformers for types. Pass a type-function pair. Each instance of the type will be converted with the function. For example, use ``-tdict=collections.OrderedDict`` to convert all ``dict`` objects to ``OrderedDict``. The right-hand side of the ``=`` is Python code which will be executed. It may use ``x`` as a reference to the input value."

[[command.stages]]
command = 'map'
Expand Down
47 changes: 42 additions & 5 deletions src/mario_addons/plugins/walking.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Functions for walking a tree."""

import builtins
import collections.abc
import typing as t

Expand Down Expand Up @@ -48,17 +49,53 @@ def make_func(code: str, namespace: t.Dict[str, object]) -> t.Callable:
return lambda x: eval(code, {**namespace, "x": x})


def prefixes(multi_attribute_access: str) -> t.List[str]:
"""Get prefixes of a namepsaced name.

>>> prefixes('a.b.c')
['a.b.c', 'a.b', 'a']
"""
split = multi_attribute_access.split(".")
out = []
while split:
out.append(".".join(split))
split.pop(-1)
return out


def get_type_object(namespaced_type_name: str) -> t.Type:
"""Turns a qualname into the corresponding object."""

if "." not in namespaced_type_name:
return getattr(builtins, namespaced_type_name)

name_to_module = mario.interpret.build_name_to_module(namespaced_type_name)
for module_name in prefixes(namespaced_type_name):
module = name_to_module.get(module_name)
if module is not None:
break
else:
raise ImportError(module_name)

obj = module
# pylint: disable=undefined-loop-variable
remainder = namespaced_type_name[len(module_name) :]
names = [name for name in remainder.split(".") if name]

while names:
obj = getattr(obj, names[0])
names.pop()

return obj


def build_mapping(pairs: t.Iterable[t.Tuple[str, str]]):
"""Build a type-to-transformer mapping."""
mapping = {}

for input_type, converter in pairs:
input_type_namespace = mario.interpret.build_name_to_module(input_type)
converter_namespace = mario.interpret.build_name_to_module(converter)
# pylint: disable=eval-used
mapping[eval(input_type, input_type_namespace)] = make_func(
converter, converter_namespace
)
mapping[get_type_object(input_type)] = make_func(converter, converter_namespace)
return mapping


Expand Down
17 changes: 17 additions & 0 deletions tests/test_walking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Test the walking module."""

import pytest

from mario_addons.plugins import walking


@pytest.mark.parametrize("name", ["collections.OrderedDict", "int"])
def test_get_type_object(name):
"""The type object has the right the module name and qualname."""
result = walking.get_type_object(name)
assert isinstance(result, type)

expected_name = result.__module__ + "." + result.__qualname__
if expected_name.startswith("builtins."):
expected_name = expected_name[len("builtins.") :]
assert expected_name == name