From 109d680dcd227e873c6f76f1a875c4508fe166f8 Mon Sep 17 00:00:00 2001 From: Muhammad Aliyan Madni <80422517+whoisalyan@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:21:44 +0500 Subject: [PATCH] Add comprehensive docstrings to documentation management script - Add detailed module and function-level docstrings - Document all parameters, return values, and exceptions - Include usage examples and command descriptions - Add type hints and comprehensive function explanations - Improve code maintainability and developer onboarding --- scripts/docs.py | 233 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 206 insertions(+), 27 deletions(-) diff --git a/scripts/docs.py b/scripts/docs.py index 56ffb9d36..eab245620 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -1,3 +1,21 @@ +""" +FastAPI Documentation Management Script. + +This script provides a comprehensive set of commands for managing multi-language +documentation for FastAPI. It handles building, serving, verifying, and maintaining +documentation across multiple languages. + +Key features: +- Building documentation for individual languages or all languages +- Generating and verifying README content +- Managing language configurations +- Serving documentation for development +- Verifying documentation integrity + +The script uses Typer for CLI interface and supports parallel processing for +efficient builds. +""" + import json import logging import os @@ -17,16 +35,19 @@ import yaml from jinja2 import Template from ruff.__main__ import find_ruff_bin +# Configure logging logging.basicConfig(level=logging.INFO) +# Initialize Typer app app = typer.Typer() +# Constants mkdocs_name = "mkdocs.yml" - missing_translation_snippet = """ {!../../docs/missing-translation.md!} """ +# Sections that should not be translated non_translated_sections = [ "reference/", "release-notes.md", @@ -38,30 +59,59 @@ non_translated_sections = [ "contributing.md", ] +# Path configurations docs_path = Path("docs") en_docs_path = Path("docs/en") en_config_path: Path = en_docs_path / mkdocs_name site_path = Path("site").absolute() build_site_path = Path("site_build").absolute() +# Regex pattern for header permalinks header_with_permalink_pattern = re.compile(r"^(#{1,6}) (.+?)(\s*\{\s*#.*\s*\})\s*$") @lru_cache def is_mkdocs_insiders() -> bool: + """ + Check if mkdocs-material insiders version is installed. + + Returns: + bool: True if insiders version is detected, False otherwise + """ version = metadata.version("mkdocs-material") return "insiders" in version def get_en_config() -> Dict[str, Any]: + """ + Load and parse the English documentation configuration file. + + Returns: + Dict[str, Any]: Parsed YAML content from mkdocs.yml + """ return mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8")) def get_lang_paths() -> List[Path]: + """ + Get all language directory paths from the docs directory. + + Returns: + List[Path]: Sorted list of Path objects for each language directory + """ return sorted(docs_path.iterdir()) def lang_callback(lang: Optional[str]) -> Union[str, None]: + """ + Typer callback function to normalize language input. + + Args: + lang: Language code to normalize + + Returns: + Normalized lowercase language code or None if input is None + """ if lang is None: return None lang = lang.lower() @@ -69,6 +119,15 @@ def lang_callback(lang: Optional[str]) -> Union[str, None]: def complete_existing_lang(incomplete: str): + """ + Autocomplete function for existing languages. + + Args: + incomplete: Partial language name to complete + + Yields: + str: Matching language directory names + """ lang_path: Path for lang_path in get_lang_paths(): if lang_path.is_dir() and lang_path.name.startswith(incomplete): @@ -77,6 +136,11 @@ def complete_existing_lang(incomplete: str): @app.callback() def callback() -> None: + """ + Global callback function executed before any command. + + Sets up environment variables for mkdocs-insiders and MacOS Cairo support. + """ if is_mkdocs_insiders(): os.environ["INSIDERS_FILE"] = "../en/mkdocs.insiders.yml" # For MacOS with insiders and Cairo @@ -86,7 +150,16 @@ def callback() -> None: @app.command() def new_lang(lang: str = typer.Argument(..., callback=lang_callback)): """ - Generate a new docs translation directory for the language LANG. + Generate a new docs translation directory for the specified language. + + Creates the necessary directory structure, configuration file, and initial + content for a new language translation. + + Args: + lang: Language code for the new translation (e.g., 'es', 'fr') + + Raises: + typer.Abort: If the language directory already exists """ new_path: Path = Path("docs") / lang if new_path.exists(): @@ -113,7 +186,16 @@ def build_lang( ), ) -> None: """ - Build the docs for a language. + Build the documentation for a specific language. + + Uses mkdocs to build the documentation site for the specified language + and copies the output to the appropriate location. + + Args: + lang: Language code to build (e.g., 'en', 'es') + + Raises: + typer.Abort: If the language directory doesn't exist """ insiders_env_file = os.environ.get("INSIDERS_FILE") print(f"Insiders file {insiders_env_file}") @@ -144,6 +226,7 @@ def build_lang( typer.secho(f"Successfully built docs for: {lang}", color=typer.colors.GREEN) +# Template for sponsors section in README index_sponsors_template = """ {% if sponsors %} {% for sponsor in sponsors.gold -%} @@ -156,7 +239,16 @@ index_sponsors_template = """ """ -def remove_header_permalinks(content: str): +def remove_header_permalinks(content: str) -> str: + """ + Remove permalinks from markdown headers. + + Args: + content: Markdown content with header permalinks + + Returns: + str: Markdown content with permalinks removed from headers + """ lines: list[str] = [] for line in content.split("\n"): match = header_with_permalink_pattern.match(line) @@ -168,6 +260,18 @@ def remove_header_permalinks(content: str): def generate_readme_content() -> str: + """ + Generate README.md content from the main English index.md file. + + Processes the index.md file to remove mkdocs-specific elements and + generates appropriate content for GitHub README. + + Returns: + str: Generated README content + + Raises: + RuntimeError: If required sections are not found in the source content + """ en_index = en_docs_path / "docs" / "index.md" content = en_index.read_text("utf-8") content = remove_header_permalinks(content) # remove permalinks from headers @@ -201,7 +305,11 @@ def generate_readme_content() -> str: @app.command() def generate_readme() -> None: """ - Generate README.md content from main index.md + Generate README.md file from the main English documentation index.md. + + This command processes the main documentation index file to create + a GitHub-appropriate README with sponsors information and without + mkdocs-specific elements. """ typer.echo("Generating README") readme_path = Path("README.md") @@ -212,7 +320,13 @@ def generate_readme() -> None: @app.command() def verify_readme() -> None: """ - Verify README.md content from main index.md + Verify that README.md is up-to-date with the main documentation index. + + Compares the current README content with what would be generated + from the main index.md file and reports any discrepancies. + + Raises: + typer.Abort: If README.md is outdated """ typer.echo("Verifying README") readme_path = Path("README.md") @@ -229,8 +343,15 @@ def verify_readme() -> None: @app.command() def build_all() -> None: """ - Build mkdocs site for en, and then build each language inside, end result is located - at directory ./site/ with each language inside. + Build documentation for all available languages. + + This command: + 1. Updates language configurations + 2. Cleans the output directory + 3. Builds documentation for all languages in parallel + 4. Places the final output in the site/ directory + + Uses multiprocessing for efficient parallel builds across all CPU cores. """ update_languages() shutil.rmtree(site_path, ignore_errors=True) @@ -245,7 +366,10 @@ def build_all() -> None: @app.command() def update_languages() -> None: """ - Update the mkdocs.yml file Languages section including all the available languages. + Update the mkdocs.yml file with all available languages. + + This command regenerates the language configuration section in the + main mkdocs.yml file to include all currently available translations. """ update_config() @@ -253,13 +377,12 @@ def update_languages() -> None: @app.command() def serve() -> None: """ - A quick server to preview a built site with translations. - - For development, prefer the command live (or just mkdocs serve). - - This is here only to preview a site with translations already built. - - Make sure you run the build-all command first. + Start a simple HTTP server to preview the built documentation site. + + This is a basic server for previewing the complete built site with + all translations. For development, use the 'live' command instead. + + Note: Requires running 'build-all' first to generate the site. """ typer.echo("Warning: this is a very simple server.") typer.echo("For development, use the command live instead.") @@ -280,13 +403,14 @@ def live( dirty: bool = False, ) -> None: """ - Serve with livereload a docs site for a specific language. - - This only shows the actual translated files, not the placeholders created with - build-all. - - Takes an optional LANG argument with the name of the language to serve, by default - en. + Serve documentation with live reload for a specific language. + + This command starts a mkdocs development server with livereload + functionality for real-time preview during documentation development. + + Args: + lang: Language to serve (defaults to 'en' if not specified) + dirty: Use dirty build mode for faster rebuilds """ # Enable line numbers during local development to make it easier to highlight if lang is None: @@ -302,6 +426,18 @@ def live( def get_updated_config_content() -> Dict[str, Any]: + """ + Generate updated mkdocs configuration with all available languages. + + Reads the language names from language_names.yml and generates the + complete alternate language configuration for the mkdocs.yml file. + + Returns: + Dict[str, Any]: Updated mkdocs configuration with all languages + + Raises: + typer.Abort: If a language code is missing from language_names.yml + """ config = get_en_config() languages = [{"en": "/"}] new_alternate: List[Dict[str, str]] = [] @@ -333,6 +469,12 @@ def get_updated_config_content() -> Dict[str, Any]: def update_config() -> None: + """ + Update the main English mkdocs.yml file with current language configuration. + + Writes the updated configuration generated by get_updated_config_content() + back to the mkdocs.yml file. + """ config = get_updated_config_content() en_config_path.write_text( yaml.dump(config, sort_keys=False, width=200, allow_unicode=True), @@ -343,7 +485,13 @@ def update_config() -> None: @app.command() def verify_config() -> None: """ - Verify main mkdocs.yml content to make sure it uses the latest language names. + Verify that mkdocs.yml is up-to-date with language names. + + Checks if the current mkdocs.yml configuration matches what would be + generated from the current language_names.yml file. + + Raises: + typer.Abort: If mkdocs.yml is outdated """ typer.echo("Verifying mkdocs.yml") config = get_en_config() @@ -362,7 +510,13 @@ def verify_config() -> None: @app.command() def verify_non_translated() -> None: """ - Verify there are no files in the non translatable pages. + Verify that non-translatable sections don't exist in translation directories. + + Checks all language directories (except English) to ensure they don't + contain files or directories that are marked as non-translatable. + + Raises: + typer.Abort: If non-translatable content is found in translation directories """ print("Verifying non translated pages") lang_paths = get_lang_paths() @@ -383,14 +537,30 @@ def verify_non_translated() -> None: @app.command() -def verify_docs(): +def verify_docs() -> None: + """ + Run all documentation verification checks. + + This command runs a comprehensive verification including: + - README.md validation + - mkdocs.yml configuration validation + - Non-translated content validation + + This is useful as a pre-commit or CI check to ensure documentation integrity. + """ verify_readme() verify_config() verify_non_translated() @app.command() -def langs_json(): +def langs_json() -> None: + """ + Output all available language codes as JSON. + + Prints a JSON array containing all available language codes found + in the docs directory. Useful for scripting and automation. + """ langs = [] for lang_path in get_lang_paths(): if lang_path.is_dir(): @@ -400,6 +570,15 @@ def langs_json(): @app.command() def generate_docs_src_versions_for_file(file_path: Path) -> None: + """ + Generate Python source file versions for different Python versions. + + Uses ruff to generate version-specific Python files for documentation + examples, targeting different Python versions. + + Args: + file_path: Path to the source Python file to generate versions for + """ target_versions = ["py39", "py310"] base_content = file_path.read_text(encoding="utf-8") previous_content = {base_content}