Source code for ts_backend_check.cli.generate_config_file

# SPDX-License-Identifier: GPL-3.0-or-later
"""
Configure cli to run based on a YAML configuration file.
"""

from pathlib import Path
from typing import Any, Dict

from rich import print as rprint
from yaml import dump, safe_load

from ts_backend_check.utils import get_config_file_path

CWD_PATH = Path.cwd()
YAML_CONFIG_FILE_PATH = get_config_file_path()


[docs] def path_exists(path: str) -> bool: """ Check if path entered by the user exists withing the filesystem. Parameters ---------- path : str Path should be entered as a string from the user. Returns ------- bool Return true or false based on if path exists. """ full_path = Path.cwd() / path return bool(Path(full_path).is_file())
[docs] def config_file_is_valid() -> bool: """ Check that the configuration file for ts-backend-check is not empty and has the necessary keys. Returns ------- bool True if the ts-backend-check configuration file is valid. False otherwise. """ with open(YAML_CONFIG_FILE_PATH, "r", encoding="utf-8") as file: config = safe_load(file) if config is not None: for i in config.keys(): if not isinstance(config[i], dict): rprint( f"[red]The ts-backend-check identifier '{i}' in the configuration file is not a dictionary. Please check the configuration file and try again.[/red]" ) return False if config[i]["backend_model_path"] is None: rprint( f"[red]The ts-backend-check identifier '{i}' in the configuration file does not have a backend_model_path argument. Please check the configuration file and try again.[/red]" ) return False if config[i]["ts_interface_paths"] is None: rprint( f"[red]The ts-backend-check identifier '{i}' in the configuration file does not have a ts_interface_paths argument. Please check the configuration file and try again.[/red]" ) return False return True else: rprint( "[red]The ts-backend-check configuration file is empty. Please regenerate your config file with tsbc -gcf.[/red]" ) return False
[docs] def write_config(config: dict[str, dict[str, object]]) -> None: """ Function to write into .ts-backend-check.yaml file. Parameters ---------- config : dict[str, dict[str, object]] Passing a dictionary as key str with another dict as value. """ try: options = f"""# Configuration file for ts-backend-check validation. # See https://github.com/activist-org/ts-backend-check for details. {dump(config, sort_keys=False)}""" with open(YAML_CONFIG_FILE_PATH, "w") as file: file.write(options) except IOError as e: print(f"Error while writing configuration file: {e}")
[docs] def configure_model_interface_arguments() -> None: """ Function to receive paths from user. """ config_options: Dict[str, Any] = {} while True: print( "\nAdding new model-interface configuration. Please provide the information as directed:" ) key = input( "Enter a model-interface identifier (eg: auth, user, event): " ).strip() if not key: rprint( "\n[red]The model-interface identifier cannot be empty. Please try again.[/red]" ) continue # Get backend path. while True: backend_path = input( "Enter the path for the Django models.py file: " ).strip() if not backend_path: rprint( "[red]The path for the Django models.py file cannot be empty. Please try again.[/red]" ) continue if path_exists(backend_path): break rprint(f"[red]File not found: {CWD_PATH / backend_path}[/red]") rprint("[yellow]Please check the path and try again.[/yellow]") # Get frontend paths. frontend_path_lists: list[str] = [] while True: frontend_path = input( "Enter the path to a TypeScript interface file: " ).strip() if not frontend_path: rprint( "[red]The path for the TypeScript interface file cannot be empty. Please try again.[/red]" ) continue if path_exists(frontend_path) is True: frontend_path_lists.append(frontend_path) stop_frontend_path_input = input( "Do you want to continue to add more TypeScript interface file paths? (y/[n]): " ) if stop_frontend_path_input in ["n", ""]: break else: continue else: rprint(f"[red]File not found: {CWD_PATH / frontend_path}[/red]") rprint("[yellow]Please check the path and try again.[/yellow]") # Get whether to check blank model fields. while True: check_blank_model_fields_response = ( input( "The check should assert that model fields that can be blank must also be optional in interfaces ([y]/n): " ) .strip() .lower() ) if check_blank_model_fields_response in ["y", ""]: check_blank_model_fields = True break elif check_blank_model_fields_response == "n": check_blank_model_fields = False break else: rprint("[red]Invalid response. Please try again.[/red]") # Get Backend models to ignore. backend_models_to_ignore: list[str] = [] confirm_backend_models_to_ignore = ( input( "Are there backend models that should be ignored as they don't have frontend interfaces? (y/[n]): " ) .strip() .lower() ) while True: if confirm_backend_models_to_ignore in ["n", ""]: break model = input("Enter name of the model to ignore: ").strip() backend_models_to_ignore.append(model) confirm_continue = ( input("Add another model to ignore? (y/[n]): ").strip().lower() ) if confirm_continue in ["n", ""]: break # Get model name conversions. rprint( "[yellow]💡 Note: You need model name conversions if your TypeScript interfaces are not named exactly the same as the corresponding models (i.e. UserModel in Django and User in TS).[/yellow]" ) backend_to_ts_model_name_conversions: Dict[str, list[str]] = {} while True: name_conversions_needed = ( input("Model name conversions are needed (y/[n]): ").strip().lower() ) if name_conversions_needed in ["n", ""]: break while True: while True: if backend_model_name := input( "Enter the backend model name: " ).strip(): break else: rprint("[red]Invalid response. Please try again.[/red]") while True: if ts_interface_name := [ name.strip() for name in input( "Enter the TypeScript interface name (separate multiple interface names with commas): " ).split(",") ]: break else: rprint("[red]Invalid response. Please try again.[/red]") backend_to_ts_model_name_conversions[backend_model_name] = ( ts_interface_name ) further_name_conversions_needed = ( input("Add more model name conversions (y/[n]): ").strip().lower() ) if further_name_conversions_needed in ["n", ""]: break break config_options[key] = { "backend_model_path": backend_path, "ts_interface_paths": frontend_path_lists, "check_blank_model_fields": check_blank_model_fields, "backend_models_to_ignore": backend_models_to_ignore if len(backend_models_to_ignore) > 0 else None, } if backend_to_ts_model_name_conversions: config_options[key]["backend_to_ts_model_name_conversions"] = ( backend_to_ts_model_name_conversions ) write_config(config_options) rprint(f"[green]✅ Added configuration for the '{key}' check.[/green]") continue_config = input( "Add another model-interface configuration (y/[n]): " ).strip() if continue_config.lower() in ["n", ""]: if config_options: optional_s = "s" if len(config_options) > 1 else "" rprint( f"[green]✅ Configuration complete! Added {len(config_options)} configuration{optional_s} to check.[/green]" ) break
[docs] def generate_config_file() -> None: """ Main function to create or update configuration. """ header = "ts-backend-check Configuration Setup" print(header) print("=" * len(header)) if YAML_CONFIG_FILE_PATH.is_file(): reconfigure_choice = input( "Configuration file exists. Confirm if you want to re-configure your .ts-backend-check.yaml file (y/[n]): " ) if reconfigure_choice.lower() in ["n", ""]: print("Exiting without changes.") return print("Reconfiguring...") else: print("Creating new configuration file...") try: configure_model_interface_arguments() except KeyboardInterrupt: print("\n\nConfiguration cancelled by user.") except Exception as e: print(f"\nError during configuration: {e}") print("Configuration cancelled.")