Ticket #3332: mimeview_api_refactoring-r3612.diff
| File mimeview_api_refactoring-r3612.diff, 117.3 kB (added by cboos, 2 years ago) |
|---|
-
trac/attachment.py
26 26 from trac.config import BoolOption, IntOption 27 27 from trac.core import * 28 28 from trac.env import IEnvironmentSetupParticipant 29 from trac.mimeview import *29 from trac.mimeview.api import Mimeview, MimeType, FileMimeContent, TEXT_PLAIN 30 30 from trac.util import get_reporter_id, create_unique_file 31 31 from trac.util.datefmt import format_datetime, pretty_timedelta 32 32 from trac.util.html import Markup, html … … 124 124 def parent_href(self, req): 125 125 return req.href(self.parent_type, self.parent_id) 126 126 127 def _get_title(self): 127 def _get_title(self): # TODO: should extend to other `parent_type`s 128 128 return '%s%s: %s' % (self.parent_type == 'ticket' and '#' or '', 129 129 self.parent_id, self.filename) 130 130 title = property(_get_title) … … 245 245 select = classmethod(select) 246 246 delete_all = classmethod(delete_all) 247 247 248 def open(self): 248 def open(self): # deprecate? 249 249 self.env.log.debug('Trying to open attachment at %s', self.path) 250 250 try: 251 251 fd = open(self.path, 'rb') … … 523 523 'author': get_reporter_id(req)} 524 524 525 525 def _render_view(self, req, attachment): 526 # FIXME: perm_map should extend to other `parent_type`s 526 527 perm_map = {'ticket': 'TICKET_VIEW', 'wiki': 'WIKI_VIEW'} 527 528 req.perm.assert_permission(perm_map[attachment.parent_type]) 528 529 … … 533 534 req.hdf['attachment'] = attachment_to_hdf(self.env, req, None, 534 535 attachment) 535 536 # Override the 'oneliner' 536 req.hdf['attachment.description'] = wiki_to_html( attachment.description,537 self.env, req)537 req.hdf['attachment.description'] = wiki_to_html( 538 attachment.description, self.env, req) 538 539 539 540 perm_map = {'ticket': 'TICKET_ADMIN', 'wiki': 'WIKI_DELETE'} 540 541 if req.perm.has_permission(perm_map[attachment.parent_type]): 541 542 req.hdf['attachment.can_delete'] = 1 542 543 543 fd = attachment.open()544 try:545 mimeview = Mimeview(self.env)544 content = FileMimeContent(self.env, attachment.path, 545 url=attachment.href(req, format='raw'), 546 kind='Attachment') 546 547 547 # MIME type detection 548 str_data = fd.read(1000) 549 fd.seek(0) 550 551 binary = is_binary(str_data) 552 mime_type = mimeview.get_mimetype(attachment.filename, str_data) 548 # Eventually send the file directly 549 format = req.args.get('format') 550 if format in ('raw', 'txt'): 551 disposition = 'inline' 552 if self.render_unsafe_content: 553 if content.type.is_unknown or \ 554 (format == 'txt' and not content.is_binary): 555 # Force the content to be displayed as text 556 content.type = TEXT_PLAIN ### SHOULD BE ENOUGH 557 content.type = MimeType('text/plain', content.encoding) 558 elif not content.is_binary: 559 # Force browser to download HTML/SVG/etc pages that may 560 # contain malicious code enabling XSS aattacks 561 disposition = 'attachment' 562 content.send(req, disposition) 553 563 554 # Eventually send the file directly 555 format = req.args.get('format') 556 if format in ('raw', 'txt'): 557 if not self.render_unsafe_content and not binary: 558 # Force browser to download HTML/SVG/etc pages that may 559 # contain malicious code enabling XSS attacks 560 req.send_header('Content-Disposition', 'attachment;' + 561 'filename=' + attachment.filename) 562 if not mime_type or (self.render_unsafe_content and \ 563 not binary and format == 'txt'): 564 mime_type = 'text/plain' 565 if 'charset=' not in mime_type: 566 charset = mimeview.get_charset(str_data, mime_type) 567 mime_type = mime_type + '; charset=' + charset 568 req.send_file(attachment.path, mime_type) 564 # add ''Plain Text'' alternate link if needed 565 if self.render_unsafe_content and not \ 566 (content.is_binary or 567 content.type.is_unknown or 568 TEXT_PLAIN.match(content.type)): 569 add_link(req, 'alternate', attachment.href(req, format='txt'), 570 TEXT_PLAIN.name, TEXT_PLAIN.mimetype) 569 571 570 # add ''Plain Text'' alternate link if needed 571 if self.render_unsafe_content and not binary and \ 572 mime_type and not mime_type.startswith('text/plain'): 573 plaintext_href = attachment.href(req, format='txt') 574 add_link(req, 'alternate', plaintext_href, 'Plain Text', 575 mime_type) 572 # add ''Original Format'' alternate link (always) 573 add_link(req, 'alternate', content.url, 574 'Original Format', content.type.mimetype) 576 575 577 # add ''Original Format'' alternate link (always) 578 raw_href = attachment.href(req, format='raw') 579 add_link(req, 'alternate', raw_href, 'Original Format', mime_type) 576 req.hdf['attachment'] = Mimeview(self.env).preview_to_hdf( 577 req, content, annotations=['lineno']) 580 578 581 self.log.debug("Rendering preview of file %s with mime-type %s"582 % (attachment.filename, mime_type))583 584 req.hdf['attachment'] = mimeview.preview_to_hdf(585 req, fd, os.fstat(fd.fileno()).st_size, mime_type,586 attachment.filename, raw_href, annotations=['lineno'])587 finally:588 fd.close()589 590 579 def _render_list(self, req, p_type, p_id): 591 580 self._parent_to_hdf(req, p_type, p_id) 592 581 req.hdf['attachment'] = { -
trac/mimeview/rst.py
28 28 import re 29 29 30 30 from trac.core import * 31 from trac.mimeview.api import IHTMLPreviewRenderer , content_to_unicode31 from trac.mimeview.api import IHTMLPreviewRenderer 32 32 from trac.util.html import Element 33 33 from trac.web.href import Href 34 34 from trac.wiki.formatter import WikiProcessor … … 180 180 181 181 _inliner = rst.states.Inliner() 182 182 _parser = rst.Parser(inliner=_inliner) 183 content = content_to_unicode(self.env, content, mimetype)183 content = unicode(content) 184 184 parts = publish_parts(content, writer_name='html', parser=_parser, 185 185 settings_overrides={'halt_level': 6, 186 186 'file_insertion_enabled': 0, -
trac/mimeview/api.py
19 19 # Christian Boos <cboos@neuf.fr> 20 20 21 21 """ 22 The `trac.mimeview` module centralize the intelligence related to 23 file metadata, principally concerning the `type` (MIME type) of the content 24 and, if relevant, concerning the text encoding (charset) used by the content. 22 The `trac.mimeview` module centralizes the intelligence about typed content. 25 23 26 There are primarily two approaches for getting the MIME type of a given file: 27 * taking advantage of existing conventions for the file name 28 * examining the file content and applying various heuristics 24 Originally, this was about file metadata, principally its MIME type and 25 eventually the text encoding (charset) used by that content. 29 26 30 The module also knows how to convert the file content from one type 31 to another type. 27 Now, this has evolved into managing any kind of typed content, 28 and deals also with converting a content from one type to another. 29 A common situation which is now handled part of the general case 30 is the conversion of any kind of content to a text/html representation. 32 31 33 In some cases, only the `url` pointing to the file's content is actually 34 needed, that's why we avoid to read the file's content when it's not needed. 32 In order to keep the API of conversion interface IContentConverter simple, 33 we introduced a few classes, each encapsulating a part of the knowledge 34 related to the content and the conversion: 35 35 36 The actual `content` to be converted might be a `unicode` object, 37 but it can also be the raw byte string (`str`) object, or simply 38 an object that can be `read()`. 36 * the `ContentType` is used to describe the type of a content. 37 This is an abstract superclass for: 38 39 - `MimeType`, used for storing the mime type string, the charset, 40 and eventually the name and the commonly used file extension 41 for that type 42 43 - `ObjectType`, used when wrapping an arbitrary Python object 44 45 * the `AbstractContent`, which wraps access to the actual content, 46 and provides a few generic methods, among which `convert()` is 47 the most important. 48 49 - the `ObjectContent`, when the content is an arbitrary Python object 50 51 - the `MimeContent` abstract class, which offers an uniform API to 52 access the data over a wide range of containers: 53 * the `StringMimeContent`, for handling string content 54 (like `str`, `unicode` or `Markup` objects) 55 * the `StructuredMimeContent`, for handling structured content 56 (like `Element` objects) 57 * the `LineIteratorMimeContent`, for handling line-oriented string 58 content (like string iterators or string arrays) 59 * the `FileMimeContent`, for handling file content 60 * etc. (as an example, the repository layer defines a `NodeMimeContent`) 61 62 That API provides different ways to get access to the data: 63 * chunks(), an iterator for reading chunks of raw content 64 * lines(), an iterator for reading lines from text content 65 * markup(), when it makes sense to interpret the content as markup 66 * `__unicode()__`, for converting the content to an `unicode` object 67 * encode(), for converting the content to a `str` object. Note that 68 we don't use `__str__` for that on purpose, as we need to be able 69 to specify the charset. 70 * size(), for getting a hint about the content size without actually 71 reading it. 72 73 * the `Conversion` class, which is used to specify how a given 74 `IContentConverter` component will perform a content conversion. 39 75 """ 40 76 77 import os 41 78 import re 42 79 from StringIO import StringIO 43 80 44 81 from trac.config import IntOption, ListOption, Option 45 82 from trac.core import * 46 83 from trac.util import sorted 47 from trac.util.text import to_utf8, to_unicode 84 from trac.util.datefmt import http_date 85 from trac.util.text import to_utf8, to_unicode, IDENTITY_CHARSET 48 86 from trac.util.html import escape, Markup, Fragment, html 49 87 50 88 51 __all__ = ['get_mimetype', 'is_binary', 'detect_unicode', 'Mimeview', 52 'content_to_unicode'] 89 __all__ = ['get_mimetype', 'is_binary', 'detect_unicode', 90 'IContentConverter', 'IHTMLPreviewAnnotator', 91 'IHTMLPreviewRenderer', # deprecated 92 'ObjectType', 'MimeType', 93 'ObjectContent', 94 'MimeContent', 'FileMimeContent', 'StringMimeContent', 95 'StructuredMimeContent', 'LineIteratorMimeContent', 96 'Mimeview', 'Conversion', 97 'TEXT_PLAIN', 'TEXT_HTML', 'TEXT_CSV', 'TEXT_TSV', 98 'APPLICATION_RSS_XML', 'APPLICATION_OCTET_STREAM'] 53 99 54 100 55 101 # Some common MIME types and their associated keywords and/or file extensions 56 102 103 APPLICATION_OCTET_STREAM_STR = 'application/octet-stream' 104 57 105 KNOWN_MIME_TYPES = { 58 106 'application/pdf': ['pdf'], 59 107 'application/postscript': ['ps'], … … 114 162 for e in exts: 115 163 MIME_MAP[e] = t 116 164 117 # Simple builtin autodetection from the content using a regexp118 MODE_RE = re.compile(119 r"#!(?:[/\w.-_]+/)?(\w+)|" # look for shebang120 r"-\*-\s*(?:mode:\s*)?([\w+-]+)\s*-\*-|" # look for Emacs' -*- mode -*-121 r"vim:.*?syntax=(\w+)" # look for VIM's syntax=<n>122 )123 165 124 def get_mimetype(filename, content=None, mime_map=MIME_MAP): 125 """Guess the most probable MIME type of a file with the given name. 166 # -- get_mimetype, is_binary, detect_unicode: a few functions for dealing 167 # with MIME types, binary and text content in a simple way 126 168 169 def get_mimetype_from_filename(filename, mime_map=MIME_MAP): 170 """Guess the most probable MIME type of file with the given `filename`. 171 127 172 `filename` is either a filename (the lookup will then use the suffix) 128 173 or some arbitrary keyword. 129 130 `content` is either a `str` or an `unicode` string. 174 `mime_map` maps keywords to MIME types. 175 176 Return the MIME type as a string, or `None` if nothing was detected. 131 177 """ 132 178 suffix = filename.split('.')[-1] 133 179 if suffix in mime_map: … … 141 187 mimetype = mimetypes.guess_type(filename)[0] 142 188 except: 143 189 pass 144 if not mimetype and content:145 match = re.search(MODE_RE, content[:1000])146 if match:147 mode = match.group(1) or match.group(3) or \148 match.group(2).lower()149 if mode in mime_map:150 # 3) mimetype from the content, using the `MODE_RE`151 return mime_map[mode]152 else:153 if is_binary(content):154 # 4) mimetype from the content, using`is_binary`155 return 'application/octet-stream'156 190 return mimetype 157 191 192 # Simple builtin autodetection from the content using a regexp 193 MODE_RE = re.compile( 194 # look for /usr/bin/prog1 or /usr/bin/env prog2 195 r"#!(?:[/\w.-_]+/)?(?P<prog1>\w+)(?:\s+(?P<prog2>\w+))?|" 196 # look for Emacs' -*- mode -*- 197 r"-\*-\s*(?:mode:\s*)?(?P<emacsmode>[\w+-]+)\s*-\*-|" 198 # look for VIM's syntax=<n> 199 r"vim:.*?syntax=(?P<vimsyntax>\w+)" 200 ) 201 202 def get_mimetype_from_content(content, mime_map=MIME_MAP): 203 """Guess the most probable MIME type of file with the given `filename`. 204 205 `content` is either a `str` or an `unicode` string 206 207 Return the MIME type as a string, or `None` if not detected. 208 """ 209 match = re.search(MODE_RE, content[:1000]) 210 if match: 211 mode = match.group('prog1') 212 if mode and mode == 'env': 213 mode = match.group('prog2') 214 if not mode: 215 mode = match.group('vimsyntax') or \ 216 match.group('emacsmode').lower() 217 if mode in mime_map: 218 # 3) mimetype from the content, using the `MODE_RE` 219 return mime_map[mode] 220 else: 221 if is_binary(content): 222 # 4) mimetype from the content, using `is_binary` 223 return APPLICATION_OCTET_STREAM_STR 224 225 def get_mimetype(filename, content=None, mime_map=MIME_MAP): 226 """Auto-detect MIME type either from the `filename` or from the `content`. 227 """ 228 mimetype = get_mimetype_from_filename(filename, mime_map) 229 if not mimetype and content: 230 mimetype = get_mimetype_from_content(content, mime_map) 231 return mimetype 232 158 233 def is_binary(data): 159 234 """Detect binary content by checking the first thousand bytes for zeroes. 160 235 … … 165 240 return '\0' in data[:1000] 166 241 167 242 def detect_unicode(data): 168 """Detect different unicode charsets by looking for B OMs (Byte Order Marks).243 """Detect different unicode charsets by looking for Byte Order Marks. 169 244 170 245 Operate obviously only on `str` objects. 171 246 """ … … 178 253 else: 179 254 return None 180 255 181 def content_to_unicode(env, content, mimetype):182 """Retrieve an `unicode` object from a `content` to be previewed"""183 mimeview = Mimeview(env)184 if hasattr(content, 'read'):185 content = content.read(mimeview.max_preview_size)186 return mimeview.to_unicode(content, mimetype)187 256 257 # -- ContentType and subclasses 188 258 189 class IHTMLPreviewRenderer(Interface): 190 """Extension point interface for components that add HTML renderers of 191 specific content types to the `Mimeview` component. 259 class ContentType(object): 260 """Abstract representation of the "type" of some content.""" 192 261 193 (Deprecated) 262 def match(self, other): 263 """Compare this instance with another `ContentType` instance. 264 265 Return True if `other` can be say to ''match'' our type. 266 """ 267 raise NotImplementedError 268 269 is_binary = property(lambda x: x._is_binary()) 270 271 mimetype = property(lambda x: x._get_mimetype(), 272 doc="MIME Type string (without charset information)") 273 274 name = 'unknown' 275 276 277 class ObjectType(ContentType): 278 """Represent the "type" of a content by using a Python class. 279 280 The corresponding content is then expected to be an `ObjectContent`, 281 wrapping a Python instance of that class. 194 282 """ 195 283 196 # implementing classes should set this property to True if they 197 # support text content where Trac should expand tabs into spaces 198 expand_tabs = False 284 def __init__(self, obj): 285 self._class = isinstance(obj, type) and obj or obj.__class__ 286 self._mimetype = self._charset = None 287 self.name = self._class.__name__ 199 288 200 def get_quality_ratio(mimetype): 201 """Return the level of support this renderer provides for the `content` 202 of the specified MIME type. The return value must be a number between 203 0 and 9, where 0 means no support and 9 means "perfect" support. 289 def __repr__(self): 290 return "<ObjectType %s>" % self.name 291 292 # Reimplemented methods 293 294 def _is_binary(self): return True 295 def _get_mimetype(self): return None 296 # or return 'application/x-python-'+self.name ? 297 298 def match(self, other, regexp=False): 299 other_class = isinstance(other, type) and other or \ 300 isinstance(other, ObjectType) and other._class or \ 301 other.__class__ 302 return other_class == self._class 303 304 305 class MimeType(ContentType): 306 """Represent the "type" of a content by using a MIME type string. 307 308 If the type is not binary (i.e. is some kind of text), the `charset` 309 information can also be given, when creating the instance. 310 311 A MIME type has a `name` and has an `extension` that can be used 312 for storing the converted data in a file. 313 314 All the properties of this class are read-only. 315 """ 316 317 def __init__(self, mimetype, charset=None, name=None, extension=None, 318 env=None): 319 """Create a MimeType based on a `mimetype` string. 320 321 That string can eventually contain a `charset=...`, which will be 322 retained as the charset for this instance, unless there's an 323 explicit `charset` parameter given. 324 325 Another possibility is to give a pattern object for the `mimetype` 326 argument, in which case this instance can be used to do pattern 327 matching when using `match()`. 328 329 When optional `env` parameter is given, additional knowledge is 330 used for determining whether this type is binary or not. 204 331 """ 332 self.env = env 333 self._mimetype = mimetype 334 # determine charset 335 self._charset = charset 336 if not self._charset and isinstance(self._mimetype, basestring): 337 sep_idx = mimetype.find(';') 338 if sep_idx >= 0: 339 self._mimetype = mimetype[:sep_idx].strip() 340 charset_idx = mimetype.find('charset=', sep_idx) 341 if charset_idx >= 0: 342 self._charset = mimetype[charset_idx+8:].strip() 343 if extension and not extension.startswith('.'): 344 extension = '.' + extension 345 self._extension = extension 346 self._name = name 205 347 206 def render(req, mimetype, content, filename=None, url=None):207 """Render an XHTML preview of the raw `content`.348 def __repr__(self): 349 return '<MimeType "%s">' % self.mimetype_charset 208 350 209 The `content` might be: 210 * a `str` object 211 * an `unicode` string 212 * any object with a `read` method, returning one of the above 351 def _get_extension(self): 352 if self._extension is None: 353 self._extension = KNOWN_MIME_TYPES.get(self.mimetype) ### use env 354 if not self._extension: 355 detail = self.mimetype.split('/', 1)[1] 356 if detail.startswith('x-'): 357 detail = detail[2:] 358 self._extension = '.' + detail 359 return self._extension 213 360 214 It is assumed that the content will correspond to the given `mimetype`. 361 def _get_mimetype_charset(self): 362 """Combine in a single string the MIME type and charset information.""" 363 if self._mimetype and self._charset: 364 return '%s; charset=%s' % (self.mimetype, self.charset) 365 else: 366 return self.mimetype ### or default to utf-8 if not binary? 367 368 charset = property(lambda x: x._charset, 369 doc="Charset information if available") 215 370 216 Besides the `content` value, the same content may eventually 217 be available through the `filename` or `url` parameters. 218 This is useful for renderers that embed objects, using <object> or 219 <img> instead of including the content inline. 371 name = property(lambda x: x._name or x._get_extension()) 372 extension = property(lambda x: x._get_extension()) 373 mimetype_charset = property(lambda x: x._get_mimetype_charset(), 374 doc="MIME Type plus charset information") 375 376 is_unknown = property(lambda x: x._mimetype is None) 377 378 # Reimplemented methods 379 380 def _is_binary(self): 381 return (self._mimetype == APPLICATION_OCTET_STREAM_STR or 382 (self.env and self.mimetype in 383 Mimeview(self.env).treat_as_binary) or 384 self.is_unknown) 385 386 def _get_mimetype(self): 387 return self._mimetype or APPLICATION_OCTET_STREAM_STR 388 389 def match(self, other): 390 if isinstance(other, MimeType): 391 other = other.mimetype 392 if not isinstance(other, basestring): 393 return False 394 if hasattr(self.mimetype, 'match'): 395 return self.mimetype.match(other) 396 else: 397 return self.mimetype == other 398 399 400 # A few commonly used MIME types: 401 402 TEXT_PLAIN = MimeType('text/plain', 'utf-8', 'Plain Text', 'txt') 403 TEXT_HTML = MimeType('text/html', 'utf-8', 'HTML', 'html') 404 TEXT_CSV = MimeType('text/csv', 'utf-8', 'Comma-delimited Text', 'csv') 405 TEXT_TSV = MimeType('text/tab-separated-values', 'utf-8', 406 'Tab-delimited Text', 'tsv') 407 408 APPLICATION_RSS_XML = MimeType('application/rss+xml', 'utf-8', 409 'RSS Feed', 'xml') 410 APPLICATION_OCTET_STREAM = MimeType(APPLICATION_OCTET_STREAM_STR, 411 IDENTITY_CHARSET, 412 'Undefined (binary)', 'bin') 413 414 415 # -- AbstractContent and subclasses 416 417 class AbstractContent(object): 418 """An abstract content, with an associated content type. 419 420 There can also be a `filename` eventually associated to this content. 421 """ 422 423 def __init__(self, env, content, type, filename=None): 424 self.env = env 425 self.content = content 426 self._type = type 427 self.filename = filename 428 429 def _is_binary(self): raise NotImplementedError 430 431 def _get_type(self): return self._type 432 def _set_type(self, type): self._type = type 433 434 # Properties 435 436 type = property(fget=lambda x: x._get_type(), 437 fset=lambda x, y: x._set_type(y), doc= 438 """The corresponding `ContentType` for this content.""") 439 440 is_binary = property(lambda x: x._is_binary(), doc= 441 "True if content should be considered to be binary") 442 443 # Methods 444 445 def basename(self): 446 """Return a possible file basename for this content.""" 447 return self.filename and os.path.splitext(self.filename)[0] or '' 448 449 def convert(self, req, format=None, typespec=None, context=None): 450 """Convert this content to a `MimeContent`. 451 452 The converter can be selected by either: 220 453 221 Can return the generated XHTML text as a single string or as an 222 iterable that yields strings. In the latter case, the list will 223 be considered to correspond to lines of text in the original content. 454 - specifying the desired output `format`, which must match the 455 `format` of one of the `Conversion` object returned by 456 `Mimeview.get_conversions(self.type)`. 457 There's usually only one such converter. 458 459 - giving a `typespec`, which is a `MimeType` object. 460 All matching converters are tried in turn, until one succeeds. 461 This is quite flexible, as one can use a regexp match ### FIXME 462 463 Either succeeds or raise `NoConversion` error. 224 464 """ 465 if not typespec and not format: 466 self.env.log.error("Convert called but no conversion specified.") 467 return self 468 if isinstance(format, MimeType): # or signal a programming error? 469 typespec = format 470 format = None 225 471 472 # Get all available conversions for our `type`, and keep those 473 # which are matching either the `format` or the `typespec`. 474 mimeview = Mimeview(self.env) 475 candidates = [] 476 for conversion, converter in mimeview.get_converters(self.type): 477 if conversion.format == format or \ 478 (typespec and typespec.match(conversion.output)): 479 candidates.append((conversion, converter)) 480 if not candidates: 481 raise NoConversion('No available MIME conversions', 482 self.type, typespec, format) 483 484 tab_expanded = None # we don't want to expand tabs more than once. 485 486 # First candidate which converts successfully wins. 487 self.env.log.debug('Converting %s' % repr(self)) 488 if not context: 489 class ToplevelContext(object): 490 def __init__(self, req): 491 self.req = req 492 context = ToplevelContext(req) 493 content = self 494 for conversion, converter in candidates: 495 self.env.log.debug('Trying converter %s using %s' % 496 (repr(converter), repr(conversion))) 497 if conversion.expand_tabs and not tab_expanded: 498 expanded = unicode(content).expandtabs(mimeview.tab_width) 499 content = StringMimeContent(self.env, expanded, content.type, 500 filename=content.filename) 501 self.env.log.debug('(tab expansion performed)') 502 try: 503 result = converter.convert_content(context, conversion, content) 504 if result is not None: 505 # Check if the conversion is really valid, by trying to 506 # access an excerpt of the result. This might trigger 507 # errors with legacy renderers returning an iterator... 508 check_result = result.excerpt 509 return result 510 except Exception, e: 511 self.env.log.warning('MIME conversion using %s failed (%s)' 512 % (repr(converter), e), exc_info=True) 513 raise NoConversion('No MIME conversions succeeded', 514 content.type, typespec, format) 515 516 517 class ObjectContent(AbstractContent): 518 """Content is a Python a object, and its type is an `ObjectType`. 519 520 Only supports the bare minimum of the `AbstractContent` methods. 521 `filename` in this case will be the suggested base name to be used 522 when creating a filename for a converted content. 523 """ 524 525 def __init__(self, env, obj, filename=None): 526 AbstractContent.__init__(self, env, obj, ObjectType(obj), 527 filename=filename) 528 529 # Reimplemented property readers 530 531 def _is_binary(self): 532 return True 533 534 def basename(self): 535 return AbstractContent.basename(self) or self.type.name 536 537 538 c
