SmartEDT/backend/utils.py

55 lines
1.8 KiB
Python

from __future__ import annotations
import logging
import os
from datetime import datetime, timezone
from logging.handlers import TimedRotatingFileHandler
from pathlib import Path
def utc_now() -> datetime:
return datetime.now(timezone.utc)
def configure_logging(level: str, log_file: Path | None = None) -> None:
level_value = getattr(logging, level.upper(), logging.INFO)
logging_handlers: list[logging.Handler] = [logging.StreamHandler()]
if log_file is not None:
log_file.parent.mkdir(parents=True, exist_ok=True)
rotating_handler = TimedRotatingFileHandler(
log_file,
when="midnight",
interval=1,
backupCount=90,
encoding="utf-8",
delay=True,
)
rotating_handler.suffix = "%Y-%m-%d"
logging_handlers.append(rotating_handler)
logging.basicConfig(
level=level_value,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
handlers=logging_handlers,
force=True,
)
for logger_name in ("uvicorn", "uvicorn.error", "uvicorn.access", "uvicorn.asgi"):
logger = logging.getLogger(logger_name)
logger.handlers = []
logger.propagate = True
def project_root() -> Path:
return Path(__file__).resolve().parents[1]
def safe_join(root: Path, untrusted_path: str) -> Path:
if untrusted_path.startswith(("\\\\", "//")):
raise ValueError("UNC path is not allowed")
if os.path.isabs(untrusted_path):
raise ValueError("Absolute path is not allowed")
candidate = (root / untrusted_path).resolve()
root_resolved = root.resolve()
if root_resolved not in candidate.parents and candidate != root_resolved:
raise ValueError("Path traversal detected")
return candidate