forked from LiveCarta/CommentAutomation
Added settings module
This commit is contained in:
@@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
46
src/comment_automation/logging_config.py
Normal file
46
src/comment_automation/logging_config.py
Normal 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")
|
||||||
@@ -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")
|
||||||
|
|||||||
29
src/comment_automation/settings.py
Normal file
29
src/comment_automation/settings.py
Normal 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()
|
||||||
@@ -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
2
uv.lock
generated
@@ -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" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user