"""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", ]