"""RenderBackend: config-driven factory for image and video generators. Reads the ``image_generator`` and ``video_generator`` sections from a ViMax YAML config, instantiates the concrete classes via *class_path*, and wires up rate limiters. Usage:: backend = RenderBackend.from_config(config) image = await backend.image_generator.generate_single_image(...) video = await backend.video_generator.generate_single_video(...) """ import importlib import logging from dataclasses import dataclass from typing import Any, Dict from utils.rate_limiter import RateLimiter @dataclass class RenderBackend: """Bundles an image generator and a video generator.""" image_generator: Any video_generator: Any @classmethod def from_config(cls, config: Dict[str, Any]) -> "RenderBackend": """Build a RenderBackend from a parsed YAML config dict. Rate limiters are created from ``max_requests_per_minute`` / ``max_requests_per_day`` if present in each generator section. """ img_cfg = config["image_generator"] vid_cfg = config["video_generator"] image_gen = _instantiate(img_cfg, _build_rate_limiter(img_cfg)) video_gen = _instantiate(vid_cfg, _build_rate_limiter(vid_cfg)) logging.info("RenderBackend: image=%s, video=%s", img_cfg["class_path"], vid_cfg["class_path"]) return cls(image_generator=image_gen, video_generator=video_gen) def _build_rate_limiter(section: Dict[str, Any]) -> RateLimiter | None: rpm = section.get("max_requests_per_minute") rpd = section.get("max_requests_per_day") if rpm or rpd: return RateLimiter(max_requests_per_minute=rpm, max_requests_per_day=rpd) return None def _instantiate(section: Dict[str, Any], rate_limiter: RateLimiter | None) -> Any: module_path, cls_name = section["class_path"].rsplit(".", 1) cls = getattr(importlib.import_module(module_path), cls_name) init_args = dict(section.get("init_args", {})) if rate_limiter is not None: init_args["rate_limiter"] = rate_limiter return cls(**init_args)