def add_content(self, more_content: Any, no_docstring: bool = False) -> None: """ Add content from docstrings, attribute documentation and user. :param more_content: :param no_docstring: """ obj: _TypeVar = self.object sourcename = self.get_sourcename() constraints = [ self.resolve_type(c) if isinstance(c, ForwardRef) else c for c in obj.__constraints__ ] description = [] bound_to: Optional[Type] if isinstance(obj.__bound__, ForwardRef): bound_to = self.resolve_type(obj.__bound__) else: bound_to = obj.__bound__ if obj.__covariant__: description.append("Covariant") elif obj.__contravariant__: description.append("Contravariant") else: description.append("Invariant") description.append(":class:`~typing.TypeVar`") if constraints: description.append("constrained to") description.append( word_join( format_annotation(c, fully_qualified=True) for c in constraints)) elif bound_to: description.append("bound to") description.append( format_annotation(bound_to, fully_qualified=True)) # 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: # return self.add_line('', sourcename) self.add_line(' '.join(description).rstrip() + '.', sourcename) # " " + self.add_line('', sourcename) super().add_content(more_content, no_docstring)
def get_variable_type(documenter: Documenter) -> str: """ Returns the formatted type annotation for a variable. :param documenter: """ try: annotations = get_type_hints(documenter.parent) except NameError: # Failed to evaluate ForwardRef (maybe TYPE_CHECKING) annotations = safe_getattr(documenter.parent, "__annotations__", {}) except TypeError: annotations = {} except KeyError: # a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084) annotations = {} except AttributeError: # AttributeError is raised on 3.5.2 (fixed by 3.5.3) annotations = {} if documenter.objpath[-1] in annotations: ann = annotations.get(documenter.objpath[-1]) if isinstance(ann, str): return format_annotation(ann.strip("'\"")) return format_annotation(ann) else: # Instance attribute key = ('.'.join(documenter.objpath[:-1]), documenter.objpath[-1]) if documenter.analyzer and key in documenter.analyzer.annotations: # Forward references will have quotes annotation: str = documenter.analyzer.annotations[key].strip("'\"") try: module_dict = sys.modules[ documenter.parent.__module__].__dict__ if annotation.isidentifier() and annotation in module_dict: return format_annotation(module_dict[annotation]) else: if sys.version_info < (3, 9): return format_annotation( ForwardRef(annotation)._evaluate( module_dict, module_dict)) else: return format_annotation( ForwardRef(annotation)._evaluate( module_dict, module_dict, set())) except (NameError, TypeError, ValueError, AttributeError): return annotation else: return ''
def create_body_overloads(self) -> StringList: """ Create the overloaded implementations for insertion into to the body of the documenter's output. """ output = StringList() formatted_overloads = [] output.blankline() # output.append(":Overloaded Implementations:") output.append(":Overloads:") output.blankline() # Size varies depending on docutils config output.indent_type = ' ' output.indent_size = self.env.app.config.docutils_tab_width # type: ignore if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads: for overload in self.analyzer.overloads.get('.'.join(self.objpath)): # type: ignore overload = self.process_overload_signature(overload) buf = [format_annotation(self.object), r"\("] for name, param in overload.parameters.items(): buf.append(f"**{name}**") if param.annotation is not Parameter.empty: buf.append(r"\: ") buf.append(format_annotation(param.annotation)) if param.default is not Parameter.empty: buf.append(" = ") buf.append(param.default) buf.append(r"\, ") if buf[-2][-1] != '`': buf[-1] = r" )" else: buf[-1] = r")" if overload.return_annotation is not Parameter.empty: buf.append(" -> ") buf.append(format_annotation(overload.return_annotation)) formatted_overloads.append(''.join(buf)) if len(formatted_overloads) == 1: output.append(formatted_overloads[0]) else: for line in formatted_overloads: output.append(f"* {line}") output.blankline(ensure_single=True) return output return StringList()
def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() if self.doc_as_attr: self.directivetype = "attribute" Documenter.add_directive_header(self, sig) if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(" :final:", sourcename) # add inheritance info, if wanted if not self.doc_as_attr and self.options.show_inheritance: sourcename = self.get_sourcename() self.add_line('', sourcename) if hasattr(self.object, "__bases__") and len( self.object.__bases__): bases = [] for b in self.object.__bases__: if b is TemporaryDirectory: bases.append(":py:obj:`~tempfile.TemporaryDirectory`") elif b.__module__ in ("__builtin__", "builtins"): bases.append(f':class:`{b.__name__}`') else: bases.append(format_annotation(b)) self.add_line(" " + _("Bases: %s") % ", ".join(bases), sourcename)
def add_content(self, more_content: Any, no_docstring: bool = False): """ Add the autodocumenter content. :param more_content: :param no_docstring: """ name = format_annotation(self.object) content = StringList([_("Alias of %s") % name], source='') DataDocumenter.add_content(self, content)
def _add_generic_bases(documenter: Documenter) -> None: """ Add the generic bases to the output of the given Documenter. .. versionadded:: 2.13.0 (undocumented) :param documenter: """ sourcename = documenter.get_sourcename() # add inheritance info, if wanted fully_qualified = getattr(documenter.env.config, "generic_bases_fully_qualified", False) documenter.add_line('', sourcename) bases = [] if (hasattr(documenter.object, "__orig_bases__") and len(documenter.object.__orig_bases__) and get_origin(documenter.object.__orig_bases__[0]) is documenter.object.__bases__[0]): # Last condition guards against classes that don't directly subclass a Generic. bases = [ format_annotation(b, fully_qualified) for b in documenter.object.__orig_bases__ ] elif hasattr(documenter.object, "__bases__") and len( documenter.object.__bases__): bases = [ format_annotation(b, fully_qualified) for b in documenter.object.__bases__ ] if bases: bases_string = ", ".join(bases).replace("typing_extensions.", "typing.") documenter.add_line(" " + _("Bases: %s") % bases_string, sourcename)
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 add_directive_header(self, sig: str) -> None: """ Add the directive header. :param sig: """ sourcename = self.get_sourcename() if self.doc_as_attr: self.directivetype = "attribute" Documenter.add_directive_header(self, sig) if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(" :final:", sourcename) # add inheritance info, if wanted if not self.doc_as_attr and self.options.show_inheritance: self.add_line('', sourcename) bases = [] if (hasattr(self.object, "__orig_bases__") and len(self.object.__orig_bases__) and get_origin(self.object.__orig_bases__[0]) is self.object.__bases__[0]): # Last condition guards against classes that don't directly subclass a Generic. bases = [ format_annotation(b) for b in self.object.__orig_bases__ ] elif hasattr(self.object, "__bases__") and len( self.object.__bases__): bases = [format_annotation(b) for b in self.object.__bases__] if bases: self.add_line(" " + _("Bases: %s") % ", ".join(bases), sourcename)
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)
def sort_members( self, documenters: List[Tuple[Documenter, bool]], order: str, ) -> List[Tuple[Documenter, bool]]: r""" Sort the :class:`typing.NamedTuple`\'s members. :param documenters: :param order: """ # The documenters for the fields and methods, in the desired order # The fields will be in bysource order regardless of the order option documenters = super().sort_members(documenters, order) # Size varies depending on docutils config a_tab = ' ' * self.env.app.config.docutils_tab_width # Mapping of member names to docstrings (as list of strings) member_docstrings: Dict[str, List[str]] try: namedtuple_source = textwrap.dedent(inspect.getsource(self.object)) # Mapping of member names to docstrings (as list of strings) member_docstrings = { k[1]: v for k, v in ModuleAnalyzer.for_string( namedtuple_source, self.object.__module__).find_attr_docs().items() } except (TypeError, OSError): member_docstrings = {} # set sourcename and add content from attribute documentation sourcename = self.get_sourcename() params, pre_output, post_output = self._get_docstring() self.add_line('', sourcename) self.add_line(":Fields:", sourcename) # TODO: Add xref targets for each field as an attribute # TODO: support for default_values self.add_line('', sourcename) fields = self.object._fields for pos, field in enumerate(fields): doc: List[str] = [''] arg_type: str = '' # Prefer doc from class docstring if field in params: doc, arg_type = params.pop(field).values() # type: ignore # Otherwise use attribute docstring if not ''.join(doc).strip() and field in member_docstrings: doc = member_docstrings[field] # Fallback to namedtuple's default docstring if not ''.join(doc).strip(): doc = [getattr(self.object, field).__doc__] # Prefer annotations over docstring types type_hints = get_type_hints(self.object) if type_hints: if field in type_hints: arg_type = format_annotation(type_hints[field]) field_entry = [f"{a_tab}{pos})", "|nbsp|", f"**{field}**"] if arg_type: field_entry.append(f"({arg_type}\\)") field_entry.append("--") field_entry.extend(doc) if field_alias_re.match(getattr(self.object, field).__doc__ or ''): getattr(self.object, field).__doc__ = ' '.join(doc) self.add_line(' '.join(field_entry), sourcename) self.add_line('', sourcename) for line in post_output: self.add_line(line, sourcename) self.add_line('', sourcename) # Remove documenters corresponding to fields and return the rest return [ d for d in documenters if d[0].name.split('.')[-1] not in fields ]
def test_format_annotation(annotation: Any, expected: str): assert typehints.format_annotation(annotation, True) == expected