def get_import_string(self): # type: () -> ImportString """ Get import string from a node. Returns: An absolute import string. """ if self.source: return ImportString(self.source) + self.name return ImportString(self.name)
def generate_modules(self): # type: () -> None """ Generate `<output>/README.md` file with title from `<root>/README.md` and `Modules` section that contains a Tree of all modules in the project. """ self._logger.debug("Generating {}".format( self._root_path_finder.relative(self.md_modules.path))) with self.md_modules as md_modules: if not md_modules.title: md_modules.title = "{} {}".format(self._project_name, self.MODULES_TITLE) autogenerated_marker = "> Auto-generated documentation modules index." subtitle_parts = [autogenerated_marker] if md_modules.subtitle: subtitle_parts.append(md_modules.subtitle) subtitle_parts.append("Full list of {} project modules.".format( md_modules.render_md_doc_link(self.md_index, title=self._project_name))) md_modules.subtitle = "\n\n".join(subtitle_parts) modules_toc_lines = self._build_modules_toc_lines( import_string=ImportString(""), max_depth=10, md_document=md_modules, start_level=1, ) md_doc_link = md_modules.render_md_doc_link(self.md_index) modules_toc_lines.insert( 0, md_modules.get_toc_line(md_doc_link, level=0)) md_modules.toc_section = "\n".join(modules_toc_lines)
def test_build_children(self, ModuleAnalyzerMock): node = MagicMock() node.name = "name" node.body = ["body"] node.mock_add_spec(ast.Module) record = ModuleRecord(node) record.import_string = ImportString("my_module") record.name = "class_node" class_node = MagicMock() class_node.name = "ClassNode" method_node = MagicMock() method_node.name = "class_method" method_node.body = ["class_method"] method_node.mock_add_spec(ast.FunctionDef) class_node.body = [method_node] class_node.decorator_list = [] class_node.bases = [] class_node.mock_add_spec(ast.ClassDef) function_node = MagicMock() function_node.decorator_list = [] function_node.name = "function_node" function_node.body = ["function_body"] function_node.mock_add_spec(ast.FunctionDef) attribute_node = MagicMock() attribute_target = MagicMock() attribute_target.id = "attribute_target" attribute_target.mock_add_spec(ast.Name) attribute_node.targets = [attribute_target] attribute_node.value = "attribute_value" attribute_node.mock_add_spec(ast.Assign) import_node = MagicMock() import_name = MagicMock() import_name.name = "import_name" import_name_2 = MagicMock() import_name_2.name = "import_name_2" import_node.names = [import_name, import_name_2] import_node.mock_add_spec(ast.Import) ModuleAnalyzerMock().class_nodes = [class_node] ModuleAnalyzerMock().function_nodes = [function_node] ModuleAnalyzerMock().attribute_nodes = [attribute_node] ModuleAnalyzerMock().import_nodes = [import_node] self.assertIsNone(record.build_children()) self.assertEqual(record.title, "ClassNode") self.assertEqual(record.class_records[0].node, class_node) self.assertEqual(record.function_records[0].node, function_node) self.assertEqual(record.attribute_records[0].node, attribute_node) self.assertEqual(record.import_records[0].node, import_node) self.assertEqual(record.import_records[0].name, "import_name") self.assertEqual(record.import_records[1].node, import_node) self.assertEqual(record.import_records[1].name, "import_name_2") self.assertEqual( record.class_records[0].import_string.value, "my_module.ClassNode" ) self.assertEqual( record.class_records[0].method_records[0].import_string.value, "my_module.ClassNode.class_method", ) self.assertEqual( record.function_records[0].import_string.value, "my_module.function_node" )
def test_generate_docs(self, PathFinderMock, MDDocumentMock, ModuleRecordListMock, LoaderMock): source_path_mock = MagicMock() generator = Generator( project_name="test", input_path=Path("/input"), output_path=Path("/output"), source_paths=[source_path_mock], ) MDDocumentMock().render_md_doc_link.return_value = "md_doc_link" MDDocumentMock().render_doc_link.return_value = "doc_link" module_record_mock = MagicMock() module_record_mock.title = "Title" module_record_mock.docstring = "Docstring" module_record_mock.import_string = ImportString("my.import.string") ModuleRecordListMock().__iter__ = MagicMock( return_value=iter([module_record_mock])) generator.generate_docs() LoaderMock.assert_called_with(output_path=Path("/output"), root_path=Path("/input")) PathFinderMock.assert_called_with(Path("/output"))
def _replace_links(self, module_record, record, md_document, docstring): # type: (ModuleRecord, NodeRecord, MDDocument, Text) -> Text parent_import_string = None if not record.import_string.is_top_level(): parent_import_string = record.import_string.parent for match in self._short_link_re.findall(docstring): related_record_name = match.replace("`", "") related_import_string = None related_record = None target_path = md_document.path # find record in parent if parent_import_string: related_import_string = parent_import_string + related_record_name if related_import_string != record.import_string: related_record = module_record.find_record( related_import_string) # find record in module if not related_record: related_import_string = (module_record.import_string + related_record_name) related_record = module_record.find_record( related_import_string) # find record globally if not related_record: related_import_string = ImportString(related_record_name) related_module_record = self._module_records.find_module_record( related_import_string) if related_module_record: related_record = related_module_record.find_record( related_import_string) target_path = self._loader.get_output_path( related_module_record.source_path) if not related_record: continue if related_record.import_string.startswith(record.import_string): continue title = related_record.title anchor = md_document.get_anchor(related_record.title) if isinstance(related_record, AttributeRecord): parent_related_record = module_record.find_record( related_record.import_string.parent) if parent_related_record: anchor = md_document.get_anchor( parent_related_record.title) link = md_document.render_doc_link(title, anchor=anchor, target_path=target_path) docstring = docstring.replace(match, link) self._logger.debug("Adding local link '{}' to '{}'".format( title, record.title)) return docstring
def test_generate_doc(self, PathFinderMock, MDDocumentMock, ModuleRecordListMock, LoaderMock): source_path_mock = MagicMock() generator = Generator( project_name="test", input_path=Path("/input"), output_path=Path("/output"), source_paths=[source_path_mock], raise_errors=True, ) MDDocumentMock().render_md_doc_link.return_value = "md_doc_link" MDDocumentMock().render_doc_link.return_value = "doc_link" module_record_mock = MagicMock() module_record_mock.title = "Title" module_record_mock.docstring = "Docstring" module_record_mock.source_path = Path("/input/source.py") module_record_mock.import_string = ImportString("my.import.string") module_record_mock2 = MagicMock() module_record_mock2.title = "Title" module_record_mock2.docstring = "Docstring" module_record_mock2.source_path = Path("/input/source2.py") module_record_mock2.import_string = ImportString("my.import.string2") ModuleRecordListMock().__iter__ = MagicMock( return_value=iter([module_record_mock, module_record_mock2])) generator.generate_doc(Path("/input/source2.py")) LoaderMock.assert_called_with(output_path=Path("/output"), root_path=Path("/input")) PathFinderMock.assert_called_with(Path("/output")) ModuleRecordListMock().__iter__ = MagicMock( return_value=iter([module_record_mock])) with self.assertRaises(GeneratorError): generator.generate_doc(Path("/input/source2.py")) LoaderMock().parse_module_record.side_effect = ValueError( "loader_error") ModuleRecordListMock().__iter__ = MagicMock( return_value=iter([module_record_mock2])) with self.assertRaises(ValueError): generator.generate_doc(Path("/input/source2.py"))
def __init__(self, node): # type: (ast.AST) -> None self.docstring = "" self.import_string = ImportString("") self.node = node self.name = self.node.__class__.__name__ self.title = "" self.is_method = False self.attribute_records = [] # type: List[AttributeRecord] self.parsed = False self._line_number = None # type: Optional[int]
def __init__(self, node): # type: (ast.Module) -> None super(ModuleRecord, self).__init__(node) self.all_names = [] # type: List[Text] self.class_records = [] # type: List[ClassRecord] self.function_records = [] # type: List[FunctionRecord] self.import_records = [] # type: List[ImportRecord] self.source_path = Path("") self.source_lines = [] # type: List[Text] self.name = "module" self.title = "" self.import_string = ImportString("") self.import_string_map = {} # type: Dict[ImportString, NodeRecord] self.docstring = self._get_docstring()
def test_startswith(self): self.assertTrue( ImportString("parent.parent2.value").startswith(ImportString("parent")) ) self.assertTrue( ImportString("parent.parent2.value").startswith( ImportString("parent.parent2") ) ) self.assertFalse( ImportString("parent.parent2.value").startswith(ImportString("parent2")) ) self.assertFalse( ImportString("parent.parent2.value").startswith( ImportString("parent.parent2value") ) )
def get_module_record(self, source_path): # type: (Path) -> Optional[ModuleRecord] """ Build `ModuleRecord` for given `source_path`. Arguments: source_path -- Absolute path to source file. Returns: A new `ModuleRecord` instance or None if there is ntohing to import. Raises: LoaderError -- If python source cannot be loaded. """ if not (source_path.parent / "__init__.py").exists(): return None if source_path.name == "__init__.py" and source_path.parent == self._root_path: return None import_string = self.get_import_string(source_path) docstring_parts = [] try: module_record = ModuleRecord.create_from_source( source_path, ImportString(import_string)) module_record.build_children() except Exception as e: raise LoaderError("{} while loading {}: {}".format( e.__class__.__name__, source_path, e)) if module_record.docstring: docstring_parts.append(module_record.docstring) if source_path.name == "__init__.py": readme_md_path = source_path.parent / "README.md" if readme_md_path.exists(): docstring_parts.append(readme_md_path.read_text()) docstring = "\n\n".join(docstring_parts) title, docstring = extract_md_title(docstring) if title: module_record.title = title module_record.docstring = docstring return module_record
def match(self, name): # type: (Text) -> Optional[ImportString] """ Check if `name` matches or stats with a local name. Examples:: import_node = ast.parse('from my_module import Name as LocalName') import_record = ImportRecord(import_node) import_record.match('LocalName') True import_record.match('LocalName.child') True import_record.match('OtherName') False import_record.match('LocalNameOther') False Returns: True if name is imported object itself on one of his children. """ if name == self.local_name: return self.get_import_string() lookup = "{}.".format(self.local_name) if name.startswith(lookup): if self.source: trailing_import = name[len(lookup):] return ImportString("{}.{}".format(self.get_import_string(), trailing_import)) return None
def test_init(self): self.assertEqual(ImportString("value").value, "value") self.assertEqual(str(ImportString("value")), "value") self.assertTrue(hash(ImportString("value"))) self.assertTrue(ImportString("value")) self.assertFalse(ImportString("")) self.assertEqual((ImportString("value") + "add").value, "value.add") self.assertEqual((ImportString("") + "add").value, "add") self.assertEqual(ImportString("value"), ImportString("value")) self.assertEqual(ImportString("value"), "value") self.assertNotEqual(ImportString("value"), ImportString("value1")) self.assertNotEqual(ImportString("value"), "value1") self.assertNotEqual(ImportString("value"), b"value") self.assertEqual( ImportString("parent.parent2.value").parent.value, "parent.parent2" ) with self.assertRaises(ImportStringError): _ = ImportString("value").parent
def test_is_top_level(self): self.assertTrue(ImportString("value").is_top_level()) self.assertTrue(ImportString("").is_top_level()) self.assertFalse(ImportString("parent.value").is_top_level())