- Added configuration for file storage and software plugins in `software_config.yaml`. - Created core components for batch processing including `CadBatchManager`, `CadTaskRouter`, and `SerialBatchExecutor`. - Implemented plugin callback handling with `PluginCallbackRegistry` and HTTP client for task submission. - Developed API endpoint for receiving plugin callbacks in `plugin_callbacks.py`. - Enhanced data models for batch processing including `BatchJob`, `BatchItem`, and callback payloads. - Introduced WebSocket support for real-time updates on batch processing status. - Added comprehensive tests for routing, callback API, and serial executor behavior. - Documented the implementation plan and core execution rules in `cad-batch-plan.md`.
57 lines
1.9 KiB
Python
57 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Dict, Optional, Tuple
|
|
|
|
from app.config import software_config
|
|
|
|
|
|
class RouteNotFoundError(ValueError):
|
|
pass
|
|
|
|
|
|
class CadTaskRouter:
|
|
"""Route model file path to software_id based on file extension."""
|
|
|
|
def __init__(self, extension_to_software: Optional[Dict[str, str]] = None):
|
|
self._extension_to_software = self._normalize_mapping(
|
|
extension_to_software or software_config.get_extension_routing()
|
|
)
|
|
|
|
@staticmethod
|
|
def _normalize_mapping(mapping: Dict[str, str]) -> Dict[str, str]:
|
|
normalized: Dict[str, str] = {}
|
|
for ext, software_id in mapping.items():
|
|
if not ext:
|
|
continue
|
|
normalized_ext = ext.lower().strip()
|
|
if not normalized_ext.startswith("."):
|
|
normalized_ext = f".{normalized_ext}"
|
|
normalized[normalized_ext] = software_id
|
|
return normalized
|
|
|
|
@staticmethod
|
|
def _normalize_extension(model_path: str, configured_ext: str) -> Optional[str]:
|
|
lower_name = Path(model_path).name.lower()
|
|
if lower_name.endswith(configured_ext):
|
|
return configured_ext
|
|
|
|
# Support versioned suffixes such as .prt.1/.asm.2
|
|
version_pattern = rf"{re.escape(configured_ext)}\.\d+$"
|
|
if re.search(version_pattern, lower_name):
|
|
return configured_ext
|
|
|
|
return None
|
|
|
|
def resolve(self, model_path: str) -> Tuple[str, str]:
|
|
if not model_path:
|
|
raise RouteNotFoundError("model_path is empty")
|
|
|
|
for configured_ext in sorted(self._extension_to_software.keys(), key=len, reverse=True):
|
|
normalized_ext = self._normalize_extension(model_path, configured_ext)
|
|
if normalized_ext:
|
|
return normalized_ext, self._extension_to_software[configured_ext]
|
|
|
|
raise RouteNotFoundError(f"No software route found for model path: {model_path}")
|