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