0001"""
0002Finds formats given criteria or name.
0003"""
0004
0005import pkg_resources
0006
0007class NoFormatError(LookupError):
0008    """
0009    Raised when no matching format can be found
0010    """
0011
0012__all__ = ['find_format_match', 'get_format',
0013           'NoFormatError']
0014
0015
0016# Dict of {format_name: (dist, ep_name)}
0017_format_names = {}
0018# Dict of {mimetype: {type: (dist, ep_name)}}
0019_format_mimetypes = {}
0020# Dict of {type: {mimetype: (dist, ep_name)}}
0021_format_types = {}
0022
0023def find_format_match(type, mimetype):
0024    """
0025    Find a format object that can accept the given mimetype and produce the
0026    given type.
0027
0028    If ``debug`` is true, then information will be logged about how a
0029    format is selected.
0030    """
0031    assert type
0032    assert mimetype
0033    if ';' in mimetype:
0034        mimetype = mimetype.split(';', 1)[0]
0035    try:
0036        value = _format_mimetypes[mimetype][type]
0037    except KeyError:
0038        raise NoFormatError(
0039            "No format found providing %s to %s"
0040            % (mimetype, type))
0041    else:
0042        return _load_ep(value)
0043
0044def find_format_accept(type, accept_list):
0045    """
0046    Find a format that converts the given type to one of the provided
0047    mimetypes in accept_list, choosing whatever the first item in the
0048    list is.  Returns (format, content_type)
0049
0050    If force_load is true, then we'll scan for items regardless of whether
0051    they are loaded.
0052    """
0053    assert accept_list, ("Empty accept list passed in")
0054    # First we'll try to see if the best match is loaded, in which case
0055    # we don't have to look further
0056    for mimetype in accept_list:
0057        try:
0058            value = _format_mimetypes[mimetype][type]
0059        except KeyError:
0060            pass
0061        else:
0062            return _load_ep(value), mimetype
0063    raise NoFormatError(
0064        "No format available to convert any of %s to %s"
0065        % (accept_list, type))
0066
0067def find_accept_for_type(type):
0068    """
0069    Return a list of all the mimetypes we know how to convert into the
0070    given Python type
0071    """
0072    return _format_types.get(type, {}).keys()
0073
0074def get_format(name):
0075    """
0076    Gets a format object by name
0077
0078    Formats are named by having an entry point in [httpencode.format]
0079    named ``name <name>``
0080    """
0081    try:
0082        value = _format_names[name]
0083    except KeyError:
0084        raise NoFormatError(
0085            "No format found by the name %r" % name)
0086    else:
0087        return _load_ep(value)
0088
0089def find_format_by_type(type, mimetypes):
0090    """
0091    Finds a format by its type, prefering the mimetypes given (in
0092    order).
0093
0094    ``'*'`` can be used as a final mimetype, but if that is ambiguous
0095    it is an error.  (That is, if there are different formats that
0096    work with that Python type).
0097    """
0098    if isinstance(mimetypes, basestring):
0099        raise TypeError(
0100            "mimetypes must be a list (not %r)" % mimetypes)
0101    possible = _format_types.get(type)
0102    if not possible:
0103        raise NoFormatError(
0104            "No formats available for type %r" % type)
0105    for mimetype in mimetypes:
0106        if mimetype == '*':
0107            found = None
0108            for data in possible.values():
0109                if found is None:
0110                    found = data
0111                elif found != data:
0112                    raise TypeError(
0113                        "There are multiple formats that can serialize "
0114                        "the type %r (at least %r and %r)"
0115                        % (type, found, data))
0116            # They are all the same format, or there is only one
0117            # format
0118            return _load_ep(found)
0119        if mimetype in possible:
0120            return _load_ep(possible[mimetype])
0121    raise NoFormatError(
0122        "There is not format that serializes the type %r "
0123        "and produces any of the mimetypes %r"
0124        % (type, mimetypes))
0125
0126def _load_ep(data):
0127    # Loads data=(dist_name, ep_name)
0128    dist = pkg_resources.get_distribution(data[0])
0129    return dist.load_entry_point(
0130        'httpencode.format', data[1])
0131
0132def _dist_activated(dist):
0133    entries = dist.get_entry_map('httpencode.format')
0134    for name in entries:
0135        data = (dist.key, name)
0136        if name.startswith('name '):
0137            format_name = name[5:].strip()
0138            if _format_names.get(format_name, data) != data:
0139                raise pkg_resources.VersionConflict(
0140                    "Distribution %r has identical format name (%r) as distribution %r"
0141                    % (dist, format_name, _format_names[format_nmame][0]))
0142            _format_names[format_name] = data
0143            continue
0144        parts = name.split()
0145        if len(parts) != 3 or parts[1] != 'to':
0146            warnings.warn(
0147                'Entry point [httpencode.format] %r in distribution '
0148                '%r is not a valid format'
0149                % (name, dist))
0150            continue
0151        mimetype, type = parts[0], parts[2]
0152        mdict = _format_mimetypes.setdefault(mimetype, {})
0153        if mdict.get(type, data) != data:
0154            raise pkg_resources.VersionConflict(
0155                "Distribution %r has an identical conversion (%r) as distribution %r"
0156                % (dist, name, mdict[type][0]))
0157        mdict[type] = data
0158        tdict = _format_types.setdefault(type, {})
0159        # If mdict didn't have a dup, this shouldn't
0160        # either
0161        assert tdict.get(mimetype, data) == data
0162        tdict[mimetype] = data
0163
0164# This calls dist_activated for all existing and future distributions
0165pkg_resources.add_activation_listener(_dist_activated)