Skip to content

Commit

Permalink
Merge pull request #4 from rasulkireev/fatsecret-logging
Browse files Browse the repository at this point in the history
Fatsecret Logging
  • Loading branch information
rasulkireev authored Jan 13, 2025
2 parents cc51c4e + df2e2c7 commit 3fbae52
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 44 deletions.
32 changes: 24 additions & 8 deletions core/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,30 @@
def schedule_products_creation(product_name: str):
logger.info("Scheduling Products Creation", product_name=product_name)

results = fatsecret_client.search(product_name)
count = 0
for result in results:
food_id = result.get("food_id", "")
async_task(create_product, food_id)
count += 1

return f"Scheduled {count} products to get created for '{product_name}' query."
try:
results = fatsecret_client.search(product_name)
if not results:
logger.warning("No products found for search query", product_name=product_name)
return f"No products found for '{product_name}'"

count = 0
for result in results:
food_id = result.get("food_id")
if not food_id:
logger.warning("Skipping product with no food_id", product_data=result)
continue

async_task(create_product, food_id)
count += 1

logger.info("Successfully scheduled products creation", product_name=product_name, products_count=count)
return f"Scheduled {count} products to get created for '{product_name}' query."

except Exception as e:
logger.error(
"Failed to schedule products creation", error=str(e), error_type=type(e).__name__, product_name=product_name
)
raise


def create_product(food_id):
Expand Down
200 changes: 164 additions & 36 deletions integrations/fatsecret.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,193 @@
import base64
from typing import Any, Dict, List, Optional

import requests
from requests.exceptions import RequestException

from isitketo.utils import get_isitketo_logger

logger = get_isitketo_logger(__name__)


class FatSecretError(Exception):
"""Base exception for FatSecret related errors"""

pass


class FatSecretAuthError(FatSecretError):
"""Authentication related errors"""

pass


class FatSecretAPIError(FatSecretError):
"""API related errors"""

pass


class FatSecretClient:
def __init__(self, client_id, client_secret):
BASE_URL = "https://oauth.fatsecret.com/connect/token"
API_BASE_URL = "https://platform.fatsecret.com/rest"

def __init__(self, client_id: str, client_secret: str):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = "https://oauth.fatsecret.com/connect/token"
self.access_token = None

def get_access_token(self):
def _make_request(
self,
method: str,
endpoint: str,
params: Optional[Dict] = None,
data: Optional[Dict] = None,
headers: Optional[Dict] = None,
) -> Dict[str, Any]:
"""
Helper method to make HTTP requests with proper error handling and logging
"""
try:
url = f"{self.API_BASE_URL}/{endpoint}"

if not headers and self.access_token:
headers = {"Authorization": f"Bearer {self.access_token}"}

logger.debug("[FatSecret] Making API request", method=method, endpoint=endpoint, params=params)

response = requests.request(method=method, url=url, params=params, data=data, headers=headers)

logger.debug(
"[FatSecret] Received API response",
status_code=response.status_code,
endpoint=endpoint,
response_length=len(response.text),
)

response.raise_for_status()

return response.json()

except RequestException as e:
logger.error(
"[FatSecret] API request failed",
error=str(e),
method=method,
endpoint=endpoint,
status_code=getattr(e.response, "status_code", None) if hasattr(e, "response") else None,
response_text=getattr(e.response, "text", None) if hasattr(e, "response") else None,
)
raise FatSecretAPIError(f"API request failed: {str(e)}") from e

except ValueError as e:
logger.error("[FatSecret] Failed to parse JSON response", error=str(e), endpoint=endpoint)
raise FatSecretAPIError("Failed to parse API response") from e

def get_access_token(self) -> str:
"""Get or refresh the access token"""
if self.access_token:
return self.access_token

# Encode client_id and client_secret
credentials = f"{self.client_id}:{self.client_secret}"
encoded_credentials = base64.b64encode(credentials.encode()).decode()
try:
logger.info("[FatSecret] Obtaining new access token")

headers = {"Authorization": f"Basic {encoded_credentials}", "Content-Type": "application/x-www-form-urlencoded"}
# Encode client credentials
credentials = f"{self.client_id}:{self.client_secret}"
encoded_credentials = base64.b64encode(credentials.encode()).decode()

data = {"grant_type": "client_credentials", "scope": "basic"}
headers = {
"Authorization": f"Basic {encoded_credentials}",
"Content-Type": "application/x-www-form-urlencoded",
}

response = requests.post(self.base_url, headers=headers, data=data)
data = {"grant_type": "client_credentials", "scope": "basic"}

response = requests.post(self.BASE_URL, headers=headers, data=data)
response.raise_for_status()

if response.status_code == 200:
token_data = response.json()
self.access_token = token_data["access_token"]
return self.access_token
else:
raise Exception(f"Failed to obtain access token: {response.text}")

def search(self, query: str):
logger.info(
"Searching FatSecret Database",
query=query,
)
if not self.access_token:
self.get_access_token()

res = requests.get(
"https://platform.fatsecret.com/rest/foods/search/v1",
params={"search_expression": query, "format": "json"},
headers={"Authorization": f"Bearer {self.access_token}"},
)
logger.info("[FatSecret] Successfully obtained access token")
return self.access_token

return res.json()["foods"]["food"]
except (RequestException, ValueError, KeyError) as e:
logger.error("[FatSecret] Failed to obtain access token", error=str(e), error_type=type(e).__name__)
raise FatSecretAuthError(f"Failed to obtain access token: {str(e)}") from e

def get_product_info(self, food_id):
logger.info("Getting Detailed Product Info from Fatsecret", food_id=food_id)
def _ensure_auth(self) -> None:
"""Ensure we have a valid access token"""
if not self.access_token:
self.get_access_token()

res = requests.get(
"https://platform.fatsecret.com/rest/food/v4",
params={"food_id": food_id, "include_food_images": "true", "format": "json"},
headers={"Authorization": f"Bearer {self.access_token}"},
)

return res.json()["food"]
def search(self, query: str) -> List[Dict[str, Any]]:
"""
Search for foods in FatSecret database
"""
try:
logger.info(
"[FatSecret] Searching database",
query=query,
)

self._ensure_auth()

data = self._make_request(
method="GET", endpoint="foods/search/v1", params={"search_expression": query, "format": "json"}
)

# Handle empty or invalid responses
if not data:
logger.warning("[FatSecret] Empty response received", query=query)
return []

if "foods" not in data:
logger.warning(
"[FatSecret] Missing 'foods' key in response", query=query, available_keys=list(data.keys())
)
return []

foods_data = data["foods"]
if "food" not in foods_data:
logger.warning("[FatSecret] No food results found", query=query)
return []

results = foods_data["food"]
logger.info("[FatSecret] Search completed successfully", query=query, results_count=len(results))
return results

except Exception as e:
logger.error("[FatSecret] Search operation failed", error=str(e), error_type=type(e).__name__, query=query)
raise

def get_product_info(self, food_id: str) -> Dict[str, Any]:
"""
Get detailed product information by food_id
"""
try:
logger.info("[FatSecret] Getting product details", food_id=food_id)

self._ensure_auth()

data = self._make_request(
method="GET",
endpoint="food/v4",
params={"food_id": food_id, "include_food_images": "true", "format": "json"},
)

if not data or "food" not in data:
logger.error(
"[FatSecret] Invalid product info response",
food_id=food_id,
response_keys=list(data.keys()) if data else None,
)
raise FatSecretAPIError(f"Invalid response for food_id: {food_id}")

logger.info("[FatSecret] Successfully retrieved product details", food_id=food_id)
return data["food"]

except Exception as e:
logger.error(
"[FatSecret] Failed to get product details", error=str(e), error_type=type(e).__name__, food_id=food_id
)
raise

0 comments on commit 3fbae52

Please sign in to comment.