feat(phash): import_settings.phash_threshold (migration 0006) exposed + validated in API
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
"""fc2d: import_settings.phash_threshold
|
||||||
|
|
||||||
|
Revision ID: 0006
|
||||||
|
Revises: 0005
|
||||||
|
Create Date: 2026-05-17
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "0006"
|
||||||
|
down_revision: Union[str, None] = "0005"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.add_column(
|
||||||
|
"import_settings",
|
||||||
|
sa.Column(
|
||||||
|
"phash_threshold", sa.Integer(),
|
||||||
|
nullable=False, server_default="10",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_column("import_settings", "phash_threshold")
|
||||||
@@ -17,6 +17,7 @@ _EDITABLE_FIELDS = (
|
|||||||
"skip_single_color",
|
"skip_single_color",
|
||||||
"single_color_threshold",
|
"single_color_threshold",
|
||||||
"single_color_tolerance",
|
"single_color_tolerance",
|
||||||
|
"phash_threshold",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ async def get_import_settings():
|
|||||||
"skip_single_color": row.skip_single_color,
|
"skip_single_color": row.skip_single_color,
|
||||||
"single_color_threshold": row.single_color_threshold,
|
"single_color_threshold": row.single_color_threshold,
|
||||||
"single_color_tolerance": row.single_color_tolerance,
|
"single_color_tolerance": row.single_color_tolerance,
|
||||||
|
"phash_threshold": row.phash_threshold,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -43,6 +45,15 @@ async def update_import_settings():
|
|||||||
if not isinstance(body, dict):
|
if not isinstance(body, dict):
|
||||||
return jsonify({"error": "body must be a JSON object"}), 400
|
return jsonify({"error": "body must be a JSON object"}), 400
|
||||||
|
|
||||||
|
if "phash_threshold" in body and (
|
||||||
|
not isinstance(body["phash_threshold"], int)
|
||||||
|
or isinstance(body["phash_threshold"], bool)
|
||||||
|
or body["phash_threshold"] < 0
|
||||||
|
):
|
||||||
|
return jsonify(
|
||||||
|
{"error": "phash_threshold must be a non-negative integer"}
|
||||||
|
), 400
|
||||||
|
|
||||||
async with get_session() as session:
|
async with get_session() as session:
|
||||||
row = (
|
row = (
|
||||||
await session.execute(select(ImportSettings).where(ImportSettings.id == 1))
|
await session.execute(select(ImportSettings).where(ImportSettings.id == 1))
|
||||||
|
|||||||
@@ -28,3 +28,5 @@ class ImportSettings(Base):
|
|||||||
skip_single_color: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
skip_single_color: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||||
single_color_threshold: Mapped[float] = mapped_column(Float, nullable=False, default=0.95)
|
single_color_threshold: Mapped[float] = mapped_column(Float, nullable=False, default=0.95)
|
||||||
single_color_tolerance: Mapped[int] = mapped_column(Integer, nullable=False, default=30)
|
single_color_tolerance: Mapped[int] = mapped_column(Integer, nullable=False, default=30)
|
||||||
|
|
||||||
|
phash_threshold: Mapped[int] = mapped_column(Integer, nullable=False, default=10)
|
||||||
|
|||||||
@@ -47,6 +47,30 @@ async def test_patch_rejects_non_object(client):
|
|||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_import_settings_phash_threshold_default(client):
|
||||||
|
resp = await client.get("/api/settings/import")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert (await resp.get_json())["phash_threshold"] == 10
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_patch_phash_threshold(client):
|
||||||
|
resp = await client.patch(
|
||||||
|
"/api/settings/import", json={"phash_threshold": 4}
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert (await resp.get_json())["phash_threshold"] == 4
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_patch_phash_threshold_rejects_negative(client):
|
||||||
|
resp = await client.patch(
|
||||||
|
"/api/settings/import", json={"phash_threshold": -1}
|
||||||
|
)
|
||||||
|
assert resp.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_system_stats_shape(client):
|
async def test_system_stats_shape(client):
|
||||||
resp = await client.get("/api/system/stats")
|
resp = await client.get("/api/system/stats")
|
||||||
|
|||||||
Reference in New Issue
Block a user