def test_ModuleAnalyzer_find_tags(): code = ( 'class Foo(object):\n' # line: 1 ' """class Foo!"""\n' ' def __init__(self):\n' ' pass\n' '\n' ' def bar(self, arg1, arg2=True, *args, **kwargs):\n' ' """method Foo.bar"""\n' ' pass\n' '\n' ' class Baz(object):\n' ' def __init__(self):\n' # line: 11 ' pass\n' '\n' 'def qux():\n' ' """function baz"""\n' ' pass\n' '\n' '@decorator\n' 'def quux():\n' ' pass\n') analyzer = ModuleAnalyzer.for_string(code, 'module') tags = analyzer.find_tags() assert set(tags.keys()) == { 'Foo', 'Foo.__init__', 'Foo.bar', 'Foo.Baz', 'Foo.Baz.__init__', 'qux', 'quux' } assert tags['Foo'] == ('class', 1, 13) # type, start, end assert tags['Foo.__init__'] == ('def', 3, 5) assert tags['Foo.bar'] == ('def', 6, 9) assert tags['Foo.Baz'] == ('class', 10, 13) assert tags['Foo.Baz.__init__'] == ('def', 11, 13) assert tags['qux'] == ('def', 14, 17) assert tags['quux'] == ('def', 18, 21) # decorator
def test_ModuleAnalyzer_find_tags(): code = ('class Foo(object):\n' # line: 1 ' """class Foo!"""\n' ' def __init__(self):\n' ' pass\n' '\n' ' def bar(self, arg1, arg2=True, *args, **kwargs):\n' ' """method Foo.bar"""\n' ' pass\n' '\n' ' class Baz(object):\n' ' def __init__(self):\n' # line: 11 ' pass\n' '\n' 'def qux():\n' ' """function baz"""\n' ' pass\n' '\n' '@decorator\n' 'def quux():\n' ' pass\n') analyzer = ModuleAnalyzer.for_string(code, 'module') tags = analyzer.find_tags() assert set(tags.keys()) == {'Foo', 'Foo.__init__', 'Foo.bar', 'Foo.Baz', 'Foo.Baz.__init__', 'qux', 'quux'} assert tags['Foo'] == ('class', 1, 13) # type, start, end assert tags['Foo.__init__'] == ('def', 3, 5) assert tags['Foo.bar'] == ('def', 6, 9) assert tags['Foo.Baz'] == ('class', 10, 13) assert tags['Foo.Baz.__init__'] == ('def', 11, 13) assert tags['qux'] == ('def', 14, 17) assert tags['quux'] == ('def', 18, 21) # decorator
def test_ModuleAnalyzer_for_file(): analyzer = ModuleAnalyzer.for_string(SPHINX_MODULE_PATH, 'sphinx') assert analyzer.modname == 'sphinx' assert analyzer.srcname == '<string>' if PY2: assert analyzer.encoding == 'ascii' else: assert analyzer.encoding is None
def test_ModuleAnalyzer_for_string(): analyzer = ModuleAnalyzer.for_string('print("Hello world")', 'module_name') assert analyzer.modname == 'module_name' assert analyzer.srcname == '<string>' if PY2: assert analyzer.encoding == 'ascii' else: assert analyzer.encoding is None
def test_ModuleAnalyzer_find_attr_docs_for_posonlyargs_method(): code = ('class Foo(object):\n' ' def __init__(self, /):\n' ' self.attr = None #: attribute comment\n') analyzer = ModuleAnalyzer.for_string(code, 'module') docs = analyzer.find_attr_docs() assert set(docs) == {('Foo', 'attr')} assert docs[('Foo', 'attr')] == ['attribute comment', ''] assert analyzer.tagorder == {'Foo': 0, 'Foo.__init__': 1, 'Foo.attr': 2}
def test_ModuleAnalyzer_pep3132(): code = ('class Foo(object):\n' ' """class Foo!"""\n' ' #: comment before attr1\n' ' attr1 = None\n' '\n' ' def bar(self, *args, **kwargs):\n' ' """method Foo.bar"""\n' ' head, *tail = kwargs\n') analyzer = ModuleAnalyzer.for_string(code, 'module') docs = analyzer.find_attr_docs() assert set(docs) == {('Foo', 'attr1')}
def test_ModuleAnalyzer_find_attr_docs(): code = ('class Foo(object):\n' ' """class Foo!"""\n' ' #: comment before attr1\n' ' attr1 = None\n' ' attr2 = None # attribute comment for attr2 (without colon)\n' ' attr3 = None #: attribute comment for attr3\n' ' attr4 = None #: long attribute comment\n' ' #: for attr4\n' ' #: comment before attr5\n' ' attr5 = None #: attribute comment for attr5\n' ' attr6, attr7 = 1, 2 #: this comment is ignored\n' '\n' ' def __init__(self):\n' ' self.attr8 = None #: first attribute comment (ignored)\n' ' self.attr8 = None #: attribute comment for attr8\n' ' #: comment before attr9\n' ' self.attr9 = None #: comment after attr9\n' ' "string after attr9"\n' '\n' ' def bar(self, arg1, arg2=True, *args, **kwargs):\n' ' """method Foo.bar"""\n' ' pass\n' '\n' 'def baz():\n' ' """function baz"""\n' ' pass\n') analyzer = ModuleAnalyzer.for_string(code, 'module') docs = analyzer.find_attr_docs() assert set(docs) == {('Foo', 'attr1'), ('Foo', 'attr3'), ('Foo', 'attr4'), ('Foo', 'attr5'), ('Foo', 'attr8'), ('Foo', 'attr9')} assert docs[('Foo', 'attr1')] == ['comment before attr1', ''] assert docs[('Foo', 'attr3')] == ['attribute comment for attr3', ''] assert docs[('Foo', 'attr4')] == ['long attribute comment', ''] assert docs[('Foo', 'attr4')] == ['long attribute comment', ''] assert docs[('Foo', 'attr5')] == ['attribute comment for attr5', ''] assert docs[('Foo', 'attr8')] == ['attribute comment for attr8', ''] assert docs[('Foo', 'attr9')] == ['string after attr9', '']
def test_ModuleAnalyzer_find_attr_docs(): code = ('class Foo(object):\n' ' """class Foo!"""\n' ' #: comment before attr1\n' ' attr1 = None\n' ' attr2 = None # attribute comment for attr2 (without colon)\n' ' attr3 = None #: attribute comment for attr3\n' ' attr4 = None #: long attribute comment\n' ' #: for attr4\n' ' #: comment before attr5\n' ' attr5 = None #: attribute comment for attr5\n' ' attr6, attr7 = 1, 2 #: this comment is ignored\n' '\n' ' def __init__(self):\n' ' self.attr8 = None #: first attribute comment (ignored)\n' ' self.attr8 = None #: attribute comment for attr8\n' ' #: comment before attr9\n' ' self.attr9 = None #: comment after attr9\n' ' "string after attr9"\n' '\n' ' def bar(self, arg1, arg2=True, *args, **kwargs):\n' ' """method Foo.bar"""\n' ' pass\n' '\n' 'def baz():\n' ' """function baz"""\n' ' pass\n' '\n' 'class Qux: attr1 = 1; attr2 = 2') analyzer = ModuleAnalyzer.for_string(code, 'module') docs = analyzer.find_attr_docs() assert set(docs) == {('Foo', 'attr1'), ('Foo', 'attr3'), ('Foo', 'attr4'), ('Foo', 'attr5'), ('Foo', 'attr6'), ('Foo', 'attr7'), ('Foo', 'attr8'), ('Foo', 'attr9')} assert docs[('Foo', 'attr1')] == ['comment before attr1', ''] assert docs[('Foo', 'attr3')] == ['attribute comment for attr3', ''] assert docs[('Foo', 'attr4')] == ['long attribute comment', ''] assert docs[('Foo', 'attr4')] == ['long attribute comment', ''] assert docs[('Foo', 'attr5')] == ['attribute comment for attr5', ''] assert docs[('Foo', 'attr6')] == ['this comment is ignored', ''] assert docs[('Foo', 'attr7')] == ['this comment is ignored', ''] assert docs[('Foo', 'attr8')] == ['attribute comment for attr8', ''] assert docs[('Foo', 'attr9')] == ['string after attr9', ''] assert analyzer.tagorder == { 'Foo': 0, 'Foo.__init__': 8, 'Foo.attr1': 1, 'Foo.attr2': 2, 'Foo.attr3': 3, 'Foo.attr4': 4, 'Foo.attr5': 5, 'Foo.attr6': 6, 'Foo.attr7': 7, 'Foo.attr8': 10, 'Foo.attr9': 12, 'Foo.bar': 13, 'baz': 14, 'Qux': 15, 'Qux.attr1': 16, 'Qux.attr2': 17 }
def test_ModuleAnalyzer_for_file(): analyzer = ModuleAnalyzer.for_string(SPHINX_MODULE_PATH, 'sphinx') assert analyzer.modname == 'sphinx' assert analyzer.srcname == '<string>'
def test_ModuleAnalyzer_for_string(): analyzer = ModuleAnalyzer.for_string('print("Hello world")', 'module_name') assert analyzer.modname == 'module_name' assert analyzer.srcname == '<string>'
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_ModuleAnalyzer_find_attr_docs(): code = ('class Foo(object):\n' ' """class Foo!"""\n' ' #: comment before attr1\n' ' attr1 = None\n' ' attr2 = None # attribute comment for attr2 (without colon)\n' ' attr3 = None #: attribute comment for attr3\n' ' attr4 = None #: long attribute comment\n' ' #: for attr4\n' ' #: comment before attr5\n' ' attr5 = None #: attribute comment for attr5\n' ' attr6, attr7 = 1, 2 #: this comment is ignored\n' '\n' ' def __init__(self):\n' ' self.attr8 = None #: first attribute comment (ignored)\n' ' self.attr8 = None #: attribute comment for attr8\n' ' #: comment before attr9\n' ' self.attr9 = None #: comment after attr9\n' ' "string after attr9"\n' '\n' ' def bar(self, arg1, arg2=True, *args, **kwargs):\n' ' """method Foo.bar"""\n' ' pass\n' '\n' 'def baz():\n' ' """function baz"""\n' ' pass\n' '\n' 'class Qux: attr1 = 1; attr2 = 2') analyzer = ModuleAnalyzer.for_string(code, 'module') docs = analyzer.find_attr_docs() assert set(docs) == {('Foo', 'attr1'), ('Foo', 'attr3'), ('Foo', 'attr4'), ('Foo', 'attr5'), ('Foo', 'attr6'), ('Foo', 'attr7'), ('Foo', 'attr8'), ('Foo', 'attr9')} assert docs[('Foo', 'attr1')] == ['comment before attr1', ''] assert docs[('Foo', 'attr3')] == ['attribute comment for attr3', ''] assert docs[('Foo', 'attr4')] == ['long attribute comment', ''] assert docs[('Foo', 'attr4')] == ['long attribute comment', ''] assert docs[('Foo', 'attr5')] == ['attribute comment for attr5', ''] assert docs[('Foo', 'attr6')] == ['this comment is ignored', ''] assert docs[('Foo', 'attr7')] == ['this comment is ignored', ''] assert docs[('Foo', 'attr8')] == ['attribute comment for attr8', ''] assert docs[('Foo', 'attr9')] == ['string after attr9', ''] assert analyzer.tagorder == {'Foo': 0, 'Foo.__init__': 8, 'Foo.attr1': 1, 'Foo.attr2': 2, 'Foo.attr3': 3, 'Foo.attr4': 4, 'Foo.attr5': 5, 'Foo.attr6': 6, 'Foo.attr7': 7, 'Foo.attr8': 10, 'Foo.attr9': 12, 'Foo.bar': 13, 'baz': 14, 'Qux': 15, 'Qux.attr1': 16, 'Qux.attr2': 17}