Summary
• Implemented global offline mode for all HuggingFace-dependent modules to eliminate unauthenticated request warnings and prevent external connection attempts during startup and runtime. Changes • Global Offline Configuration: Added HF_HUB_OFFLINE=1 and TRANSFORMERS_OFFLINE=1 to the environment variables at the top of edit_api.py, embeddings.py, watcher.py, and pose_llm/pose_llm_api.py. • Explicit Local Files Only: Updated all huggingface_hub.hf_hub_download calls in edit_api.py to include local_files_only=True, ensuring they never attempt to reach the Hub if models are already cached. • LLM Service Optimization: Updated AutoTokenizer and AutoModelForCausalLM calls in pose_llm/pose_llm_api.py to use local_files_only=True. • Consistency: Ensured that shared modules like embeddings.py which uses open_clip are also locked to offline mode to prevent background downloads.
This commit is contained in:
@@ -390,5 +390,214 @@ class TestAPIRegression(unittest.TestCase):
|
||||
self.assertEqual(person_other[8], 0)
|
||||
self.assertEqual(person_ref[8], 1)
|
||||
|
||||
def test_06_list_images_with_bypass_static(self):
|
||||
# Call with bypass_static=True
|
||||
response = self.client.get("/images?archived=true&bypass_static=true")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
res_data = response.json()
|
||||
self.assertIn("images", res_data)
|
||||
|
||||
# Find our reference image in the list
|
||||
images = res_data["images"]
|
||||
ref_img = next((img for img in images if img["filename"] == self.test_ref_filename), None)
|
||||
self.assertIsNotNone(ref_img, "Test reference image not found in bypassed images list")
|
||||
|
||||
# Verify complete mapped metadata fields are correctly returned when bypassing static file
|
||||
self.assertEqual(ref_img["name"], self.name)
|
||||
self.assertEqual(ref_img["group_id"], self.group_id)
|
||||
self.assertEqual(ref_img["prompt"], self.prompt)
|
||||
self.assertEqual(ref_img["pose"], self.pose)
|
||||
self.assertEqual(ref_img["group_name"], self.group_name)
|
||||
self.assertEqual(ref_img["hidden"], self.hidden)
|
||||
self.assertEqual(ref_img["has_background"], self.has_background)
|
||||
self.assertEqual(ref_img["has_clothing"], self.has_clothing)
|
||||
self.assertEqual(ref_img["is_source"], self.is_source)
|
||||
self.assertEqual(ref_img["pose_description"], self.pose_description)
|
||||
|
||||
# Verify tags list
|
||||
self.assertIn("LIKE", ref_img["tags"])
|
||||
self.assertIn("21+", ref_img["tags"])
|
||||
|
||||
# Verify skeleton
|
||||
self.assertEqual(ref_img["pose_skeleton"], self.pose_skeleton)
|
||||
|
||||
def test_07_concurrent_reordering_deadlock_resilience(self):
|
||||
import threading
|
||||
import random
|
||||
import time
|
||||
|
||||
exceptions = []
|
||||
|
||||
def worker(thread_idx):
|
||||
try:
|
||||
for i in range(15):
|
||||
# Alternate the order to trigger lock conflicts
|
||||
if i % 2 == 0:
|
||||
order = [self.test_other_filename, self.test_ref_filename]
|
||||
else:
|
||||
order = [self.test_ref_filename, self.test_other_filename]
|
||||
|
||||
database.set_group_order(self.group_id, order)
|
||||
time.sleep(0.01)
|
||||
except Exception as e:
|
||||
exceptions.append((thread_idx, e))
|
||||
|
||||
threads = []
|
||||
for j in range(5):
|
||||
t = threading.Thread(target=worker, args=(j,))
|
||||
threads.append(t)
|
||||
t.start()
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
# If any deadlock or serialization error was uncaught, exceptions won't be empty
|
||||
if exceptions:
|
||||
for tid, err in exceptions:
|
||||
print(f"[test] Thread {tid} encountered error: {err}")
|
||||
self.assertEqual(len(exceptions), 0, f"Encountered concurrent set_group_order errors: {exceptions}")
|
||||
|
||||
def test_08_deep_metadata_exposure_and_automatic_background_processing(self):
|
||||
# Trigger the /images list
|
||||
response = self.client.get("/images?archived=true&bypass_static=true")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
res_data = response.json()
|
||||
self.assertIn("images", res_data)
|
||||
|
||||
# Verify that the new keys are present in each image dict
|
||||
for img in res_data["images"]:
|
||||
self.assertIn("people_count", img)
|
||||
self.assertIn("anatomical_completeness", img)
|
||||
self.assertIn("facial_direction", img)
|
||||
self.assertIn("objects", img)
|
||||
|
||||
# Verify that manually triggering metadata backfill for our ref image returns expected structure
|
||||
backfill_payload = {"filenames": [self.test_ref_filename]}
|
||||
response = self.client.post("/images/backfill-metadata", json=backfill_payload)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
res_backfill = response.json()
|
||||
self.assertEqual(res_backfill["status"], "completed")
|
||||
self.assertEqual(res_backfill["total"], 1)
|
||||
|
||||
def test_09_upload_group_assignment(self):
|
||||
from unittest.mock import patch
|
||||
import io
|
||||
|
||||
# 1. Test skip_poses=True, group_id=None
|
||||
# It should assign group_id to the base name of the uploaded file.
|
||||
img_bytes = io.BytesIO()
|
||||
Image.new("RGB", (100, 100), color=(0, 0, 255)).save(img_bytes, format="PNG")
|
||||
img_bytes.seek(0)
|
||||
|
||||
response = self.client.post(
|
||||
"/upload",
|
||||
files={"image": ("test_upload_image.png", img_bytes, "image/png")},
|
||||
data={"skip_poses": "true"}
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
res_data = response.json()
|
||||
self.assertEqual(res_data["status"], "added")
|
||||
self.assertEqual(res_data["group_id"], "test_upload_image.png")
|
||||
self.created_db_rows.append(res_data["filename"])
|
||||
self.created_files.append(os.path.join(self.output_dir, res_data["filename"]))
|
||||
|
||||
# 2. Test skip_poses=False, group_id=None (e.g. CTRL+v New group with run poses)
|
||||
# It should NOT collapse to "paste.png" or "test_upload_image.png", but generate a unique up_XXXX group ID!
|
||||
img_bytes2 = io.BytesIO()
|
||||
Image.new("RGB", (100, 100), color=(0, 255, 255)).save(img_bytes2, format="PNG")
|
||||
img_bytes2.seek(0)
|
||||
|
||||
with patch("edit_api._process_upload") as mock_process:
|
||||
response = self.client.post(
|
||||
"/upload",
|
||||
files={"image": ("test_upload_image2.png", img_bytes2, "image/png")},
|
||||
data={"skip_poses": "false"}
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
res_data2 = response.json()
|
||||
self.assertEqual(res_data2["status"], "processing")
|
||||
# It should generate a unique ID starting with up_
|
||||
self.assertTrue(res_data2["group_id"].startswith("up_"))
|
||||
self.assertNotEqual(res_data2["group_id"], "test_upload_image2.png")
|
||||
|
||||
# Verify background task was called with the unique group_id
|
||||
mock_process.assert_called_once()
|
||||
args, kwargs = mock_process.call_args
|
||||
# group_id is the 5th positional arg or a kwarg
|
||||
called_gid = kwargs.get("group_id") or args[4]
|
||||
self.assertTrue(called_gid.startswith("up_"))
|
||||
|
||||
self.created_files.append(os.path.join(self.output_dir, res_data2["filename"]))
|
||||
|
||||
def test_10_idle_backfill_non_image_filtering(self):
|
||||
from unittest.mock import patch, MagicMock
|
||||
import edit_api
|
||||
|
||||
# Reset failed backfill filenames set before testing
|
||||
edit_api._failed_backfill_filenames.clear()
|
||||
|
||||
# Mock database.list_persons to return rows with:
|
||||
# 1. A video file
|
||||
# 2. A non-image file
|
||||
# 3. An image that does exist on disk but we want to backfill
|
||||
# 4. A normal image where people_count is already present
|
||||
mock_persons = [
|
||||
("video.mp4", "Video", "g1", None, "", None, 0, "", False, False, None, False, "video", None, False, False, [], None, None, None, None, None, []),
|
||||
("notes.txt", "Text", "g1", None, "", None, 1, "", False, False, None, False, "image", None, False, False, [], None, None, None, None, None, []),
|
||||
("processed_image.png", "Image1", "g1", None, "", None, 2, "", False, False, None, False, "image", None, False, False, [], None, None, 1, "Full", "Front", []),
|
||||
("test_ref_image_123.png", "Image2", "g1", None, "", None, 3, "", False, False, None, False, "image", None, False, False, [], None, None, None, None, None, []),
|
||||
]
|
||||
|
||||
# First, test the fast-fail guard inside _process_image_for_metadata directly (not mocked yet)
|
||||
res_mp4 = edit_api._process_image_for_metadata("video.mp4")
|
||||
self.assertIsNone(res_mp4)
|
||||
|
||||
res_txt = edit_api._process_image_for_metadata("notes.txt")
|
||||
self.assertIsNone(res_txt)
|
||||
|
||||
# Let's mock os.path.exists to return True for any path we check
|
||||
with patch("os.path.exists", return_value=True), \
|
||||
patch("database.list_persons", return_value=mock_persons), \
|
||||
patch("edit_api._process_image_for_metadata") as mock_process:
|
||||
|
||||
# Second, let's replicate/test the candidate selection loop inside edit_api._idle_turntable_daemon
|
||||
output_dir = "/mnt/zim/tour-comfy/output"
|
||||
_failed_backfill_filenames = edit_api._failed_backfill_filenames
|
||||
|
||||
persons = mock_persons
|
||||
legacy_candidate = None
|
||||
for row in persons:
|
||||
fname = row[0]
|
||||
content_type = row[12] if len(row) > 12 else None
|
||||
people_count = row[19] if len(row) > 19 else None
|
||||
if fname.startswith("_turntable/"):
|
||||
continue
|
||||
if content_type == 'video':
|
||||
continue
|
||||
if not fname.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
|
||||
continue
|
||||
fpath = os.path.join(output_dir, fname)
|
||||
if not os.path.exists(fpath):
|
||||
continue
|
||||
if people_count is None and fname not in _failed_backfill_filenames:
|
||||
legacy_candidate = fname
|
||||
break
|
||||
|
||||
self.assertEqual(legacy_candidate, "test_ref_image_123.png")
|
||||
|
||||
# Let's test failed backfill insertion if _process_image_for_metadata returns None
|
||||
mock_process.return_value = None
|
||||
|
||||
# Simulating the idle turntable loop processing the candidate
|
||||
if legacy_candidate:
|
||||
try:
|
||||
res = edit_api._process_image_for_metadata(legacy_candidate)
|
||||
if res is None:
|
||||
_failed_backfill_filenames.add(legacy_candidate)
|
||||
except Exception as ex:
|
||||
_failed_backfill_filenames.add(legacy_candidate)
|
||||
|
||||
self.assertIn("test_ref_image_123.png", _failed_backfill_filenames)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user