0001"""
0002Implements Format objects, which represent different formats and
0003serializations. Concrete implementations instantiate ``Format``.
0004"""
0005
0006from paste.response import header_value
0007from httpencode.wrappers import LazySerialize, ReplacementInput, FileLengthWrapper, FileAppIterWrapper
0009from httpencode import api
0010
0011_doc_template = """\
0012Format object that produces %(type)s Python objects
0013from mimetypes: %(mimetypes)s"""
0014
0015class Format(object):
0016
0017 """
0018 Instances of this class represent some particular format. This is
0019 the entry point for both response and request handling for the
0020 format.
0021 """
0022
0023 def __init__(self, name, content_types, type, secure,
0024 load, dump_iter,
0025 want_unicode=False, choose_mimetype=None,
0026 doc=None):
0027 """
0028 name:
0029 A nice readable name for the format.
0030
0031 content_types:
0032 A list of mime types; the first one will be preferred.
0033
0034 type:
0035 A string giving some indication what kind of Python object
0036 this produces. 'python' is just a generic python object;
0037 other kinds of objects should use the module or class
0038 name.
0039
0040 load, dump_iter:
0041 load and dump_iter take two arguments -- either a file
0042 object or the Python object to be dumped, and then a
0043 content type.
0044
0045 want_unicode:
0046 if true, then load and dump produce/consume unicode
0047 objects
0048
0049 secure:
0050 if true, then this format can decode arbitrary strings without
0051 having to trust the source
0052
0053 choose_mimetype:
0054 if given, this takes a Python object, preferred content
0055 type, and header dict and returns the content type
0056
0057 doc:
0058 Any extra documentation you want associated with the
0059 object
0060 """
0061 self.name = name
0062 self.content_types = content_types
0063 self.type = type
0064 self.load = load
0065 self.dump_iter = dump_iter
0066 if choose_mimetype is not None:
0067 self.choose_mimetype = choose_mimetype
0068 self.want_unicode = want_unicode
0069 self.secure = secure
0070 if doc is None:
0071 doc = _doc_template % dict(
0072 type=type, mimetypes=', '.join(content_types))
0073 self.__doc__ = doc
0074
0075 def __repr__(self):
0076 return '<Format %s convert %s to type=%s>' % (
0077 self.name, ', '.join(self.content_types), self.type)
0078
0079 def choose_mimetype(self, request_headers, data):
0080
0081 return self.content_types[0]
0082
0083 def parse_request(self, environ, trusted=False):
0084 """
0085 Takes the WSGI environment and parses the request for this
0086 format, possibly using a shortcut to avoid decoding.
0087 """
0088 if not trusted and not self.secure:
0089 raise api.InsecureFormatError(
0090 "%r cannot parse untrusted data" % self)
0091 input = environ['wsgi.input']
0092 if 'CONTENT_LENGTH' in environ:
0093 length = int(environ['CONTENT_LENGTH'])
0094 else:
0095
0096
0097
0098
0099 length = None
0100 if hasattr(input, 'decoded') and input.decoded[0] == self.type:
0101 return input.decoded[1]
0102 else:
0103 content_type = api.get_mimetype_from_environ(environ)
0104 if hasattr(input, 'httpencode_dump_iter'):
0105 input = FileAppIterWrapper(
0106 input.httpencode_dump_iter(content_type))
0107 elif length:
0108 input = FileLengthWrapper(input, length)
0109 parsed = self.load(input, content_type)
0110 decoded = (self.type, parsed)
0111 environ['wsgi.input'] = ReplacementInput(
0112 self, decoded)
0113 return parsed
0114
0115 def parse_wsgi_response(self, status, headers, app_iter, trusted=False):
0116 """
0117 Parses the app_iter.
0118
0119 Note: does not call ``app_iter.close()``
0120 """
0121 if not trusted and not self.secure:
0122 raise api.InsecureFormatError(
0123 "%r cannot parse untrusted data" % self)
0124 if hasattr(app_iter, 'decoded'):
0125 type, data = app_iter.decoded
0126 if type == self.type:
0127 return data
0128 content_type = api.get_mimetype_from_headers(headers)
0129 input = FileAppIterWrapper(app_iter)
0130 data = self.load(input, content_type)
0131 return data
0132
0133 def parse_response(self, response):
0134 """
0135 Parses the HTTPResponse-like object
0136 """
0137 content_type = api.get_mimetype_from_response(response)
0138
0139 data = self.load(response, content_type)
0140 return data
0141
0142 def make_wsgi_input_length(self, body, headers, internal):
0143 content_type = header_value(headers, 'content-type')
0144 if internal:
0145 return LazySerialize(self, content_type, body), '1'
0146 else:
0147 data = ''.join(self.dump_iter(body, content_type))
0148 return StringIO(data), str(len(data))
0149
0150 def responder(self, data, content_type=None, headers=None):
0151 """
0152 Returns a WSGI application that serves the given data, with an
0153 optional explicit content_type and optional additional
0154 headers.
0155 """
0156 return RPCResponder(self, data, content_type, headers)
0157
0158class RPCResponder(object):
0159
0160 def __init__(self, format, data, content_type=None, headers=None):
0161 self.format = format
0162 self.data = data
0163 if headers is None:
0164 headers = []
0165 elif hasattr(headers, 'items'):
0166 headers = headers.items()
0167 else:
0168 headers = list(headers)
0169 header_content_type = header_value(headers, 'content-type')
0170 if (header_content_type is not None
0171 and content_type is not None
0172 and header_content_type != content_type):
0173 raise TypeError(
0174 "You've given an explicit header of Content-Type: "
0175 "%s and passed content_type=%r, which is ambiguous"
0176 % (header_content_type, content_type))
0177 if header_content_type is None:
0178 if content_type is None:
0179 content_type = format.content_types[0]
0180 headers.append(('Content-Type', content_type))
0181 elif content_type is None:
0182 content_type = header_content_type
0183 self.content_type = content_type
0184 self.headers = headers
0185
0186 def __call__(self, environ, start_response):
0187 start_response('200 OK', self.headers)
0188
0189 return LazySerialize(
0190 self.format, self.content_type, self.data)