Files
template-fastapi/{{project_slug}}/__main__.py.jinja
Aleksei Sokol 5dd68b7114
Some checks failed
Run linters on applied template / Python 3.13 lint and build (push) Failing after 34s
Initial commit
This is a FastAPI backend microservice template used with `copier` utility.

Features of applied template are:
- Configuration file processing logic
- Metrics and tracing (both optional) configuration available
- Debug endpoints
- Database migration commands, prepared Alembic environment
- Database usage example in ping_db endpoint
- gitea sanity check pipeline
2025-11-29 21:50:32 +03:00

137 lines
3.7 KiB
Django/Jinja

"""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