310 lines
11 KiB
Python
310 lines
11 KiB
Python
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
# Licensed under the MIT License. See LICENSE in the project root
|
|
# for license information.
|
|
|
|
import sys
|
|
|
|
|
|
# Py3 compat - alias unicode to str, and xrange to range
|
|
try:
|
|
unicode # noqa
|
|
except NameError:
|
|
unicode = str
|
|
try:
|
|
xrange # noqa
|
|
except NameError:
|
|
xrange = range
|
|
|
|
|
|
class SafeRepr(object):
|
|
# String types are truncated to maxstring_outer when at the outer-
|
|
# most level, and truncated to maxstring_inner characters inside
|
|
# collections.
|
|
maxstring_outer = 2 ** 16
|
|
maxstring_inner = 30
|
|
if sys.version_info >= (3, 0):
|
|
string_types = (str, bytes)
|
|
set_info = (set, '{', '}', False)
|
|
frozenset_info = (frozenset, 'frozenset({', '})', False)
|
|
int_types = (int,)
|
|
long_iter_types = (list, tuple, bytearray, range,
|
|
dict, set, frozenset)
|
|
else:
|
|
string_types = (str, unicode)
|
|
set_info = (set, 'set([', '])', False)
|
|
frozenset_info = (frozenset, 'frozenset([', '])', False)
|
|
int_types = (int, long) # noqa
|
|
long_iter_types = (list, tuple, bytearray, xrange,
|
|
dict, set, frozenset, buffer) # noqa
|
|
|
|
# Collection types are recursively iterated for each limit in
|
|
# maxcollection.
|
|
maxcollection = (15, 10)
|
|
|
|
# Specifies type, prefix string, suffix string, and whether to include a
|
|
# comma if there is only one element. (Using a sequence rather than a
|
|
# mapping because we use isinstance() to determine the matching type.)
|
|
collection_types = [
|
|
(tuple, '(', ')', True),
|
|
(list, '[', ']', False),
|
|
frozenset_info,
|
|
set_info,
|
|
]
|
|
try:
|
|
from collections import deque
|
|
collection_types.append((deque, 'deque([', '])', False))
|
|
except Exception:
|
|
pass
|
|
|
|
# type, prefix string, suffix string, item prefix string,
|
|
# item key/value separator, item suffix string
|
|
dict_types = [(dict, '{', '}', '', ': ', '')]
|
|
try:
|
|
from collections import OrderedDict
|
|
dict_types.append((OrderedDict, 'OrderedDict([', '])', '(', ', ', ')'))
|
|
except Exception:
|
|
pass
|
|
|
|
# All other types are treated identically to strings, but using
|
|
# different limits.
|
|
maxother_outer = 2 ** 16
|
|
maxother_inner = 30
|
|
|
|
convert_to_hex = False
|
|
raw_value = False
|
|
|
|
def __call__(self, obj):
|
|
try:
|
|
return ''.join(self._repr(obj, 0))
|
|
except Exception:
|
|
try:
|
|
return 'An exception was raised: %r' % sys.exc_info()[1]
|
|
except Exception:
|
|
return 'An exception was raised'
|
|
|
|
def _repr(self, obj, level):
|
|
'''Returns an iterable of the parts in the final repr string.'''
|
|
|
|
try:
|
|
obj_repr = type(obj).__repr__
|
|
except Exception:
|
|
obj_repr = None
|
|
|
|
def has_obj_repr(t):
|
|
r = t.__repr__
|
|
try:
|
|
return obj_repr == r
|
|
except Exception:
|
|
return obj_repr is r
|
|
|
|
for t, prefix, suffix, comma in self.collection_types:
|
|
if isinstance(obj, t) and has_obj_repr(t):
|
|
return self._repr_iter(obj, level, prefix, suffix, comma)
|
|
|
|
for t, prefix, suffix, item_prefix, item_sep, item_suffix in self.dict_types: # noqa
|
|
if isinstance(obj, t) and has_obj_repr(t):
|
|
return self._repr_dict(obj, level, prefix, suffix,
|
|
item_prefix, item_sep, item_suffix)
|
|
|
|
for t in self.string_types:
|
|
if isinstance(obj, t) and has_obj_repr(t):
|
|
return self._repr_str(obj, level)
|
|
|
|
if self._is_long_iter(obj):
|
|
return self._repr_long_iter(obj)
|
|
|
|
return self._repr_other(obj, level)
|
|
|
|
# Determines whether an iterable exceeds the limits set in
|
|
# maxlimits, and is therefore unsafe to repr().
|
|
def _is_long_iter(self, obj, level=0):
|
|
try:
|
|
# Strings have their own limits (and do not nest). Because
|
|
# they don't have __iter__ in 2.x, this check goes before
|
|
# the next one.
|
|
if isinstance(obj, self.string_types):
|
|
return len(obj) > self.maxstring_inner
|
|
|
|
# If it's not an iterable (and not a string), it's fine.
|
|
if not hasattr(obj, '__iter__'):
|
|
return False
|
|
|
|
# If it's not an instance of these collection types then it
|
|
# is fine. Note: this is a fix for
|
|
# https://github.com/Microsoft/ptvsd/issues/406
|
|
if not isinstance(obj, self.long_iter_types):
|
|
return False
|
|
|
|
# Iterable is its own iterator - this is a one-off iterable
|
|
# like generator or enumerate(). We can't really count that,
|
|
# but repr() for these should not include any elements anyway,
|
|
# so we can treat it the same as non-iterables.
|
|
if obj is iter(obj):
|
|
return False
|
|
|
|
# xrange reprs fine regardless of length.
|
|
if isinstance(obj, xrange):
|
|
return False
|
|
|
|
# numpy and scipy collections (ndarray etc) have
|
|
# self-truncating repr, so they're always safe.
|
|
try:
|
|
module = type(obj).__module__.partition('.')[0]
|
|
if module in ('numpy', 'scipy'):
|
|
return False
|
|
except Exception:
|
|
pass
|
|
|
|
# Iterables that nest too deep are considered long.
|
|
if level >= len(self.maxcollection):
|
|
return True
|
|
|
|
# It is too long if the length exceeds the limit, or any
|
|
# of its elements are long iterables.
|
|
if hasattr(obj, '__len__'):
|
|
try:
|
|
size = len(obj)
|
|
except Exception:
|
|
size = None
|
|
if size is not None and size > self.maxcollection[level]:
|
|
return True
|
|
return any((self._is_long_iter(item, level + 1) for item in obj)) # noqa
|
|
return any(i > self.maxcollection[level] or self._is_long_iter(item, level + 1) for i, item in enumerate(obj)) # noqa
|
|
|
|
except Exception:
|
|
# If anything breaks, assume the worst case.
|
|
return True
|
|
|
|
def _repr_iter(self, obj, level, prefix, suffix,
|
|
comma_after_single_element=False):
|
|
yield prefix
|
|
|
|
if level >= len(self.maxcollection):
|
|
yield '...'
|
|
else:
|
|
count = self.maxcollection[level]
|
|
yield_comma = False
|
|
for item in obj:
|
|
if yield_comma:
|
|
yield ', '
|
|
yield_comma = True
|
|
|
|
count -= 1
|
|
if count <= 0:
|
|
yield '...'
|
|
break
|
|
|
|
for p in self._repr(item, 100 if item is obj else level + 1):
|
|
yield p
|
|
else:
|
|
if comma_after_single_element:
|
|
if count == self.maxcollection[level] - 1:
|
|
yield ','
|
|
yield suffix
|
|
|
|
def _repr_long_iter(self, obj):
|
|
try:
|
|
length = hex(len(obj)) if self.convert_to_hex else len(obj)
|
|
obj_repr = '<%s, len() = %s>' % (type(obj).__name__, length)
|
|
except Exception:
|
|
try:
|
|
obj_repr = '<' + type(obj).__name__ + '>'
|
|
except Exception:
|
|
obj_repr = '<no repr available for object>'
|
|
yield obj_repr
|
|
|
|
def _repr_dict(self, obj, level, prefix, suffix,
|
|
item_prefix, item_sep, item_suffix):
|
|
if not obj:
|
|
yield prefix + suffix
|
|
return
|
|
if level >= len(self.maxcollection):
|
|
yield prefix + '...' + suffix
|
|
return
|
|
|
|
yield prefix
|
|
|
|
count = self.maxcollection[level]
|
|
yield_comma = False
|
|
|
|
try:
|
|
sorted_keys = sorted(obj)
|
|
except Exception:
|
|
sorted_keys = list(obj)
|
|
|
|
for key in sorted_keys:
|
|
if yield_comma:
|
|
yield ', '
|
|
yield_comma = True
|
|
|
|
count -= 1
|
|
if count <= 0:
|
|
yield '...'
|
|
break
|
|
|
|
yield item_prefix
|
|
for p in self._repr(key, level + 1):
|
|
yield p
|
|
|
|
yield item_sep
|
|
|
|
try:
|
|
item = obj[key]
|
|
except Exception:
|
|
yield '<?>'
|
|
else:
|
|
for p in self._repr(item, 100 if item is obj else level + 1):
|
|
yield p
|
|
yield item_suffix
|
|
|
|
yield suffix
|
|
|
|
def _repr_str(self, obj, level):
|
|
return self._repr_obj(obj, level,
|
|
self.maxstring_inner, self.maxstring_outer)
|
|
|
|
def _repr_other(self, obj, level):
|
|
return self._repr_obj(obj, level,
|
|
self.maxother_inner, self.maxother_outer)
|
|
|
|
def _repr_obj(self, obj, level, limit_inner, limit_outer):
|
|
try:
|
|
if self.raw_value:
|
|
# For raw value retrieval, ignore all limits.
|
|
try:
|
|
mv = memoryview(obj)
|
|
except Exception:
|
|
yield unicode(obj)
|
|
return
|
|
else:
|
|
# Map bytes to Unicode codepoints with same values.
|
|
yield mv.tobytes().decode('latin-1')
|
|
return
|
|
elif self.convert_to_hex and isinstance(obj, self.int_types):
|
|
obj_repr = hex(obj)
|
|
else:
|
|
obj_repr = repr(obj)
|
|
except Exception:
|
|
try:
|
|
obj_repr = object.__repr__(obj)
|
|
except Exception:
|
|
try:
|
|
obj_repr = '<no repr available for ' + type(obj).__name__ + '>' # noqa
|
|
except Exception:
|
|
obj_repr = '<no repr available for object>'
|
|
|
|
limit = limit_inner if level > 0 else limit_outer
|
|
|
|
if limit >= len(obj_repr):
|
|
yield obj_repr
|
|
return
|
|
|
|
# Slightly imprecise calculations - we may end up with a string that is
|
|
# up to 3 characters longer than limit. If you need precise formatting,
|
|
# you are using the wrong class.
|
|
left_count, right_count = max(1, int(2 * limit / 3)), max(1, int(limit / 3)) # noqa
|
|
|
|
yield obj_repr[:left_count]
|
|
yield '...'
|
|
yield obj_repr[-right_count:]
|