diff --git a/media_manager/auth/users.py b/media_manager/auth/users.py index 4311517..e5549fb 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 +from typing import Any, Optional, override from fastapi import Depends, Request from fastapi.responses import RedirectResponse, Response @@ -44,6 +44,7 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): reset_password_token_secret = SECRET verification_token_secret = SECRET + @override async def on_after_update( self, user: models.UP, @@ -57,12 +58,14 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): updated_user = UserUpdate(is_verified=True) await self.update(user=user, user_update=updated_user) + @override async def on_after_register(self, user: User, request: Optional[Request] = 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) await self.update(user=user, user_update=updated_user) + @override async def on_after_forgot_password( self, user: User, token: str, request: Optional[Request] = None ): @@ -93,11 +96,13 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): ) log.info(f"Sent password reset email to {user.email}") + @override async def on_after_reset_password( self, user: User, request: Optional[Request] = 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 ): @@ -105,6 +110,7 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): f"Verification requested for user {user.id}. Verification token: {token}" ) + @override async def on_after_verify(self, user: User, request: Optional[Request] = None): log.info(f"User {user.id} has been verified") diff --git a/media_manager/exceptions.py b/media_manager/exceptions.py index 03cb2be..ec4e924 100644 --- a/media_manager/exceptions.py +++ b/media_manager/exceptions.py @@ -1,4 +1,4 @@ -from fastapi import Request +from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from psycopg.errors import UniqueViolation from sqlalchemy.exc import IntegrityError @@ -72,53 +72,53 @@ class UnprocessableEntityError(MediaManagerException): # Exception handlers async def media_already_exists_exception_handler( - request: Request, exc: MediaAlreadyExists + _request: Request, exc: MediaAlreadyExists ) -> JSONResponse: return JSONResponse(status_code=409, content={"detail": exc.message}) async def not_found_error_exception_handler( - request: Request, exc: NotFoundError + _request: Request, exc: NotFoundError ) -> JSONResponse: return JSONResponse(status_code=404, content={"detail": exc.message}) async def invalid_config_error_exception_handler( - request: Request, exc: InvalidConfigError + _request: Request, exc: InvalidConfigError ) -> JSONResponse: return JSONResponse(status_code=500, content={"detail": exc.message}) async def bad_request_error_handler( - request: Request, exc: BadRequestError + _request: Request, exc: BadRequestError ) -> JSONResponse: return JSONResponse(status_code=400, content={"detail": exc.message}) async def unauthorized_error_handler( - request: Request, exc: UnauthorizedError + _request: Request, exc: UnauthorizedError ) -> JSONResponse: return JSONResponse(status_code=401, content={"detail": exc.message}) async def forbidden_error_handler( - request: Request, exc: ForbiddenError + _request: Request, exc: ForbiddenError ) -> JSONResponse: return JSONResponse(status_code=403, content={"detail": exc.message}) -async def conflict_error_handler(request: Request, exc: ConflictError) -> JSONResponse: +async def conflict_error_handler(_request: Request, exc: ConflictError) -> JSONResponse: return JSONResponse(status_code=409, content={"detail": exc.message}) async def unprocessable_entity_error_handler( - request: Request, exc: UnprocessableEntityError + _request: Request, exc: UnprocessableEntityError ) -> JSONResponse: return JSONResponse(status_code=422, content={"detail": exc.message}) async def sqlalchemy_integrity_error_handler( - request: Request, exc: Exception + _request: Request, _exc: Exception ) -> JSONResponse: return JSONResponse( status_code=409, @@ -128,7 +128,7 @@ async def sqlalchemy_integrity_error_handler( ) -def register_exception_handlers(app): +def register_exception_handlers(app: FastAPI): app.add_exception_handler(NotFoundError, not_found_error_exception_handler) app.add_exception_handler( MediaAlreadyExists, media_already_exists_exception_handler diff --git a/media_manager/logging.py b/media_manager/logging.py index 0b252c6..2d63833 100644 --- a/media_manager/logging.py +++ b/media_manager/logging.py @@ -4,11 +4,13 @@ import sys from datetime import datetime, timezone from logging.config import dictConfig from pathlib import Path +from typing import override from pythonjsonlogger.json import JsonFormatter class ISOJsonFormatter(JsonFormatter): + @override def formatTime(self, record, datefmt=None): dt = datetime.fromtimestamp(record.created, tz=timezone.utc) return dt.isoformat(timespec="milliseconds").replace("+00:00", "Z") diff --git a/media_manager/main.py b/media_manager/main.py index 10c6e39..53ada7d 100644 --- a/media_manager/main.py +++ b/media_manager/main.py @@ -51,7 +51,7 @@ log = logging.getLogger(__name__) if config.misc.development: log.warning("Development Mode activated!") -scheduler = setup_scheduler(config, log) +scheduler = setup_scheduler(config) run_filesystem_checks(config, log) @@ -159,7 +159,7 @@ async def login(): # 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, _exc): 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 d9ec2bc..d2a4716 100644 --- a/media_manager/metadataProvider/tmdb.py +++ b/media_manager/metadataProvider/tmdb.py @@ -1,4 +1,5 @@ import logging +from typing import override import requests @@ -211,6 +212,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider): ) raise + @override def download_show_poster_image(self, show: Show) -> bool: # Determine which language to use based on show's original_language language = self.__get_language_param(show.original_language) @@ -236,6 +238,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider): return False return True + @override def get_show_metadata( self, show_id: int | None = None, language: str | None = None ) -> Show: @@ -310,6 +313,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider): return show + @override def search_show( self, query: str | None = None, max_pages: int = 5 ) -> list[MetaDataProviderSearchResult]: @@ -369,6 +373,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider): log.warning(f"Error processing search result: {e}") return formatted_results + @override def get_movie_metadata( self, movie_id: int | None = None, language: str | None = None ) -> Movie: @@ -413,6 +418,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider): return movie + @override def search_movie( self, query: str | None = None, max_pages: int = 5 ) -> list[MetaDataProviderSearchResult]: @@ -472,6 +478,7 @@ class TmdbMetadataProvider(AbstractMetadataProvider): log.warning(f"Error processing search result: {e}") return formatted_results + @override def download_movie_poster_image(self, movie: Movie) -> bool: # Determine which language to use based on movie's original_language language = self.__get_language_param(movie.original_language) diff --git a/media_manager/metadataProvider/tvdb.py b/media_manager/metadataProvider/tvdb.py index 3d88ebc..3007975 100644 --- a/media_manager/metadataProvider/tvdb.py +++ b/media_manager/metadataProvider/tvdb.py @@ -1,4 +1,5 @@ import logging +from typing import override import requests @@ -46,6 +47,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider): def __get_trending_movies(self) -> dict: return requests.get(url=f"{self.url}/movies/trending", timeout=60).json() + @override def download_show_poster_image(self, show: Show) -> bool: show_metadata = self.__get_show(show_id=show.external_id) @@ -61,6 +63,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider): log.warning(f"image for show {show.name} could not be downloaded") return False + @override def get_show_metadata( self, show_id: int | None = None, language: str | None = None ) -> Show: @@ -127,6 +130,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider): return show + @override def search_show( self, query: str | None = None ) -> list[MetaDataProviderSearchResult]: @@ -186,6 +190,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider): log.warning(f"Error processing search result: {e}") return formatted_results + @override def search_movie( self, query: str | None = None ) -> list[MetaDataProviderSearchResult]: @@ -253,6 +258,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider): log.warning(f"Error processing search result: {e}") return formatted_results + @override def download_movie_poster_image(self, movie: Movie) -> bool: movie_metadata = self.__get_movie(movie.external_id) @@ -268,6 +274,7 @@ class TvdbMetadataProvider(AbstractMetadataProvider): log.warning(f"image for show {movie.name} could not be downloaded") return False + @override def get_movie_metadata( self, movie_id: int | None = None, language: str | None = None ) -> Movie: diff --git a/media_manager/scheduler.py b/media_manager/scheduler.py index cd335a6..5d2e152 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, log): +def setup_scheduler(config): from media_manager.database import init_engine init_engine(config.database) diff --git a/ruff.toml b/ruff.toml index 00719e2..fa401ad 100644 --- a/ruff.toml +++ b/ruff.toml @@ -3,9 +3,9 @@ line-ending = "lf" quote-style = "double" [lint] -# to be enabled: ANN, ARG, BLE, C90, CPY, D, DOC, DTZ, FBT, G, INP, INT, N, PERF, PIE, PL, PTH, RET, RSE, SLF, SIM, TC, TRY, UP +# to be enabled: ANN, BLE, C90, CPY, D, DOC, DTZ, FBT, G, INP, INT, N, PERF, PIE, PL, PTH, RET, RSE, SLF, SIM, TC, TRY, UP extend-select = [ - "A", "ASYNC", + "A", "ARG", "ASYNC", "B", "C4", "COM", "E", "EM", "EXE",