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"}