Takes an iterable of trait names, and tries to compose a property type
    from that.  Raises an exception if this is not possible.  Extra traits not
    requested are not acceptable.

    If this automatic generation doesn't work for you for some reason, then
    compose your property types manually.

    The details of this composition should not be relied upon; it may change in
    future releases.  However, a given normalize version should behave
    consistently for multiple runs, given the same starting sets of properties,
    the composition order will be the same every time.

def create_property_type_from_traits(trait_set):
    """Takes an iterable of trait names, and tries to compose a property type
    from that.  Raises an exception if this is not possible.  Extra traits not
    requested are not acceptable.

    If this automatic generation doesn't work for you for some reason, then
    compose your property types manually.

    The details of this composition should not be relied upon; it may change in
    future releases.  However, a given normalize version should behave
    consistently for multiple runs, given the same starting sets of properties,
    the composition order will be the same every time.
    """
    wanted_traits = set(trait_set)
    stock_types = dict(
        (k, v) for k, v in PROPERTY_TYPES.items() if
        set(k).issubset(wanted_traits)
    )

    traits_available = set()
    for key in stock_types.keys():
        traits_available.update(key)

    missing_traits = wanted_traits - traits_available
    if missing_traits:
        raise exc.PropertyTypeMixinNotPossible(
            traitlist=repr(trait_set),
            missing=repr(tuple(sorted(missing_traits))),
        )

    made_types = []
    # mix together property types, until we have made the right type.
    while trait_set not in PROPERTY_TYPES:

        # be somewhat deterministic: always start with types which provide the
        # 'first' trait on the list
        start_with = set(
            k for k in stock_types.keys() if k and k[0] == trait_set[0]
        )

        # prefer extending already composed trait sets, by only adding to the
        # longest ones
        longest = max(len(x) for x in start_with)
        made_type = False

        for base in sorted(start_with):
            if len(base) != longest:
                continue

            # pick a type to join on which reduces the short-fall as much as
            # possible.
            shortfall = len(wanted_traits) - len(base)
            mix_in = None
            for other in sorted(stock_types.keys()):
                # skip mixes that will fail; this means that the type on the
                # list is a trait subset of 'base'
                mixed_traits = tuple(sorted(set(base) | set(other)))
                if mixed_traits in PROPERTY_TYPES:
                    continue

                this_shortfall = len(wanted_traits - (set(base) | set(other)))
                if this_shortfall < shortfall:
                    mix_in = other
                    mixed_in_product = mixed_traits
                    shortfall = this_shortfall
                    if shortfall == 0:
                        break

            if mix_in:
                base_type = PROPERTY_TYPES[base]
                other_type = PROPERTY_TYPES[other]
                new_name = _merge_camel_case_names(
                    base_type.__name__, other_type.__name__,
                )
                new_type = type(new_name, (base_type, other_type), {})
                stock_types[mixed_in_product] = new_type
                made_types.append(new_type)
                made_type = True

        if not made_type:
            raise exc.PropertyTypeMixinFailure(
                traitlist=repr(trait_set),
                newtypelist=", ".join(
                    "%r (%s)" % (x.traits, x.__name__) for x in made_types
                )
            )