refactor: address code review feedback on public image info, API key resolution, and serialization
This commit is contained in:
parent
d7c43f7f2f
commit
19a9894dea
3 changed files with 69 additions and 24 deletions
30
app.py
30
app.py
|
|
@ -10,10 +10,10 @@ from fastapi.staticfiles import StaticFiles
|
||||||
from cleaner import (
|
from cleaner import (
|
||||||
DEFAULT_MODEL,
|
DEFAULT_MODEL,
|
||||||
CleanerError,
|
CleanerError,
|
||||||
_get_image_info,
|
|
||||||
build_prompt,
|
build_prompt,
|
||||||
clean_garment,
|
clean_garment,
|
||||||
encode_image_bytes,
|
get_image_info,
|
||||||
|
resolve_api_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
DEFAULT_PROMPT = build_prompt("beige")
|
DEFAULT_PROMPT = build_prompt("beige")
|
||||||
|
|
@ -53,23 +53,25 @@ async def clean_endpoint(
|
||||||
api_key: str | None = Form(default=None),
|
api_key: str | None = Form(default=None),
|
||||||
restore_res: bool = Form(default=True),
|
restore_res: bool = Form(default=True),
|
||||||
):
|
):
|
||||||
effective_api_key = (api_key.strip() if api_key and api_key.strip() else None) or os.environ.get(
|
effective_api_key = resolve_api_key(api_key)
|
||||||
"OPENROUTER_API_KEY"
|
|
||||||
)
|
|
||||||
if not effective_api_key:
|
if not effective_api_key:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="API key is required. Provide it in the form or set OPENROUTER_API_KEY.",
|
detail="API key is required. Provide it in the form or set OPENROUTER_API_KEY.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
file_bytes = await file.read()
|
file_bytes = await file.read()
|
||||||
|
finally:
|
||||||
|
await file.close()
|
||||||
|
|
||||||
if not file_bytes:
|
if not file_bytes:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Uploaded file is empty.",
|
detail="Uploaded file is empty.",
|
||||||
)
|
)
|
||||||
|
|
||||||
orig_dims, _ = _get_image_info(file_bytes)
|
orig_dims, _ = get_image_info(file_bytes)
|
||||||
if orig_dims is None:
|
if orig_dims is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
|
@ -89,21 +91,7 @@ async def clean_endpoint(
|
||||||
detail=str(exc),
|
detail=str(exc),
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return clean_result.to_payload()
|
||||||
"image": encode_image_bytes(clean_result.image_bytes, mime=clean_result.media_type),
|
|
||||||
"cost": clean_result.cost,
|
|
||||||
"width": clean_result.width,
|
|
||||||
"height": clean_result.height,
|
|
||||||
"original_dimensions": list(clean_result.original_dimensions)
|
|
||||||
if clean_result.original_dimensions
|
|
||||||
else None,
|
|
||||||
"model_dimensions": list(clean_result.model_dimensions)
|
|
||||||
if clean_result.model_dimensions
|
|
||||||
else None,
|
|
||||||
"was_rescaled": clean_result.was_rescaled,
|
|
||||||
"media_type": clean_result.media_type,
|
|
||||||
}
|
|
||||||
|
|
||||||
if STATIC_DIR.is_dir():
|
if STATIC_DIR.is_dir():
|
||||||
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
||||||
|
|
||||||
|
|
|
||||||
29
cleaner.py
29
cleaner.py
|
|
@ -26,7 +26,14 @@ EXTENSIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _get_image_info(data_or_path: bytes | str) -> tuple[tuple[int, int] | None, str | None]:
|
def resolve_api_key(api_key: str | None = None) -> str | None:
|
||||||
|
"""Resolve OpenRouter API key from parameter or OPENROUTER_API_KEY environment variable."""
|
||||||
|
if api_key and api_key.strip():
|
||||||
|
return api_key.strip()
|
||||||
|
return os.environ.get("OPENROUTER_API_KEY")
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_info(data_or_path: bytes | str) -> tuple[tuple[int, int] | None, str | None]:
|
||||||
"""Extract (width, height) dimensions and mime type from bytes, file path, or data URI."""
|
"""Extract (width, height) dimensions and mime type from bytes, file path, or data URI."""
|
||||||
try:
|
try:
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
@ -34,7 +41,6 @@ def _get_image_info(data_or_path: bytes | str) -> tuple[tuple[int, int] | None,
|
||||||
if isinstance(data_or_path, bytes):
|
if isinstance(data_or_path, bytes):
|
||||||
raw_bytes = data_or_path
|
raw_bytes = data_or_path
|
||||||
elif isinstance(data_or_path, str) and data_or_path.startswith("data:"):
|
elif isinstance(data_or_path, str) and data_or_path.startswith("data:"):
|
||||||
# Parse data URI: data:<mime>;base64,<payload>
|
|
||||||
comma_idx = data_or_path.find(",")
|
comma_idx = data_or_path.find(",")
|
||||||
if comma_idx != -1:
|
if comma_idx != -1:
|
||||||
raw_bytes = base64.b64decode(data_or_path[comma_idx + 1:])
|
raw_bytes = base64.b64decode(data_or_path[comma_idx + 1:])
|
||||||
|
|
@ -50,6 +56,9 @@ def _get_image_info(data_or_path: bytes | str) -> tuple[tuple[int, int] | None,
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
_get_image_info = get_image_info
|
||||||
|
|
||||||
def build_prompt(bg_choice: str) -> str:
|
def build_prompt(bg_choice: str) -> str:
|
||||||
bg_description = BACKGROUND_PRESETS.get(bg_choice.lower(), bg_choice)
|
bg_description = BACKGROUND_PRESETS.get(bg_choice.lower(), bg_choice)
|
||||||
return (
|
return (
|
||||||
|
|
@ -132,6 +141,22 @@ class CleanResult:
|
||||||
return self.model_dimensions != self.original_dimensions
|
return self.model_dimensions != self.original_dimensions
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def to_payload(self) -> dict[str, Any]:
|
||||||
|
"""Serialize result to a dictionary suitable for API responses."""
|
||||||
|
return {
|
||||||
|
"image": encode_image_bytes(self.image_bytes, mime=self.media_type),
|
||||||
|
"cost": self.cost,
|
||||||
|
"width": self.width,
|
||||||
|
"height": self.height,
|
||||||
|
"original_dimensions": list(self.original_dimensions)
|
||||||
|
if self.original_dimensions
|
||||||
|
else None,
|
||||||
|
"model_dimensions": list(self.model_dimensions)
|
||||||
|
if self.model_dimensions
|
||||||
|
else None,
|
||||||
|
"was_rescaled": self.was_rescaled,
|
||||||
|
"media_type": self.media_type,
|
||||||
|
}
|
||||||
|
|
||||||
def call_openrouter_images(
|
def call_openrouter_images(
|
||||||
image_url_or_data_uri: str,
|
image_url_or_data_uri: str,
|
||||||
|
|
|
||||||
|
|
@ -285,3 +285,35 @@ def test_clean_garment_file(tmp_path, monkeypatch):
|
||||||
assert res.width == 250
|
assert res.width == 250
|
||||||
assert res.height == 350
|
assert res.height == 350
|
||||||
assert res.cost == 0.03
|
assert res.cost == 0.03
|
||||||
|
|
||||||
|
def test_resolve_api_key(monkeypatch):
|
||||||
|
import cleaner
|
||||||
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||||
|
assert cleaner.resolve_api_key(None) is None
|
||||||
|
assert cleaner.resolve_api_key(" ") is None
|
||||||
|
assert cleaner.resolve_api_key("custom-key") == "custom-key"
|
||||||
|
monkeypatch.setenv("OPENROUTER_API_KEY", "env-key")
|
||||||
|
assert cleaner.resolve_api_key(None) == "env-key"
|
||||||
|
assert cleaner.resolve_api_key("custom-key") == "custom-key"
|
||||||
|
|
||||||
|
|
||||||
|
def test_clean_result_to_payload():
|
||||||
|
raw = b"img-data"
|
||||||
|
res = CleanResult(
|
||||||
|
image_bytes=raw,
|
||||||
|
media_type="image/jpeg",
|
||||||
|
cost=0.015,
|
||||||
|
width=100,
|
||||||
|
height=200,
|
||||||
|
original_dimensions=(100, 200),
|
||||||
|
model_dimensions=(50, 100),
|
||||||
|
)
|
||||||
|
payload = res.to_payload()
|
||||||
|
assert payload["cost"] == 0.015
|
||||||
|
assert payload["width"] == 100
|
||||||
|
assert payload["height"] == 200
|
||||||
|
assert payload["original_dimensions"] == [100, 200]
|
||||||
|
assert payload["model_dimensions"] == [50, 100]
|
||||||
|
assert payload["was_rescaled"] is True
|
||||||
|
assert payload["media_type"] == "image/jpeg"
|
||||||
|
assert payload["image"].startswith("data:image/jpeg;base64,")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue