mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-23 22:22:11 -03:00
chore(routes): dedupe os import
This commit is contained in:
109
py/routes/base_recipe_routes.py
Normal file
109
py/routes/base_recipe_routes.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""Base infrastructure shared across recipe routes."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Callable, Mapping
|
||||
|
||||
import jinja2
|
||||
from aiohttp import web
|
||||
|
||||
from ..config import config
|
||||
from ..services.server_i18n import server_i18n
|
||||
from ..services.service_registry import ServiceRegistry
|
||||
from ..services.settings_manager import settings
|
||||
from .recipe_route_registrar import ROUTE_DEFINITIONS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseRecipeRoutes:
|
||||
"""Common dependency and startup wiring for recipe routes."""
|
||||
|
||||
_HANDLER_NAMES: tuple[str, ...] = tuple(
|
||||
definition.handler_name for definition in ROUTE_DEFINITIONS
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.recipe_scanner = None
|
||||
self.lora_scanner = None
|
||||
self.civitai_client = None
|
||||
self.settings = settings
|
||||
self.server_i18n = server_i18n
|
||||
self.template_env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(config.templates_path),
|
||||
autoescape=True,
|
||||
)
|
||||
|
||||
self._i18n_registered = False
|
||||
self._startup_hooks_registered = False
|
||||
self._handler_mapping: dict[str, Callable] | None = None
|
||||
|
||||
async def attach_dependencies(self, app: web.Application | None = None) -> None:
|
||||
"""Resolve shared services from the registry."""
|
||||
|
||||
await self._ensure_services()
|
||||
self._ensure_i18n_filter()
|
||||
|
||||
async def ensure_dependencies_ready(self) -> None:
|
||||
"""Ensure dependencies are available for request handlers."""
|
||||
|
||||
if self.recipe_scanner is None or self.civitai_client is None:
|
||||
await self.attach_dependencies()
|
||||
|
||||
def register_startup_hooks(self, app: web.Application) -> None:
|
||||
"""Register startup hooks once for dependency wiring."""
|
||||
|
||||
if self._startup_hooks_registered:
|
||||
return
|
||||
|
||||
app.on_startup.append(self.attach_dependencies)
|
||||
app.on_startup.append(self.prewarm_cache)
|
||||
self._startup_hooks_registered = True
|
||||
|
||||
async def prewarm_cache(self, app: web.Application | None = None) -> None:
|
||||
"""Pre-load recipe and LoRA caches on startup."""
|
||||
|
||||
try:
|
||||
await self.attach_dependencies(app)
|
||||
|
||||
if self.lora_scanner is not None:
|
||||
await self.lora_scanner.get_cached_data()
|
||||
hash_index = getattr(self.lora_scanner, "_hash_index", None)
|
||||
if hash_index is not None and hasattr(hash_index, "_hash_to_path"):
|
||||
_ = len(hash_index._hash_to_path)
|
||||
|
||||
if self.recipe_scanner is not None:
|
||||
await self.recipe_scanner.get_cached_data(force_refresh=True)
|
||||
except Exception as exc:
|
||||
logger.error("Error pre-warming recipe cache: %s", exc, exc_info=True)
|
||||
|
||||
def to_route_mapping(self) -> Mapping[str, Callable]:
|
||||
"""Return a mapping of handler name to coroutine for registrar binding."""
|
||||
|
||||
if self._handler_mapping is None:
|
||||
owner = self.get_handler_owner()
|
||||
self._handler_mapping = {
|
||||
name: getattr(owner, name) for name in self._HANDLER_NAMES
|
||||
}
|
||||
return self._handler_mapping
|
||||
|
||||
# Internal helpers -------------------------------------------------
|
||||
|
||||
async def _ensure_services(self) -> None:
|
||||
if self.recipe_scanner is None:
|
||||
self.recipe_scanner = await ServiceRegistry.get_recipe_scanner()
|
||||
self.lora_scanner = getattr(self.recipe_scanner, "_lora_scanner", None)
|
||||
|
||||
if self.civitai_client is None:
|
||||
self.civitai_client = await ServiceRegistry.get_civitai_client()
|
||||
|
||||
def _ensure_i18n_filter(self) -> None:
|
||||
if not self._i18n_registered:
|
||||
self.template_env.filters["t"] = self.server_i18n.create_template_filter()
|
||||
self._i18n_registered = True
|
||||
|
||||
def get_handler_owner(self):
|
||||
"""Return the object supplying bound handler coroutines."""
|
||||
|
||||
return self
|
||||
|
||||
64
py/routes/recipe_route_registrar.py
Normal file
64
py/routes/recipe_route_registrar.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Route registrar for recipe endpoints."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Mapping
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RouteDefinition:
|
||||
"""Declarative definition for a recipe HTTP route."""
|
||||
|
||||
method: str
|
||||
path: str
|
||||
handler_name: str
|
||||
|
||||
|
||||
ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = (
|
||||
RouteDefinition("GET", "/loras/recipes", "render_page"),
|
||||
RouteDefinition("GET", "/api/lm/recipes", "list_recipes"),
|
||||
RouteDefinition("GET", "/api/lm/recipe/{recipe_id}", "get_recipe"),
|
||||
RouteDefinition("POST", "/api/lm/recipes/analyze-image", "analyze_uploaded_image"),
|
||||
RouteDefinition("POST", "/api/lm/recipes/analyze-local-image", "analyze_local_image"),
|
||||
RouteDefinition("POST", "/api/lm/recipes/save", "save_recipe"),
|
||||
RouteDefinition("DELETE", "/api/lm/recipe/{recipe_id}", "delete_recipe"),
|
||||
RouteDefinition("GET", "/api/lm/recipes/top-tags", "get_top_tags"),
|
||||
RouteDefinition("GET", "/api/lm/recipes/base-models", "get_base_models"),
|
||||
RouteDefinition("GET", "/api/lm/recipe/{recipe_id}/share", "share_recipe"),
|
||||
RouteDefinition("GET", "/api/lm/recipe/{recipe_id}/share/download", "download_shared_recipe"),
|
||||
RouteDefinition("GET", "/api/lm/recipe/{recipe_id}/syntax", "get_recipe_syntax"),
|
||||
RouteDefinition("PUT", "/api/lm/recipe/{recipe_id}/update", "update_recipe"),
|
||||
RouteDefinition("POST", "/api/lm/recipe/lora/reconnect", "reconnect_lora"),
|
||||
RouteDefinition("GET", "/api/lm/recipes/find-duplicates", "find_duplicates"),
|
||||
RouteDefinition("POST", "/api/lm/recipes/bulk-delete", "bulk_delete"),
|
||||
RouteDefinition("POST", "/api/lm/recipes/save-from-widget", "save_recipe_from_widget"),
|
||||
RouteDefinition("GET", "/api/lm/recipes/for-lora", "get_recipes_for_lora"),
|
||||
RouteDefinition("GET", "/api/lm/recipes/scan", "scan_recipes"),
|
||||
)
|
||||
|
||||
|
||||
class RecipeRouteRegistrar:
|
||||
"""Bind declarative recipe definitions to an aiohttp router."""
|
||||
|
||||
_METHOD_MAP = {
|
||||
"GET": "add_get",
|
||||
"POST": "add_post",
|
||||
"PUT": "add_put",
|
||||
"DELETE": "add_delete",
|
||||
}
|
||||
|
||||
def __init__(self, app: web.Application) -> None:
|
||||
self._app = app
|
||||
|
||||
def register_routes(self, handler_lookup: Mapping[str, Callable[[web.Request], object]]) -> None:
|
||||
for definition in ROUTE_DEFINITIONS:
|
||||
handler = handler_lookup[definition.handler_name]
|
||||
self._bind_route(definition.method, definition.path, handler)
|
||||
|
||||
def _bind_route(self, method: str, path: str, handler: Callable) -> None:
|
||||
add_method_name = self._METHOD_MAP[method.upper()]
|
||||
add_method = getattr(self._app.router, add_method_name)
|
||||
add_method(path, handler)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import os
|
||||
import time
|
||||
import base64
|
||||
import jinja2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import io
|
||||
@@ -12,20 +11,18 @@ import tempfile
|
||||
import json
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from .base_recipe_routes import BaseRecipeRoutes
|
||||
from .recipe_route_registrar import RecipeRouteRegistrar
|
||||
from ..utils.exif_utils import ExifUtils
|
||||
from ..recipes import RecipeParserFactory
|
||||
from ..utils.constants import CARD_PREVIEW_WIDTH
|
||||
|
||||
from ..services.settings_manager import settings
|
||||
from ..services.server_i18n import server_i18n
|
||||
from ..config import config
|
||||
from ..services.downloader import get_downloader
|
||||
|
||||
# Check if running in standalone mode
|
||||
standalone_mode = os.environ.get("HF_HUB_DISABLE_TELEMETRY", "0") == "0"
|
||||
|
||||
from ..services.service_registry import ServiceRegistry # Add ServiceRegistry import
|
||||
from ..services.downloader import get_downloader
|
||||
|
||||
# Only import MetadataRegistry in non-standalone mode
|
||||
if not standalone_mode:
|
||||
# Import metadata_collector functions and classes conditionally
|
||||
@@ -35,111 +32,35 @@ if not standalone_mode:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class RecipeRoutes:
|
||||
"""API route handlers for Recipe management"""
|
||||
|
||||
def __init__(self):
|
||||
# Initialize service references as None, will be set during async init
|
||||
self.recipe_scanner = None
|
||||
self.civitai_client = None
|
||||
self.template_env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(config.templates_path),
|
||||
autoescape=True
|
||||
)
|
||||
|
||||
# Pre-warm the cache
|
||||
self._init_cache_task = None
|
||||
|
||||
async def init_services(self):
|
||||
"""Initialize services from ServiceRegistry"""
|
||||
self.recipe_scanner = await ServiceRegistry.get_recipe_scanner()
|
||||
self.civitai_client = await ServiceRegistry.get_civitai_client()
|
||||
class RecipeRoutes(BaseRecipeRoutes):
|
||||
"""API route handlers for Recipe management."""
|
||||
|
||||
@classmethod
|
||||
def setup_routes(cls, app: web.Application):
|
||||
"""Register API routes"""
|
||||
"""Register API routes using the declarative registrar."""
|
||||
|
||||
routes = cls()
|
||||
app.router.add_get('/loras/recipes', routes.handle_recipes_page)
|
||||
registrar = RecipeRouteRegistrar(app)
|
||||
registrar.register_routes(routes.to_route_mapping())
|
||||
routes.register_startup_hooks(app)
|
||||
|
||||
app.router.add_get('/api/lm/recipes', routes.get_recipes)
|
||||
app.router.add_get('/api/lm/recipe/{recipe_id}', routes.get_recipe_detail)
|
||||
app.router.add_post('/api/lm/recipes/analyze-image', routes.analyze_recipe_image)
|
||||
app.router.add_post('/api/lm/recipes/analyze-local-image', routes.analyze_local_image)
|
||||
app.router.add_post('/api/lm/recipes/save', routes.save_recipe)
|
||||
app.router.add_delete('/api/lm/recipe/{recipe_id}', routes.delete_recipe)
|
||||
|
||||
# Add new filter-related endpoints
|
||||
app.router.add_get('/api/lm/recipes/top-tags', routes.get_top_tags)
|
||||
app.router.add_get('/api/lm/recipes/base-models', routes.get_base_models)
|
||||
|
||||
# Add new sharing endpoints
|
||||
app.router.add_get('/api/lm/recipe/{recipe_id}/share', routes.share_recipe)
|
||||
app.router.add_get('/api/lm/recipe/{recipe_id}/share/download', routes.download_shared_recipe)
|
||||
|
||||
# Add new endpoint for getting recipe syntax
|
||||
app.router.add_get('/api/lm/recipe/{recipe_id}/syntax', routes.get_recipe_syntax)
|
||||
|
||||
# Add new endpoint for updating recipe metadata (name, tags and source_path)
|
||||
app.router.add_put('/api/lm/recipe/{recipe_id}/update', routes.update_recipe)
|
||||
|
||||
# Add new endpoint for reconnecting deleted LoRAs
|
||||
app.router.add_post('/api/lm/recipe/lora/reconnect', routes.reconnect_lora)
|
||||
|
||||
# Add new endpoint for finding duplicate recipes
|
||||
app.router.add_get('/api/lm/recipes/find-duplicates', routes.find_duplicates)
|
||||
|
||||
# Add new endpoint for bulk deletion of recipes
|
||||
app.router.add_post('/api/lm/recipes/bulk-delete', routes.bulk_delete)
|
||||
|
||||
# Start cache initialization
|
||||
app.on_startup.append(routes._init_cache)
|
||||
|
||||
app.router.add_post('/api/lm/recipes/save-from-widget', routes.save_recipe_from_widget)
|
||||
|
||||
# Add route to get recipes for a specific Lora
|
||||
app.router.add_get('/api/lm/recipes/for-lora', routes.get_recipes_for_lora)
|
||||
|
||||
# Add new endpoint for scanning and rebuilding the recipe cache
|
||||
app.router.add_get('/api/lm/recipes/scan', routes.scan_recipes)
|
||||
|
||||
async def _init_cache(self, app):
|
||||
"""Initialize cache on startup"""
|
||||
async def render_page(self, request: web.Request) -> web.Response:
|
||||
"""Handle GET /loras/recipes request."""
|
||||
try:
|
||||
# Initialize services first
|
||||
await self.init_services()
|
||||
|
||||
# Now that services are initialized, get the lora scanner
|
||||
lora_scanner = self.recipe_scanner._lora_scanner
|
||||
|
||||
# Get lora cache to ensure it's initialized
|
||||
lora_cache = await lora_scanner.get_cached_data()
|
||||
|
||||
# Verify hash index is built
|
||||
if hasattr(lora_scanner, '_hash_index'):
|
||||
hash_index_size = len(lora_scanner._hash_index._hash_to_path) if hasattr(lora_scanner._hash_index, '_hash_to_path') else 0
|
||||
|
||||
# Now that lora scanner is initialized, initialize recipe cache
|
||||
await self.recipe_scanner.get_cached_data(force_refresh=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Error pre-warming recipe cache: {e}", exc_info=True)
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
async def handle_recipes_page(self, request: web.Request) -> web.Response:
|
||||
"""Handle GET /loras/recipes request"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
|
||||
# 获取用户语言设置
|
||||
user_language = settings.get('language', 'en')
|
||||
|
||||
user_language = self.settings.get('language', 'en')
|
||||
|
||||
# 设置服务端i18n语言
|
||||
server_i18n.set_locale(user_language)
|
||||
|
||||
self.server_i18n.set_locale(user_language)
|
||||
|
||||
# 为模板环境添加i18n过滤器
|
||||
if not hasattr(self.template_env, '_i18n_filter_added'):
|
||||
self.template_env.filters['t'] = server_i18n.create_template_filter()
|
||||
self._ensure_i18n_filter()
|
||||
self.template_env._i18n_filter_added = True
|
||||
|
||||
|
||||
# Skip initialization check and directly try to get cached data
|
||||
try:
|
||||
# Recipe scanner will initialize cache if needed
|
||||
@@ -148,10 +69,10 @@ class RecipeRoutes:
|
||||
rendered = template.render(
|
||||
recipes=[], # Frontend will load recipes via API
|
||||
is_initializing=False,
|
||||
settings=settings,
|
||||
settings=self.settings,
|
||||
request=request,
|
||||
# 添加服务端翻译函数
|
||||
t=server_i18n.get_translation,
|
||||
t=self.server_i18n.get_translation,
|
||||
)
|
||||
except Exception as cache_error:
|
||||
logger.error(f"Error loading recipe cache data: {cache_error}")
|
||||
@@ -159,10 +80,10 @@ class RecipeRoutes:
|
||||
template = self.template_env.get_template('recipes.html')
|
||||
rendered = template.render(
|
||||
is_initializing=True,
|
||||
settings=settings,
|
||||
settings=self.settings,
|
||||
request=request,
|
||||
# 添加服务端翻译函数
|
||||
t=server_i18n.get_translation,
|
||||
t=self.server_i18n.get_translation,
|
||||
)
|
||||
logger.info("Recipe cache error, returning initialization page")
|
||||
|
||||
@@ -178,11 +99,11 @@ class RecipeRoutes:
|
||||
status=500
|
||||
)
|
||||
|
||||
async def get_recipes(self, request: web.Request) -> web.Response:
|
||||
"""API endpoint for getting paginated recipes"""
|
||||
async def list_recipes(self, request: web.Request) -> web.Response:
|
||||
"""API endpoint for getting paginated recipes."""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Get query parameters with defaults
|
||||
page = int(request.query.get('page', '1'))
|
||||
@@ -250,11 +171,11 @@ class RecipeRoutes:
|
||||
logger.error(f"Error retrieving recipes: {e}", exc_info=True)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
async def get_recipe_detail(self, request: web.Request) -> web.Response:
|
||||
"""Get detailed information about a specific recipe"""
|
||||
async def get_recipe(self, request: web.Request) -> web.Response:
|
||||
"""Get detailed information about a specific recipe."""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
recipe_id = request.match_info['recipe_id']
|
||||
|
||||
@@ -305,12 +226,12 @@ class RecipeRoutes:
|
||||
from datetime import datetime
|
||||
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
async def analyze_recipe_image(self, request: web.Request) -> web.Response:
|
||||
"""Analyze an uploaded image or URL for recipe metadata"""
|
||||
async def analyze_uploaded_image(self, request: web.Request) -> web.Response:
|
||||
"""Analyze an uploaded image or URL for recipe metadata."""
|
||||
temp_path = None
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Check if request contains multipart data (image) or JSON data (url)
|
||||
content_type = request.headers.get('Content-Type', '')
|
||||
@@ -480,7 +401,7 @@ class RecipeRoutes:
|
||||
"""Analyze a local image file for recipe metadata"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Get JSON data from request
|
||||
data = await request.json()
|
||||
@@ -573,7 +494,7 @@ class RecipeRoutes:
|
||||
"""Save a recipe to the recipes folder"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
reader = await request.multipart()
|
||||
|
||||
@@ -779,7 +700,7 @@ class RecipeRoutes:
|
||||
"""Delete a recipe by ID"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
recipe_id = request.match_info['recipe_id']
|
||||
|
||||
@@ -829,7 +750,7 @@ class RecipeRoutes:
|
||||
"""Get top tags used in recipes"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Get limit parameter with default
|
||||
limit = int(request.query.get('limit', '20'))
|
||||
@@ -864,7 +785,7 @@ class RecipeRoutes:
|
||||
"""Get base models used in recipes"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Get all recipes from cache
|
||||
cache = await self.recipe_scanner.get_cached_data()
|
||||
@@ -895,7 +816,7 @@ class RecipeRoutes:
|
||||
"""Process a recipe image for sharing by adding metadata to EXIF"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
recipe_id = request.match_info['recipe_id']
|
||||
|
||||
@@ -957,7 +878,7 @@ class RecipeRoutes:
|
||||
"""Serve a processed recipe image for download"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
recipe_id = request.match_info['recipe_id']
|
||||
|
||||
@@ -1016,7 +937,7 @@ class RecipeRoutes:
|
||||
"""Save a recipe from the LoRAs widget"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Get metadata using the metadata collector instead of workflow parsing
|
||||
raw_metadata = get_metadata()
|
||||
@@ -1216,7 +1137,7 @@ class RecipeRoutes:
|
||||
"""Generate recipe syntax for LoRAs in the recipe, looking up proper file names using hash_index"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
recipe_id = request.match_info['recipe_id']
|
||||
|
||||
@@ -1299,7 +1220,7 @@ class RecipeRoutes:
|
||||
"""Update recipe metadata (name and tags)"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
recipe_id = request.match_info['recipe_id']
|
||||
data = await request.json()
|
||||
@@ -1329,7 +1250,7 @@ class RecipeRoutes:
|
||||
"""Reconnect a deleted LoRA in a recipe to a local LoRA file"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Parse request data
|
||||
data = await request.json()
|
||||
@@ -1438,7 +1359,7 @@ class RecipeRoutes:
|
||||
"""Get recipes that use a specific Lora"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
lora_hash = request.query.get('hash')
|
||||
|
||||
@@ -1487,7 +1408,7 @@ class RecipeRoutes:
|
||||
"""API endpoint for scanning and rebuilding the recipe cache"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Force refresh the recipe cache
|
||||
logger.info("Manually triggering recipe cache rebuild")
|
||||
@@ -1508,7 +1429,7 @@ class RecipeRoutes:
|
||||
"""Find all duplicate recipes based on fingerprints"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Get all duplicate recipes
|
||||
duplicate_groups = await self.recipe_scanner.find_all_duplicate_recipes()
|
||||
@@ -1566,7 +1487,7 @@ class RecipeRoutes:
|
||||
"""Delete multiple recipes by ID"""
|
||||
try:
|
||||
# Ensure services are initialized
|
||||
await self.init_services()
|
||||
await self.ensure_dependencies_ready()
|
||||
|
||||
# Parse request data
|
||||
data = await request.json()
|
||||
@@ -1650,3 +1571,9 @@ class RecipeRoutes:
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
# Legacy method aliases retained for compatibility with existing imports.
|
||||
handle_recipes_page = render_page
|
||||
get_recipes = list_recipes
|
||||
get_recipe_detail = get_recipe
|
||||
analyze_recipe_image = analyze_uploaded_image
|
||||
|
||||
Reference in New Issue
Block a user