def is_transition_allowed(constraint_type: str,
                          from_tag: str,
                          to_tag: str):
    """
    Given a constraint type and strings ``from_tag`` and ``to_tag`` that
    represent the origin and destination of the transition, return whether
    the transition is allowed under the given constraint type.

    Parameters
    ----------
    constraint_type : ``str``, required
        Indicates which constraint to apply. Current choices are
        "BIO", "IOB1", "BIOUL", and "BMES".
    from_tag : ``str``, required
        The tag that the transition originates from. For example, if the
        label is ``I-PER``, the ``from_tag`` is ``I``.
    from_entity: ``str``, required
        The entity corresponding to the ``from_tag``. For example, if the
        label is ``I-PER``, the ``from_entity`` is ``PER``.
    to_tag : ``str``, required
        The tag that the transition leads to. For example, if the
        label is ``I-PER``, the ``to_tag`` is ``I``.
    to_entity: ``str``, required
        The entity corresponding to the ``to_tag``. For example, if the
        label is ``I-PER``, the ``to_entity`` is ``PER``.

    Returns
    -------
    ``bool``
        Whether the transition is allowed under the given ``constraint_type``.
    """
    # pylint: disable=too-many-return-statements
    if to_tag == "START" or from_tag == "END":
        # Cannot transition into START or from END
        return False

    if constraint_type == "BIOUL":
        if from_tag == "START":
            return to_tag in ('O', 'B', 'U')
        if to_tag == "END":
            return from_tag in ('O', 'L', 'U')
        return any([
                # O can transition to O, B-* or U-*
                # L-x can transition to O, B-*, or U-*
                # U-x can transition to O, B-*, or U-*
                from_tag in ('O', 'L', 'U') and to_tag in ('O', 'B', 'U'),
                # B-x can only transition to I-x or L-x
                # I-x can only transition to I-x or L-x
                from_tag in ('B', 'I') and to_tag in ('I', 'L')
        ])
    elif constraint_type == "BIO":
        if from_tag == "START":
            return to_tag in ('O', 'B')
        if to_tag == "END":
            return from_tag in ('O', 'B', 'I')
        return any([
                # Can always transition to O or B-x
                to_tag in ('O', 'B'),
                # Can only transition to I-x from B-x or I-x
                to_tag == 'I' and from_tag in ('B', 'I')
        ])
    elif constraint_type == "IOB1":
        if from_tag == "START":
            return to_tag in ('O', 'I')
        if to_tag == "END":
            return from_tag in ('O', 'B', 'I')
        return any([
                # Can always transition to O or I-x
                to_tag in ('O', 'I'),
                # Can only transition to B-x from B-x or I-x, where
                # x is the same tag.
                to_tag == 'B' and from_tag in ('B', 'I')
        ])
    elif constraint_type == "BMES":
        if from_tag == "START":
            return to_tag in ('B', 'S')
        if to_tag == "END":
            return from_tag in ('E', 'S')
        return any([
                # Can only transition to B or S from E or S.
                to_tag in ('B', 'S') and from_tag in ('E', 'S'),
                # Can only transition to M-x from B-x, where
                # x is the same tag.
                to_tag == 'M' and from_tag in ('B', 'M'),
                # Can only transition to E-x from B-x or M-x, where
                # x is the same tag.
                to_tag == 'E' and from_tag in ('B', 'M')
        ])
    else:
        raise TypeError("Unknown constraint type: {constraint_type}")