Initial commit
Some checks failed
Run linters on applied template / Python 3.13 lint and build (push) Failing after 34s
Some checks failed
Run linters on applied template / Python 3.13 lint and build (push) Failing after 34s
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
This commit is contained in:
136
{{project_slug}}/__main__.py.jinja
Normal file
136
{{project_slug}}/__main__.py.jinja
Normal file
@@ -0,0 +1,136 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user