🔴 {self}

Using plain {self} in the docstring will cause a recursion. As well as something like this:

examples/recursion.py

import sys

from documented import DocumentedError

sys.setrecursionlimit(30)


class Recursion(DocumentedError):
    """
    This exception is a bugger.

    It will crash when rendering {self.recursive_property}.
    """

    @property
    def recursive_property(self):
        return self


raise Recursion()

python

Traceback (most recent call last):
  File "📂/recursion.py", line 20, in <module>
    raise Recursion()
RecursionCould not print a Recursion object.
Traceback (most recent call last):
  File "/home/runner/work/documented/documented/documented/documented.py", line 22, in __str__
    return template.format(self=self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/work/documented/documented/documented/documented.py", line 13, in __str__
    template = self.__docstring_template()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/work/documented/documented/documented/documented.py", line 32, in __docstring_template
    return textwrap.dedent(
           ^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/textwrap.py", line 466, in dedent
    text = re.sub(r'(?m)^' + margin, '', text)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/re/__init__.py", line 185, in sub
    return _compile(pattern, flags).sub(repl, string, count)
           ^^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/runner/work/documented/documented/documented/documented.py", line 22, in __str__
    return template.format(self=self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/work/documented/documented/documented/documented.py", line 25, in __str__
    logger.exception(
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1524, in exception
    self.error(msg, *args, exc_info=exc_info, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1518, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1632, in _log
    record = self.makeRecord(self.name, level, fn, lno, msg, args,
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1601, in makeRecord
    rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 322, in __init__
    self.levelname = getLevelName(level)
                     ^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 142, in getLevelName
    result = _levelToName.get(level)
             ^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded while calling a Python object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/runner/work/documented/documented/documented/documented.py", line 22, in __str__
    return template.format(self=self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/work/documented/documented/documented/documented.py", line 25, in __str__
    logger.exception(
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1524, in exception
    self.error(msg, *args, exc_info=exc_info, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1518, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1634, in _log
    self.handle(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1644, in handle
    self.callHandlers(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1714, in callHandlers
    lastResort.handle(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 978, in handle
    self.emit(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1110, in emit
    msg = self.format(record)
          ^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 953, in format
    return fmt.format(record)
           ^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 688, in format
    if self.usesTime():
       ^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 656, in usesTime
    return self._style.usesTime()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 433, in usesTime
    return self._fmt.find(self.asctime_search) >= 0
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded while calling a Python object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/runner/work/documented/documented/documented/documented.py", line 22, in __str__
    return template.format(self=self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/work/documented/documented/documented/documented.py", line 25, in __str__
    logger.exception(
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1524, in exception
    self.error(msg, *args, exc_info=exc_info, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1518, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1634, in _log
    self.handle(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1644, in handle
    self.callHandlers(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1714, in callHandlers
    lastResort.handle(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 978, in handle
    self.emit(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1110, in emit
    msg = self.format(record)
          ^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 953, in format
    return fmt.format(record)
           ^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 695, in format
    record.exc_text = self.formatException(record.exc_info)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 645, in formatException
    traceback.print_exception(ei[0], ei[1], tb, None, sio)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 124, in print_exception
    te = TracebackException(type(value), value, tb, limit=limit, compact=True)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 702, in __init__
    self.stack = StackSummary._extract_from_extended_frame_gen(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 405, in _extract_from_extended_frame_gen
    limit = getattr(sys, 'tracebacklimit', None)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded while calling a Python object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/runner/work/documented/documented/documented/documented.py", line 22, in __str__
    return template.format(self=self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/work/documented/documented/documented/documented.py", line 25, in __str__
    logger.exception(
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1524, in exception
    self.error(msg, *args, exc_info=exc_info, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1518, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1634, in _log
    self.handle(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1644, in handle
    self.callHandlers(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1714, in callHandlers
    lastResort.handle(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 978, in handle
    self.emit(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1110, in emit
    msg = self.format(record)
          ^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 953, in format
    return fmt.format(record)
           ^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 695, in format
    record.exc_text = self.formatException(record.exc_info)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 645, in formatException
    traceback.print_exception(ei[0], ei[1], tb, None, sio)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 124, in print_exception
    te = TracebackException(type(value), value, tb, limit=limit, compact=True)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 702, in __init__
    self.stack = StackSummary._extract_from_extended_frame_gen(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 436, in _extract_from_extended_frame_gen
    f.line
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 321, in line
    self._line = linecache.getline(self.filename, self.lineno)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/linecache.py", line 30, in getline
    lines = getlines(filename, module_globals)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/linecache.py", line 46, in getlines
    return updatecache(filename, module_globals)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/linecache.py", line 136, in updatecache
    with tokenize.open(fullname) as fp:
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/tokenize.py", line 396, in open
    buffer = _builtin_open(filename, 'rb')
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded while calling a Python object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/runner/work/documented/documented/documented/documented.py", line 22, in __str__
    return template.format(self=self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/work/documented/documented/documented/documented.py", line 25, in __str__
    logger.exception(
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1524, in exception
    self.error(msg, *args, exc_info=exc_info, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1518, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1634, in _log
    self.handle(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1644, in handle
    self.callHandlers(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1714, in callHandlers
    lastResort.handle(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 978, in handle
    self.emit(record)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 1110, in emit
    msg = self.format(record)
          ^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 953, in format
    return fmt.format(record)
           ^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 695, in format
    record.exc_text = self.formatException(record.exc_info)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/logging/__init__.py", line 645, in formatException
    traceback.print_exception(ei[0], ei[1], tb, None, sio)
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 124, in print_exception
    te = TracebackException(type(value), value, tb, limit=limit, compact=True)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 702, in __init__
    self.stack = StackSummary._extract_from_extended_frame_gen(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 436, in _extract_from_extended_frame_gen
    f.line
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/traceback.py", line 321, in line
    self._line = linecache.getline(self.filename, self.lineno)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/linecache.py", line 30, in getline
    lines = getlines(filename, module_globals)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/linecache.py", line 46, in getlines
    return updatecache(filename, module_globals)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.11.6/x64/lib/python3.11/linecache.py", line 136, in updatecache
    with tokenize.open(fullname) as fp:
RecursionError: maximum recursion depth exceeded while calling a Python object
: <exception str() failed>

So please do not do that

We could have filtered out the plain call to {self} but we're unable to do so for more involved cases as illustrated above, so for now we are keeping it as it is.