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

@@ -4,12 +4,19 @@ from collections import OrderedDict
from dataclasses import asdict, dataclass, field, fields
from pathlib import Path
from types import NoneType, UnionType
from typing import Any, Literal, TextIO, Type
from typing import Any, Literal, TextIO, Type, Union, get_origin
import yaml
from {{project_slug}}.db.config import DBConfig, MultipleDBsConfig
from {{project_slug}}.utils.observability import LoggingConfig, FileLogger, ExporterConfig
from {{project_slug}}.observability.config import (
ExporterConfig,
FileLogger,
JaegerConfig,
LoggingConfig,
ObservabilityConfig,
PrometheusConfig,
)
from {{project_slug}}.utils.secrets import SecretStr, representSecretStrYAML
@@ -22,32 +29,20 @@ class CORSConfig:
@dataclass
class AppConfig:
class UvicornConfig:
host: str
port: int
reload: bool = False
forwarded_allow_ips: list[str] = field(default_factory=list)
@dataclass
class AppConfig:
uvicorn: UvicornConfig
debug: bool
cors: CORSConfig
@dataclass
class PrometheusConfig:
host: str
port: int
urls_mapping: dict[str, str] = field(default_factory=dict)
@dataclass
class JaegerConfig:
endpoint: str
@dataclass
class ObservabilityConfig:
logging: LoggingConfig
prometheus: PrometheusConfig | None = None
jaeger: JaegerConfig | None = None
@dataclass
class {{ProjectName}}Config:
app: AppConfig
@@ -96,7 +91,11 @@ class {{ProjectName}}Config:
"""Generate an example of configuration."""
res = cls(
app=AppConfig(host="0.0.0.0", port=8080, debug=False, cors=CORSConfig(["*"], ["*"], ["*"], True)),
app=AppConfig(
uvicorn=UvicornConfig(host="0.0.0.0", port=8080, reload=True, forwarded_allow_ips=["127.0.0.1"]),
debug=True,
cors=CORSConfig(["*"], ["*"], ["*"], True),
),
db=MultipleDBsConfig(
master=DBConfig(
host="localhost",
@@ -119,12 +118,12 @@ class {{ProjectName}}Config:
),
observability=ObservabilityConfig(
logging=LoggingConfig(
level="INFO",
stderr_level="INFO",
root_logger_level="INFO",
exporter=ExporterConfig(endpoint="http://127.0.0.1:4317", level="INFO", tls_insecure=True),
files=[FileLogger(filename="logs/info.log", level="INFO")],
),
prometheus=PrometheusConfig(host="0.0.0.0", port=9090, urls_mapping={"/api/debug/.*": "/api/debug/*"}),
prometheus=PrometheusConfig(host="0.0.0.0", port=9090),
jaeger=JaegerConfig(endpoint="http://127.0.0.1:4318/v1/traces"),
),
)
@@ -152,7 +151,7 @@ class {{ProjectName}}Config:
"""Try to initialize given type field-by-field recursively with data from dictionary substituting {} and None
if no value provided.
"""
if isinstance(t, UnionType):
if get_origin(t) is Union or get_origin(t) is UnionType: # both actually required
for inner_type in t.__args__:
if inner_type is NoneType and data is None:
return None