1
0

Added settings module

This commit is contained in:
2026-03-30 16:20:08 +00:00
parent 4fc7b40224
commit 8837ebe470
7 changed files with 108 additions and 8 deletions

View File

@@ -9,6 +9,7 @@ dependencies = [
"fastapi[standard]>=0.116.0", "fastapi[standard]>=0.116.0",
"httpx>=0.28.1", "httpx>=0.28.1",
"pydantic>=2.11.0", "pydantic>=2.11.0",
"pydantic-settings>=2.6.1",
"uplink>=0.9.7", "uplink>=0.9.7",
] ]

View File

@@ -1,11 +1,16 @@
import os
from celery import Celery from celery import Celery
from comment_automation.logging_config import configure_logging, get_celery_logger
from comment_automation.settings import get_settings
configure_logging()
logger = get_celery_logger()
settings = get_settings()
celery_app = Celery( celery_app = Celery(
"comment_automation", "comment_automation",
broker=os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0"), broker=settings.celery_broker_url,
backend=os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/0"), backend=settings.celery_result_backend,
include=["comment_automation.webhooks.tasks"], include=["comment_automation.webhooks.tasks"],
) )
@@ -16,3 +21,5 @@ celery_app.conf.update(
timezone="UTC", timezone="UTC",
enable_utc=True, enable_utc=True,
) )
logger.info("Celery app configured")

View File

@@ -0,0 +1,46 @@
import logging
from logging.config import dictConfig
from comment_automation.settings import get_settings
def configure_logging() -> None:
settings = get_settings()
dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s | %(levelname)s | %(name)s | %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "standard",
}
},
"loggers": {
"comment_automation.http": {
"handlers": ["console"],
"level": settings.http_log_level,
"propagate": False,
},
"comment_automation.celery": {
"handlers": ["console"],
"level": settings.celery_log_level,
"propagate": False,
},
},
}
)
def get_http_logger() -> logging.Logger:
return logging.getLogger("comment_automation.http")
def get_celery_logger() -> logging.Logger:
return logging.getLogger("comment_automation.celery")

View File

@@ -1,8 +1,12 @@
from fastapi import FastAPI from fastapi import FastAPI
from pydantic import BaseModel from pydantic import BaseModel
from comment_automation.logging_config import configure_logging, get_http_logger
from comment_automation.webhooks.app import webhooks_app from comment_automation.webhooks.app import webhooks_app
configure_logging()
logger = get_http_logger()
app = FastAPI(title="CommentAutomation") app = FastAPI(title="CommentAutomation")
app.mount("/webhooks", webhooks_app) app.mount("/webhooks", webhooks_app)
@@ -13,4 +17,5 @@ class HealthResponse(BaseModel):
@app.get("/health", response_model=HealthResponse) @app.get("/health", response_model=HealthResponse)
def health() -> HealthResponse: def health() -> HealthResponse:
logger.debug("Health check requested")
return HealthResponse(status="ok") return HealthResponse(status="ok")

View File

@@ -0,0 +1,29 @@
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(extra="ignore")
celery_broker_url: str = Field(
default="redis://localhost:6379/0", alias="CELERY_BROKER_URL"
)
celery_result_backend: str = Field(
default="redis://localhost:6379/0",
alias="CELERY_RESULT_BACKEND",
)
llm_endpoint_url: str | None = Field(default=None, alias="LLM_ENDPOINT_URL")
llm_endpoint_api_key: str | None = Field(default=None, alias="LLM_ENDPOINT_API_KEY")
llm_endpoint_timeout_seconds: float = Field(
default=10.0,
alias="LLM_ENDPOINT_TIMEOUT_SECONDS",
gt=0,
)
http_log_level: str = Field(default="INFO", alias="HTTP_LOG_LEVEL")
celery_log_level: str = Field(default="INFO", alias="CELERY_LOG_LEVEL")
def get_settings() -> AppSettings:
return AppSettings()

View File

@@ -1,9 +1,11 @@
import os
from typing import Any from typing import Any
import httpx import httpx
from celery import shared_task from celery import shared_task
from comment_automation.logging_config import get_celery_logger
from comment_automation.settings import get_settings
class LLMEndpointTemporaryError(Exception): class LLMEndpointTemporaryError(Exception):
"""Raised when the LLM endpoint fails with a transient/retryable error.""" """Raised when the LLM endpoint fails with a transient/retryable error."""
@@ -13,6 +15,9 @@ class LLMEndpointConfigurationError(Exception):
"""Raised when required LLM endpoint configuration is missing.""" """Raised when required LLM endpoint configuration is missing."""
logger = get_celery_logger()
@shared_task( @shared_task(
bind=True, bind=True,
name="comment_automation.tasks.send_instagram_comment_to_llm", name="comment_automation.tasks.send_instagram_comment_to_llm",
@@ -33,18 +38,21 @@ def send_instagram_comment_to_llm(self, payload: dict[str, Any]) -> None:
def _forward_payload_to_llm(payload: dict[str, Any]) -> None: def _forward_payload_to_llm(payload: dict[str, Any]) -> None:
"""Send payload to LLM endpoint, mapping transient failures to retries.""" """Send payload to LLM endpoint, mapping transient failures to retries."""
endpoint_url = os.getenv("LLM_ENDPOINT_URL") settings = get_settings()
endpoint_url = settings.llm_endpoint_url
if not endpoint_url: if not endpoint_url:
raise LLMEndpointConfigurationError("LLM_ENDPOINT_URL is not configured") raise LLMEndpointConfigurationError("LLM_ENDPOINT_URL is not configured")
api_key = os.getenv("LLM_ENDPOINT_API_KEY") api_key = settings.llm_endpoint_api_key
timeout = float(os.getenv("LLM_ENDPOINT_TIMEOUT_SECONDS", "10")) timeout = settings.llm_endpoint_timeout_seconds
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
if api_key: if api_key:
headers["Authorization"] = f"Bearer {api_key}" headers["Authorization"] = f"Bearer {api_key}"
try: try:
logger.debug("Sending webhook payload to LLM endpoint")
response = httpx.post( response = httpx.post(
endpoint_url, endpoint_url,
json=payload, json=payload,
@@ -52,11 +60,13 @@ def _forward_payload_to_llm(payload: dict[str, Any]) -> None:
timeout=timeout, timeout=timeout,
) )
except (httpx.TimeoutException, httpx.NetworkError) as exc: except (httpx.TimeoutException, httpx.NetworkError) as exc:
logger.warning("Transient network failure while calling LLM endpoint")
raise LLMEndpointTemporaryError( raise LLMEndpointTemporaryError(
"Temporary network issue while calling LLM endpoint" "Temporary network issue while calling LLM endpoint"
) from exc ) from exc
if response.status_code in {429, 500, 502, 503, 504}: if response.status_code in {429, 500, 502, 503, 504}:
logger.warning("Retryable response from LLM endpoint: %s", response.status_code)
raise LLMEndpointTemporaryError( raise LLMEndpointTemporaryError(
f"Retryable LLM endpoint response status: {response.status_code}" f"Retryable LLM endpoint response status: {response.status_code}"
) )

2
uv.lock generated
View File

@@ -305,6 +305,7 @@ dependencies = [
{ name = "fastapi", extra = ["standard"] }, { name = "fastapi", extra = ["standard"] },
{ name = "httpx" }, { name = "httpx" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "uplink" }, { name = "uplink" },
] ]
@@ -326,6 +327,7 @@ requires-dist = [
{ name = "fastapi", extras = ["standard"], specifier = ">=0.116.0" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.116.0" },
{ name = "httpx", specifier = ">=0.28.1" }, { name = "httpx", specifier = ">=0.28.1" },
{ name = "pydantic", specifier = ">=2.11.0" }, { name = "pydantic", specifier = ">=2.11.0" },
{ name = "pydantic-settings", specifier = ">=2.6.1" },
{ name = "uplink", specifier = ">=0.9.7" }, { name = "uplink", specifier = ">=0.9.7" },
] ]