diff options
Diffstat (limited to 'lib/subunit/python/testtools/content.py')
-rw-r--r-- | lib/subunit/python/testtools/content.py | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/lib/subunit/python/testtools/content.py b/lib/subunit/python/testtools/content.py new file mode 100644 index 0000000000..00c782347b --- /dev/null +++ b/lib/subunit/python/testtools/content.py @@ -0,0 +1,91 @@ +# Copyright (c) 2009 Jonathan M. Lange. See LICENSE for details. + +"""Content - a MIME-like Content object.""" + +import codecs +from unittest import TestResult + +from testtools.content_type import ContentType +from testtools.utils import _b + + +class Content(object): + """A MIME-like Content object. + + Content objects can be serialised to bytes using the iter_bytes method. + If the Content-Type is recognised by other code, they are welcome to + look for richer contents that mere byte serialisation - for example in + memory object graphs etc. However, such code MUST be prepared to receive + a generic Content object that has been reconstructed from a byte stream. + + :ivar content_type: The content type of this Content. + """ + + def __init__(self, content_type, get_bytes): + """Create a ContentType.""" + if None in (content_type, get_bytes): + raise ValueError("None not permitted in %r, %r" % ( + content_type, get_bytes)) + self.content_type = content_type + self._get_bytes = get_bytes + + def __eq__(self, other): + return (self.content_type == other.content_type and + ''.join(self.iter_bytes()) == ''.join(other.iter_bytes())) + + def iter_bytes(self): + """Iterate over bytestrings of the serialised content.""" + return self._get_bytes() + + def iter_text(self): + """Iterate over the text of the serialised content. + + This is only valid for text MIME types, and will use ISO-8859-1 if + no charset parameter is present in the MIME type. (This is somewhat + arbitrary, but consistent with RFC2617 3.7.1). + + :raises: ValueError If the content type is not text/*. + """ + if self.content_type.type != "text": + raise ValueError("Not a text type %r" % self.content_type) + return self._iter_text() + + def _iter_text(self): + """Worker for iter_text - does the decoding.""" + encoding = self.content_type.parameters.get('charset', 'ISO-8859-1') + try: + # 2.5+ + decoder = codecs.getincrementaldecoder(encoding)() + for bytes in self.iter_bytes(): + yield decoder.decode(bytes) + final = decoder.decode(_b(''), True) + if final: + yield final + except AttributeError: + # < 2.5 + bytes = ''.join(self.iter_bytes()) + yield bytes.decode(encoding) + + def __repr__(self): + return "<Content type=%r, value=%r>" % ( + self.content_type, ''.join(self.iter_bytes())) + + +class TracebackContent(Content): + """Content object for tracebacks. + + This adapts an exc_info tuple to the Content interface. + text/x-traceback;language=python is used for the mime type, in order to + provide room for other languages to format their tracebacks differently. + """ + + def __init__(self, err, test): + """Create a TracebackContent for err.""" + if err is None: + raise ValueError("err may not be None") + content_type = ContentType('text', 'x-traceback', + {"language": "python", "charset": "utf8"}) + self._result = TestResult() + value = self._result._exc_info_to_string(err, test) + super(TracebackContent, self).__init__( + content_type, lambda: [value.encode("utf8")]) |