Source code for pynenc.cli.config_cli

import argparse
import re
from functools import wraps
from typing import Callable, Type, TypeVar

from pynenc.app import Pynenc
from pynenc.conf.config_base import ConfigPynencBase

T = TypeVar("T", bound="ConfigPynencBase")


[docs] def config_cls_cache( func: Callable[[Type[T]], dict[str, str]] ) -> Callable[[Type[T]], dict[str, str]]: """ Decorator for caching the output of functions that extract field descriptions. This decorator is designed to be applied to functions that process and return descriptions of configuration fields from class docstrings. It caches the results based on the class name to optimize performance by avoiding redundant processing. param Callable[[Type[T]], dict[str, str]] func: The function to be decorated. :return: A wrapped function with caching capability. """ cache: dict[str, dict[str, str]] = {} @wraps(func) def wrapper(config_cls: Type[T]) -> dict[str, str]: class_name = config_cls.__name__ if class_name not in cache: cache[class_name] = func(config_cls) return cache[class_name] return wrapper
[docs] @config_cls_cache def extract_descriptions_from_docstring( config_cls: Type["ConfigPynencBase"], ) -> dict[str, str]: """ Extract field descriptions from the docstring of a configuration class. Parses the docstring of the given configuration class (and its parent classes) to extract descriptions for each configuration field. The descriptions are expected to be formatted in a specific way within the docstring. :param Type[ConfigPynencBase] config_cls: The configuration class to extract descriptions from. :return: A dictionary mapping field names to their descriptions. """ if config_cls == ConfigPynencBase: return {} field_docs = {} for parent in config_cls.__bases__: if parent != ConfigPynencBase and issubclass(parent, ConfigPynencBase): field_docs.update(extract_descriptions_from_docstring(parent)) if not (docstring := config_cls.__doc__): return field_docs # Updated regular expression pattern to match :cvar style pattern = r":cvar\s+(\w+\[?.*?\]?)\s+(\w+):\s*(.*?)(?=\n\s*:cvar|$)" matches = re.finditer(pattern, docstring, re.DOTALL) for match in matches: _, field_name, description = match.groups() # Process multiline description description_lines = description.split("\n") description = " ".join(line.strip() for line in description_lines) field_docs[field_name] = description.strip() return field_docs
[docs] def add_config_subparser(subparsers: argparse._SubParsersAction) -> None: """ Add a subparser for the 'show_config' command to the main argparse parser. This function is responsible for setting up the CLI structure for the 'show_config' command, including defining its help message and setting the default function to be executed when this command is selected. :param argparse._SubParsersAction subparsers: The subparsers object from the main parser. """ show_config_parser = subparsers.add_parser( "show_config", help="Show app configuration" ) show_config_parser.set_defaults(func=show_config_command)
[docs] def show_config_command(args: argparse.Namespace) -> None: """ Execute the 'show_config' command for the Pynenc CLI. This command displays the current configuration of the Pynenc application instance. It uses the configuration class's docstring to provide detailed information about each configuration field, including its current value and description. :param argparse.Namespace args: The parsed CLI arguments, including the Pynenc application instance. """ if not isinstance(args.app_instance, Pynenc): raise TypeError("app_instance must be an instance of ConfigPynencBase") if hasattr(args, "runner_command"): config: ConfigPynencBase = args.app_instance.runner.conf else: config = args.app_instance.conf print("Showing configuration for Pynenc instance:") print(f" - location: {args.app}") print(f" - id: {args.app_instance.app_id}") print(f"Config {config.__class__.__name__}:") for field, description in extract_descriptions_from_docstring( config.__class__ ).items(): # Format and print each field print("-" * 50) print(f"{field}: ") print(f" Default: {getattr(config, field)}") # Format multiline description formatted_description = " ".join(description.splitlines()) print(f" Description: {formatted_description}") print("-" * 50) # Closing separator after the last field