Beispiel #1
0
def test_empty_path():
    with pytest.raises(TypeError):
        dictify(
            (
                # empty path
                ((), 0),
            )
        )
Beispiel #2
0
def test_dict_items():
    with pytest.raises(TypeError):
        dictify(
            (
                # dict item
                (("a", "b"), {"foo": "bar"}),
            )
        )
Beispiel #3
0
def test_collide_parent():
    with pytest.raises(TypeError):
        dictify(
            (
                (("a", "b", "c"), 0),
                # higher path
                (("a", "b"), 1),
            )
        )
Beispiel #4
0
def test_collide_child():
    with pytest.raises(TypeError):
        dictify(
            (
                (("a", "b"), 0),
                # lower path
                (("a", "b", "c"), 1),
            )
        )
Beispiel #5
0
def test_collide_item():
    with pytest.raises(TypeError):
        dictify(
            (
                (("a", "b"), 0),
                # same path
                (("a", "b"), 1),
            )
        )
Beispiel #6
0
def create_handler(*args):
    handlers_with_markers = []
    handlers_without_markers = []
    for arg in args:
        markers = getattr(arg, _ATTR_MARKER, None)
        if markers is None:
            # syntactic sugar cases
            if isinstance(arg, str):
                arg = (arg, )
            if isinstance(arg, list):
                arg = tuple(arg)
            if isinstance(arg, tuple):
                handlers_with_markers.append((arg, _handler_identity))

            # no markers
            else:
                handlers_without_markers.append(arg)
        else:
            # markers
            for marker in markers:
                handlers_with_markers.append((marker, arg))

    if handlers_without_markers:
        # both handlers with and without markers
        if handlers_with_markers:
            raise TypeError(
                "Catchall handler with other non-catchall handlers")

        # multiple handlers without markers
        if len(handlers_without_markers) > 1:
            raise TypeError("Several catchall handlers")

        # only one handler without markers
        leaf = handlers_without_markers[0]
        handler = _handle_from_leaf(leaf)

    else:
        # only handlers with markers
        tree = dictify(handlers_with_markers)
        handler = _handler_from_tree(tree)

    # if handler returns None, makes it return empty iterable instead
    return transform_none_return_value(handler)
Beispiel #7
0
def test_multiple_iterators():
    def gen_x():
        yield (("a", "b"), 0)
        yield (("c", "d"), 1)

    def gen_y():
        yield (("a", "e"), 2)

    def gen_z():
        yield (("c", "f"), 3)

    assert dictify(gen_x(), gen_y(), gen_z()) == {
        "a": {
            "b": 0,
            "e": 2,
        },
        "c": {
            "d": 1,
            "f": 3,
        },
    }
Beispiel #8
0
def test_base():
    assert dictify(
        (
            (("a", "b"), 0),
            (("a", "c", "d"), 1),
            (("a", "c", "e"), 2),
            (("a", "e"), 3),
            (("c",), 4),
            (("d", "f"), 5),
        )
    ) == {
        "a": {
            "b": 0,
            "c": {
                "d": 1,
                "e": 2,
            },
            "e": 3,
        },
        "c": 4,
        "d": {
            "f": 5,
        },
    }
Beispiel #9
0
def _handle_from_leaf(leaf):
    # class
    if isclass(leaf):
        try:
            init_mandatory_params = get_mandatory_params(leaf)
        except ValueError:
            init_mandatory_params = ()  # probably a built-in
        try:
            _test_one_mandatory_param(init_mandatory_params, "__init__")
        except TypeError as ex:
            if is_dataclass(leaf):
                raise TypeError(
                    f"{ex}. Add a default value for dataclass fields.") from ex
            raise

        def handle(node):
            instance = leaf(node) if init_mandatory_params else leaf()
            try:
                sub_handle = transform_none_return_value(
                    _handle_from_leaf(instance))
                items = sub_handle(node)
            except TypeError:
                # no marker on public attributes
                items = ()  # empty iterable

            wrapper = getattr(instance, CLASS_HANDLER_METHOD_NAME, None)
            wrapper_exists = wrapper is not None
            if wrapper is None:

                def wrapper():
                    yield instance

            wrapper_mandatory_params = get_mandatory_params(wrapper)
            _test_one_mandatory_param(
                wrapper_mandatory_params,
                CLASS_HANDLER_METHOD_NAME,
            )

            if wrapper_mandatory_params:
                return wrapper(items)

            if consume(items):
                warning_msg = ("Items were yielded by some sub-handler"
                               f" of class {instance.__class__.__name__}.")
                if wrapper_exists:
                    warning_msg += (
                        f" Add an argument to the {CLASS_HANDLER_METHOD_NAME}"
                        " method to handle them properly.")
                else:
                    warning_msg += (f" Create a {CLASS_HANDLER_METHOD_NAME}"
                                    " method to handle them properly.")
                warnings.warn(warning_msg, RuntimeWarning)

            return wrapper()

        return handle

    # callable
    if callable(leaf):
        return leaf

    # object with markers on public attributes
    handlers_with_markers = []
    for handler_name, handler in getmembers(leaf):
        if handler_name.startswith("__"):
            continue
        for marker in getattr(handler, _ATTR_MARKER, ()):
            handlers_with_markers.append((marker, handler))
    if handlers_with_markers:
        handler = _handler_from_tree(dictify(handlers_with_markers))
        if hasattr(leaf, _ATTR_MARKER):
            return lambda node: node.iter_from(handler)
        return handler

    raise TypeError(f"Invalid handler type: {type(leaf).__name__}")