# -*- coding: utf-8 -*-"""Automatic API reference documentation generator for Sphinx.While Sphinx's `autodoc extension <https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html>`_lets you document Python modules, it still requiresmanually including each module and defining members in your documentation. This moduleautomates that process by scanning your entire package structure and generating thenecessary .rst files with proper autodoc directives for all modules and packages. It supportsignoring specific modules or packages through pattern matching, allowing you to excludeinternal, private, or third-party code from your documentation."""importshutilimportdataclassesfrompathlibimportPathfromfunctoolsimportcached_propertyfrompicage.apiimportPackagefrom.templateimport(render_module,PackageTemplateParams,render_package,)
[docs]defnormalize_ignore_patterns(patterns:list[str])->list[str]:""" Normalize ignore patterns by removing ``.py`` extensions. :param patterns: List of patterns to normalize :return: List of normalized patterns """normalized=[]forpatterninpatterns:ifpattern.endswith(".py"):pattern=pattern[:-3]normalized.append(pattern)returnnormalized
[docs]defshould_ignore(fullname:str,normalized_patterns:list[str],)->bool:""" Determine if a module or package should be ignored based on its name. Checks if the module or package fullname matches any of the ignore patterns. A match occurs when the fullname starts with any pattern in the list. :param fullname: Full name of the module or package (e.g., "docfly.auto_api_doc") :param normalized_patterns: List of normalized ignore patterns :return: True if the module/package should be ignored, False otherwise Examples: >>> should_ignore("docfly.vendor", ["docfly.vendor"]) True >>> should_ignore("docfly.vendor.picage", ["docfly.vendor"]) True >>> should_ignore("docfly.auto_api_doc", ["docfly.vendor"]) False >>> should_ignore("docfly.tests", ["docfly.tests", "docfly.vendor"]) True >>> should_ignore("docfly._version", ["docfly._"]) True """forpatterninnormalized_patterns:iffullname.startswith(pattern):returnTruereturnFalse
[docs]defwrite_file(path:Path,text:str):""" Write text to a file, creating parent directories if needed. """try:path.write_text(text,encoding="utf-8")exceptFileNotFoundError:path.parent.mkdir(parents=True)path.write_text(text,encoding="utf-8")
[docs]@dataclasses.dataclassclassApiDocGenerator:""" Generator for Sphinx API reference documentation. This class traverses a Python package structure and generates ``.rst`` files with appropriate autodoc directives for each module and package. The generated files maintain the package hierarchy and can be directly included in Sphinx documentation. Example generated autodoc code: .. code-block:: bash /path/to/dir_output/package /path/to/dir_output/package/subpackage1 /path/to/dir_output/package/subpackage1/__init__.rst /path/to/dir_output/package/subpackage1/module.rst /path/to/dir_output/package/subpackage2/ /path/to/dir_output/package/subpackage2/__init__.rst /path/to/dir_output/package/subpackage2/module.rst /path/to/dir_output/package/__init__.rst /path/to/dir_output/package/module1.rst /path/to/dir_output/package/module2.rst :param dir_output: Directory where generated ``.rst`` files will be written. Usually it is next to the sphinx doc ``conf.py`` file. For example, if your ``conf.py`` file is at ``docs/source/conf.py``, and your package name is ``docfly``, you should set ``dir_output`` to ``docs/source/api``. Then the autodoc code looks like:: /docs/source/api/docfly /docs/source/api/docfly/__init__.rst /docs/source/api/docfly/module1.rst /docs/source/api/docfly/module2.rst /docs/source/api/docfly/... :param package_name: Name of the package to document. :param ignore_patterns: List of patterns for modules/packages to ignore. See :func:`should_ignore` for details on how patterns are matched. """dir_output:Path=dataclasses.field()package_name:str=dataclasses.field()ignore_patterns:list[str]=dataclasses.field(default_factory=list)def__post_init__(self):self.ignore_patterns=normalize_ignore_patterns(self.ignore_patterns)@cached_propertydefpackage(self)->Package:""" Get the package object for the specified package name. """returnPackage(self.package_name)
[docs]deffly(self,cleanup_before_fly:bool=True,):""" Generate the API documentation .rst files. Traverses the package structure and creates ``.rst`` files for each package and module. Each package gets an ``__init__.rst`` file that includes links to its sub-packages and modules. :param cleanup_before_fly: If True, remove existing output directory before generating new documentation """# clearn up existing api documentifcleanup_before_fly:shutil.rmtree(self.dir_output,ignore_errors=True)# create .rst filesforpackage,parent,sub_packages,sub_modulesinself.package.walk():ifshould_ignore(package.fullname,self.ignore_patterns):continuefiltered_sub_packages=[sub_packageforsub_packageinsub_packagesifshould_ignore(sub_package.fullname,self.ignore_patterns)isFalse]filtered_sub_modules=[sub_moduleforsub_moduleinsub_modulesifshould_ignore(sub_module.fullname,self.ignore_patterns)isFalse]package_template_params=PackageTemplateParams(package=package,sub_packages=filtered_sub_packages,sub_modules=filtered_sub_modules,)dir_package=self.dir_output.joinpath(*package.fullname.split("."))path_init_rst=dir_package.joinpath("__init__.rst")content=render_package(package_template_params)write_file(path_init_rst,content)formoduleinfiltered_sub_modules:path_module=dir_package.joinpath(f"{module.shortname}.rst")content=render_module(module)write_file(path_module,content)