Browse Source

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
pull/14108/head
Muhammad Aliyan Madni 10 months ago
committed by GitHub
parent
commit
109d680dcd
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 233
      scripts/docs.py

233
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}

Loading…
Cancel
Save