"""Executable application entrypoint is defined here.""" import os import tempfile import typing as tp from pathlib import Path import click import uvicorn from dotenv import load_dotenv from .config import {{ProjectName}}Config, LoggingConfig LogLevel = tp.Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR"] def _run_uvicorn(configuration: dict[str, tp.Any]) -> tp.NoReturn: uvicorn.run( "{{project_slug}}.fastapi_init:app", **configuration, ) @click.group() def cli(): "{{project_name}} service executable script" @cli.command("config-example") @click.option( "--config_path", envvar="CONFIG_PATH", default="config.yaml", type=click.Path(dir_okay=False, path_type=Path), show_default=True, show_envvar=True, help="Path to YAML configuration file", ) def get_config_example(config_path: Path): config = {{ProjectName}}Config.get_example() config.dump(config_path) @cli.command("launch") @click.option( "--port", "-p", envvar="PORT", type=int, show_envvar=True, help="Service port number", ) @click.option( "--host", envvar="HOST", show_envvar=True, help="Service HOST address", ) @click.option( "--logger_verbosity", "-v", type=click.Choice(("TRACE", "DEBUG", "INFO", "WARNING", "ERROR")), envvar="LOGGER_VERBOSITY", show_envvar=True, help="Logger verbosity", ) @click.option( "--debug", envvar="DEBUG", is_flag=True, help="Enable debug mode (auto-reload on change, traceback returned to user, etc.)", ) @click.option( "--config_path", envvar="CONFIG_PATH", default="config.yaml", type=click.Path(exists=True, dir_okay=False, path_type=Path), show_default=True, show_envvar=True, help="Path to YAML configuration file", ) def launch( port: int, host: str, logger_verbosity: LogLevel, debug: bool, config_path: Path, ): """ {{project_description}} Performs configuration via config and command line + environment variables overrides. """ print( "This is a simple method to run the API." " You might want to use 'uvicorn {{project_slug}}.fastapi_init:app' instead" ) config = {{ProjectName}}Config.load(config_path) config.app.host = host or config.app.host config.app.port = port or config.app.port config.app.debug = debug or config.app.debug config.logging = config.logging if logger_verbosity is None else LoggingConfig(level=logger_verbosity) with tempfile.NamedTemporaryFile(delete=False) as temp_file: temp_yaml_config_path = temp_file.name config.dump(temp_yaml_config_path) with tempfile.NamedTemporaryFile(delete=False) as temp_file: temp_envfile_path = temp_file.name with open(temp_envfile_path, "w", encoding="utf-8") as env_file: env_file.write(f"CONFIG_PATH={temp_yaml_config_path}\n") try: uvicorn_config = { "host": config.app.host, "port": config.app.port, "log_level": config.logging.level.lower(), "env_file": temp_envfile_path, } if config.app.debug: try: _run_uvicorn(uvicorn_config | {"reload": True}) except: # pylint: disable=bare-except print("Debug reload is not supported and will be disabled") _run_uvicorn(uvicorn_config) else: _run_uvicorn(uvicorn_config) finally: if os.path.exists(temp_envfile_path): os.remove(temp_envfile_path) if os.path.exists(temp_yaml_config_path): os.remove(temp_yaml_config_path) if __name__ in ("__main__", "{{project_slug}}.__main__"): load_dotenv(os.environ.get("ENVFILE", ".env")) cli() # pylint: disable=no-value-for-parameter