fix(images): filename search survives a failed prompt-link read
On main a workspace q matched only file paths, so search worked with a broken database. Since search also matches linked prompts, any q turned a failed link read into a 500. A q without category, tags or the rating sort now falls back to filename matches with a null positive_count and logs the failure with its traceback; those linked filters still fail.
This commit is contained in:
+4
-2
@@ -490,8 +490,10 @@ set, not the page:
|
||||
- Absent on a plain listing's cursor pages, which read no links. The frontend
|
||||
keeps the first page's value.
|
||||
- Null when a plain first page's link read failed. The listing still returns 200,
|
||||
and consumers then hide "· K positive prompts". A failure under a filter or
|
||||
the rating sort stays a safe 500.
|
||||
and consumers then hide "· K positive prompts". A `q` search alone also
|
||||
survives the failure: it matches filenames only, as before linked search, and
|
||||
every page's count is null (logged with the traceback). A failure under
|
||||
`category`, `tags` or the rating sort stays a safe 500.
|
||||
|
||||
Library parents (list, detail and command receipts) gain `run_count` (an int; the
|
||||
D23 migration estimate for rows from before 3.2.4) and `last_used_at`, a UTC ISO
|
||||
|
||||
+13
-3
@@ -1141,7 +1141,8 @@ class ImageRoutesMixin:
|
||||
count is _COUNT_OMITTED, so the response leaves ``positive_count`` out
|
||||
and the frontend keeps the first page's value. On that plain first
|
||||
page a failed read leaves the listing usable and the count unknown
|
||||
(None).
|
||||
(None); so does a failed read under ``q`` alone, whose search then
|
||||
matches filenames only (as before the linked search existed).
|
||||
"""
|
||||
order, category, tags = filters["order"], filters["category"], filters["tags"]
|
||||
needed = (
|
||||
@@ -1161,10 +1162,19 @@ class ImageRoutesMixin:
|
||||
wanted=self._workspace_wanted(listings),
|
||||
)
|
||||
except Exception:
|
||||
if needed:
|
||||
# Category, tags and the rating sort cannot be served without
|
||||
# the links. A plain listing and a filename search can: the
|
||||
# search then matches filenames only, and the count is None.
|
||||
if category is not None or tags or order == "rating":
|
||||
raise
|
||||
self.logger.warning(
|
||||
"Workspace positive count could not be read", exc_info=True
|
||||
"Workspace prompt links could not be read; %s",
|
||||
(
|
||||
"searching filenames only"
|
||||
if filters["query"]
|
||||
else "positive count unknown"
|
||||
),
|
||||
exc_info=True,
|
||||
)
|
||||
if order == "rating":
|
||||
|
||||
|
||||
@@ -988,8 +988,14 @@ class TestWorkspaceImages(ImageAPITestCase):
|
||||
self.assertEqual(data["total"], 10)
|
||||
self.assertIsNone(data["positive_count"])
|
||||
self.assertNotIn("database gone", json.dumps(data))
|
||||
# A filter or the rating sort needs the read, so its failure is an error.
|
||||
for params in ({"q": "zebra"}, {"category": "Night"}, {"sort": "rating"}):
|
||||
# A linked filter or the rating sort needs the read, so its failure is
|
||||
# an error (filename-only search survives it, see below).
|
||||
for params in (
|
||||
{"category": "Night"},
|
||||
{"tags": "a"},
|
||||
{"q": "zebra", "category": "Night"},
|
||||
{"sort": "rating"},
|
||||
):
|
||||
with (
|
||||
self.subTest(failing=params),
|
||||
patch.object(
|
||||
@@ -1000,6 +1006,35 @@ class TestWorkspaceImages(ImageAPITestCase):
|
||||
self.assertEqual(response.status, 500, data)
|
||||
self.assertNotIn("images", data)
|
||||
|
||||
async def test_filename_search_survives_a_database_failure(self):
|
||||
for name, mtime in (("zebra-1.png", 300), ("zebra-2.png", 200)):
|
||||
self.media(name, mtime)
|
||||
self.media("other.png", 100)
|
||||
with (
|
||||
patch.object(
|
||||
self.api.db,
|
||||
"get_image_link_matches",
|
||||
side_effect=RuntimeError("database gone"),
|
||||
),
|
||||
self.assertLogs(self.api.logger, "WARNING") as logged,
|
||||
):
|
||||
response, first = await self.get(q="ZEBRA", limit=1)
|
||||
self.assertEqual(response.status, 200, first)
|
||||
response, second = await self.get(
|
||||
q="ZEBRA", limit=1, cursor=first["next_cursor"]
|
||||
)
|
||||
self.assertEqual(response.status, 200, second)
|
||||
# Filename matches only; the count of linked positives is unknown.
|
||||
self.assertEqual(self._names(first), ["zebra-1.png"])
|
||||
self.assertEqual(self._names(second), ["zebra-2.png"])
|
||||
for page in (first, second):
|
||||
self.assertEqual(page["total"], 2)
|
||||
self.assertIsNone(page["positive_count"])
|
||||
self.assertNotIn("database gone", json.dumps(page))
|
||||
self.assertTrue(
|
||||
all(record.exc_info for record in logged.records), logged.output
|
||||
)
|
||||
|
||||
async def test_search_matches_filename_prompt_text_and_tags(self):
|
||||
from database.models import normalize_image_path
|
||||
|
||||
|
||||
Reference in New Issue
Block a user