private void buildFromAttributeDoc() {
    PyClass cls = PsiTreeUtil.getParentOfType(myElement, PyClass.class);
    assert cls != null;
    String type =
        PyUtil.isInstanceAttribute((PyExpression) myElement)
            ? "Instance attribute "
            : "Class attribute ";
    myProlog
        .addItem(type)
        .addWith(TagBold, $().addWith(TagCode, $(((PyTargetExpression) myElement).getName())))
        .addItem(" of class ")
        .addWith(PythonDocumentationProvider.LinkMyClass, $().addWith(TagCode, $(cls.getName())))
        .addItem(BR);

    final String docString = ((PyTargetExpression) myElement).getDocStringValue();
    if (docString != null) {
      addFormattedDocString(myElement, docString, myBody, myEpilog);
    }
  }
  private void addInheritedDocString(
      @NotNull final PyFunction pyFunction, @Nullable final PyClass pyClass) {
    boolean notFound = true;
    final String methodName = pyFunction.getName();
    if (pyClass == null || methodName == null) {
      return;
    }
    final boolean isConstructor = PyNames.INIT.equals(methodName);
    Iterable<PyClass> classes = pyClass.getAncestorClasses(null);
    if (isConstructor) {
      // look at our own class again and maybe inherit class's doc
      classes = new ChainIterable<PyClass>(pyClass).add(classes);
    }
    for (PyClass ancestor : classes) {
      PyStringLiteralExpression docstringElement = null;
      PyFunction inherited = null;
      boolean isFromClass = false;
      if (isConstructor) docstringElement = pyClass.getDocStringExpression();
      if (docstringElement != null) {
        isFromClass = true;
      } else {
        inherited = ancestor.findMethodByName(methodName, false);
      }
      if (inherited != null) {
        docstringElement = inherited.getDocStringExpression();
      }
      if (docstringElement != null) {
        final String inheritedDoc = docstringElement.getStringValue();
        if (inheritedDoc.length() > 1) {
          myEpilog.addItem(BR).addItem(BR);
          String ancestor_name = ancestor.getName();
          String marker =
              (pyClass == ancestor)
                  ? PythonDocumentationProvider.LINK_TYPE_CLASS
                  : PythonDocumentationProvider.LINK_TYPE_PARENT;
          final String ancestor_link =
              $().addWith(new LinkWrapper(marker + ancestor_name), $(ancestor_name)).toString();
          if (isFromClass) {
            myEpilog.addItem(PyBundle.message("QDOC.copied.from.class.$0", ancestor_link));
          } else {
            myEpilog.addItem(PyBundle.message("QDOC.copied.from.$0.$1", ancestor_link, methodName));
          }
          myEpilog.addItem(BR).addItem(BR);
          ChainIterable<String> formatted = new ChainIterable<String>();
          ChainIterable<String> unformatted = new ChainIterable<String>();
          addFormattedDocString(pyFunction, inheritedDoc, formatted, unformatted);
          myEpilog.addWith(TagCode, formatted).add(unformatted);
          notFound = false;
          break;
        }
      }
    }

    if (notFound) {
      // above could have not worked because inheritance is not searched down to 'object'.
      // for well-known methods, copy built-in doc string.
      // TODO: also handle predefined __xxx__ that are not part of 'object'.
      if (PyNames.UnderscoredAttributes.contains(methodName)) {
        addPredefinedMethodDoc(pyFunction, methodName);
      }
    }
  }