docs: 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
                  )
              )