Source code for formelsammlung.flask_sphinx_docs

"""
    formelsammlung.flask_sphinx_docs
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Serve sphinx docs in your flask app.

    :copyright: (c) 2020, Christian Riedel and AUTHORS
    :license: GPL-3.0-or-later, see LICENSE for details
"""  # noqa: D205,D208,D400
from pathlib import Path
from typing import Optional

from flask import Flask, Response


[docs]class SphinxDocServer: # noqa: R0903 """Serve your sphinx docs under `/docs/` on your own flask app.""" def __init__( self, app: Optional[Flask] = None, *, doc_dir: Optional[str] = None, index_file: str = "index.html", ) -> None: """Init SphinxDocServer class. .. highlight:: python You can invoke it in your app factory:: sds = SphinxDocServer() def create_app(): app = Flask(__name__) sds.init_app(app) return app or you can include the plugin directly without setting a ``doc_dir``:: app = Flask(__name__) SphinxDocServer(app) or with setting a ``doc_dir``:: app = Flask(__name__) SphinxDocServer(app, doc_dir="../../docs/build/html") .. highlight:: default :param app: Same argument as for and gets given to :meth:`SphinxDocServer.init_app`. :param doc_dir: Same argument as for and gets given to :meth:`SphinxDocServer.init_app`. :param index_file: Same argument as for and gets given to :meth:`SphinxDocServer.init_app`. """ if app is not None: self.init_app(app, doc_dir, index_file)
[docs] def init_app( self, app: Flask, doc_dir: Optional[str] = None, index_file: str = "index.html" ) -> None: """Add the `/docs/` route to the `app` object. :param app: Flask object to add the route to. :param doc_dir: The base directory holding the sphinx docs to serve. If not set the ``doc_dir`` is guessed up to 3 directories above. :param index_file: The html file containing the base toctree. Default: "index.html" """ @app.route("/docs/", defaults={"filename": index_file}) @app.route("/docs/<path:filename>") def web_docs(filename: str) -> Response: # noqa: W0612 """Route the given doc page. :param filename: File name from URL :return: Requested doc page """ static_folder = app.static_folder app.static_folder = doc_dir or str( self._find_built_docs(app.root_path or "") ) doc_file = app.send_static_file(filename) app.static_folder = static_folder return doc_file
@staticmethod def _find_build_dir(doc_dir: Path) -> Path: """Find build dir in given doc dir. :param doc_dir: Path to doc dir :raises IOError: if no '_build' or 'build' directory is found in the doc/docs dir. :return: Path to build dir """ build_dir = None if (doc_dir / "_build").is_dir(): build_dir = doc_dir / "_build" if (doc_dir / "build").is_dir(): build_dir = doc_dir / "build" if not build_dir: raise OSError( f"No '_build' or 'build' directory found in {doc_dir}. " "Maybe you forgot to build the docs." ) return build_dir @staticmethod def _find_built_docs(app_root: str, steps_up_the_tree: int = 3) -> Path: """Find built sphinx html docs. :param app_root: Root directory of the app. :param steps_up_the_tree: Amount of steps to go up the file tree, defaults to 3 :raises IOError: if no root dir path for the app is given. :raises IOError: if no 'doc' or 'docs' directory is found. :raises IOError: if no 'html' directory is found in the _build/build dir. :return: Path to directory holding the build sphinx docs. """ if not app_root: raise OSError("Got no root dir for the flask app to start search.") check_dir = file_dir = Path(app_root).parent #: Search doc(s) dir up the tree doc_dir = None for i in range(0, steps_up_the_tree + 1): if (check_dir / "doc").is_dir(): doc_dir = check_dir / "doc" if (check_dir / "docs").is_dir(): doc_dir = check_dir / "docs" if doc_dir: break check_dir = file_dir.parents[i] if not doc_dir: raise OSError("No 'doc' or 'docs' directory found.") #: search for (_)build dir build_dir = SphinxDocServer._find_build_dir(doc_dir) #: check for html dir if (build_dir / "html").is_dir(): return build_dir / "html" raise OSError( f"No 'html' directory found in {build_dir}. " "Maybe you forgot to build the HTML docs." )