diff --git a/alembic/env.py b/alembic/env.py index b09aad9..af9b744 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -106,7 +106,13 @@ def run_migrations_online() -> None: """ - def include_object(_object, name, type_, _reflected, _compare_to): + def include_object( + _object: object, + name: str, + type_: str, + _reflected: bool, + _compare_to: object, + ) -> bool: if type_ == "table" and name == "apscheduler_jobs": return False return True diff --git a/media_manager/auth/config.py b/media_manager/auth/config.py index a664c2b..32c3cc6 100644 --- a/media_manager/auth/config.py +++ b/media_manager/auth/config.py @@ -22,5 +22,5 @@ class AuthConfig(BaseSettings): openid_connect: OpenIdConfig = OpenIdConfig() @property - def jwt_signing_key(self): + def jwt_signing_key(self) -> str: return self._jwt_signing_key diff --git a/media_manager/auth/db.py b/media_manager/auth/db.py index 145e8e4..12e961e 100644 --- a/media_manager/auth/db.py +++ b/media_manager/auth/db.py @@ -39,5 +39,7 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]: yield session -async def get_user_db(session: AsyncSession = Depends(get_async_session)): +async def get_user_db( + session: AsyncSession = Depends(get_async_session), +) -> AsyncGenerator[SQLAlchemyUserDatabase, None]: yield SQLAlchemyUserDatabase(session, User, OAuthAccount) diff --git a/media_manager/auth/router.py b/media_manager/auth/router.py index 2e1e410..80db648 100644 --- a/media_manager/auth/router.py +++ b/media_manager/auth/router.py @@ -19,7 +19,7 @@ users_router = APIRouter() auth_metadata_router = APIRouter() -def get_openid_router(): +def get_openid_router() -> APIRouter: if openid_client: return get_oauth_router( oauth_client=openid_client, diff --git a/media_manager/auth/users.py b/media_manager/auth/users.py index e5549fb..9081ecc 100644 --- a/media_manager/auth/users.py +++ b/media_manager/auth/users.py @@ -1,7 +1,7 @@ import contextlib import logging import uuid -from typing import Any, Optional, override +from typing import Any, AsyncGenerator, Optional, override from fastapi import Depends, Request from fastapi.responses import RedirectResponse, Response @@ -59,7 +59,9 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): await self.update(user=user, user_update=updated_user) @override - async def on_after_register(self, user: User, request: Optional[Request] = None): + async def on_after_register( + self, user: User, request: Optional[Request] = None + ) -> None: log.info(f"User {user.id} has registered.") if user.email in config.admin_emails: updated_user = UserUpdate(is_superuser=True, is_verified=True) @@ -68,7 +70,7 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): @override async def on_after_forgot_password( self, user: User, token: str, request: Optional[Request] = None - ): + ) -> None: link = f"{MediaManagerConfig().misc.frontend_url}web/login/reset-password?token={token}" log.info(f"User {user.id} has forgot their password. Reset Link: {link}") @@ -83,7 +85,7 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):

Hi {user.email},

- if you forgot your password, reset you password here.
+ if you forgot your password, reset you password here.
If you did not request a password reset, you can ignore this email.



@@ -99,23 +101,27 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): @override async def on_after_reset_password( self, user: User, request: Optional[Request] = None - ): + ) -> None: log.info(f"User {user.id} has reset their password.") @override async def on_after_request_verify( self, user: User, token: str, request: Optional[Request] = None - ): + ) -> None: log.info( f"Verification requested for user {user.id}. Verification token: {token}" ) @override - async def on_after_verify(self, user: User, request: Optional[Request] = None): + async def on_after_verify( + self, user: User, request: Optional[Request] = None + ) -> None: log.info(f"User {user.id} has been verified") -async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): +async def get_user_manager( + user_db: SQLAlchemyUserDatabase = Depends(get_user_db), +) -> AsyncGenerator[UserManager, None]: yield UserManager(user_db) @@ -124,7 +130,7 @@ get_user_db_context = contextlib.asynccontextmanager(get_user_db) get_user_manager_context = contextlib.asynccontextmanager(get_user_manager) -async def create_default_admin_user(): +async def create_default_admin_user() -> None: """Create a default admin user if no users exist in the database""" try: async with get_async_session_context() as session: @@ -177,10 +183,6 @@ async def create_default_admin_user(): ) -async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): - yield UserManager(user_db) - - def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]: return JWTStrategy(secret=SECRET, lifetime_seconds=LIFETIME) diff --git a/media_manager/database/__init__.py b/media_manager/database/__init__.py index e69ea7e..e34a73e 100644 --- a/media_manager/database/__init__.py +++ b/media_manager/database/__init__.py @@ -9,6 +9,8 @@ from sqlalchemy.engine import Engine from sqlalchemy.engine.url import URL from sqlalchemy.orm import Session, declarative_base, sessionmaker +from media_manager.database.config import DbConfig + log = logging.getLogger(__name__) Base = declarative_base() @@ -35,7 +37,7 @@ def build_db_url( def init_engine( - db_config: Any | None = None, + db_config: DbConfig | None = None, url: str | URL | None = None, ) -> Engine: """ diff --git a/media_manager/exceptions.py b/media_manager/exceptions.py index c1ee8b7..d83926d 100644 --- a/media_manager/exceptions.py +++ b/media_manager/exceptions.py @@ -7,7 +7,7 @@ from sqlalchemy.exc import IntegrityError class MediaManagerError(Exception): """Base exception for MediaManager errors.""" - def __init__(self, message: str = "An error occurred."): + def __init__(self, message: str = "An error occurred.") -> None: super().__init__(message) self.message = message @@ -17,56 +17,56 @@ class MediaAlreadyExistsError(MediaManagerError): def __init__( self, message: str = "Entity with this ID or other identifier already exists" - ): + ) -> None: super().__init__(message) class NotFoundError(MediaManagerError): """Raised when an entity is not found (HTTP 404).""" - def __init__(self, message: str = "The requested entity was not found."): + def __init__(self, message: str = "The requested entity was not found.") -> None: super().__init__(message) class InvalidConfigError(MediaManagerError): """Raised when the server is improperly configured (HTTP 500).""" - def __init__(self, message: str = "The server is improperly configured."): + def __init__(self, message: str = "The server is improperly configured.") -> None: super().__init__(message) class BadRequestError(MediaManagerError): """Raised for invalid client requests (HTTP 400).""" - def __init__(self, message: str = "Bad request."): + def __init__(self, message: str = "Bad request.") -> None: super().__init__(message) class UnauthorizedError(MediaManagerError): """Raised for authentication failures (HTTP 401).""" - def __init__(self, message: str = "Unauthorized."): + def __init__(self, message: str = "Unauthorized.") -> None: super().__init__(message) class ForbiddenError(MediaManagerError): """Raised for forbidden actions (HTTP 403).""" - def __init__(self, message: str = "Forbidden."): + def __init__(self, message: str = "Forbidden.") -> None: super().__init__(message) class ConflictError(MediaManagerError): """Raised for resource conflicts (HTTP 409).""" - def __init__(self, message: str = "Conflict."): + def __init__(self, message: str = "Conflict.") -> None: super().__init__(message) class UnprocessableEntityError(MediaManagerError): """Raised for validation errors (HTTP 422).""" - def __init__(self, message: str = "Unprocessable entity."): + def __init__(self, message: str = "Unprocessable entity.") -> None: super().__init__(message) @@ -128,7 +128,7 @@ async def sqlalchemy_integrity_error_handler( ) -def register_exception_handlers(app: FastAPI): +def register_exception_handlers(app: FastAPI) -> None: app.add_exception_handler(NotFoundError, not_found_error_exception_handler) app.add_exception_handler( MediaAlreadyExistsError, media_already_exists_exception_handler diff --git a/media_manager/filesystem_checks.py b/media_manager/filesystem_checks.py index d9573d1..5ae2797 100644 --- a/media_manager/filesystem_checks.py +++ b/media_manager/filesystem_checks.py @@ -1,8 +1,11 @@ import shutil +from logging import Logger from pathlib import Path +from media_manager.config import MediaManagerConfig -def run_filesystem_checks(config, log): + +def run_filesystem_checks(config: MediaManagerConfig, log: Logger) -> None: log.info("Creating directories if they don't exist...") config.misc.tv_directory.mkdir(parents=True, exist_ok=True) config.misc.movie_directory.mkdir(parents=True, exist_ok=True) diff --git a/media_manager/indexer/indexers/generic.py b/media_manager/indexer/indexers/generic.py index d70e700..f4abe1b 100644 --- a/media_manager/indexer/indexers/generic.py +++ b/media_manager/indexer/indexers/generic.py @@ -8,7 +8,7 @@ from media_manager.tv.schemas import Show class GenericIndexer(ABC): name: str - def __init__(self, name: str): + def __init__(self, name: str) -> None: self.name = name @abstractmethod diff --git a/media_manager/indexer/indexers/jackett.py b/media_manager/indexer/indexers/jackett.py index 2527176..ca02d8d 100644 --- a/media_manager/indexer/indexers/jackett.py +++ b/media_manager/indexer/indexers/jackett.py @@ -1,4 +1,5 @@ import concurrent +import concurrent.futures import logging from concurrent.futures.thread import ThreadPoolExecutor @@ -15,7 +16,7 @@ log = logging.getLogger(__name__) class Jackett(GenericIndexer, TorznabMixin): - def __init__(self): + def __init__(self) -> None: """ A subclass of GenericIndexer for interacting with the Jacket API. diff --git a/media_manager/indexer/indexers/prowlarr.py b/media_manager/indexer/indexers/prowlarr.py index 38b48ff..1ef4603 100644 --- a/media_manager/indexer/indexers/prowlarr.py +++ b/media_manager/indexer/indexers/prowlarr.py @@ -1,7 +1,7 @@ import logging from dataclasses import dataclass -from requests import Session +from requests import Response, Session from media_manager.config import MediaManagerConfig from media_manager.indexer.indexers.generic import GenericIndexer @@ -31,14 +31,14 @@ class IndexerInfo: class Prowlarr(GenericIndexer, TorznabMixin): - def __init__(self): + def __init__(self) -> None: """ A subclass of GenericIndexer for interacting with the Prowlarr API. """ super().__init__(name="prowlarr") self.config = MediaManagerConfig().indexers.prowlarr - def _call_prowlarr_api(self, path: str, parameters: dict | None = None): + def _call_prowlarr_api(self, path: str, parameters: dict | None = None) -> Response: url = f"{self.config.url}/api/v1{path}" headers = {"X-Api-Key": self.config.api_key} with Session() as session: diff --git a/media_manager/indexer/repository.py b/media_manager/indexer/repository.py index 343a6d5..c1b1a56 100644 --- a/media_manager/indexer/repository.py +++ b/media_manager/indexer/repository.py @@ -14,7 +14,7 @@ log = logging.getLogger(__name__) class IndexerRepository: - def __init__(self, db: Session): + def __init__(self, db: Session) -> None: self.db = db def get_result(self, result_id: IndexerQueryResultId) -> IndexerQueryResultSchema: diff --git a/media_manager/indexer/schemas.py b/media_manager/indexer/schemas.py index 0d28c55..5c2cf28 100644 --- a/media_manager/indexer/schemas.py +++ b/media_manager/indexer/schemas.py @@ -62,7 +62,7 @@ class IndexerQueryResult(BaseModel): result = [] return result - def __gt__(self, other) -> bool: + def __gt__(self, other: "IndexerQueryResult") -> bool: if self.quality.value != other.quality.value: return self.quality.value < other.quality.value if self.score != other.score: @@ -76,7 +76,7 @@ class IndexerQueryResult(BaseModel): return self.size < other.size - def __lt__(self, other) -> bool: + def __lt__(self, other: "IndexerQueryResult") -> bool: if self.quality.value != other.quality.value: return self.quality.value > other.quality.value if self.score != other.score: diff --git a/media_manager/indexer/service.py b/media_manager/indexer/service.py index 5100851..e9e724d 100644 --- a/media_manager/indexer/service.py +++ b/media_manager/indexer/service.py @@ -14,7 +14,7 @@ log = logging.getLogger(__name__) class IndexerService: - def __init__(self, indexer_repository: IndexerRepository): + def __init__(self, indexer_repository: IndexerRepository) -> None: config = MediaManagerConfig() self.repository = indexer_repository self.indexers: list[GenericIndexer] = [] @@ -55,7 +55,7 @@ class IndexerService: return results - def search_movie(self, movie: Movie): + def search_movie(self, movie: Movie) -> list[IndexerQueryResult]: query = f"{movie.name} {movie.year}" query = remove_special_chars_and_parentheses(query) @@ -75,7 +75,7 @@ class IndexerService: return results - def search_season(self, show: Show, season_number: int): + def search_season(self, show: Show, season_number: int) -> list[IndexerQueryResult]: query = f"{show.name} {show.year} S{season_number:02d}" query = remove_special_chars_and_parentheses(query) diff --git a/media_manager/logging.py b/media_manager/logging.py index 2d63833..9fae9a5 100644 --- a/media_manager/logging.py +++ b/media_manager/logging.py @@ -11,7 +11,7 @@ from pythonjsonlogger.json import JsonFormatter class ISOJsonFormatter(JsonFormatter): @override - def formatTime(self, record, datefmt=None): + def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str: dt = datetime.fromtimestamp(record.created, tz=timezone.utc) return dt.isoformat(timespec="milliseconds").replace("+00:00", "Z") @@ -62,7 +62,7 @@ LOGGING_CONFIG = { } -def setup_logging(): +def setup_logging() -> None: dictConfig(LOGGING_CONFIG) logging.basicConfig( level=LOG_LEVEL, diff --git a/media_manager/main.py b/media_manager/main.py index 8586e42..772f8c2 100644 --- a/media_manager/main.py +++ b/media_manager/main.py @@ -2,12 +2,12 @@ import logging import os import uvicorn -from fastapi import APIRouter, FastAPI +from fastapi import APIRouter, FastAPI, Request, Response from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from psycopg.errors import UniqueViolation from sqlalchemy.exc import IntegrityError -from starlette.responses import FileResponse, RedirectResponse, Response +from starlette.responses import FileResponse, RedirectResponse from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware import media_manager.movies.router as movies_router @@ -143,23 +143,23 @@ else: @app.get("/") -async def root(): +async def root() -> RedirectResponse: return RedirectResponse(url="/web/") @app.get("/dashboard") -async def dashboard(): +async def dashboard() -> RedirectResponse: return RedirectResponse(url="/web/") @app.get("/login") -async def login(): +async def login() -> RedirectResponse: return RedirectResponse(url="/web/") # this will serve the custom 404 page for frontend routes, so SvelteKit can handle routing @app.exception_handler(404) -async def not_found_handler(request, _exc): +async def not_found_handler(request: Request, _exc: Exception) -> Response: if not DISABLE_FRONTEND_MOUNT and any( base_path in ["/web", "/dashboard", "/login"] for base_path in request.url.path ): diff --git a/media_manager/metadataProvider/tmdb.py b/media_manager/metadataProvider/tmdb.py index 0fe3477..d512f1a 100644 --- a/media_manager/metadataProvider/tmdb.py +++ b/media_manager/metadataProvider/tmdb.py @@ -21,7 +21,7 @@ log = logging.getLogger(__name__) class TmdbMetadataProvider(AbstractMetadataProvider): name = "tmdb" - def __init__(self): + def __init__(self) -> None: config = MediaManagerConfig().metadata.tmdb self.url = config.tmdb_relay_url self.primary_languages = config.primary_languages @@ -248,8 +248,8 @@ class TmdbMetadataProvider(AbstractMetadataProvider): ) -> Show: """ - :param id: the external id of the show - :type id: int + :param show_id: the external id of the show + :type show_id: int :param language: optional language code (ISO 639-1) to fetch metadata in :type language: str | None :return: returns a Show object @@ -379,8 +379,8 @@ class TmdbMetadataProvider(AbstractMetadataProvider): """ Get movie metadata with language-aware fetching. - :param id: the external id of the movie - :type id: int + :param movie_id: the external id of the movie + :type movie_id: int :param language: optional language code (ISO 639-1) to fetch metadata in :type language: str | None :return: returns a Movie object diff --git a/media_manager/metadataProvider/tvdb.py b/media_manager/metadataProvider/tvdb.py index a3d100b..0fe9cd0 100644 --- a/media_manager/metadataProvider/tvdb.py +++ b/media_manager/metadataProvider/tvdb.py @@ -18,7 +18,7 @@ log = logging.getLogger(__name__) class TvdbMetadataProvider(AbstractMetadataProvider): name = "tvdb" - def __init__(self): + def __init__(self) -> None: config = MediaManagerConfig().metadata.tvdb self.url = config.tvdb_relay_url diff --git a/media_manager/movies/repository.py b/media_manager/movies/repository.py index f78e0cf..06b8058 100644 --- a/media_manager/movies/repository.py +++ b/media_manager/movies/repository.py @@ -40,7 +40,7 @@ class MovieRepository: Provides methods to retrieve, save, and delete movies. """ - def __init__(self, db: Session): + def __init__(self, db: Session) -> None: self.db = db def get_movie_by_id(self, movie_id: MovieId) -> MovieSchema: diff --git a/media_manager/movies/router.py b/media_manager/movies/router.py index 1ffe82e..544b88e 100644 --- a/media_manager/movies/router.py +++ b/media_manager/movies/router.py @@ -97,7 +97,7 @@ def get_all_importable_movies( ) def import_detected_movie( movie_service: movie_service_dep, movie: movie_dep, directory: str -): +) -> None: """ Import a detected movie from the specified directory into the library. """ @@ -145,7 +145,7 @@ def add_a_movie( metadata_provider: metadata_provider_dep, movie_id: int, language: str | None = None, -): +) -> Movie: """ Add a new movie to the library. """ @@ -254,7 +254,7 @@ def authorize_request( movie_request_id: MovieRequestId, user: Annotated[UserRead, Depends(current_superuser)], authorized_status: bool = False, -): +) -> None: """ Authorize or de-authorize a movie request. """ @@ -276,7 +276,7 @@ def authorize_request( ) def delete_movie_request( movie_service: movie_service_dep, movie_request_id: MovieRequestId -): +) -> None: """ Delete a movie request. """ @@ -309,7 +309,7 @@ def delete_a_movie( movie: movie_dep, delete_files_on_disk: bool = False, delete_torrents: bool = False, -): +) -> None: """ Delete a movie from the library. """ diff --git a/media_manager/movies/service.py b/media_manager/movies/service.py index a309ed8..764db21 100644 --- a/media_manager/movies/service.py +++ b/media_manager/movies/service.py @@ -59,7 +59,7 @@ class MovieService: torrent_service: TorrentService, indexer_service: IndexerService, notification_service: NotificationService = None, - ): + ) -> None: self.movie_repository = movie_repository self.torrent_service = torrent_service self.indexer_service = indexer_service diff --git a/media_manager/notification/manager.py b/media_manager/notification/manager.py index a1e95ef..2e7558c 100644 --- a/media_manager/notification/manager.py +++ b/media_manager/notification/manager.py @@ -31,7 +31,7 @@ class NotificationManager: Manages and orchestrates notifications across all configured service providers. """ - def __init__(self): + def __init__(self) -> None: self.config = MediaManagerConfig().notifications self.providers: List[AbstractNotificationServiceProvider] = [] self._initialize_providers() diff --git a/media_manager/notification/repository.py b/media_manager/notification/repository.py index a080176..943f953 100644 --- a/media_manager/notification/repository.py +++ b/media_manager/notification/repository.py @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) class NotificationRepository: - def __init__(self, db: Session): + def __init__(self, db: Session) -> None: self.db = db def get_notification(self, nid: NotificationId) -> NotificationSchema: @@ -60,7 +60,7 @@ class NotificationRepository: log.error(f"Database error while retrieving notifications: {e}") raise - def save_notification(self, notification: NotificationSchema): + def save_notification(self, notification: NotificationSchema) -> None: try: self.db.add( Notification( diff --git a/media_manager/notification/router.py b/media_manager/notification/router.py index 227723e..61a18ed 100644 --- a/media_manager/notification/router.py +++ b/media_manager/notification/router.py @@ -69,7 +69,7 @@ def get_notification( ) def mark_notification_as_read( notification_id: NotificationId, notification_service: notification_service_dep -): +) -> None: """ Mark a notification as read. """ @@ -86,7 +86,7 @@ def mark_notification_as_read( ) def mark_notification_as_unread( notification_id: NotificationId, notification_service: notification_service_dep -): +) -> None: """ Mark a notification as unread. """ @@ -103,7 +103,7 @@ def mark_notification_as_unread( ) def delete_notification( notification_id: NotificationId, notification_service: notification_service_dep -): +) -> None: """ Delete a notification. """ diff --git a/media_manager/notification/service.py b/media_manager/notification/service.py index a97515e..c7f7a09 100644 --- a/media_manager/notification/service.py +++ b/media_manager/notification/service.py @@ -7,7 +7,7 @@ class NotificationService: def __init__( self, notification_repository: NotificationRepository, - ): + ) -> None: self.notification_repository = notification_repository self.notification_manager = notification_manager diff --git a/media_manager/notification/service_providers/email.py b/media_manager/notification/service_providers/email.py index 9339252..a369ef0 100644 --- a/media_manager/notification/service_providers/email.py +++ b/media_manager/notification/service_providers/email.py @@ -7,7 +7,7 @@ from media_manager.notification.service_providers.abstract_notification_service_ class EmailNotificationServiceProvider(AbstractNotificationServiceProvider): - def __init__(self): + def __init__(self) -> None: self.config = MediaManagerConfig().notifications.email_notifications def send_notification(self, message: MessageNotification) -> bool: diff --git a/media_manager/notification/service_providers/gotify.py b/media_manager/notification/service_providers/gotify.py index 13610c7..ce57e2b 100644 --- a/media_manager/notification/service_providers/gotify.py +++ b/media_manager/notification/service_providers/gotify.py @@ -12,7 +12,7 @@ class GotifyNotificationServiceProvider(AbstractNotificationServiceProvider): Gotify Notification Service Provider """ - def __init__(self): + def __init__(self) -> None: self.config = MediaManagerConfig().notifications.gotify def send_notification(self, message: MessageNotification) -> bool: diff --git a/media_manager/notification/service_providers/ntfy.py b/media_manager/notification/service_providers/ntfy.py index a5e8439..304ccc0 100644 --- a/media_manager/notification/service_providers/ntfy.py +++ b/media_manager/notification/service_providers/ntfy.py @@ -12,7 +12,7 @@ class NtfyNotificationServiceProvider(AbstractNotificationServiceProvider): Ntfy Notification Service Provider """ - def __init__(self): + def __init__(self) -> None: self.config = MediaManagerConfig().notifications.ntfy def send_notification(self, message: MessageNotification) -> bool: diff --git a/media_manager/notification/service_providers/pushover.py b/media_manager/notification/service_providers/pushover.py index acee023..b66ad13 100644 --- a/media_manager/notification/service_providers/pushover.py +++ b/media_manager/notification/service_providers/pushover.py @@ -8,7 +8,7 @@ from media_manager.notification.service_providers.abstract_notification_service_ class PushoverNotificationServiceProvider(AbstractNotificationServiceProvider): - def __init__(self): + def __init__(self) -> None: self.config = MediaManagerConfig().notifications.pushover def send_notification(self, message: MessageNotification) -> bool: diff --git a/media_manager/scheduler.py b/media_manager/scheduler.py index 5d2e152..28a0e5f 100644 --- a/media_manager/scheduler.py +++ b/media_manager/scheduler.py @@ -15,7 +15,7 @@ from media_manager.tv.service import ( ) -def setup_scheduler(config): +def setup_scheduler(config: object) -> None: from media_manager.database import init_engine init_engine(config.database) diff --git a/media_manager/torrent/download_clients/qbittorrent.py b/media_manager/torrent/download_clients/qbittorrent.py index 538c3c5..d20a078 100644 --- a/media_manager/torrent/download_clients/qbittorrent.py +++ b/media_manager/torrent/download_clients/qbittorrent.py @@ -43,7 +43,7 @@ class QbittorrentDownloadClient(AbstractDownloadClient): ERROR_STATE = ("missingFiles", "error", "checkingResumeData") UNKNOWN_STATE = ("unknown",) - def __init__(self): + def __init__(self) -> None: self.config = MediaManagerConfig().torrents.qbittorrent self.api_client = qbittorrentapi.Client( host=self.config.host, diff --git a/media_manager/torrent/download_clients/sabnzbd.py b/media_manager/torrent/download_clients/sabnzbd.py index af83c6f..38247e8 100644 --- a/media_manager/torrent/download_clients/sabnzbd.py +++ b/media_manager/torrent/download_clients/sabnzbd.py @@ -27,7 +27,7 @@ class SabnzbdDownloadClient(AbstractDownloadClient): ERROR_STATE = ("Failed",) UNKNOWN_STATE = ("Unknown",) - def __init__(self): + def __init__(self) -> None: self.config = MediaManagerConfig().torrents.sabnzbd self.client = sabnzbd_api.SabnzbdClient( host=self.config.host, diff --git a/media_manager/torrent/download_clients/transmission.py b/media_manager/torrent/download_clients/transmission.py index 509c2e0..eaba85c 100644 --- a/media_manager/torrent/download_clients/transmission.py +++ b/media_manager/torrent/download_clients/transmission.py @@ -30,7 +30,7 @@ class TransmissionDownloadClient(AbstractDownloadClient): } ) - def __init__(self): + def __init__(self) -> None: self.config = MediaManagerConfig().torrents.transmission try: self._client = transmission_rpc.Client( diff --git a/media_manager/torrent/manager.py b/media_manager/torrent/manager.py index e76dad2..65b12a4 100644 --- a/media_manager/torrent/manager.py +++ b/media_manager/torrent/manager.py @@ -30,7 +30,7 @@ class DownloadManager: Only one torrent client and one usenet client are active at a time. """ - def __init__(self): + def __init__(self) -> None: self._torrent_client: AbstractDownloadClient | None = None self._usenet_client: AbstractDownloadClient | None = None self.config = MediaManagerConfig().torrents diff --git a/media_manager/torrent/repository.py b/media_manager/torrent/repository.py index aba9fc0..382e0df 100644 --- a/media_manager/torrent/repository.py +++ b/media_manager/torrent/repository.py @@ -18,7 +18,7 @@ from media_manager.tv.schemas import Show as ShowSchema class TorrentRepository: - def __init__(self, db: DbSessionDependency): + def __init__(self, db: DbSessionDependency) -> None: self.db = db def get_seasons_files_of_torrent( @@ -62,7 +62,7 @@ class TorrentRepository: def delete_torrent( self, torrent_id: TorrentId, delete_associated_media_files: bool = False - ): + ) -> None: if delete_associated_media_files: movie_files_stmt = delete(MovieFile).where( MovieFile.torrent_id == torrent_id @@ -76,7 +76,7 @@ class TorrentRepository: self.db.delete(self.db.get(Torrent, torrent_id)) - def get_movie_of_torrent(self, torrent_id: TorrentId): + def get_movie_of_torrent(self, torrent_id: TorrentId) -> MovieSchema | None: stmt = ( select(Movie) .join(MovieFile, Movie.id == MovieFile.movie_id) @@ -87,7 +87,7 @@ class TorrentRepository: return None return MovieSchema.model_validate(result) - def get_movie_files_of_torrent(self, torrent_id: TorrentId): + def get_movie_files_of_torrent(self, torrent_id: TorrentId) -> list[MovieFileSchema]: stmt = select(MovieFile).where(MovieFile.torrent_id == torrent_id) result = self.db.execute(stmt).scalars().all() return [MovieFileSchema.model_validate(movie_file) for movie_file in result] diff --git a/media_manager/torrent/router.py b/media_manager/torrent/router.py index abb03c3..a412d2b 100644 --- a/media_manager/torrent/router.py +++ b/media_manager/torrent/router.py @@ -36,7 +36,7 @@ def delete_torrent( service: torrent_service_dep, torrent: torrent_dep, delete_files: bool = False, -): +) -> None: if delete_files: try: service.cancel_download(torrent=torrent, delete_files=False) @@ -54,7 +54,7 @@ def delete_torrent( def retry_torrent_download( service: torrent_service_dep, torrent: torrent_dep, -): +) -> None: service.pause_download(torrent=torrent) service.resume_download(torrent=torrent) diff --git a/media_manager/torrent/service.py b/media_manager/torrent/service.py index f27eaf9..824cf6e 100644 --- a/media_manager/torrent/service.py +++ b/media_manager/torrent/service.py @@ -1,7 +1,7 @@ import logging from media_manager.indexer.schemas import IndexerQueryResult -from media_manager.movies.schemas import Movie +from media_manager.movies.schemas import Movie, MovieFile from media_manager.torrent.manager import DownloadManager from media_manager.torrent.repository import TorrentRepository from media_manager.torrent.schemas import Torrent, TorrentId @@ -15,7 +15,7 @@ class TorrentService: self, torrent_repository: TorrentRepository, download_manager: DownloadManager = None, - ): + ) -> None: self.torrent_repository = torrent_repository self.download_manager = download_manager or DownloadManager() @@ -101,7 +101,7 @@ class TorrentService: self.torrent_repository.get_torrent_by_id(torrent_id=torrent_id) ) - def delete_torrent(self, torrent_id: TorrentId): + def delete_torrent(self, torrent_id: TorrentId) -> None: log.info(f"Deleting torrent with ID: {torrent_id}") t = self.torrent_repository.get_torrent_by_id(torrent_id=torrent_id) delete_media_files = not t.imported @@ -109,5 +109,5 @@ class TorrentService: torrent_id=torrent_id, delete_associated_media_files=delete_media_files ) - def get_movie_files_of_torrent(self, torrent: Torrent): + def get_movie_files_of_torrent(self, torrent: Torrent) -> list[MovieFile]: return self.torrent_repository.get_movie_files_of_torrent(torrent_id=torrent.id) diff --git a/media_manager/torrent/utils.py b/media_manager/torrent/utils.py index 5ef54d5..90cfa6d 100644 --- a/media_manager/torrent/utils.py +++ b/media_manager/torrent/utils.py @@ -34,7 +34,7 @@ def list_files_recursively(path: Path = Path()) -> list[Path]: return valid_files -def extract_archives(files): +def extract_archives(files: list) -> None: archive_types = { "application/zip", "application/x-zip-compressedapplication/x-compressed", @@ -61,11 +61,11 @@ def extract_archives(files): log.error(f"Failed to extract archive {file}. Error: {e}") -def get_torrent_filepath(torrent: Torrent): +def get_torrent_filepath(torrent: Torrent) -> Path: return MediaManagerConfig().misc.torrent_directory / torrent.title -def import_file(target_file: Path, source_file: Path): +def import_file(target_file: Path, source_file: Path) -> None: if target_file.exists(): target_file.unlink() diff --git a/media_manager/tv/repository.py b/media_manager/tv/repository.py index c0529b3..e60cfb8 100644 --- a/media_manager/tv/repository.py +++ b/media_manager/tv/repository.py @@ -44,7 +44,7 @@ class TvRepository: Provides methods to retrieve, save, and delete shows and seasons. """ - def __init__(self, db: Session): + def __init__(self, db: Session) -> None: self.db = db def get_show_by_id(self, show_id: ShowId) -> ShowSchema: diff --git a/media_manager/tv/router.py b/media_manager/tv/router.py index 914cf16..7b7cb0c 100644 --- a/media_manager/tv/router.py +++ b/media_manager/tv/router.py @@ -94,7 +94,7 @@ def get_all_importable_shows( dependencies=[Depends(current_superuser)], status_code=status.HTTP_204_NO_CONTENT, ) -def import_detected_show(tv_service: tv_service_dep, tv_show: show_dep, directory: str): +def import_detected_show(tv_service: tv_service_dep, tv_show: show_dep, directory: str) -> None: """ Import a detected show from the specified directory into the library. """ @@ -140,7 +140,7 @@ def add_a_show( metadata_provider: metadata_provider_dep, show_id: int, language: str | None = None, -): +) -> Show: """ Add a new show to the library. """ @@ -205,7 +205,7 @@ def delete_a_show( show: show_dep, delete_files_on_disk: bool = False, delete_torrents: bool = False, -): +) -> None: """ Delete a show from the library. """ @@ -296,7 +296,7 @@ def request_a_season( user: Annotated[User, Depends(current_active_user)], season_request: CreateSeasonRequest, tv_service: tv_service_dep, -): +) -> None: """ Create a new season request. """ @@ -314,7 +314,7 @@ def update_request( tv_service: tv_service_dep, user: Annotated[User, Depends(current_active_user)], season_request: UpdateSeasonRequest, -): +) -> None: """ Update an existing season request. """ @@ -336,7 +336,7 @@ def authorize_request( user: Annotated[User, Depends(current_superuser)], season_request_id: SeasonRequestId, authorized_status: bool = False, -): +) -> None: """ Authorize or de-authorize a season request. """ @@ -359,7 +359,7 @@ def delete_season_request( tv_service: tv_service_dep, user: Annotated[User, Depends(current_active_user)], request_id: SeasonRequestId, -): +) -> None: """ Delete a season request. """ @@ -367,11 +367,11 @@ def delete_season_request( if user.is_superuser or request.requested_by.id == user.id: tv_service.delete_season_request(season_request_id=request_id) log.info(f"User {user.id} deleted season request {request_id}.") - return None + return log.warning( f"User {user.id} tried to delete season request {request_id} but is not authorized." ) - return HTTPException( + raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to delete this request", ) diff --git a/media_manager/tv/service.py b/media_manager/tv/service.py index 6f7978f..19a9875 100644 --- a/media_manager/tv/service.py +++ b/media_manager/tv/service.py @@ -67,7 +67,7 @@ class TvService: torrent_service: TorrentService, indexer_service: IndexerService, notification_service: NotificationService = None, - ): + ) -> None: self.tv_repository = tv_repository self.torrent_service = torrent_service self.indexer_service = indexer_service @@ -572,7 +572,7 @@ class TvService: self.delete_season_request(season_request.id) return True - def get_root_show_directory(self, show: Show): + def get_root_show_directory(self, show: Show) -> Path: misc_config = MediaManagerConfig().misc show_directory_name = f"{remove_special_characters(show.name)} ({show.year}) [{show.metadata_provider}id-{show.external_id}]" log.debug( diff --git a/metadata_relay/app/tmdb.py b/metadata_relay/app/tmdb.py index 45660f5..39a9744 100644 --- a/metadata_relay/app/tmdb.py +++ b/metadata_relay/app/tmdb.py @@ -16,39 +16,39 @@ else: tmdbsimple.API_KEY = tmdb_api_key @router.get("/tv/trending") - async def get_tmdb_trending_tv(language: str = "en"): + async def get_tmdb_trending_tv(language: str = "en") -> dict: return Trending(media_type="tv").info(language=language) @router.get("/tv/search") - async def search_tmdb_tv(query: str, page: int = 1, language: str = "en"): + async def search_tmdb_tv(query: str, page: int = 1, language: str = "en") -> dict: return Search().tv(page=page, query=query, language=language) @router.get("/tv/shows/{show_id}") - async def get_tmdb_show(show_id: int, language: str = "en"): + async def get_tmdb_show(show_id: int, language: str = "en") -> dict: return TV(show_id).info(language=language) @router.get("/tv/shows/{show_id}/external_ids") - async def get_tmdb_show_external_ids(show_id: int): + async def get_tmdb_show_external_ids(show_id: int) -> dict: return TV(show_id).external_ids() @router.get("/tv/shows/{show_id}/{season_number}") - async def get_tmdb_season(season_number: int, show_id: int, language: str = "en"): + async def get_tmdb_season(season_number: int, show_id: int, language: str = "en") -> dict: return TV_Seasons(season_number=season_number, tv_id=show_id).info( language=language ) @router.get("/movies/trending") - async def get_tmdb_trending_movies(language: str = "en"): + async def get_tmdb_trending_movies(language: str = "en") -> dict: return Trending(media_type="movie").info(language=language) @router.get("/movies/search") - async def search_tmdb_movies(query: str, page: int = 1, language: str = "en"): + async def search_tmdb_movies(query: str, page: int = 1, language: str = "en") -> dict: return Search().movie(page=page, query=query, language=language) @router.get("/movies/{movie_id}") - async def get_tmdb_movie(movie_id: int, language: str = "en"): + async def get_tmdb_movie(movie_id: int, language: str = "en") -> dict: return Movies(movie_id).info(language=language) @router.get("/movies/{movie_id}/external_ids") - async def get_tmdb_movie_external_ids(movie_id: int): + async def get_tmdb_movie_external_ids(movie_id: int) -> dict: return Movies(movie_id).external_ids() diff --git a/metadata_relay/app/tvdb.py b/metadata_relay/app/tvdb.py index 4f62b82..4c56f1a 100644 --- a/metadata_relay/app/tvdb.py +++ b/metadata_relay/app/tvdb.py @@ -16,29 +16,29 @@ else: tvdb_client = tvdb_v4_official.TVDB(tvdb_api_key) @router.get("/tv/trending") - async def get_tvdb_trending_tv(): + async def get_tvdb_trending_tv() -> list: return tvdb_client.get_all_series() @router.get("/tv/search") - async def search_tvdb_tv(query: str): + async def search_tvdb_tv(query: str) -> list: return tvdb_client.search(query) @router.get("/tv/shows/{show_id}") - async def get_tvdb_show(show_id: int): + async def get_tvdb_show(show_id: int) -> dict: return tvdb_client.get_series_extended(show_id) @router.get("/tv/seasons/{season_id}") - async def get_tvdb_season(season_id: int): + async def get_tvdb_season(season_id: int) -> dict: return tvdb_client.get_season_extended(season_id) @router.get("/movies/trending") - async def get_tvdb_trending_movies(): + async def get_tvdb_trending_movies() -> list: return tvdb_client.get_all_movies() @router.get("/movies/search") - async def search_tvdb_movies(query: str): + async def search_tvdb_movies(query: str) -> list: return tvdb_client.search(query) @router.get("/movies/{movie_id}") - async def get_tvdb_movie(movie_id: int): + async def get_tvdb_movie(movie_id: int) -> dict: return tvdb_client.get_movie_extended(movie_id) diff --git a/metadata_relay/main.py b/metadata_relay/main.py index 5cc0616..2badabf 100644 --- a/metadata_relay/main.py +++ b/metadata_relay/main.py @@ -15,5 +15,5 @@ app.include_router(tvdb_router) @app.get("/") -async def root(): +async def root() -> dict: return {"message": "Hello World"}