Given an XML node describing a limit, return a Limit object.

    :param db: Handle for the Redis database.
    :param idx: The index of the limit in the XML file; used for error
                reporting.
    :param limit: The XML node describing the limit.

def parse_limit_node(db, idx, limit):
    """
    Given an XML node describing a limit, return a Limit object.

    :param db: Handle for the Redis database.
    :param idx: The index of the limit in the XML file; used for error
                reporting.
    :param limit: The XML node describing the limit.
    """

    # First, try to import the class; this will raise ImportError if
    # we can't import it
    klass = utils.find_entrypoint('turnstile.limit', limit.get('class'),
                                  required=True)

    # Build the list of required attributes
    required = set(k for k, v in klass.attrs.items()
                   if 'default' not in v)

    # Now, use introspection on the class to interpret the attributes
    attrs = {}
    for child in limit:
        # Basic validation of child elements
        if child.tag != 'attr':
            warnings.warn("Unrecognized element %r while parsing limit at "
                          "index %d; ignoring..." % (child.tag, idx))
            continue

        # Get the attribute name
        attr = child.get('name')

        # Be liberal in what we accept--ignore unrecognized attributes
        # (with a warning)
        if attr not in klass.attrs:
            warnings.warn("Limit at index %d does not accept an attribute "
                          "%r; ignoring..." % (idx, attr))
            continue

        # OK, get the attribute descriptor
        desc = klass.attrs[attr]

        # Grab the attribute type
        attr_type = desc.get('type', str)

        if attr_type == list:
            # Lists are expressed as child elements; we ignore the
            # child element names
            subtype = desc.get('subtype', str)
            value = []
            try:
                for j, grandchild in enumerate(child):
                    if grandchild.tag != 'value':
                        warnings.warn("Unrecognized element %r while parsing "
                                      "%r attribute of limit at index %d; "
                                      "ignoring element..." %
                                      (grandchild.tag, attr, idx))
                        continue

                    value.append(subtype(grandchild.text))
            except ValueError:
                warnings.warn("Invalid value %r while parsing element %d "
                              "of %r attribute of limit at index %d; "
                              "ignoring attribute..." %
                              (grandchild.text, j, attr, idx))
                continue
        elif attr_type == dict:
            # Dicts are expressed as child elements, with the tags
            # identifying the attribute name
            subtype = desc.get('subtype', str)
            value = {}
            for grandchild in child:
                if grandchild.tag != 'value':
                    warnings.warn("Unrecognized element %r while parsing "
                                  "%r attribute of limit at index %d; "
                                  "ignoring element..." %
                                  (grandchild.tag, attr, idx))
                    continue
                elif 'key' not in grandchild.attrib:
                    warnings.warn("Missing 'key' attribute of 'value' "
                                  "element while parsing %r attribute of "
                                  "limit at index %d; ignoring element..." %
                                  (attr, idx))
                    continue

                try:
                    value[grandchild.get('key')] = subtype(grandchild.text)
                except ValueError:
                    warnings.warn("Invalid value %r while parsing %r element "
                                  "of %r attribute of limit at index %d; "
                                  "ignoring element..." %
                                  (grandchild.text, grandchild.get('key'),
                                   attr, idx))
                    continue
        elif attr_type == bool:
            try:
                value = config.Config.to_bool(child.text)
            except ValueError:
                warnings.warn("Unrecognized boolean value %r while parsing "
                              "%r attribute of limit at index %d; "
                              "ignoring..." % (child.text, attr, idx))
                continue
        else:
            # Simple type conversion
            try:
                value = attr_type(child.text)
            except ValueError:
                warnings.warn("Invalid value %r while parsing %r attribute "
                              "of limit at index %d; ignoring..." %
                              (child.text, attr, idx))
                continue

        # Save the attribute
        attrs[attr] = value

        # Remove from the required set
        required.discard(attr)

    # Did we get all required attributes?
    if required:
        raise TypeError("Missing required attributes %s" %
                        (', '.join(repr(a) for a in sorted(required))))

    # OK, instantiate and return the class
    return klass(db, **attrs)