switch metadataprovider module to dependency injection and update roadmap

This commit is contained in:
maxDorninger
2025-06-10 20:46:27 +02:00
parent 611d5a2b03
commit e6d65be94e
7 changed files with 62 additions and 71 deletions

View File

@@ -34,8 +34,8 @@ torrents and authentication.
- [x] make API return proper error codes
- [x] optimize images for web in the backend
- [x] responsive ui
- [x] automatically update metadata of shows
- [ ] support for movies
- [ ] automatically update metadata of shows
- [ ] automatically download new seasons/episodes of shows
- [ ] expand README with more information and a quickstart guide
- [ ] add notification system
@@ -43,6 +43,7 @@ torrents and authentication.
- [ ] add check at startup if hardlinks work
- [ ] add support for deluge and transmission
- [ ] make indexer module multithreaded
- [ ] improve reliability of scheduled tasks
- [ ] _maybe_ rework the logo
- [ ] _maybe_ add support for configuration via toml config file

View File

@@ -8,3 +8,9 @@ class NotFoundError(Exception):
"""Custom exception for when an entity is not found."""
pass
class InvalidConfigError(Exception):
"""Custom exception for when an entity is not found."""
pass

View File

@@ -1,37 +0,0 @@
import logging
from cachetools import TTLCache, cached
import media_manager.metadataProvider.tmdb as tmdb
import media_manager.metadataProvider.tvdb as tvdb
from media_manager.metadataProvider.abstractMetaDataProvider import metadata_providers
from media_manager.metadataProvider.schemas import MetaDataProviderShowSearchResult
from media_manager.tv.schemas import Show
_ = tvdb, tmdb
log = logging.getLogger(__name__)
search_show_cache = TTLCache(maxsize=128, ttl=24 * 60 * 60) # Cache for 24 hours
def get_show_metadata(id: int = None, provider: str = "tmdb") -> Show:
if id is None or provider is None:
raise ValueError("Show Metadata requires id and provider")
return metadata_providers[provider].get_show_metadata(id)
def download_show_poster_image(show: Show) -> bool:
"""
Downloads the poster image for a show.
:param show: The show to download the poster image for.
:return: True if the image was downloaded successfully, False otherwise.
"""
return metadata_providers[show.metadata_provider].download_show_poster_image(show)
@cached(search_show_cache)
def search_show(
query: str | None = None, provider: str = "tmdb"
) -> list[MetaDataProviderShowSearchResult]:
"""
If no query is provided, it will return the most popular shows.
"""
return metadata_providers[provider].search_show(query)

View File

@@ -18,11 +18,13 @@ class AbstractMetadataProvider(ABC):
@abstractmethod
def get_show_metadata(self, id: int = None) -> Show:
pass
raise NotImplementedError()
@abstractmethod
def search_show(self, query) -> list[MetaDataProviderShowSearchResult]:
pass
def search_show(
self, query: str | None = None
) -> list[MetaDataProviderShowSearchResult]:
raise NotImplementedError()
@abstractmethod
def download_show_poster_image(self, show: Show) -> bool:
@@ -31,7 +33,7 @@ class AbstractMetadataProvider(ABC):
:param show: The show to download the poster image for.
:return: True if the image was downloaded successfully, False otherwise.
"""
pass
raise NotImplementedError()
metadata_providers = {}

View File

@@ -0,0 +1,36 @@
from typing import Annotated, Literal
from fastapi import Depends
from media_manager.exceptions import InvalidConfigError
from fastapi.exceptions import HTTPException
from media_manager.metadataProvider.tmdb import TmdbMetadataProvider
from media_manager.metadataProvider.abstractMetaDataProvider import (
AbstractMetadataProvider,
)
from media_manager.metadataProvider.tvdb import TvdbMetadataProvider
def get_metadata_provider(
metadata_provider: Literal["tmdb", "tvdb"] = "tmdb",
) -> AbstractMetadataProvider:
try:
if metadata_provider == "tmdb":
return TmdbMetadataProvider()
elif metadata_provider == "tvdb":
return TvdbMetadataProvider()
else:
raise HTTPException(
status_code=400,
detail=f"Invalid metadata provider: {metadata_provider}. Supported providers are 'tmdb' and/or 'tvdb'.",
)
except InvalidConfigError as e:
raise HTTPException(
status_code=500,
detail=f"Metadata provider '{metadata_provider}' not configured: {str(e)}",
)
metadata_provider_dep = Annotated[
AbstractMetadataProvider, Depends(get_metadata_provider)
]

View File

@@ -5,9 +5,9 @@ from pydantic_settings import BaseSettings
from tmdbsimple import TV, TV_Seasons
import media_manager.metadataProvider.utils
from media_manager.exceptions import InvalidConfigError
from media_manager.metadataProvider.abstractMetaDataProvider import (
AbstractMetadataProvider,
register_metadata_provider,
)
from media_manager.metadataProvider.schemas import MetaDataProviderShowSearchResult
from media_manager.tv.schemas import Episode, Season, Show, SeasonNumber, EpisodeNumber
@@ -19,7 +19,6 @@ class TmdbConfig(BaseSettings):
ENDED_STATUS = {"Ended", "Canceled"}
config = TmdbConfig()
log = logging.getLogger(__name__)
@@ -27,7 +26,10 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
name = "tmdb"
def __init__(self, api_key: str = None):
tmdbsimple.API_KEY = api_key
config = TmdbConfig()
if config.TMDB_API_KEY is None:
raise InvalidConfigError("TMDB_API_KEY is not set")
tmdbsimple.API_KEY = config.TMDB_API_KEY
def download_show_poster_image(self, show: Show) -> bool:
show_metadata = TV(show.external_id).info()
@@ -150,12 +152,4 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
)
except Exception as e:
log.warning(f"Error processing search result {result}: {e}")
return formatted_results
if config.TMDB_API_KEY is not None:
log.info("Registering TMDB as metadata provider")
register_metadata_provider(
metadata_provider=TmdbMetadataProvider(config.TMDB_API_KEY)
)
return formatted_results

View File

@@ -6,6 +6,7 @@ import logging
from pydantic_settings import BaseSettings
import media_manager.metadataProvider.utils
from media_manager.exceptions import InvalidConfigError
from media_manager.metadataProvider.abstractMetaDataProvider import (
AbstractMetadataProvider,
register_metadata_provider,
@@ -18,7 +19,6 @@ class TvdbConfig(BaseSettings):
TVDB_API_KEY: str | None = None
config = TvdbConfig()
log = logging.getLogger(__name__)
@@ -28,7 +28,10 @@ class TvdbMetadataProvider(AbstractMetadataProvider):
tvdb_client: tvdb_v4_official.TVDB
def __init__(self, api_key: str = None):
self.tvdb_client = tvdb_v4_official.TVDB(api_key)
config = TvdbConfig()
if config.TVDB_API_KEY is None:
raise InvalidConfigError("TVDB_API_KEY is not set")
self.tvdb_client = tvdb_v4_official.TVDB(config.TVDB_API_KEY)
def download_show_poster_image(self, show: Show) -> bool:
show_metadata = self.tvdb_client.get_series_extended(show.external_id)
@@ -122,18 +125,4 @@ class TvdbMetadataProvider(AbstractMetadataProvider):
)
except Exception as e:
log.warning(f"Error processing search result {result}: {e}")
return formatted_results
if config.TVDB_API_KEY is not None:
log.info("Registering TVDB as metadata provider")
register_metadata_provider(
metadata_provider=TvdbMetadataProvider(config.TVDB_API_KEY)
)
if __name__ == "__main__":
tvdb = TvdbMetadataProvider(config.TVDB_API_KEY)
# show_metadata = tvdb.get_show_metadata(id=328724) # Replace with a valid TVDB ID
# pprint.pprint(dict(show_metadata))
search_results = tvdb.search_show("Simpsons Declassified")
pprint.pprint(search_results)
return formatted_results