Version 0.4.0
All checks were successful
Run linters on applied template / Python 3.13 lint and build (push) Successful in 1m40s

Changes:
- put ObservabilityMiddleware before ExceptionHandlerMiddleware to avoid repetative code
- add application startup and last metrics update metrics along with CPU usage metric and threads count
- move host and port to new uvicorn section at config along with new reload and forwarded_allow_ips
- add request_id and remove trace_id/span_id generation if tracing is disabled
- move logging logic from utils to observability
- pass trace_id/span_id in HEX form
This commit is contained in:
2026-01-03 11:01:43 +03:00
parent b8acb017fd
commit 53f14a8624
26 changed files with 901 additions and 730 deletions

View File

@@ -9,16 +9,7 @@ import click
import uvicorn
from dotenv import load_dotenv
from .config import LoggingConfig, {{ProjectName}}Config
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,
)
from .config import {{ProjectName}}Config
@click.group()
@@ -42,34 +33,6 @@ def get_config_example(config_path: 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",
@@ -80,10 +43,6 @@ def get_config_example(config_path: Path):
help="Path to YAML configuration file",
)
def launch(
port: int,
host: str,
logger_verbosity: LogLevel,
debug: bool,
config_path: Path,
):
"""
@@ -93,14 +52,10 @@ def launch(
"""
print(
"This is a simple method to run the API. You might want to use"
"'uvicorn {{project_slug}}.fastapi_init:app' instead to configure more uvicorn options."
" 'uvicorn {{project_slug}}.fastapi_init:app' instead to configure more uvicorn options."
)
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.observability.logging = config.observability.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
@@ -111,16 +66,18 @@ def launch(
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.observability.logging.level.lower(),
"host": config.app.uvicorn.host,
"port": config.app.uvicorn.port,
"forwarded_allow_ips": config.app.uvicorn.forwarded_allow_ips,
"log_level": config.observability.logging.root_logger_level.lower(),
"env_file": temp_envfile_path,
"access_log": False,
}
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")
print("Retrying with Uvicorn reload disabled")
_run_uvicorn(uvicorn_config)
else:
_run_uvicorn(uvicorn_config)
@@ -131,6 +88,13 @@ def launch(
os.remove(temp_yaml_config_path)
def _run_uvicorn(configuration: dict[str, tp.Any]) -> tp.NoReturn:
uvicorn.run(
"{{project_slug}}.fastapi_init:app",
**configuration,
)
if __name__ in ("__main__", "{{project_slug}}.__main__"):
load_dotenv(os.environ.get("ENVFILE", ".env"))
cli() # pylint: disable=no-value-for-parameter