
fix: :zap: Handle Server timeout errors + Add whitespace additions and deletions in text diff
161967b
from typing import Dict, Tuple, Optional, TypeAlias | |
import requests | |
import base64 | |
import json | |
from dataclasses import dataclass | |
from utils import get_logger, LANGS | |
from openfoodfacts.api import ProductResource, APIConfig | |
import gradio as gr | |
logger = get_logger() | |
BASE_URL = "https://robotoff.openfoodfacts.org/api/v1" # Prod | |
# BASE_URL = "http://localhost:5500/api/v1" # Dev | |
UPDATE = 1 | |
Annotation: TypeAlias = Tuple[str, str, str, str, str] | |
class Authentification: | |
username: str | |
password: str | |
def get_credentials(self) -> str: | |
credentials = f"{self.username}:{self.password}" | |
return base64.b64encode(credentials.encode()).decode() | |
def next_annotation(lang: Optional[str]) -> Annotation: | |
lang_key = LANGS.get(lang) | |
if not lang_key: | |
logger.warning("The provided Lang was not found. 'All' returned by default.") | |
insight = import_random_insight(lang_key=lang_key) | |
logger.info("Imported insight: %s", insight) | |
return ( | |
insight["id"], | |
insight["data"]["original"], | |
insight["data"]["correction"], # Saved for comparison with annotator changes | |
insight["data"]["correction"], | |
get_image_url(insight["barcode"]), | |
get_product_url(insight["barcode"]), | |
) | |
def submit_correction( | |
insight_id: str, | |
annotator_correction: str, | |
model_correction: str, | |
username: str, | |
password: str, | |
lang: str, | |
update: bool = UPDATE, | |
): | |
auth = Authentification(username, password) | |
correction = ( | |
annotator_correction if annotator_correction != model_correction else None | |
) | |
try: | |
submit_to_product_opener( | |
insight_id=insight_id, | |
update=update, | |
annotator_correction=correction, | |
auth=auth, | |
) | |
except gr.Error as e: | |
gr.Warning(e.message) # We use gr.Warning instead of gr.Error to keep the flow | |
return ( | |
gr.update(), | |
gr.update(), | |
gr.update(), | |
gr.update(), | |
gr.update(), | |
gr.update(), | |
) # Stay unchanged | |
gr.Info("Product successfuly updated. Many thanks!") | |
return next_annotation(lang=lang) | |
def import_random_insight( | |
insight_type: str = "ingredient_spellcheck", | |
predictor: str = "fine-tuned-mistral-7b", | |
lang_key: Optional[str] = None, | |
) -> Dict: | |
url = ( | |
f"{BASE_URL}/insights/random?count=1&type={insight_type}&predictor={predictor}" | |
) | |
if lang_key: | |
url += f"&value_tag={lang_key}" | |
try: | |
response = requests.get(url) | |
response.raise_for_status() # Raises an HTTPError for bad responses (4xx or 5xx) | |
data = response.json() | |
if "insights" in data and data["insights"]: | |
return data["insights"][0] | |
else: | |
gr.Warning("No insights found; fetching default insight instead.") | |
return import_random_insight(insight_type, predictor) | |
except requests.ReadTimeout: | |
gr.Error("There's an issue with the server... Wait a couple of minutes and try again.") | |
except requests.RequestException as e: | |
gr.Error(f"Import product from Product Opener failed: {e}") | |
def submit_to_product_opener( | |
insight_id: str, | |
update: bool, | |
auth: Authentification, | |
annotator_correction: Optional[str] = None, | |
) -> None: | |
url = f"{BASE_URL}/insights/annotate" | |
headers = { | |
"Authorization": f"Basic {auth.get_credentials()}", | |
"Content-Type": "application/x-www-form-urlencoded", | |
} | |
if annotator_correction: | |
logger.info( | |
"Change from annotator. New insight sent to Product Opener. New correction: %s", | |
annotator_correction, | |
) | |
payload = { | |
"insight_id": insight_id, | |
"annotation": 2, | |
"update": update, | |
"data": json.dumps({"annotation": annotator_correction}), | |
} | |
else: | |
logger.info( | |
"No change from annotator. Original insight sent to Product Opener." | |
) | |
payload = { | |
"insight_id": insight_id, | |
"annotation": 1, | |
"update": update, | |
} | |
try: | |
response = requests.post(url, data=payload, headers=headers) | |
response.raise_for_status() | |
except requests.ReadTimeout: | |
gr.Error("There's an issue with the server... Wait a couple of minutes and try again.") | |
except requests.RequestException as e: | |
logger.error(e) | |
logger.error(response.content) | |
raise gr.Error( | |
"Failed to submit to Product Opener. Are your username and password correct?" | |
) | |
def get_image_url( | |
code: str, | |
user_agent: str = "Spellcheck-Annotate", | |
) -> str: | |
fields = ["image_ingredients_url"] | |
data = ProductResource(api_config=APIConfig(user_agent=user_agent)).get( | |
code=code, | |
fields=fields, | |
) | |
image_url = data.get(fields[0]) | |
return image_url | |
def enable_buttons(username, password): | |
# Return the updated button states: interactive if both username and password are filled | |
state = bool(username) and bool(password) | |
return gr.update(interactive=state), gr.update(interactive=state) | |
def get_product_url(barcode: int) -> str: | |
return f"https://world.openfoodfacts.org/product/{barcode}" | |