def test_format_annotation(inv, annotation, expected_result): result = format_annotation(annotation) assert result == expected_result # Test with the "fully_qualified" flag turned on if 'typing' in expected_result or __name__ in expected_result: expected_result = expected_result.replace('~typing', 'typing') expected_result = expected_result.replace('~' + __name__, __name__) assert format_annotation(annotation, fully_qualified=True) == expected_result # Test for the correct role (class vs data) using the official Sphinx inventory if 'typing' in expected_result: m = re.match('^:py:(?P<role>class|data|func):`~(?P<name>[^`]+)`', result) assert m, 'No match' name = m.group('name') role = next((o.role for o in inv.objects if o.name == name), None) if name in {'typing.Pattern', 'typing.Match', 'typing.NoReturn'}: if sys.version_info < (3, 6): assert role is None, 'No entry in Python 3.5’s objects.inv' return assert role is not None, 'Name {} not found'.format(name) assert m.group('role') == 'func' if role == 'function' else role
def _get_lazy_property_doc(prop: LazyProperty, config: Config) -> Tuple[str, str, str]: """Get a row documenting a LazyProperty""" rtype = format_annotation(prop.type, config) lazy_property_type = format_annotation(LazyProperty, config) return (f'**{prop.__name__}** ({lazy_property_type})', rtype, prop.__doc__ or '')
def _get_property_doc(prop: property, config: Config) -> Tuple[str, str, str]: """Get a row documenting a regular @property""" fget_rtype = get_type_hints(prop.fget).get('return', Any) rtype = format_annotation(fget_rtype, config) if TYPE_CHECKING: assert prop.fget is not None doc = (prop.fget.__doc__ or '').split('\n')[0] property_type = format_annotation(property, config) return (f'**{prop.fget.__name__}** ({property_type})', rtype, doc)
def process_docstring(app, what, name, obj, options, lines): if what != 'attribute': return from importlib import import_module from typing import get_type_hints from sphinx_autodoc_typehints import format_annotation *parts, attrname = name.split('.') moduleparts = parts.copy() while moduleparts: try: cls = import_module('.'.join(moduleparts)) except ImportError: del moduleparts[-1] else: break if not moduleparts: print('XXX failed to import module') return innerparts = parts[len(moduleparts):] while innerparts: cls = getattr(cls, innerparts.pop(0)) annotations = get_type_hints(cls) if attrname in annotations: lines.extend([ '', '**Type**: {}'.format(format_annotation(annotations[attrname])) ])
def test_format_annotation(inv, annotation, expected_result): result = format_annotation(annotation) assert result == expected_result # Test with the "simplify_optional_unions" flag turned off: if re.match(r'^:py:data:`~typing\.Union`\\\[.*``None``.*\]', expected_result): # strip None - argument and copy string to avoid conflicts with # subsequent tests expected_result_not_simplified = expected_result.replace( ', ``None``', '') # encapsulate Union in typing.Optional expected_result_not_simplified = ':py:data:`~typing.Optional`\\[' + \ expected_result_not_simplified expected_result_not_simplified += ']' assert format_annotation(annotation, simplify_optional_unions=False) == \ expected_result_not_simplified # Test with the "fully_qualified" flag turned on if 'typing' in expected_result_not_simplified: expected_result_not_simplified = expected_result_not_simplified.replace( '~typing', 'typing') assert format_annotation(annotation, fully_qualified=True, simplify_optional_unions=False) == \ expected_result_not_simplified # Test with the "fully_qualified" flag turned on if 'typing' in expected_result or __name__ in expected_result: expected_result = expected_result.replace('~typing', 'typing') expected_result = expected_result.replace('~' + __name__, __name__) assert format_annotation(annotation, fully_qualified=True) == expected_result # Test for the correct role (class vs data) using the official Sphinx inventory if 'typing' in expected_result: m = re.match('^:py:(?P<role>class|data|func):`~(?P<name>[^`]+)`', result) assert m, 'No match' name = m.group('name') expected_role = next((o.role for o in inv.objects if o.name == name), None) if expected_role: if expected_role == 'function': expected_role = 'func' assert m.group('role') == expected_role
def test_format_annotation_both_libs(inv, library, annotation, params, expected_result): try: annotation_cls = getattr(library, annotation) except AttributeError: pytest.skip('{} not available in the {} module'.format(annotation, library.__name__)) ann = annotation_cls if params is None else annotation_cls[params] result = format_annotation(ann) assert result == expected_result
def _get_field_doc(field: Attribute, config: Config) -> Tuple[str, str, str]: """Get a row documenting an attrs Attribute""" rtype = format_annotation(field.type, config) doc = field.metadata.get('doc', '') options = field.metadata.get('options', []) if options: options = ', '.join([f'``{opt}``' for opt in options if opt]) doc += f'\n\n**Options:** {options}' return (f'**{field.name}**', rtype, doc)
def test_format_annotation(inv, annotation, expected_result): result = format_annotation(annotation) assert result == expected_result # Test with the "fully_qualified" flag turned on if 'typing' in expected_result or __name__ in expected_result: expected_result = expected_result.replace('~typing', 'typing') expected_result = expected_result.replace('~' + __name__, __name__) assert format_annotation(annotation, fully_qualified=True) == expected_result # Test for the correct role (class vs data) using the official Sphinx inventory if 'typing' in expected_result: m = re.match('^:py:(?P<role>class|data|func):`~(?P<name>[^`]+)`', result) assert m, 'No match' name = m.group('name') expected_role = next((o.role for o in inv.objects if o.name == name), None) if expected_role: if expected_role == 'function': expected_role = 'func' assert m.group('role') == expected_role
def process_docstring(self, app, what, name, obj, options, lines): if what == "class": self.hints = sphinx_autodoc_typehints.get_all_type_hints(obj, name) elif what == "attribute": name = name.split(".")[-1] hint = self.hints.get(name) if hint: typename = sphinx_autodoc_typehints.format_annotation(hint) lines.append(":type: " + typename) lines.append("")
def test_format_annotation_type(type_param, expected_result): annotation = Type[type_param] if type_param else Type result = format_annotation(annotation, {}) assert result == expected_result
def test_format_annotation_type(type_param, expected_result): annotation = Type[type_param] if type_param else Type result = format_annotation(annotation) assert result.startswith(expected_result)
def test_format_annotation(annotation, expected_result): result = format_annotation(annotation) assert result == expected_result
def test_format_annotation_fully_qualified(annotation, expected_result): result = format_annotation(annotation, fully_qualified=True) assert result == expected_result
def run(self): of_class = self.arguments[0].split('.') class_type = getattr( __import__('.'.join(of_class[:-1]), fromlist=[of_class[-1]]), of_class[-1]) def predicate(a): return isinstance(a, property) class_members = inspect.getmembers(class_type, predicate=predicate) return_types = [ inspect.signature(p.fget).return_annotation for _, p in class_members ] docs = [inspect.getdoc(p) or '' for _, p in class_members] table = nodes.table() t_group = nodes.tgroup(cols=4) t_group += nodes.colspec(colwidth=1) t_group += nodes.colspec(colwidth=1) t_group += nodes.colspec(colwidth=1) t_group += nodes.colspec(colwidth=10) # header t_group += nodes.thead( '', nodes.row( '', *[ nodes.entry('', nodes.line(text=c)) for c in ["field", "type", "note", "description"] ])) t_body = nodes.tbody() for (name, _), return_type, doc in zip(class_members, return_types, docs): doc_l = doc.split('\n') location = next((i for i in doc_l if i.startswith(':note:')), '')[len(':note:'):] doc_stripped = '\n'.join(i for i in doc_l if not i.startswith(':note:')) doc_stripped_node = self.render_content(doc_stripped) return_type_node = self.render_content( format_annotation(return_type)) ref_key = '{}.{}'.format(self.arguments[1], name) name_node = nodes.reference('', name, refid=ref_key) ref = nodes.paragraph('', '', name_node) t_body += nodes.row(name, nodes.entry('', ref), nodes.entry('', return_type_node), nodes.entry('', nodes.literal(text=location)), nodes.entry('', doc_stripped_node), ids=[ref_key]) t_group += t_body table += t_group return [table]
def test_format_annotation_test(): result = format_annotation(Tuple[str, ...], {}) assert result
def test_format_annotation_alias(annotation, expected_result, alias): result = format_annotation(annotation, alias) assert expected_result == result
def generate( self, more_content: Any = None, real_modname: str = None, check_module: bool = False, all_members: bool = False, ) -> None: """Generate reST for the object given by *self.name*, and possibly for its members. If *more_content* is given, include that content. If *real_modname* is given, use that module name to find attribute docs. If *check_module* is True, only generate if the object is defined in the module name it is imported from. If *all_members* is True, document all members. """ if not self.parse_name(): # need a module to import logger.warning( __('don\'t know which module to import for autodocumenting ' '%r (try placing a "module" or "currentmodule" directive ' 'in the document, or giving an explicit module name)') % self.name, type='autodoc', ) return # now, import the module and get object to document if not self.import_object(): return # Is this the right kind? If not, delegate to the superclass? # TODO # return super().generate(...) # if not isinstance(self.object, _XCType): super().generate( more_content=more_content, real_modname=real_modname, check_module=check_module, all_members=all_members, ) return schema = self.object._model.schema() try: hints = get_type_hints(self.object._model) except Exception as e: print("Get type hints for %s: %s" % (self.fullname, e)) hints = {} source = "description of %s" % (self.object_name) sig = "(%s)" % (", ".join(schema['properties'].keys())) self.add_directive_header(sig) self.add_line('', source) indent = self.indent try: self.indent += ' ' for (i, line) in enumerate(schema['description'].splitlines()): self.add_line(line.strip(), source, i) # Always do 'show-inheritance' bases = self.get_base_description() if bases: self.add_line('', source) self.add_line("A subclass of %s" % (bases), source) self.add_line('', source) source = self.get_sourcename() has_params = False for (pname, ptype) in schema['properties'].items(): has_params = True hint = hints.get(pname, '') if hint: hint = format_annotation(hint) else: hint = '(unspecified)' self.add_line(':param %s: %s' % (pname, ptype['title']), source) self.add_line(':type %s: %s' % (pname, hint), source) self.add_line('', source) if has_params: self.add_line( 'Each parameter defines an attribute of the same name.', source) self.add_line('', source) subclasses = self.object.__subclasses__() # Add the attributes # For a base class, the only inherited property that's of interest # is the status if subclasses: show_props = ('status', ) else: show_props = ('typename', 'title', 'detail', 'status') self.add_line('Properties (read-only)', source) for name in show_props: self.add_line( ' :py:attr:`%s` = %s' % (name, repr(getattr(self.object, name, '(not set)'))), source, ) self.add_line('', source) self.add_line( ('For more information about the above properties' ' please refer to the documentation for :py:mod:`rjgtoys.xc`.' ), source, ) if subclasses: self.add_line('', source) self.add_line('Subclasses:', source) self.add_line('', source) for sub in subclasses: self.add_line(' - :exc:`%s`' % (sub.__name__), source) self.add_line('', source) finally: self.indent = indent return # DEBUG: dump our output for line in self.directive.result: print("RESULT: %s" % (line))