Run mypy on CI (#1413)
authorJc2k <john.carr@unrouted.co.uk>
Sat, 29 Jun 2024 21:27:48 +0000 (22:27 +0100)
committerGitHub <noreply@github.com>
Sat, 29 Jun 2024 21:27:48 +0000 (23:27 +0200)
.pre-commit-config.yaml
music_assistant/server/helpers/tags.py
music_assistant/server/providers/jellyfin/__init__.py
music_assistant/server/providers/jellyfin/manifest.json
music_assistant/server/providers/jellyfin/parsers.py
mypy.ini [new file with mode: 0644]
requirements_all.txt
tests/test_tags.py

index 2002eb64ed615332684752b41b60343ac9becf0e..27d3ba93264a4ab3c708602e1073639cae08e517 100644 (file)
@@ -91,13 +91,13 @@ repos:
       #   types: [text]
       #   entry: scripts/run-in-env.sh trailing-whitespace-fixer
       #   stages: [commit, push, manual]
-      - id: mypy
-        name: mypy
-        entry: scripts/run-in-env.sh mypy
-        language: script
-        types: [python]
-        require_serial: true
-      #   files: ^(music_assistant|pylint)/.+\.py$
+      - id: mypy
+        name: mypy
+        entry: scripts/run-in-env.sh mypy
+        language: script
+        types: [python]
+        require_serial: true
+        pass_filenames: false
       - id: gen_requirements_all
         name: gen_requirements_all
         entry: scripts/run-in-env.sh python3 -m scripts.gen_requirements_all
index be8d66f446a4a156c6628e07d9c5774d66f323d5..a28e72c7dc1239e5f7d5432b01b0a0193e169adc 100644 (file)
@@ -115,7 +115,7 @@ class AudioTags:
         return ""
 
     @property
-    def album(self) -> str:
+    def album(self) -> str | None:
         """Return album tag (as-is) if present."""
         return self.tags.get("album")
 
index 6d2269ebaa8d487a2ad9576e1b414d30816663c5..a41f87247c9e1d8f23b1461e3dc610b6eb8102cb 100644 (file)
@@ -9,7 +9,7 @@ from asyncio import TaskGroup
 from collections.abc import AsyncGenerator
 
 from aiojellyfin import MediaLibrary as JellyMediaLibrary
-from aiojellyfin import SessionConfiguration, authenticate_by_name
+from aiojellyfin import NotFound, SessionConfiguration, authenticate_by_name
 from aiojellyfin import Track as JellyTrack
 
 from music_assistant.common.models.config_entries import (
@@ -370,10 +370,11 @@ class JellyfinProvider(MusicProvider):
 
     async def get_album(self, prov_album_id: str) -> Album:
         """Get full album details by id."""
-        if jellyfin_album := await self._client.get_album(prov_album_id):
-            return parse_album(self.logger, self.instance_id, self._client, jellyfin_album)
-        msg = f"Item {prov_album_id} not found"
-        raise MediaNotFoundError(msg)
+        try:
+            album = await self._client.get_album(prov_album_id)
+        except NotFound:
+            raise MediaNotFoundError(f"Item {prov_album_id} not found")
+        return parse_album(self.logger, self.instance_id, self._client, album)
 
     async def get_album_tracks(self, prov_album_id: str) -> list[Track]:
         """Get album tracks for given album id."""
@@ -403,24 +404,27 @@ class JellyfinProvider(MusicProvider):
             artist.mbid = UNKNOWN_ARTIST_ID_MBID
             return artist
 
-        if jellyfin_artist := await self._client.get_artist(prov_artist_id):
-            return parse_artist(self.logger, self.instance_id, self._client, jellyfin_artist)
-        msg = f"Item {prov_artist_id} not found"
-        raise MediaNotFoundError(msg)
+        try:
+            jellyfin_artist = await self._client.get_artist(prov_artist_id)
+        except NotFound:
+            raise MediaNotFoundError(f"Item {prov_artist_id} not found")
+        return parse_artist(self.logger, self.instance_id, self._client, jellyfin_artist)
 
     async def get_track(self, prov_track_id: str) -> Track:
         """Get full track details by id."""
-        if jellyfin_track := await self._client.get_track(prov_track_id):
-            return parse_track(self.logger, self.instance_id, self._client, jellyfin_track)
-        msg = f"Item {prov_track_id} not found"
-        raise MediaNotFoundError(msg)
+        try:
+            track = await self._client.get_track(prov_track_id)
+        except NotFound:
+            raise MediaNotFoundError(f"Item {prov_track_id} not found")
+        return parse_track(self.logger, self.instance_id, self._client, track)
 
     async def get_playlist(self, prov_playlist_id: str) -> Playlist:
         """Get full playlist details by id."""
-        if jellyfin_playlist := await self._client.get_playlist(prov_playlist_id):
-            return parse_playlist(self.instance_id, self._client, jellyfin_playlist)
-        msg = f"Item {prov_playlist_id} not found"
-        raise MediaNotFoundError(msg)
+        try:
+            playlist = await self._client.get_playlist(prov_playlist_id)
+        except NotFound:
+            raise MediaNotFoundError(f"Item {prov_playlist_id} not found")
+        return parse_playlist(self.instance_id, self._client, playlist)
 
     async def get_playlist_tracks(
         self, prov_playlist_id: str, offset: int, limit: int
@@ -435,8 +439,6 @@ class JellyfinProvider(MusicProvider):
         playlist_items = await self._client.tracks(
             jellyfin_playlist[ITEM_KEY_ID], enable_user_data=True, fields=TRACK_FIELDS
         )
-        if not playlist_items:
-            return result
         for index, jellyfin_track in enumerate(playlist_items["Items"], 1):
             try:
                 if track := parse_track(
@@ -487,7 +489,7 @@ class JellyfinProvider(MusicProvider):
             path=url,
         )
 
-    async def get_similar_tracks(self, prov_track_id, limit=25) -> list[Track]:
+    async def get_similar_tracks(self, prov_track_id: str, limit: int = 25) -> list[Track]:
         """Retrieve a dynamic list of tracks based on the provided item."""
         resp = await self._client.get_similar_tracks(prov_track_id, limit=limit)
         return [
index 27cd9827939d39c89c65786baeb2ae8283000446..7832540cd708987d6ad0bab61e9e48c54b4cd8f4 100644 (file)
@@ -4,7 +4,7 @@
   "name": "Jellyfin Media Server Library",
   "description": "Support for the Jellyfin streaming provider in Music Assistant.",
   "codeowners": ["@lokiberra", "@Jc2k"],
-  "requirements": ["aiojellyfin==0.9.0"],
+  "requirements": ["aiojellyfin==0.9.1"],
   "documentation": "https://music-assistant.io/music-providers/jellyfin/",
   "multi_instance": true
 }
index a552fbc864ac62332576b1a73488e320db0effa4..907f44b4121069bbcd07c6c5dafb85cc4e501bf3 100644 (file)
@@ -8,11 +8,7 @@ from typing import TYPE_CHECKING
 
 from aiojellyfin.const import ImageType as JellyImageType
 
-from music_assistant.common.models.enums import (
-    ContentType,
-    ImageType,
-    MediaType,
-)
+from music_assistant.common.models.enums import ContentType, ImageType, MediaType
 from music_assistant.common.models.errors import InvalidDataError
 from music_assistant.common.models.media_items import (
     Album,
@@ -190,7 +186,7 @@ def parse_track(
 
     track.disc_number = jellyfin_track.get(ITEM_KEY_PARENT_INDEX_NUM, 0)
     track.track_number = jellyfin_track.get("IndexNumber", 0)
-    if track.track_number >= 0:
+    if track.track_number is not None and track.track_number >= 0:
         track.position = track.track_number
 
     track.metadata.images = _get_artwork(instance_id, client, jellyfin_track)
diff --git a/mypy.ini b/mypy.ini
new file mode 100644 (file)
index 0000000..cf337fb
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,24 @@
+[mypy]
+python_version = 3.12
+platform = linux
+show_error_codes = true
+follow_imports = silent
+local_partial_types = true
+strict_equality = true
+no_implicit_optional = true
+warn_incomplete_stub = true
+warn_redundant_casts = true
+warn_unused_configs = true
+warn_unused_ignores = true
+enable_error_code = ignore-without-code, redundant-self, truthy-iterable
+disable_error_code = annotation-unchecked, import-not-found, import-untyped
+extra_checks = false
+check_untyped_defs = true
+disallow_incomplete_defs = true
+disallow_subclassing_any = true
+disallow_untyped_calls = true
+disallow_untyped_decorators = true
+disallow_untyped_defs = true
+warn_return_any = true
+warn_unreachable = true
+packages=tests,music_assistant.client,music_assistant.server.providers.jellyfin
index 49c6a98f4e1462e5df6aa208cd30c467805fbd63..1aadd07dab137e62043bd93a98c2ffaa7595631c 100644 (file)
@@ -4,7 +4,7 @@ Brotli>=1.0.9
 aiodns>=3.0.0
 aiofiles==24.1.0
 aiohttp==3.9.5
-aiojellyfin==0.9.0
+aiojellyfin==0.9.1
 aiorun==2024.5.1
 aioslimproto==3.0.1
 aiosqlite==0.20.0
index 382d25b1b58b4397c0ba830a6da6b15cf5263702..be9a94f9d177a67a33c933eb46ac1211302165ad 100644 (file)
@@ -9,7 +9,7 @@ RESOURCES_DIR = pathlib.Path(__file__).parent.resolve().joinpath("fixtures")
 FILE_1 = str(RESOURCES_DIR.joinpath("MyArtist - MyTitle.mp3"))
 
 
-async def test_parse_metadata_from_id3tags():
+async def test_parse_metadata_from_id3tags() -> None:
     """Test parsing of parsing metadata from ID3 tags."""
     filename = str(RESOURCES_DIR.joinpath("MyArtist - MyTitle.mp3"))
     _tags = await tags.parse_tags(filename)
@@ -24,26 +24,26 @@ async def test_parse_metadata_from_id3tags():
     assert _tags.musicbrainz_releasegroupid == "abcdefg"
     assert _tags.musicbrainz_recordingid == "abcdefg"
     # test parsing disc/track number
+    _tags.tags["disc"] = ""
+    assert _tags.disc is None
     _tags.tags["disc"] = "1"
     assert _tags.disc == 1
     _tags.tags["disc"] = "1/1"
     assert _tags.disc == 1
-    _tags.tags["disc"] = ""
-    assert _tags.disc is None
     # test parsing album year
+    _tags.tags["date"] = "blah"
+    assert _tags.year is None
+    _tags.tags.pop("date", None)
+    assert _tags.year is None
     _tags.tags["date"] = "2022"
     assert _tags.year == 2022
     _tags.tags["date"] = "2022-05-05"
     assert _tags.year == 2022
-    _tags.tags["date"] = "blah"
-    assert _tags.year is None
     _tags.tags["date"] = ""
     assert _tags.year is None
-    _tags.tags.pop("date", None)
-    assert _tags.year is None
 
 
-async def test_parse_metadata_from_filename():
+async def test_parse_metadata_from_filename() -> None:
     """Test parsing of parsing metadata from filename."""
     filename = str(RESOURCES_DIR.joinpath("MyArtist - MyTitle without Tags.mp3"))
     _tags = await tags.parse_tags(filename)
@@ -59,7 +59,7 @@ async def test_parse_metadata_from_filename():
     assert _tags.musicbrainz_recordingid is None
 
 
-async def test_parse_metadata_from_invalid_filename():
+async def test_parse_metadata_from_invalid_filename() -> None:
     """Test parsing of parsing metadata from (invalid) filename."""
     filename = str(RESOURCES_DIR.joinpath("test.mp3"))
     _tags = await tags.parse_tags(filename)