Files
office_translator/services/providers/registry.py
sepehr 67365918ae
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m36s
feat(ui,api): wave 3 — editorial pricing, real cancel, server history, DeepL purge
Pricing: full editorial redesign — serif card headers with accent pills
replace the colored font-black blocks, tone sweep across toggle/metrics/
features/CTAs, PLAN_COLORS removed; one design system app-wide.

Translate: decorative titles one step down (CTA hierarchy restored);
glossary and image-translation blocks hidden entirely for free users
(progressive disclosure — three controls for free).

Reviews: XLIFF hint line explains the exchange format; backend errors
routed through a friendly mapper (session/not-found/rate-limit/server).

Landing: fabricated hero UI cards (fake 'Context Engine' overlay)
removed — the photo no longer promises screens that don't exist.

Nav: single DashboardNavLinks component shared by sidebar and mobile
drawer (was duplicated markup).

API: GET /api/v1/translations (user job history, paginated; completed
jobs retained 24h) and POST /api/v1/translations/{id}/cancel —
cooperative cancellation with worker checkpoints before dispatch and
before finalisation, reserved quota released immediately. Translate
monitor now offers a real 'Cancel translation' next to 'Back to start';
recent-jobs list reads server history first, localStorage fallback.

DeepL purge (backend): provider module, registry registration, config
attrs/defaults, dispatch branch, admin settings schema + test branch,
legacy availability block, validation rules, plan provider lists,
error-code mappings, MCP enums, translator prompt mention, related
tests updated/removed. Fallback resolver skips unknown providers, so
stale chains containing 'deepl' degrade gracefully.

Verified: backend 110 tests passed; frontend build exit 0, vitest 9/9,
0 missing i18n keys, eslint 63 errors (vs 64 at HEAD).
2026-08-30 22:42:29 +02:00

149 lines
4.2 KiB
Python

"""
Provider Registry - Singleton pattern for managing translation providers.
Provides a central registry for all translation providers with:
- Registration and retrieval by name
- Listing available providers
- Fallback chain support
"""
from typing import Dict, List, Optional
import threading
from .base import TranslationProvider
class ProviderRegistry:
"""
Singleton registry for translation providers.
Thread-safe implementation for managing multiple translation providers
with support for fallback chains.
"""
_instance: Optional["ProviderRegistry"] = None
_lock: threading.Lock = threading.Lock()
def __new__(cls) -> "ProviderRegistry":
"""Create or return the singleton instance."""
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._providers: Dict[str, TranslationProvider] = {}
cls._instance._providers_lock = threading.RLock()
return cls._instance
def register(self, name: str, provider: TranslationProvider) -> None:
"""
Register a translation provider.
Args:
name: Unique name for the provider (e.g., "google", "openai")
provider: TranslationProvider instance
"""
with self._providers_lock:
self._providers[name] = provider
def unregister(self, name: str) -> bool:
"""
Unregister a translation provider.
Args:
name: Name of the provider to remove
Returns:
True if provider was removed, False if not found
"""
with self._providers_lock:
if name in self._providers:
del self._providers[name]
return True
return False
def get(self, name: str) -> Optional[TranslationProvider]:
"""
Get a registered provider by name.
Args:
name: Provider name
Returns:
TranslationProvider instance or None if not found
"""
with self._providers_lock:
return self._providers.get(name)
def list_all(self) -> List[str]:
"""
List all registered provider names.
Returns:
List of provider names
"""
with self._providers_lock:
return list(self._providers.keys())
def list_available(self) -> List[str]:
"""
List names of all available (reachable) providers.
Returns:
List of provider names that are currently available
"""
with self._providers_lock:
return [
name
for name, provider in self._providers.items()
if provider.is_available()
]
def get_first_available(self, names: List[str]) -> Optional[TranslationProvider]:
"""
Get the first available provider from a list of names (fallback chain).
Iterates through the list in order and returns the first provider
that is available. This enables graceful degradation when providers
are unavailable.
Args:
names: List of provider names in priority order
Returns:
First available TranslationProvider or None if all are unavailable
"""
for name in names:
provider = self.get(name)
if provider is not None and provider.is_available():
return provider
return None
def clear(self) -> None:
"""Remove all registered providers."""
with self._providers_lock:
self._providers.clear()
def __len__(self) -> int:
"""Return the number of registered providers."""
with self._providers_lock:
return len(self._providers)
def __contains__(self, name: str) -> bool:
"""Check if a provider is registered."""
with self._providers_lock:
return name in self._providers
def get_registry() -> ProviderRegistry:
"""
Get the global provider registry instance.
Returns:
The singleton ProviderRegistry instance
"""
return ProviderRegistry()
# Global registry instance
registry = ProviderRegistry()