"""Logic package: plugin dispatch orchestration and public re-exports.""" import asyncio import dataclasses from typing import Any import plugins as plugin_registry from errors import InvalidPluginEntityError, PluginNotFoundError, PluginTargetMismatchError from models import PluginLookupResult from logic.archive import run_archive_searcher, run_archive_searcher_bg from logic.batch import archive_executor, batch_executor, batch_state, process_book_sync, run_batch from logic.boundaries import book_spine_source, bounds_for_index, run_boundary_detector, shelf_source from logic.identification import ( AI_FIELDS, apply_ai_result, build_query, compute_status, dismiss_field, run_book_identifier, run_text_recognizer, save_user_fields, ) from logic.images import prep_img_b64, crop_save, serve_crop __all__ = [ "AI_FIELDS", "apply_ai_result", "archive_executor", "batch_executor", "batch_state", "book_spine_source", "bounds_for_index", "build_query", "compute_status", "crop_save", "dismiss_field", "dispatch_plugin", "process_book_sync", "run_archive_searcher", "run_archive_searcher_bg", "run_batch", "run_book_identifier", "run_boundary_detector", "run_text_recognizer", "save_user_fields", "serve_crop", "shelf_source", "prep_img_b64", ] async def dispatch_plugin( plugin_id: str, lookup: PluginLookupResult, entity_type: str, entity_id: str, loop: asyncio.AbstractEventLoop, ) -> dict[str, Any]: """Validate plugin/entity compatibility, run the plugin, and trigger auto-queue follow-ups. Args: plugin_id: The plugin ID string (used in error reporting). lookup: Discriminated tuple from plugins.get_plugin(); (None, None) if not found. entity_type: Entity type string (e.g. 'cabinets', 'shelves', 'books'). entity_id: ID of the entity to operate on. loop: Running event loop for executor dispatch. Returns: dataclasses.asdict() of the updated entity row. Raises: PluginNotFoundError: If lookup is (None, None). InvalidPluginEntityError: If the entity_type is not compatible with the plugin category. PluginTargetMismatchError: If a boundary_detector plugin's target mismatches the entity. """ match lookup: case (None, None): raise PluginNotFoundError(plugin_id) case ("boundary_detector", plugin): if entity_type not in ("cabinets", "shelves"): raise InvalidPluginEntityError("boundary_detector", entity_type) if entity_type == "cabinets" and plugin.target != "shelves": raise PluginTargetMismatchError(plugin.plugin_id, "shelves", plugin.target) if entity_type == "shelves" and plugin.target != "books": raise PluginTargetMismatchError(plugin.plugin_id, "books", plugin.target) result = await loop.run_in_executor(None, run_boundary_detector, plugin, entity_type, entity_id) return dataclasses.asdict(result) case ("text_recognizer", plugin): if entity_type != "books": raise InvalidPluginEntityError("text_recognizer", entity_type) result = await loop.run_in_executor(None, run_text_recognizer, plugin, entity_id) for ap in plugin_registry.get_auto_queue("archive_searchers"): loop.run_in_executor(archive_executor, run_archive_searcher_bg, ap, entity_id) return dataclasses.asdict(result) case ("book_identifier", plugin): if entity_type != "books": raise InvalidPluginEntityError("book_identifier", entity_type) result = await loop.run_in_executor(None, run_book_identifier, plugin, entity_id) return dataclasses.asdict(result) case ("archive_searcher", plugin): if entity_type != "books": raise InvalidPluginEntityError("archive_searcher", entity_type) result = await loop.run_in_executor(archive_executor, run_archive_searcher, plugin, entity_id) return dataclasses.asdict(result)