Пример #1
0
 def run(self):
     """Add a MarkdownNode when this directive is loaded
     """
     env = self.state.document.settings.env
     node = MarkdownNode()
     options = Options(self.options)
     node.filename = options.get('filename')
     node.extensions = env.config.markdown_extensions
     node.load_markdown()
     return [node]
class TypedDictDocumenter(ClassDocumenter):
    r"""
	Sphinx autodoc :class:`~sphinx.ext.autodoc.Documenter`
	for documenting :class:`typing.TypedDict`\s.
	"""  # noqa: D400

    objtype = "typeddict"
    directivetype = "typeddict"
    priority = 20
    option_spec: Dict[str, Callable] = {
        "noindex": flag,
        "alphabetical": flag,
        "show-inheritance": flag,
    }

    def __init__(self, *args: Any) -> None:
        super().__init__(*args)

        self.options = Options(self.options.copy())

        alphabetical = self.options.get("alphabetical", False)
        if alphabetical:
            self.options["member-order"] = "alphabetical"
        else:
            self.options["member-order"] = "bysource"

        for key in {"inherited-members",
                    "special-members"}:  # pragma: no cover
            if key in self.options:
                del self.options[key]

    @classmethod
    def can_document_member(
        cls,
        member: Any,
        membername: str,
        isattr: bool,
        parent: Any,
    ) -> bool:
        """
		Called to see if a member can be documented by this documenter.

		:param member: The member being checked.
		:param membername: The name of the member.
		:param isattr:
		:param parent: The parent of the member.
		"""

        for attr in {"__optional_keys__", "__required_keys__", "__total__"}:
            if not hasattr(member, attr):
                return False

        return True

    def format_signature(self, **kwargs: Any) -> str:
        """
		Typed Dicts do not have a signature.
		"""

        return ''

    def add_content(self, more_content: Any, no_docstring: bool = False):
        """
		Add the autodocumenter content.

		:param more_content:
		:param no_docstring:
		"""

        if self.doc_as_attr:  # pragma: no cover (verbatim from Sphinx)
            classname = safe_getattr(self.object, "__qualname__", None)
            if not classname:
                classname = safe_getattr(self.object, "__name__", None)
            if classname:
                module = safe_getattr(self.object, "__module__", None)
                parentmodule = safe_getattr(self.parent, "__module__", None)
                if module and module != parentmodule:
                    classname = str(module) + '.' + str(classname)
                more_content = docutils.statemachine.StringList(
                    [_("alias of :class:`%s`") % classname], source='')
                no_docstring = True

        # set sourcename and add content from attribute documentation
        sourcename = self.get_sourcename()
        if self.analyzer:
            attr_docs = self.analyzer.find_attr_docs()
            if self.objpath:
                key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
                if key in attr_docs:
                    no_docstring = True
                    # make a copy of docstring for attributes to avoid cache
                    # the change of autodoc-process-docstring event.
                    docstrings = [list(attr_docs[key])]

                    for i, line in enumerate(self.process_doc(docstrings)):
                        self.add_line(line, sourcename, i)

        # add content from docstrings
        if not no_docstring:
            docstrings = self.get_doc() or []
            if not docstrings:
                # append at least a dummy docstring, so that the event
                # autodoc-process-docstring is fired and can add some
                # content if desired
                docstrings.append([":class:`typing.TypedDict`.", ''])
            for i, line in enumerate(self.process_doc(docstrings)):
                self.add_line(line, sourcename, i)

        # add additional content (e.g. from document), if present
        if more_content:  # pragma: no cover (verbatim from Sphinx)
            for line, src in zip(more_content.data, more_content.items):
                self.add_line(line, src[0], src[1])

    def document_members(self, all_members: bool = False) -> None:
        """
		Generate reST for member documentation. All members are always documented.
		"""

        super().document_members(True)

    def sort_members(
        self,
        documenters: List[Tuple[Documenter, bool]],
        order: str,
    ) -> List[Tuple[Documenter, bool]]:
        """
		Sort the TypedDict's members.

		:param documenters:
		:param order:
		"""

        # The documenters for the keys, in the desired order
        documenters = super().sort_members(documenters, order)

        # Mapping of key names to docstrings (as list of strings)
        docstrings = {
            k[1]: v
            for k, v in ModuleAnalyzer.for_module(
                self.object.__module__).find_attr_docs().items()
        }

        required_keys = []
        optional_keys = []
        types = get_type_hints(self.object)

        for d in documenters:
            name = d[0].name.split('.')[-1]
            if name in self.object.__required_keys__:
                required_keys.append(name)
            elif name in self.object.__optional_keys__:
                optional_keys.append(name)
            # else: warn user. This shouldn't ever happen, though.

        sourcename = self.get_sourcename()

        if required_keys:
            self.add_line('', sourcename)
            self.add_line(":Required Keys:", sourcename)
            self.document_keys(required_keys, types, docstrings)
            self.add_line('', sourcename)

        if optional_keys:
            self.add_line('', sourcename)
            self.add_line(":Optional Keys:", sourcename)
            self.document_keys(optional_keys, types, docstrings)
            self.add_line('', sourcename)

        return []

    def document_keys(self, keys: List[str], types: Dict[str, Type],
                      docstrings: Dict[str, List[str]]):
        """
		Document keys in a :class:`typing.TypedDict`.

		:param keys: List of key names to document.
		:param types: Mapping of key names to types.
		:param docstrings: Mapping of key names to docstrings.
		"""

        content = StringList()

        for key in keys:
            if key in types:
                key_type = f"({format_annotation(types[key])}) "
            else:
                key_type = ''

            if key in docstrings:
                content.append(
                    f"    * **{key}** {key_type}-- {' '.join(docstrings.get(key, ''))}"
                )
            else:
                content.append(f"    * **{key}** {key_type}")

        for line in content:
            self.add_line(line, self.get_sourcename())

    def filter_members(
        self,
        members: ObjectMembers,
        want_all: bool,
    ) -> List[Tuple[str, Any, bool]]:
        """
		Filter the given member list.

		:param members:
		:param want_all:
		"""

        ret = []

        # process members and determine which to skip
        for (membername, member) in members:
            # if isattr is True, the member is documented as an attribute

            if safe_getattr(member, "__sphinx_mock__", False):
                # mocked module or object
                keep = False
            elif membername.startswith('_'):
                keep = False
            else:
                keep = True

            # give the user a chance to decide whether this member
            # should be skipped
            if self.env.app:
                # let extensions preprocess docstrings
                try:
                    skip_user = self.env.app.emit_firstresult(
                        "autodoc-skip-member",
                        self.objtype,
                        membername,
                        member,
                        not keep,
                        self.options,
                    )

                    if skip_user is not None:
                        keep = not skip_user

                except Exception as exc:
                    filter_members_warning(member, exc)
                    keep = False

            if keep:
                ret.append((membername, member, member is INSTANCEATTR))

        return ret
Пример #3
0
class TypedAttributeDocumenter(DocstringStripSignatureMixin,
                               ClassLevelDocumenter):
    """
	Alternative version of :class:`sphinx.ext.autodoc.AttributeDocumenter`
	with better type hint rendering.

	Specialized Documenter subclass for attributes.

	.. versionadded:: 0.7.0
	.. versionchanged:: 1.0.0  Now uses the type of the variable if it is not explicitly annotated.
	"""  # noqa: D400

    objtype = "attribute"
    member_order = 60
    option_spec = dict(ModuleLevelDocumenter.option_spec)
    option_spec["annotation"] = annotation_option

    # must be higher than the MethodDocumenter, else it will recognize
    # some non-data descriptors as methods
    priority = 10

    def __init__(self,
                 directive: DocumenterBridge,
                 name: str,
                 indent: str = '') -> None:
        super().__init__(directive=directive, name=name, indent=indent)
        self.options = Options(self.options.copy())
        self._datadescriptor = True

    @staticmethod
    def is_function_or_method(obj: Any) -> bool:  # noqa: D102
        return inspect.isfunction(obj) or inspect.isbuiltin(
            obj) or inspect.ismethod(obj)

    @classmethod
    def can_document_member(cls, member: Any, membername: str, isattr: bool,
                            parent: Any) -> bool:
        """
		Called to see if a member can be documented by this documenter.
		"""

        if inspect.isattributedescriptor(member):
            return True
        elif (not isinstance(parent, ModuleDocumenter)
              and not inspect.isroutine(member)
              and not isinstance(member, type)):
            return True
        else:
            return False

    def document_members(self,
                         all_members: bool = False) -> None:  # noqa: D102
        pass

    def isinstanceattribute(self) -> bool:
        """
		Check the subject is an instance attribute.
		"""

        try:
            analyzer = ModuleAnalyzer.for_module(self.modname)
            attr_docs = analyzer.find_attr_docs()
            if self.objpath:
                key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
                if key in attr_docs:
                    return True

            return False
        except PycodeError:
            return False

    def import_object(self, raiseerror: bool = False) -> bool:
        """
		Import the object given by *self.modname* and *self.objpath* and set it as ``self.object``.

		:returns: :py:obj:`True` if successful, :py:obj:`False` if an error occurred.
		"""

        try:
            ret = super().import_object(raiseerror=True)
            if inspect.isenumattribute(self.object):
                self.object = self.object.value
            if inspect.isattributedescriptor(self.object):
                self._datadescriptor = True
            else:
                # if it's not a data descriptor
                self._datadescriptor = False
        except ImportError as exc:
            if self.isinstanceattribute():
                self.object = INSTANCEATTR
                self._datadescriptor = False
                ret = True
            elif raiseerror:
                raise
            else:
                logger.warning(exc.args[0],
                               type="autodoc",
                               subtype="import_object")
                self.env.note_reread()
                ret = False

        return ret

    def get_real_modname(self) -> str:
        """
		Get the real module name of an object to document.

		It can differ from the name of the module through which the object was imported.
		"""

        return self.get_attr(self.parent or self.object, "__module__",
                             None) or self.modname

    def add_directive_header(self, sig: str):
        """
		Add the directive's header.

		:param sig:
		"""

        sourcename = self.get_sourcename()

        no_value = self.options.get("no-value", False)
        no_type = self.options.get("no-type", False)

        if not self.options.get("annotation", ''):
            ClassLevelDocumenter.add_directive_header(self, sig)

            # data descriptors do not have useful values
            if not no_value and not self._datadescriptor:
                if "value" in self.options:
                    self.add_line("   :value: " + self.options["value"],
                                  sourcename)
                else:
                    with suppress(ValueError):
                        if self.object is not INSTANCEATTR:
                            objrepr = object_description(self.object)
                            self.add_line("   :value: " + objrepr, sourcename)

            self.add_line('', sourcename)

            if not no_type:
                if "type" in self.options:
                    self.add_line(type_template % self.options["type"],
                                  sourcename)
                else:
                    # obtain type annotation for this attribute
                    the_type = get_variable_type(self)
                    if not the_type.strip():
                        obj_type = type(self.object)

                        if obj_type is object:
                            return

                        try:
                            the_type = format_annotation(obj_type)
                        except Exception:
                            return

                    line = type_template % the_type
                    self.add_line(line, sourcename)

        else:
            super().add_directive_header(sig)

    def get_doc(  # type: ignore
        self,
        encoding: Optional[str] = None,
        ignore: Optional[int] = None,
    ) -> List[List[str]]:
        """
		Decode and return lines of the docstring(s) for the object.

		:param encoding:
		:param ignore:
		"""

        try:
            # Disable `autodoc_inherit_docstring` temporarily to avoid to obtain
            # a docstring from the value which descriptor returns unexpectedly.
            # ref: https://github.com/sphinx-doc/sphinx/issues/7805
            orig = self.env.config.autodoc_inherit_docstrings
            self.env.config.autodoc_inherit_docstrings = False  # type: ignore

            # Sphinx's signature is wrong wrt Optional
            if sphinx.version_info >= (4, 0):
                if encoding is not None:
                    raise TypeError(
                        "The 'encoding' argument to get_doc was removed in Sphinx 4"
                    )
                else:
                    return super().get_doc(ignore=cast(int, ignore)) or []
            else:
                return super().get_doc(cast(str, encoding), cast(
                    int, ignore)) or []  # type: ignore
        finally:
            self.env.config.autodoc_inherit_docstrings = orig  # type: ignore

    def add_content(self,
                    more_content: Any,
                    no_docstring: bool = False) -> None:
        """
		Add content from docstrings, attribute documentation and user.
		"""

        with warnings.catch_warnings():
            # TODO: work out what to do about this
            warnings.simplefilter("ignore", RemovedInSphinx50Warning)

            if not self._datadescriptor:
                # if it's not a data descriptor, its docstring is very probably the
                # wrong thing to display
                no_docstring = True
            super().add_content(more_content, no_docstring)
Пример #4
0
class VariableDocumenter(DataDocumenter):
    """
	Specialized Documenter subclass for data items.
	"""

    directivetype = "data"
    objtype = "variable"
    priority: float = DataDocumenter.priority + 0.5  # type: ignore  # keeps it below TypeVarDocumenter
    option_spec = {
        "no-value": flag,
        "no-type": flag,
        "type": str,
        "value": str,
        **DataDocumenter.option_spec,
    }

    def __init__(self,
                 directive: DocumenterBridge,
                 name: str,
                 indent: str = '') -> None:
        super().__init__(directive=directive, name=name, indent=indent)
        self.options = Options(self.options.copy())
        add_nbsp_substitution(self.env.app.config)

    def add_directive_header(self, sig: str):
        """
		Add the directive's header.

		:param sig:
		"""

        sourcename = self.get_sourcename()

        no_value = self.options.get("no-value", False)
        no_type = self.options.get("no-type", False)

        if not self.options.get("annotation", ''):
            ModuleLevelDocumenter.add_directive_header(self, sig)

            if not no_value:
                if "value" in self.options:
                    self.add_line(f"   :value: {self.options['value']}",
                                  sourcename)
                else:
                    with suppress(ValueError):
                        if self.object is not UNINITIALIZED_ATTR:
                            objrepr = object_description(self.object)
                            self.add_line(f"   :value: {objrepr}", sourcename)

            self.add_line('', sourcename)

            if not no_type:
                if "type" in self.options:
                    the_type = self.options["type"]
                else:
                    # obtain type annotation for this data
                    the_type = get_variable_type(self)
                    if not the_type.strip():
                        obj_type = type(self.object)

                        if obj_type is object:
                            return

                        try:
                            the_type = format_annotation(obj_type)
                        except Exception:
                            return

                line = type_template % the_type
                self.add_line(line, sourcename)

        else:
            super().add_directive_header(sig)
Пример #5
0
class TypedAttributeDocumenter(AttributeDocumenter):
    """
	Alternative version of :class:`sphinx.ext.autodoc.AttributeDocumenter`
	with better type hint rendering.

	Specialized Documenter subclass for attributes.

	.. versionadded:: 0.7.0

	.. versionchanged:: 1.0.0

		Now uses the type of the variable if it is not explicitly annotated.
	"""  # noqa D400

    def __init__(self,
                 directive: DocumenterBridge,
                 name: str,
                 indent: str = '') -> None:
        super().__init__(directive=directive, name=name, indent=indent)
        self.options = Options(self.options.copy())
        self._datadescriptor = True

    def add_directive_header(self, sig: str):
        """
		Add the directive's header.

		:param sig:
		"""

        sourcename = self.get_sourcename()

        no_value = self.options.get("no-value", False)
        no_type = self.options.get("no-type", False)

        if not self.options.get("annotation", ''):
            ClassLevelDocumenter.add_directive_header(self, sig)

            # data descriptors do not have useful values
            if not no_value and not self._datadescriptor:
                if "value" in self.options:
                    self.add_line("   :value: " + self.options["value"],
                                  sourcename)
                else:
                    try:
                        if self.object is not INSTANCEATTR:
                            objrepr = object_description(self.object)
                            self.add_line("   :value: " + objrepr, sourcename)
                    except ValueError:
                        pass

            self.add_line('', sourcename)

            if not no_type:
                if "type" in self.options:
                    self.add_line(type_template % self.options["type"],
                                  sourcename)
                else:
                    # obtain type annotation for this attribute
                    the_type = get_variable_type(self)
                    if not the_type.strip():
                        try:
                            the_type = format_annotation(type(self.object))
                        except Exception:
                            return

                    line = type_template % the_type
                    self.add_line(line, sourcename)

        else:
            super().add_directive_header(sig)
Пример #6
0
class ProtocolDocumenter(ClassDocumenter):
    r"""
	Sphinx autodoc :class:`~sphinx.ext.autodoc.Documenter`
	for documenting :class:`typing.Protocol`\s.

	.. versionadded:: 0.2.0
	"""  # noqa D400

    objtype = "protocol"
    directivetype = "protocol"
    priority = 20
    option_spec: Dict[str, Callable] = {
        "noindex": flag,
        "member-order": member_order_option,
        "show-inheritance": flag,
        "exclude-protocol-members": exclude_members_option,
    }

    def __init__(self,
                 directive: DocumenterBridge,
                 name: str,
                 indent: str = '') -> None:
        super().__init__(directive, name, indent)
        self.options = Options(self.options.copy())

    @classmethod
    def can_document_member(
        cls,
        member: Any,
        membername: str,
        isattr: bool,
        parent: Any,
    ) -> bool:
        """
		Called to see if a member can be documented by this documenter.

		:param member: The member being checked.
		:param membername: The name of the member.
		:param isattr:
		:param parent: The parent of the member.
		"""

        # _is_protocol = True
        return isinstance(member, _ProtocolMeta)

    def format_signature(self, **kwargs: Any) -> str:
        """
		Protocols do not have a signature.
		"""

        return ''  # pragma: no cover

    def add_content(self, more_content: Any, no_docstring: bool = False):
        """
		Add the autodocumenter content.

		:param more_content:
		:param no_docstring:
		"""

        super().add_content(more_content=more_content,
                            no_docstring=no_docstring)

        sourcename = self.get_sourcename()

        if not getdoc(self.object) and "show-inheritance" not in self.options:
            self.add_line(":class:`typing.Protocol`.", sourcename)
            self.add_line('', sourcename)

        if hasattr(
                self.object,
                "_is_runtime_protocol") and self.object._is_runtime_protocol:
            self.add_line(runtime_message, sourcename)
            self.add_line('', sourcename)

        self.add_line(
            "Classes that implement this protocol must have the following methods / attributes:",
            sourcename)
        self.add_line('', sourcename)

    def document_members(self, all_members: bool = False) -> None:
        """
		Generate reST for member documentation.

		All members are always documented.
		"""

        super().document_members(True)

    def filter_members(
        self,
        members: List[Tuple[str, Any]],
        want_all: bool,
    ) -> List[Tuple[str, Any, bool]]:
        """
		Filter the given member list.

		:param members:
		:param want_all:
		"""

        ret = []

        # process members and determine which to skip
        for (membername, member) in members:
            # if isattr is True, the member is documented as an attribute

            if safe_getattr(member, "__sphinx_mock__", False):
                # mocked module or object
                keep = False

            elif (self.options.get("exclude-protocol-members", [])
                  and membername in self.options["exclude-protocol-members"]):
                # remove members given by exclude-protocol-members
                keep = False

            elif membername.startswith('_') and not (
                    membername.startswith("__") and membername.endswith("__")):
                keep = False

            elif membername not in globally_excluded_methods:
                # Magic method you wouldn't overload, or private method.
                if membername in dir(self.object.__base__):
                    keep = member is not getattr(self.object.__base__,
                                                 membername)
                else:
                    keep = True

            else:
                keep = False

            # give the user a chance to decide whether this member
            # should be skipped
            if self.env.app:
                # let extensions preprocess docstrings
                try:
                    skip_user = self.env.app.emit_firstresult(
                        "autodoc-skip-member",
                        self.objtype,
                        membername,
                        member,
                        not keep,
                        self.options,
                    )

                    if skip_user is not None:
                        keep = not skip_user

                except Exception as exc:
                    filter_members_warning(member, exc)
                    keep = False

            if keep:
                ret.append((membername, member, member is INSTANCEATTR))

        return ret