user_interface module
There's something in here that doesn't seem to make a lot of sense:

    try:
        if not type_:
            type_ = type(input_)
        input_ = type_(input_)
    except ValueError:
        print("Expected input of type {} but got {}".format(
            type_, type(input_)))
        continue


If type is falsy, get the type of the input and re-cast the input? That seems unnecessary. And in this case the whole type validation seems unnecessary. You could skip the validation of the specified type is falsy.

A very minor thing, the default start of a range is 0, so you could just skip that.