def compose_schemas(*schemas):
    """
    Returns a single three-ple of the following for all provided schemas:
        - a map of attribute names to attributes for all related schema
        attributes
        - a set of names of all related required schema attributes
        - a set of names of all related default schema attributes
    For the sake of performance, values are written-to and returned-from the
    jsonattrs cache.
    """
    key = 'jsonattrs:compose:' + ','.join([str(s.pk) for s in schemas])
    cached = caches['jsonattrs'].get(key)
    if cached:
        s_attrs, required_attrs, default_attrs = cached
        # Deserialize attrs when retrieving from cache
        attrs = OrderedDict((k, Attribute(**v)) for k, v in s_attrs.items())
        return attrs, required_attrs, default_attrs

    # Extract schema attributes, names of required attributes and
    # names of attributes with defaults, composing schemas.
    schema_attrs = [
        s.attributes.select_related('attr_type').all() for s in schemas]
    attrs = OrderedDict()
    required_attrs = set()
    default_attrs = set()
    for attributes in schema_attrs:
        for attr in attributes:
            if attr.omit:
                if attr.name in attrs:
                    del attrs[attr.name]
            else:
                attrs[attr.name] = attr

    required_attrs = {n for n, a in attrs.items() if a.required}
    default_attrs = {n for n, a in attrs.items()
                     if a.default is not None and a.default != ''}

    # Serialize attrs to make it smaller in cache
    s_attrs = OrderedDict((k, v.to_dict()) for k, v in attrs.items())
    caches['jsonattrs'].set(key, (s_attrs, required_attrs, default_attrs))
    return attrs, required_attrs, default_attrs