def test_empty_path(): with pytest.raises(TypeError): dictify( ( # empty path ((), 0), ) )
def test_dict_items(): with pytest.raises(TypeError): dictify( ( # dict item (("a", "b"), {"foo": "bar"}), ) )
def test_collide_parent(): with pytest.raises(TypeError): dictify( ( (("a", "b", "c"), 0), # higher path (("a", "b"), 1), ) )
def test_collide_child(): with pytest.raises(TypeError): dictify( ( (("a", "b"), 0), # lower path (("a", "b", "c"), 1), ) )
def test_collide_item(): with pytest.raises(TypeError): dictify( ( (("a", "b"), 0), # same path (("a", "b"), 1), ) )
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)
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, }, }
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, }, }
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__}")