diff --git a/media_manager/auth/users.py b/media_manager/auth/users.py index 4323728..a4abf81 100644 --- a/media_manager/auth/users.py +++ b/media_manager/auth/users.py @@ -53,7 +53,7 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): async def on_after_forgot_password( self, user: User, token: str, request: Optional[Request] = None ): - link = f"{AllEncompassingConfig().misc.FRONTEND_URL}login/reset-password?token={token}" + link = f"{AllEncompassingConfig().misc.frontend_url}login/reset-password?token={token}" log.info(f"User {user.id} has forgot their password. Reset Link: {link}") if not config.email_password_resets: @@ -109,7 +109,7 @@ def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]: class RedirectingCookieTransport(CookieTransport): async def get_login_response(self, token: str) -> Response: response = RedirectResponse( - str(AllEncompassingConfig().misc.FRONTEND_URL) + "dashboard", + str(AllEncompassingConfig().misc.frontend_url) + "dashboard", status_code=status.HTTP_302_FOUND, ) return self._set_login_cookie(response, token) diff --git a/media_manager/config.py b/media_manager/config.py index 6a6eba1..36abc60 100644 --- a/media_manager/config.py +++ b/media_manager/config.py @@ -1,10 +1,14 @@ +import logging import os from pathlib import Path +from typing import Type, Tuple from pydantic import AnyHttpUrl from pydantic_settings import ( BaseSettings, SettingsConfigDict, + PydanticBaseSettingsSource, + TomlConfigSettingsSource, ) from media_manager.auth.config import AuthConfig @@ -14,6 +18,18 @@ from media_manager.metadataProvider.config import MetadataProviderConfig from media_manager.notification.config import NotificationConfig from media_manager.torrent.config import TorrentConfig +log = logging.getLogger(__name__) +config_path = os.getenv("CONFIG_FILE") + +if config_path is None: + log.info("No CONFIG_FILE environment variable set, using default config file path.") + config_path = Path(__file__).parent.parent / "data" / "config.toml" +else: + config_path = Path(config_path) +print("SERVAS CONFIG PATH: ", config_path) +log.info("Using config file path: %s", config_path) + + class BasicConfig(BaseSettings): image_directory: Path = Path(__file__).parent.parent / "data" / "images" @@ -21,15 +37,15 @@ class BasicConfig(BaseSettings): movie_directory: Path = Path(__file__).parent.parent / "data" / "movies" torrent_directory: Path = Path(__file__).parent.parent / "data" / "torrents" - FRONTEND_URL: AnyHttpUrl = "http://localhost:3000/" - CORS_URLS: list[str] = [] - DEVELOPMENT: bool = False + frontend_url: AnyHttpUrl = "http://localhost:3000/" + cors_urls: list[str] = [] + development: bool = False api_base_path: str = "/api/v1" - class AllEncompassingConfig(BaseSettings): model_config = SettingsConfigDict( - toml_file=os.getenv("CONFIG_FILE", "./config.toml") + toml_file=config_path, + case_sensitive=False, ) """ This class is used to load all configurations from the environment variables. @@ -42,3 +58,15 @@ class AllEncompassingConfig(BaseSettings): indexers: IndexerConfig = IndexerConfig() database: DbConfig = DbConfig() auth: AuthConfig = AuthConfig() + + @classmethod + def settings_customise_sources( + cls, + settings_cls: Type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> Tuple[PydanticBaseSettingsSource, ...]: + return (TomlConfigSettingsSource(settings_cls),) + diff --git a/media_manager/database/__init__.py b/media_manager/database/__init__.py index 8b6fdf4..c527ea5 100644 --- a/media_manager/database/__init__.py +++ b/media_manager/database/__init__.py @@ -14,15 +14,15 @@ config = AllEncompassingConfig().database db_url = ( "postgresql+psycopg" + "://" - + config.USER + + config.user + ":" - + config.PASSWORD + + config.password + "@" - + config.HOST + + config.host + ":" - + str(config.PORT) + + str(config.port) + "/" - + config.DBNAME + + config.dbname ) engine = create_engine(db_url, echo=False) diff --git a/media_manager/database/config.py b/media_manager/database/config.py index ea17783..21f1b55 100644 --- a/media_manager/database/config.py +++ b/media_manager/database/config.py @@ -2,8 +2,8 @@ from pydantic_settings import BaseSettings class DbConfig(BaseSettings): - HOST: str = "localhost" - PORT: int = 5432 - USER: str = "MediaManager" - PASSWORD: str = "MediaManager" - DBNAME: str = "MediaManager" + host: str + port: int = 5432 + user: str = "MediaManager" + password: str = "MediaManager" + dbname: str = "MediaManager" diff --git a/media_manager/indexer/__init__.py b/media_manager/indexer/__init__.py index 44151f7..3988bf1 100644 --- a/media_manager/indexer/__init__.py +++ b/media_manager/indexer/__init__.py @@ -1,16 +1,3 @@ import logging -from media_manager.config import AllEncompassingConfig -from media_manager.indexer.indexers.jackett import Jackett -from media_manager.indexer.indexers.generic import GenericIndexer -from media_manager.indexer.indexers.prowlarr import Prowlarr - log = logging.getLogger(__name__) - -indexers: list[GenericIndexer] = [] - -config = AllEncompassingConfig() -if config.indexers.prowlarr.enabled: - indexers.append(Prowlarr()) -if config.indexers.jackett.enabled: - indexers.append(Jackett()) diff --git a/media_manager/indexer/service.py b/media_manager/indexer/service.py index bb29c3f..79f9d06 100644 --- a/media_manager/indexer/service.py +++ b/media_manager/indexer/service.py @@ -1,12 +1,26 @@ -from media_manager.indexer import log, indexers +import logging + +from media_manager.config import AllEncompassingConfig +from media_manager.indexer.indexers.generic import GenericIndexer +from media_manager.indexer.indexers.jackett import Jackett +from media_manager.indexer.indexers.prowlarr import Prowlarr from media_manager.indexer.schemas import IndexerQueryResultId, IndexerQueryResult from media_manager.indexer.repository import IndexerRepository from media_manager.notification.manager import notification_manager +log = logging.getLogger(__name__) + class IndexerService: def __init__(self, indexer_repository: IndexerRepository): + config = AllEncompassingConfig() self.repository = indexer_repository + self.indexers: list[GenericIndexer] = [] + + if config.indexers.prowlarr.enabled: + self.indexers.append(Prowlarr()) + if config.indexers.jackett.enabled: + self.indexers.append(Jackett()) def get_result(self, result_id: IndexerQueryResultId) -> IndexerQueryResult: return self.repository.get_result(result_id=result_id) @@ -24,7 +38,7 @@ class IndexerService: results = [] failed_indexers = [] - for indexer in indexers: + for indexer in self.indexers: try: indexer_results = indexer.search(query, is_tv=is_tv) results.extend(indexer_results) diff --git a/media_manager/main.py b/media_manager/main.py index 2101d33..ebcead1 100644 --- a/media_manager/main.py +++ b/media_manager/main.py @@ -100,7 +100,7 @@ from apscheduler.triggers.cron import CronTrigger # noqa: E402 init_db() log.info("Database initialized") config = AllEncompassingConfig() -if config.misc.DEVELOPMENT: +if config.misc.development: config.misc.torrent_directory.mkdir(parents=True, exist_ok=True) config.misc.tv_directory.mkdir(parents=True, exist_ok=True) config.misc.movie_directory.mkdir(parents=True, exist_ok=True) @@ -149,7 +149,7 @@ base_path = config.misc.api_base_path log.info("Base Path for API: %s", base_path) app = FastAPI(root_path=base_path, lifespan=lifespan) -origins = config.misc.CORS_URLS +origins = config.misc.cors_urls log.info("CORS URLs activated for following origins:") for origin in origins: log.info(f" - {origin}") diff --git a/media_manager/metadataProvider/config.py b/media_manager/metadataProvider/config.py index 7346733..a8177a3 100644 --- a/media_manager/metadataProvider/config.py +++ b/media_manager/metadataProvider/config.py @@ -1,7 +1,12 @@ from pydantic_settings import BaseSettings -from media_manager.metadataProvider.tmdb import TmdbConfig -from media_manager.metadataProvider.tvdb import TvdbConfig + +class TmdbConfig(BaseSettings): + tmdb_relay_url: str = "https://metadata-relay.maxid.me/tmdb" + + +class TvdbConfig(BaseSettings): + tvdb_relay_url: str = "https://metadata-relay.maxid.me/tvdb" class MetadataProviderConfig(BaseSettings): diff --git a/media_manager/metadataProvider/tmdb.py b/media_manager/metadataProvider/tmdb.py index f2054ba..0524634 100644 --- a/media_manager/metadataProvider/tmdb.py +++ b/media_manager/metadataProvider/tmdb.py @@ -1,7 +1,6 @@ import logging import requests -from pydantic_settings import BaseSettings import media_manager.metadataProvider.utils from media_manager.config import AllEncompassingConfig @@ -14,10 +13,6 @@ from media_manager.movies.schemas import Movie from media_manager.notification.manager import notification_manager -class TmdbConfig(BaseSettings): - TMDB_RELAY_URL: str = "https://metadata-relay.maxid.me/tmdb" - - ENDED_STATUS = {"Ended", "Canceled"} log = logging.getLogger(__name__) @@ -28,7 +23,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider): def __init__(self): config = AllEncompassingConfig().metadata.tmdb - self.url = config.TMDB_RELAY_URL + self.url = config.tmdb_relay_url def __get_show_metadata(self, id: int) -> dict: try: diff --git a/media_manager/metadataProvider/tvdb.py b/media_manager/metadataProvider/tvdb.py index 6d8e49f..100b06d 100644 --- a/media_manager/metadataProvider/tvdb.py +++ b/media_manager/metadataProvider/tvdb.py @@ -1,7 +1,6 @@ import requests import logging -from pydantic_settings import BaseSettings import media_manager.metadataProvider.utils from media_manager.config import AllEncompassingConfig @@ -13,10 +12,6 @@ from media_manager.tv.schemas import Episode, Season, Show, SeasonNumber from media_manager.movies.schemas import Movie -class TvdbConfig(BaseSettings): - TVDB_RELAY_URL: str = "https://metadata-relay.maxid.me/tvdb" - - log = logging.getLogger(__name__) @@ -25,7 +20,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider): def __init__(self): config = AllEncompassingConfig().metadata.tvdb - self.url = config.TVDB_RELAY_URL + self.url = config.tvdb_relay_url def __get_show(self, id: int) -> dict: return requests.get(f"{self.url}/tv/shows/{id}").json() diff --git a/media_manager/notification/config.py b/media_manager/notification/config.py index 286499b..01d8d90 100644 --- a/media_manager/notification/config.py +++ b/media_manager/notification/config.py @@ -1,10 +1,5 @@ from pydantic_settings import BaseSettings -from media_manager.notification.service_providers.email import EmailNotificationsConfig -from media_manager.notification.service_providers.gotify import GotifyConfig -from media_manager.notification.service_providers.ntfy import NtfyConfig -from media_manager.notification.service_providers.pushover import PushoverConfig - class EmailConfig(BaseSettings): smtp_host: str = "" @@ -15,6 +10,32 @@ class EmailConfig(BaseSettings): use_tls: bool = False +class EmailNotificationsConfig(BaseSettings): + enabled: bool = False + emails: list[str] = [] # the email addresses to send notifications to + + +class GotifyConfig(BaseSettings): + enabled: bool = False + api_key: str | None = None + url: str | None = ( + None # e.g. https://gotify.example.com (note lack of trailing slash) + ) + + +class NtfyConfig(BaseSettings): + enabled: bool = False + url: str | None = ( + None # e.g. https://ntfy.sh/your-topic (note lack of trailing slash) + ) + + +class PushoverConfig(BaseSettings): + enabled: bool = False + api_key: str | None = None + user: str | None = None + + class NotificationConfig(BaseSettings): smtp_config: EmailConfig = EmailConfig() email_notifications: EmailNotificationsConfig = EmailNotificationsConfig() diff --git a/media_manager/notification/manager.py b/media_manager/notification/manager.py index 2ff441f..3936c08 100644 --- a/media_manager/notification/manager.py +++ b/media_manager/notification/manager.py @@ -37,7 +37,7 @@ class NotificationManager: def _initialize_providers(self) -> None: # Email provider - if self.config.email: + if self.config.email_notifications.enabled: try: self.providers.append(EmailNotificationServiceProvider()) logger.info("Email notification provider initialized") @@ -45,7 +45,7 @@ class NotificationManager: logger.error(f"Failed to initialize Email provider: {e}") # Gotify provider - if self.config.gotify_api_key and self.config.gotify_url: + if self.config.gotify.enabled: try: self.providers.append(GotifyNotificationServiceProvider()) logger.info("Gotify notification provider initialized") @@ -53,7 +53,7 @@ class NotificationManager: logger.error(f"Failed to initialize Gotify provider: {e}") # Ntfy provider - if self.config.ntfy_url: + if self.config.ntfy.enabled: try: self.providers.append(NtfyNotificationServiceProvider()) logger.info("Ntfy notification provider initialized") @@ -61,7 +61,7 @@ class NotificationManager: logger.error(f"Failed to initialize Ntfy provider: {e}") # Pushover provider - if self.config.pushover_api_key and self.config.pushover_user: + if self.config.pushover.enabled: try: self.providers.append(PushoverNotificationServiceProvider()) logger.info("Pushover notification provider initialized") diff --git a/media_manager/notification/service_providers/email.py b/media_manager/notification/service_providers/email.py index 970cb41..bbf35a5 100644 --- a/media_manager/notification/service_providers/email.py +++ b/media_manager/notification/service_providers/email.py @@ -1,5 +1,3 @@ -from pydantic_settings import BaseSettings - import media_manager.notification.utils from media_manager.notification.schemas import MessageNotification from media_manager.notification.service_providers.abstractNotificationServiceProvider import ( @@ -8,11 +6,6 @@ from media_manager.notification.service_providers.abstractNotificationServicePro from media_manager.config import AllEncompassingConfig -class EmailNotificationsConfig(BaseSettings): - enabled: bool = False - emails: list[str] = [] # the email addresses to send notifications to - - class EmailNotificationServiceProvider(AbstractNotificationServiceProvider): def __init__(self): self.config = AllEncompassingConfig().notifications.email_notifications diff --git a/media_manager/notification/service_providers/gotify.py b/media_manager/notification/service_providers/gotify.py index 4aca9f9..9108167 100644 --- a/media_manager/notification/service_providers/gotify.py +++ b/media_manager/notification/service_providers/gotify.py @@ -1,5 +1,4 @@ import requests -from pydantic_settings import BaseSettings from media_manager.config import AllEncompassingConfig from media_manager.notification.schemas import MessageNotification @@ -8,14 +7,6 @@ from media_manager.notification.service_providers.abstractNotificationServicePro ) -class GotifyConfig(BaseSettings): - enabled: bool = False - api_key: str | None = None - url: str | None = ( - None # e.g. https://gotify.example.com (note lack of trailing slash) - ) - - class GotifyNotificationServiceProvider(AbstractNotificationServiceProvider): """ Gotify Notification Service Provider diff --git a/media_manager/notification/service_providers/ntfy.py b/media_manager/notification/service_providers/ntfy.py index cc0c21b..57f993d 100644 --- a/media_manager/notification/service_providers/ntfy.py +++ b/media_manager/notification/service_providers/ntfy.py @@ -1,5 +1,4 @@ import requests -from pydantic_settings import BaseSettings from media_manager.config import AllEncompassingConfig from media_manager.notification.schemas import MessageNotification @@ -8,13 +7,6 @@ from media_manager.notification.service_providers.abstractNotificationServicePro ) -class NtfyConfig(BaseSettings): - enabled: bool = False - url: str | None = ( - None # e.g. https://ntfy.sh/your-topic (note lack of trailing slash) - ) - - class NtfyNotificationServiceProvider(AbstractNotificationServiceProvider): """ Ntfy Notification Service Provider diff --git a/media_manager/notification/service_providers/pushover.py b/media_manager/notification/service_providers/pushover.py index d277205..8f3abc2 100644 --- a/media_manager/notification/service_providers/pushover.py +++ b/media_manager/notification/service_providers/pushover.py @@ -1,5 +1,4 @@ import requests -from pydantic_settings import BaseSettings from media_manager.config import AllEncompassingConfig from media_manager.notification.schemas import MessageNotification @@ -8,12 +7,6 @@ from media_manager.notification.service_providers.abstractNotificationServicePro ) -class PushoverConfig(BaseSettings): - enabled: bool = False - api_key: str | None = None - user: str | None = None - - class PushoverNotificationServiceProvider(AbstractNotificationServiceProvider): def __init__(self): self.config = AllEncompassingConfig().notifications.pushover diff --git a/media_manager/torrent/config.py b/media_manager/torrent/config.py index e2458aa..42e3a16 100644 --- a/media_manager/torrent/config.py +++ b/media_manager/torrent/config.py @@ -1,7 +1,21 @@ -from pydantic_settings import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict -from media_manager.torrent.download_clients.qbittorrent import QbittorrentConfig -from media_manager.torrent.download_clients.sabnzbd import SabnzbdConfig + +class QbittorrentConfig(BaseSettings): + model_config = SettingsConfigDict(env_prefix="QBITTORRENT_") + host: str = "localhost" + port: int = 8080 + username: str = "admin" + password: str = "admin" + enabled: bool = False + + +class SabnzbdConfig(BaseSettings): + model_config = SettingsConfigDict(env_prefix="SABNZBD_") + host: str = "localhost" + port: int = 8080 + api_key: str = "" + enabled: bool = False class TorrentConfig(BaseSettings): diff --git a/media_manager/torrent/download_clients/qbittorrent.py b/media_manager/torrent/download_clients/qbittorrent.py index b3fe51d..422a6fd 100644 --- a/media_manager/torrent/download_clients/qbittorrent.py +++ b/media_manager/torrent/download_clients/qbittorrent.py @@ -4,7 +4,6 @@ import logging import bencoder import qbittorrentapi import requests -from pydantic_settings import BaseSettings, SettingsConfigDict from media_manager.config import AllEncompassingConfig from media_manager.indexer.schemas import IndexerQueryResult @@ -16,15 +15,6 @@ from media_manager.torrent.schemas import TorrentStatus, Torrent log = logging.getLogger(__name__) -class QbittorrentConfig(BaseSettings): - model_config = SettingsConfigDict(env_prefix="QBITTORRENT_") - host: str = "localhost" - port: int = 8080 - username: str = "admin" - password: str = "admin" - enabled: bool = False - - class QbittorrentDownloadClient(AbstractDownloadClient): DOWNLOADING_STATE = ( "allocating", diff --git a/media_manager/torrent/download_clients/sabnzbd.py b/media_manager/torrent/download_clients/sabnzbd.py index 222663f..36d12cb 100644 --- a/media_manager/torrent/download_clients/sabnzbd.py +++ b/media_manager/torrent/download_clients/sabnzbd.py @@ -1,5 +1,4 @@ import logging -from pydantic_settings import BaseSettings, SettingsConfigDict from media_manager.config import AllEncompassingConfig from media_manager.indexer.schemas import IndexerQueryResult @@ -12,14 +11,6 @@ import sabnzbd_api log = logging.getLogger(__name__) -class SabnzbdConfig(BaseSettings): - model_config = SettingsConfigDict(env_prefix="SABNZBD_") - host: str = "localhost" - port: int = 8080 - api_key: str = "" - enabled: bool = False - - class SabnzbdDownloadClient(AbstractDownloadClient): DOWNLOADING_STATE = ( "Downloading",