backend/config_views.py
Rama master · réplica de lectura del repositorio original.
"""Read-only grouped views into the Settings model.
Each view exposes a curated subset of Settings fields by name. Attribute
access is delegated to the underlying Settings instance for fields listed
in the subclass's _fields frozenset; everything else raises AttributeError.
Importing rule: this module MUST NOT import from `config_settings` at
module level (would cause a circular import). The `Settings` parameter
of `_SettingsView.__init__` is duck-typed; type-checking only sees it
through TYPE_CHECKING. See config_settings.py for the matching
dependency-contract comment.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from config_settings import Settings
class _SettingsView:
"""Base for read-only Settings group views.
Delegates attribute access to the parent Settings instance for fields
declared in the subclass ``_fields`` tuple. Raises ``AttributeError``
for any field not in ``_fields``.
"""
_fields: frozenset[str] = frozenset()
def __init__(self, settings: Settings) -> None:
object.__setattr__(self, "_s", settings)
def __setattr__(self, name: str, value) -> None:
raise AttributeError(f"{type(self).__name__!r} is read-only")
def __getattr__(self, name: str):
if name in self._fields:
return getattr(self._s, name)
raise AttributeError(f"{type(self).__name__!r} has no attribute {name!r}")
class GeneralSettings(_SettingsView):
"""Infrastructure: port, logging, paths, DB, Redis, plugins, backup."""
_fields = frozenset(
(
"port",
"api_key",
"log_level",
"log_file",
"log_format",
"media_path",
"db_path",
"cors_origins",
"database_url",
"db_pool_size",
"db_pool_max_overflow",
"db_pool_recycle",
"redis_url",
"redis_cache_enabled",
"redis_queue_enabled",
"plugins_dir",
"plugin_hot_reload",
"backup_dir",
"backup_retention_daily",
"backup_retention_weekly",
"backup_retention_monthly",
)
)
class TranslationSettings(_SettingsView):
"""LLM, translation languages, prompt template, glossary."""
_fields = frozenset(
(
"source_language",
"target_language",
"source_language_name",
"target_language_name",
"auto_translate_any_source",
"auto_translate_provider_multilang",
"auto_translate_source_languages",
"prompt_template",
"ollama_url",
"ollama_model",
"batch_size",
"request_timeout",
"temperature",
"max_retries",
"backoff_base",
"translation_max_workers",
"glossary_enabled",
"glossary_max_terms",
)
)
class ProviderSettings(_SettingsView):
"""Provider credentials, rate limiting, circuit breaker, reranking."""
_fields = frozenset(
(
"provider_priorities",
"providers_enabled",
"providers_hidden",
"provider_language_excludes_json",
"provider_search_timeout",
"provider_cache_ttl_minutes",
"provider_auto_prioritize",
"provider_rate_limit_enabled",
"dedup_on_download",
"provider_dynamic_timeout_enabled",
"provider_dynamic_timeout_min_samples",
"provider_dynamic_timeout_multiplier",
"provider_dynamic_timeout_buffer_secs",
"provider_dynamic_timeout_min_secs",
"provider_dynamic_timeout_max_secs",
"provider_reranking_enabled",
"provider_reranking_min_downloads",
"provider_reranking_max_modifier",
"circuit_breaker_failure_threshold",
"circuit_breaker_cooldown_seconds",
"provider_auto_disable_cooldown_minutes",
"provider_rate_limit_throttle_minutes",
"addic7ed_username",
"addic7ed_password",
"turkcealtyazi_username",
"turkcealtyazi_password",
"titlovi_username",
"titlovi_password",
"opensubtitles_api_key",
"opensubtitles_username",
"opensubtitles_password",
"jimaku_api_key",
"jimaku_unknown_language_fallback",
"subdl_api_key",
"subsource_api_key",
"animetosho_root_attachments",
"github_token",
"anti_captcha_provider",
"anti_captcha_api_key",
"release_group_prefer",
"release_group_exclude",
"release_group_prefer_bonus",
)
)
class MediaServerSettings(_SettingsView):
"""Sonarr, Radarr, Jellyfin/Plex/Kodi, path mapping, ffmpeg, metadata."""
_fields = frozenset(
(
"sonarr_url",
"sonarr_api_key",
"sonarr_instances_json",
"radarr_url",
"radarr_api_key",
"radarr_instances_json",
"jellyfin_url",
"jellyfin_api_key",
"media_servers_json",
"path_mapping",
"streaming_enabled",
"ffmpeg_timeout",
"scan_metadata_engine",
"scan_metadata_max_workers",
"media_io_max_parallel",
)
)
class ScanningSettings(_SettingsView):
"""Wanted system, upgrade, HI, webhooks, automation, standalone, AniDB, notifications."""
_fields = frozenset(
(
"wanted_scan_interval_hours",
"wanted_anime_only",
"wanted_anime_movies_only",
"wanted_scan_on_startup",
"wanted_auto_extract",
"wanted_auto_translate",
"wanted_max_search_attempts",
# #199: bounded second chance for exhausted items (0 = off)
"wanted_revive_exhausted_after_days",
"wanted_revive_max_per_run",
"wanted_revive_on_provider_change",
"use_embedded_subs",
"scan_yield_ms",
# 0.71.0 subtitle automation bundle (see config_settings.py)
"subtitle_automation_enabled",
"subtitle_automation_queue_enabled",
"subtitle_automation_drain_interval_minutes",
"embedded_allow_sdh",
"embedded_sdh_penalty",
"embedded_extract_remove_from_container",
"embedded_extract_ass_stream_copy",
"cleanup_foreign_tracks_default",
"cleanup_foreign_tracks_keep_und",
"cleanup_track_variant_mode",
"cleanup_keep_forced",
"cleanup_keep_sdh",
"cleanup_sidecar_policy",
"cleanup_signs_removal_level",
"dubtitle_detection",
"dubtitle_min_score",
"dubtitle_min_margin",
"dubtitle_auto_min_cues",
"wanted_search_interval_hours",
"wanted_search_on_startup",
"wanted_search_max_items_per_run",
"wanted_search_order",
"provider_budget_enabled",
"provider_budget_stretch_mode",
"scheduler_profile",
"setup_wizard_completed",
"wanted_adaptive_backoff_enabled",
"wanted_backoff_base_hours",
"wanted_backoff_cap_hours",
"wanted_skip_srt_on_no_ass",
"upgrade_enabled",
"upgrade_min_score_delta",
"upgrade_window_days",
"upgrade_prefer_ass",
"upgrade_protect_user_modified",
"upgrade_scan_interval_hours",
"auto_repair_enabled",
"hi_removal_enabled",
"hi_preference",
"credit_threshold_sec",
"op_window_sec",
"webhook_delay_minutes",
"webhook_auto_scan",
"webhook_auto_search",
"webhook_auto_translate",
"jellyfin_play_translate_enabled",
"auto_sync_after_download",
"auto_sync_engine",
"auto_nfo_export",
"standalone_enabled",
"standalone_scan_interval_hours",
"standalone_debounce_seconds",
"standalone_skip_extras",
"tmdb_api_key",
"tvdb_api_key",
"tvdb_pin",
"metadata_cache_ttl_days",
"auto_cleanup_after_extract",
"auto_cleanup_keep_languages",
"auto_cleanup_keep_formats",
"subtitle_trash_retention_days",
"max_ass_subtitle_size_mb",
"anidb_enabled",
"anidb_cache_ttl_days",
"anidb_custom_field_name",
"anidb_fallback_to_mapping",
"notification_urls_json",
"notify_on_download",
"notify_on_upgrade",
"notify_on_batch_complete",
"notify_on_error",
"notify_manual_actions",
"remux_trash_dir",
"remux_backup_retention_days",
"remux_use_reflink",
"remux_arr_pause_enabled",
"remux_hardlink_policy",
)
)
__all__ = [
"_SettingsView",
"GeneralSettings",
"TranslationSettings",
"ProviderSettings",
"MediaServerSettings",
"ScanningSettings",
]