import threading
from contextlib import contextmanager
_tls = threading.local()
@contextmanager
def _nested():
_tls.level = getattr(_tls, "level", 0) + 1
try:
yield " " * _tls.level
finally:
_tls.level -= 1
@contextmanager
def _recursion_lock(obj):
if not hasattr(_tls, "history"):
_tls.history = [] # can't use set(), not all objects are hashable
if obj in _tls.history:
yield True
return
_tls.history.append(obj)
try:
yield False
finally:
_tls.history.pop(-1)
def humanize(cls):
def __repr__(self):
if getattr(_tls, "level", 0) > 0:
return str(self)
else:
attrs = ", ".join("%s = %r" % (k, v) for k, v in self.__dict__.items())
return "%s(%s)" % (self.__class__.__name__, attrs)
def __str__(self):
with _recursion_lock(self) as locked:
if locked:
return "<...>"
with _nested() as indent:
attrs = []
for k, v in self.__dict__.items():
if k.startswith("_"):
continue
if isinstance(v, (list, tuple)) and v:
attrs.append("%s%s = [" % (indent, k))
with _nested() as indent2:
for item in v:
attrs.append("%s%r," % (indent2, item))
attrs.append("%s]" % (indent,))
elif isinstance(v, dict) and v:
attrs.append("%s%s = {" % (indent, k))
with _nested() as indent2:
for k2, v2 in v.items():
attrs.append("%s%r: %r," % (indent2, k2, v2))
attrs.append("%s}" % (indent,))
else:
attrs.append("%s%s = %r" % (indent, k, v))
if not attrs:
return "%s()" % (self.__class__.__name__,)
else:
return "%s:\n%s" % (self.__class__.__name__, "\n".join(attrs))
cls.__repr__ = __repr__
cls.__str__ = __str__
return cls