testtools: Import latest upstream.
[kai/samba-autobuild/.git] / lib / testtools / testtools / content.py
1 # Copyright (c) 2009 Jonathan M. Lange. See LICENSE for details.
2
3 """Content - a MIME-like Content object."""
4
5 import codecs
6
7 from testtools.compat import _b
8 from testtools.content_type import ContentType
9 from testtools.testresult import TestResult
10
11
12 class Content(object):
13     """A MIME-like Content object.
14
15     Content objects can be serialised to bytes using the iter_bytes method.
16     If the Content-Type is recognised by other code, they are welcome to
17     look for richer contents that mere byte serialisation - for example in
18     memory object graphs etc. However, such code MUST be prepared to receive
19     a generic Content object that has been reconstructed from a byte stream.
20
21     :ivar content_type: The content type of this Content.
22     """
23
24     def __init__(self, content_type, get_bytes):
25         """Create a ContentType."""
26         if None in (content_type, get_bytes):
27             raise ValueError("None not permitted in %r, %r" % (
28                 content_type, get_bytes))
29         self.content_type = content_type
30         self._get_bytes = get_bytes
31
32     def __eq__(self, other):
33         return (self.content_type == other.content_type and
34             ''.join(self.iter_bytes()) == ''.join(other.iter_bytes()))
35
36     def iter_bytes(self):
37         """Iterate over bytestrings of the serialised content."""
38         return self._get_bytes()
39
40     def iter_text(self):
41         """Iterate over the text of the serialised content.
42
43         This is only valid for text MIME types, and will use ISO-8859-1 if
44         no charset parameter is present in the MIME type. (This is somewhat
45         arbitrary, but consistent with RFC2617 3.7.1).
46
47         :raises ValueError: If the content type is not text/\*.
48         """
49         if self.content_type.type != "text":
50             raise ValueError("Not a text type %r" % self.content_type)
51         return self._iter_text()
52
53     def _iter_text(self):
54         """Worker for iter_text - does the decoding."""
55         encoding = self.content_type.parameters.get('charset', 'ISO-8859-1')
56         try:
57             # 2.5+
58             decoder = codecs.getincrementaldecoder(encoding)()
59             for bytes in self.iter_bytes():
60                 yield decoder.decode(bytes)
61             final = decoder.decode(_b(''), True)
62             if final:
63                 yield final
64         except AttributeError:
65             # < 2.5
66             bytes = ''.join(self.iter_bytes())
67             yield bytes.decode(encoding)
68
69     def __repr__(self):
70         return "<Content type=%r, value=%r>" % (
71             self.content_type, ''.join(self.iter_bytes()))
72
73
74 class TracebackContent(Content):
75     """Content object for tracebacks.
76
77     This adapts an exc_info tuple to the Content interface.
78     text/x-traceback;language=python is used for the mime type, in order to
79     provide room for other languages to format their tracebacks differently.
80     """
81
82     def __init__(self, err, test):
83         """Create a TracebackContent for err."""
84         if err is None:
85             raise ValueError("err may not be None")
86         content_type = ContentType('text', 'x-traceback',
87             {"language": "python", "charset": "utf8"})
88         self._result = TestResult()
89         value = self._result._exc_info_to_unicode(err, test)
90         super(TracebackContent, self).__init__(
91             content_type, lambda: [value.encode("utf8")])