diff --git a/TODO.md b/TODO.md
index 7a5264a..8f630e3 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,15 +1,8 @@
# TODO
-- [ ] Add .
-- [ ] Make a `model` class and add the following methods to it:
- - [ ] `_check_api_access()`
- - [ ] `_resource_not_found()`
-- [ ] Should `CoverArt` be removed?
-- [ ] Determine if video and non-ID3 endpoints will be supported.
- - [ ] If not the Video and NonID3 checks in the `Song` model should be removed.
- [ ] Implement missing endpoints.
-- [ ] Improve error handling:
- [ ] Add the `subsonic.system.check_subsonic_extension()` method.
-- [ ] Check and rewrite all docstrings taking care about raising exceptions.
+- [ ] Improve error handling:
+ - [ ] Check and rewrite all docstrings taking care about raising exceptions.
- [ ] Spin up a `MkDocs` documentation.
- [ ] Add the URL in the GitHub page.
diff --git a/src/knuckles/chat.py b/src/knuckles/chat.py
index fb4c4aa..5e8c0ff 100644
--- a/src/knuckles/chat.py
+++ b/src/knuckles/chat.py
@@ -41,6 +41,6 @@ def get_chat_messages(self) -> list[ChatMessage]:
"chatMessages"
]["chatMessage"]
- messages = [ChatMessage(**message) for message in response]
+ messages = [ChatMessage(self.subsonic, **message) for message in response]
return messages
diff --git a/src/knuckles/exceptions.py b/src/knuckles/exceptions.py
index 7ee4abd..a975879 100644
--- a/src/knuckles/exceptions.py
+++ b/src/knuckles/exceptions.py
@@ -1,7 +1,7 @@
from typing import Type
-class NoApiAccess(Exception):
+class MissingRequiredProperty(Exception):
pass
@@ -9,16 +9,15 @@ class InvalidRatingNumber(ValueError):
pass
-class VideoArgumentsInSong(ValueError):
- pass
-
-
class ResourceNotFound(Exception):
- pass
-
-
-class AlbumOrArtistArgumentsInSong(ValueError):
- pass
+ def __init__(
+ self,
+ message: str = (
+ "Unable to generate the model as it does not exist in the server"
+ ),
+ *args: str
+ ) -> None:
+ super().__init__(message, *args)
class ShareInvalidSongList(ValueError):
diff --git a/src/knuckles/media_library_scanning.py b/src/knuckles/media_library_scanning.py
index c3837c3..50e273a 100644
--- a/src/knuckles/media_library_scanning.py
+++ b/src/knuckles/media_library_scanning.py
@@ -1,6 +1,11 @@
+from typing import TYPE_CHECKING
+
from .api import Api
from .models.scan_status import ScanStatus
+if TYPE_CHECKING:
+ from .subsonic import Subsonic
+
class MediaLibraryScanning:
"""Class that contains all the methods needed to interact
@@ -8,9 +13,12 @@ class MediaLibraryScanning:
"""
- def __init__(self, api: Api) -> None:
+ def __init__(self, api: Api, subsonic: "Subsonic") -> None:
self.api = api
+ # Only to pass it to the models
+ self.subsonic = subsonic
+
def get_scan_status(self) -> ScanStatus:
"""Calls to the "getScanStatus" endpoint of the API.
@@ -20,7 +28,7 @@ def get_scan_status(self) -> ScanStatus:
response = self.api.json_request("getScanStatus")["scanStatus"]
- return ScanStatus(**response)
+ return ScanStatus(self.subsonic, **response)
def start_scan(self) -> ScanStatus:
"""Calls to the "scanStatus" endpoint of the API.
@@ -31,4 +39,4 @@ def start_scan(self) -> ScanStatus:
response = self.api.json_request("startScan")["scanStatus"]
- return ScanStatus(**response)
+ return ScanStatus(self.subsonic, **response)
diff --git a/src/knuckles/models/album.py b/src/knuckles/models/album.py
index 55a65b6..19781bc 100644
--- a/src/knuckles/models/album.py
+++ b/src/knuckles/models/album.py
@@ -7,43 +7,49 @@
from .artist import Artist
from .cover_art import CoverArt
from .genre import ItemGenre
+from .model import Model
if TYPE_CHECKING:
from ..subsonic import Subsonic
-class RecordLabel:
- def __init__(self, name: str) -> None:
+class RecordLabel(Model):
+ def __init__(self, subsonic: "Subsonic", name: str) -> None:
+ super().__init__(subsonic)
+
self.name = name
-class Disc:
- def __init__(self, disc: int, title: str) -> None:
+class Disc(Model):
+ def __init__(self, subsonic: "Subsonic", disc: int, title: str) -> None:
+ super().__init__(subsonic)
+
self.disc_number = disc
self.title = title
-class ReleaseDate:
+class ReleaseDate(Model):
def __init__(
self,
+ subsonic: "Subsonic",
year: int,
month: int,
day: int,
) -> None:
+ super().__init__(subsonic)
+
self.year = year
self.month = month
self.day = day
-class AlbumInfo:
+class AlbumInfo(Model):
"""Representation of all the data related to an album info in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
album_id: str,
- # Subsonic fields
notes: str,
musicBrainzId: str | None,
lastFmUrl: str | None,
@@ -68,7 +74,8 @@ def __init__(
:type largeImageUrl: str
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.album_id = album_id
self.notes = notes
self.music_brainz_id = musicBrainzId
@@ -88,17 +95,15 @@ def generate(self) -> "AlbumInfo":
:rtype: AlbumInfo
"""
- return self.__subsonic.browsing.get_album_info(self.album_id)
+ return self._subsonic.browsing.get_album_info(self.album_id)
-class Album:
+class Album(Model):
"""Representation of all the data related to an album in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
id: str,
parent: str | None = None,
album: str | None = None,
@@ -176,15 +181,16 @@ def __init__(
:type song: list[dict[str, Any]]
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.id = id
self.parent = parent
self.album = album
self.name = name
self.is_dir = isDir
self.title = title
- self.artist = Artist(self.__subsonic, artistId, artist) if artistId else None
- self.cover_art = CoverArt(coverArt) if coverArt else None
+ self.artist = Artist(self._subsonic, artistId, artist) if artistId else None
+ self.cover_art = CoverArt(self._subsonic, coverArt) if coverArt else None
self.song_count = songCount
self.duration = duration
self.play_count = playCount
@@ -195,20 +201,25 @@ def __init__(
self.played = parser.parse(played) if played else None
self.user_rating = userRating
self.songs = (
- [song_model_module.Song(self.__subsonic, **song_data) for song_data in song]
+ [song_model_module.Song(self._subsonic, **song_data) for song_data in song]
if song
else None
)
self.info: AlbumInfo | None = None
self.record_labels = (
- [RecordLabel(**record_label) for record_label in recordLabels]
+ [
+ RecordLabel(self._subsonic, **record_label)
+ for record_label in recordLabels
+ ]
if recordLabels
else None
)
self.music_brainz_id = musicBrainzId
- self.genres = [ItemGenre(**genre) for genre in genres] if genres else None
+ self.genres = (
+ [ItemGenre(self._subsonic, **genre) for genre in genres] if genres else None
+ )
self.artists = (
- [Artist(self.__subsonic, **artist) for artist in artists]
+ [Artist(self._subsonic, **artist) for artist in artists]
if artists
else None
)
@@ -217,11 +228,19 @@ def __init__(
self.moods = moods
self.sort_name = sortName
self.original_release_date = (
- ReleaseDate(**originalReleaseDate) if originalReleaseDate else None
+ ReleaseDate(self._subsonic, **originalReleaseDate)
+ if originalReleaseDate
+ else None
+ )
+ self.release_date = (
+ ReleaseDate(self._subsonic, **releaseDate) if releaseDate else None
)
- self.release_date = ReleaseDate(**releaseDate) if releaseDate else None
self.is_compilation = isCompilation
- self.discs = [Disc(**disc) for disc in discTitles] if discTitles else None
+ self.discs = (
+ [Disc(self._subsonic, **disc) for disc in discTitles]
+ if discTitles
+ else None
+ )
def generate(self) -> "Album":
"""Return a new album with all the data updated from the API,
@@ -234,7 +253,7 @@ def generate(self) -> "Album":
:rtype: Album
"""
- new_album = self.__subsonic.browsing.get_album(self.id)
+ new_album = self._subsonic.browsing.get_album(self.id)
new_album.get_album_info()
return new_album
@@ -247,6 +266,6 @@ def get_album_info(self) -> AlbumInfo:
:rtype: AlbumInfo
"""
- self.info = self.__subsonic.browsing.get_album_info(self.id)
+ self.info = self._subsonic.browsing.get_album_info(self.id)
return self.info
diff --git a/src/knuckles/models/artist.py b/src/knuckles/models/artist.py
index d9ce332..0a40981 100644
--- a/src/knuckles/models/artist.py
+++ b/src/knuckles/models/artist.py
@@ -4,6 +4,7 @@
import knuckles.models.album as album_model_module
from .cover_art import CoverArt
+from .model import Model
if TYPE_CHECKING:
from ..subsonic import Subsonic
@@ -11,15 +12,13 @@
from dateutil import parser
-class ArtistInfo:
+class ArtistInfo(Model):
"""Representation of all the data related to an artist info in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
artist_id: str,
- # Subsonic fields
biography: str,
musicBrainzId: str | None,
lastFmUrl: str | None,
@@ -47,7 +46,8 @@ def __init__(
:type similarArtist: list[str, Any]
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.artist_id = artist_id
self.biography = biography
self.music_brainz_id = musicBrainzId
@@ -56,7 +56,7 @@ def __init__(
self.medium_image_url = mediumImageUrl
self.large_image_url = largeImageUrl
self.similar_artists = (
- [Artist(self.__subsonic, **artist) for artist in similarArtist]
+ [Artist(self._subsonic, **artist) for artist in similarArtist]
if similarArtist
else None
)
@@ -72,17 +72,15 @@ def generate(self) -> "ArtistInfo":
:rtype: ArtistInfo
"""
- return self.__subsonic.browsing.get_artist_info(self.artist_id)
+ return self._subsonic.browsing.get_artist_info(self.artist_id)
-class Artist:
+class Artist(Model):
"""Representation of all the data related to an artist in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
id: str,
name: str | None = None,
coverArt: str | None = None,
@@ -120,10 +118,11 @@ def __init__(
:type album: list[dict[str, Any]]
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.id = id
self.name = name
- self.cover_art = CoverArt(coverArt) if coverArt else None
+ self.cover_art = CoverArt(self._subsonic, coverArt) if coverArt else None
self.artist_image_url = artistImageUrl
self.album_count = albumCount
self.starred = parser.parse(starred) if starred else None
@@ -131,7 +130,7 @@ def __init__(
self.average_rating = averageRating
self.albums = (
[
- album_model_module.Album(self.__subsonic, **album_data)
+ album_model_module.Album(self._subsonic, **album_data)
for album_data in album
]
if album
@@ -153,7 +152,7 @@ def generate(self) -> "Artist":
:rtype: Artist
"""
- new_artist = self.__subsonic.browsing.get_artist(self.id)
+ new_artist = self._subsonic.browsing.get_artist(self.id)
new_artist.get_artist_info()
return new_artist
@@ -166,6 +165,6 @@ def get_artist_info(self) -> ArtistInfo:
:rtype: AlbumInfo
"""
- self.info = self.__subsonic.browsing.get_artist_info(self.id)
+ self.info = self._subsonic.browsing.get_artist_info(self.id)
return self.info
diff --git a/src/knuckles/models/bookmark.py b/src/knuckles/models/bookmark.py
index 804ea6f..01f442d 100644
--- a/src/knuckles/models/bookmark.py
+++ b/src/knuckles/models/bookmark.py
@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING, Any, Self
from ..exceptions import ResourceNotFound
+from .model import Model
from .song import Song
from .user import User
@@ -10,14 +11,12 @@
from dateutil import parser
-class Bookmark:
+class Bookmark(Model):
"""Representation of all the data related to a bookmark in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
entry: dict[str, Any],
position: int,
username: str | None = None,
@@ -25,11 +24,13 @@ def __init__(
changed: str | None = None,
comment: str | None = None,
) -> None:
- self.__subsonic = subsonic
- self.song = Song(self.__subsonic, **entry)
+
+ super().__init__(subsonic)
+
+ self.song = Song(self._subsonic, **entry)
self.position = position
self.user = (
- User(subsonic=self.__subsonic, username=username) if username else None
+ User(subsonic=self._subsonic, username=username) if username else None
)
self.comment = comment
self.created = parser.parse(created) if created else None
@@ -46,12 +47,10 @@ def generate(self) -> "Bookmark":
:rtype: Bookmark
"""
- get_bookmark = self.__subsonic.bookmarks.get_bookmark(self.song.id)
+ get_bookmark = self._subsonic.bookmarks.get_bookmark(self.song.id)
if get_bookmark is None:
- raise ResourceNotFound(
- "Unable to generate episode as it does not exist in the server"
- )
+ raise ResourceNotFound()
return get_bookmark
@@ -62,7 +61,7 @@ def create(self) -> Self:
:rtype: Self
"""
- self.__subsonic.bookmarks.create_bookmark(
+ self._subsonic.bookmarks.create_bookmark(
self.song.id, self.position, self.comment
)
@@ -76,7 +75,7 @@ def update(self) -> Self:
:rtype: Self
"""
- self.__subsonic.bookmarks.update_bookmark(
+ self._subsonic.bookmarks.update_bookmark(
self.song.id, self.position, self.comment
)
@@ -89,6 +88,6 @@ def delete(self) -> Self:
:rtype: Self
"""
- self.__subsonic.bookmarks.delete_bookmark(self.song.id)
+ self._subsonic.bookmarks.delete_bookmark(self.song.id)
return self
diff --git a/src/knuckles/models/chat_message.py b/src/knuckles/models/chat_message.py
index bf5cbdc..7542595 100644
--- a/src/knuckles/models/chat_message.py
+++ b/src/knuckles/models/chat_message.py
@@ -1,10 +1,18 @@
from datetime import datetime
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from ..subsonic import Subsonic
-class ChatMessage:
+from knuckles.models.model import Model
+
+
+class ChatMessage(Model):
"""Representation of all the data related to a chat message in Subsonic."""
- def __init__(self, username: str, time: int, message: str) -> None:
+ def __init__(
+ self, subsonic: "Subsonic", username: str, time: int, message: str
+ ) -> None:
"""Representation of all the data related to a chat message in Subsonic.
:param username: The username of the creator of the message
@@ -15,8 +23,10 @@ def __init__(self, username: str, time: int, message: str) -> None:
:type message: str
"""
+ super().__init__(subsonic)
+
self.username: str = username
+ self.message: str = message
# Divide by 1000 as the Subsonic API return in milliseconds instead of seconds
self.time: datetime = datetime.fromtimestamp(time / 1000)
- self.message: str = message
diff --git a/src/knuckles/models/cover_art.py b/src/knuckles/models/cover_art.py
index 94f611f..69a81d8 100644
--- a/src/knuckles/models/cover_art.py
+++ b/src/knuckles/models/cover_art.py
@@ -1,11 +1,21 @@
-class CoverArt:
+from typing import TYPE_CHECKING
+
+from .model import Model
+
+if TYPE_CHECKING:
+ from ..subsonic import Subsonic
+
+
+class CoverArt(Model):
"""Representation of all the data related to a cover art in Subsonic."""
- def __init__(self, id: str) -> None:
+ def __init__(self, subsonic: "Subsonic", id: str) -> None:
"""Representation of all the data related to a cover art in Subsonic.
:param id: The ID of the cover art.
:type id: str
"""
+ super().__init__(subsonic)
+
self.id: str = id
diff --git a/src/knuckles/models/genre.py b/src/knuckles/models/genre.py
index f35a8d3..b623301 100644
--- a/src/knuckles/models/genre.py
+++ b/src/knuckles/models/genre.py
@@ -1,17 +1,21 @@
from typing import TYPE_CHECKING
from ..exceptions import ResourceNotFound
+from .model import Model
if TYPE_CHECKING:
from ..subsonic import Subsonic
-class ItemGenre:
- def __init__(self, name: str) -> None:
+class ItemGenre(Model):
+ def __init__(self, subsonic: "Subsonic", name: str) -> None:
+
+ super().__init__(subsonic)
+
self.name = name
-class Genre:
+class Genre(Model):
"""Representation of all the data related to a genre in Subsonic."""
def __init__(
@@ -33,7 +37,8 @@ def __init__(
:type albumCount: int | None, optional
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.value = value
self.song_count = songCount
self.album_count = albumCount
@@ -48,7 +53,7 @@ def generate(self) -> "Genre":
:return: A new genre object with all the data updated.
:rtype: Genre
"""
- get_genre = self.__subsonic.browsing.get_genre(self.value)
+ get_genre = self._subsonic.browsing.get_genre(self.value)
if get_genre is None:
raise ResourceNotFound(
diff --git a/src/knuckles/models/internet_radio_station.py b/src/knuckles/models/internet_radio_station.py
index e1b1d0c..84d7621 100644
--- a/src/knuckles/models/internet_radio_station.py
+++ b/src/knuckles/models/internet_radio_station.py
@@ -1,12 +1,13 @@
from typing import TYPE_CHECKING, Self
from ..exceptions import ResourceNotFound
+from .model import Model
if TYPE_CHECKING:
from ..subsonic import Subsonic
-class InternetRadioStation:
+class InternetRadioStation(Model):
"""Representation of all the data related to
an internet radio station in Subsonic.
"""
@@ -34,7 +35,8 @@ def __init__(
:type homepageUrl: str
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.id = id
self.name = name
self.stream_url = streamUrl
@@ -51,7 +53,7 @@ def generate(self) -> "InternetRadioStation | None":
:rtype: InternetRadioStation
"""
- get_station = self.__subsonic.internet_radio.get_internet_radio_station(self.id)
+ get_station = self._subsonic.internet_radio.get_internet_radio_station(self.id)
if get_station is None:
raise ResourceNotFound(
@@ -70,7 +72,7 @@ def create(self) -> Self:
:rtype: Self
"""
- self.__subsonic.internet_radio.create_internet_radio_station(
+ self._subsonic.internet_radio.create_internet_radio_station(
self.stream_url, self.name, self.homepage_url
)
@@ -83,7 +85,7 @@ def update(self) -> Self:
:rtype: Self
"""
- self.__subsonic.internet_radio.update_internet_radio_station(
+ self._subsonic.internet_radio.update_internet_radio_station(
self.id, self.stream_url, self.name, self.homepage_url
)
@@ -96,6 +98,6 @@ def delete(self) -> Self:
:rtype: Self
"""
- self.__subsonic.internet_radio.delete_internet_radio_station(self.id)
+ self._subsonic.internet_radio.delete_internet_radio_station(self.id)
return self
diff --git a/src/knuckles/models/jukebox.py b/src/knuckles/models/jukebox.py
index b1baa1e..e0441c5 100644
--- a/src/knuckles/models/jukebox.py
+++ b/src/knuckles/models/jukebox.py
@@ -1,19 +1,18 @@
from typing import TYPE_CHECKING, Any, Self
+from .model import Model
from .song import Song
if TYPE_CHECKING:
from ..subsonic import Subsonic
-class Jukebox:
+class Jukebox(Model):
"""Representation of all the data related to the jukebox in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
currentIndex: int,
playing: bool,
gain: float,
@@ -36,7 +35,8 @@ def __init__(
:type entry: list[dict[str, Any]] | None, optional
"""
- self.__subsonic: "Subsonic" = subsonic
+ super().__init__(subsonic)
+
self.current_index: int = currentIndex
self.playing: bool = playing
self.gain: float = gain
@@ -49,7 +49,7 @@ def __init__(
self.playlist = []
for song in entry:
- self.playlist.append(Song(subsonic=self.__subsonic, **song))
+ self.playlist.append(Song(subsonic=self._subsonic, **song))
def generate(self) -> "Jukebox":
"""Return a new jukebox with all the data updated from the API,
@@ -62,7 +62,7 @@ def generate(self) -> "Jukebox":
:rtype: Jukebox
"""
- return self.__subsonic.jukebox.get()
+ return self._subsonic.jukebox.get()
def start(self) -> Self:
"""Calls the "jukeboxControl" endpoint of the API with the action "start".
@@ -71,7 +71,7 @@ def start(self) -> Self:
:rtype: Self
"""
- self.__subsonic.jukebox.start()
+ self._subsonic.jukebox.start()
return self
@@ -82,7 +82,7 @@ def stop(self) -> Self:
:rtype: Self
"""
- self.__subsonic.jukebox.stop()
+ self._subsonic.jukebox.stop()
return self
@@ -97,7 +97,7 @@ def skip(self, index: int, offset: float = 0) -> Self:
:rtype: Self
"""
- self.__subsonic.jukebox.skip(index, offset)
+ self._subsonic.jukebox.skip(index, offset)
return self
@@ -108,11 +108,11 @@ def shuffle(self) -> Self:
:rtype: Self
"""
- self.__subsonic.jukebox.shuffle()
+ self._subsonic.jukebox.shuffle()
# The shuffle is server side so a call to the API is necessary
# to get the new order of the playlist
- self.playlist = self.__subsonic.jukebox.get().playlist
+ self.playlist = self._subsonic.jukebox.get().playlist
return self
@@ -125,7 +125,7 @@ def set_gain(self, gain: float) -> Self:
:rtype: Self
"""
- self.__subsonic.jukebox.set_gain(gain)
+ self._subsonic.jukebox.set_gain(gain)
self.gain = gain
return self
@@ -137,7 +137,7 @@ def clear(self) -> Self:
:rtype: Self
"""
- self.__subsonic.jukebox.clear()
+ self._subsonic.jukebox.clear()
self.playlist = []
return self
@@ -152,9 +152,9 @@ def set(self, id: str) -> Self:
:rtype: Self
"""
- song_to_set: Song = Song(self.__subsonic, id)
+ song_to_set: Song = Song(self._subsonic, id)
- self.__subsonic.jukebox.set(song_to_set.id)
+ self._subsonic.jukebox.set(song_to_set.id)
self.playlist = [song_to_set]
return self
@@ -170,9 +170,9 @@ def add(self, id: str) -> Self:
:rtype: Self
"""
- song_to_add: Song = Song(self.__subsonic, id)
+ song_to_add: Song = Song(self._subsonic, id)
- self.__subsonic.jukebox.add(song_to_add.id)
+ self._subsonic.jukebox.add(song_to_add.id)
if self.playlist is not None:
self.playlist.append(song_to_add)
@@ -192,7 +192,7 @@ def remove(self, index: int) -> Self:
:rtype: Self
"""
- self.__subsonic.jukebox.remove(index)
+ self._subsonic.jukebox.remove(index)
if self.playlist is not None:
del self.playlist[index]
diff --git a/src/knuckles/models/model.py b/src/knuckles/models/model.py
new file mode 100644
index 0000000..0543048
--- /dev/null
+++ b/src/knuckles/models/model.py
@@ -0,0 +1,9 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from ..subsonic import Subsonic
+
+
+class Model:
+ def __init__(self, subsonic: "Subsonic") -> None:
+ self._subsonic = subsonic
diff --git a/src/knuckles/models/music_folder.py b/src/knuckles/models/music_folder.py
index 9fbcf7c..e7b3967 100644
--- a/src/knuckles/models/music_folder.py
+++ b/src/knuckles/models/music_folder.py
@@ -1,10 +1,12 @@
from typing import TYPE_CHECKING
+from .model import Model
+
if TYPE_CHECKING:
from ..subsonic import Subsonic
-class MusicFolder:
+class MusicFolder(Model):
"""Representation of all the data related to a music folder in Subsonic."""
def __init__(self, subsonic: "Subsonic", id: str, name: str | None = None) -> None:
@@ -18,7 +20,8 @@ def __init__(self, subsonic: "Subsonic", id: str, name: str | None = None) -> No
:type name: str | None, optional
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.id = id
self.name = name
@@ -33,7 +36,7 @@ def generate(self) -> "MusicFolder":
:rtype: MusicFolder
"""
- music_folders = self.__subsonic.browsing.get_music_folders()
+ music_folders = self._subsonic.browsing.get_music_folders()
# Get the first element with the same ID
music_folder = next(
diff --git a/src/knuckles/models/play_queue.py b/src/knuckles/models/play_queue.py
index 8cb1520..3df42c5 100644
--- a/src/knuckles/models/play_queue.py
+++ b/src/knuckles/models/play_queue.py
@@ -2,6 +2,7 @@
from dateutil import parser
+from .model import Model
from .song import Song
from .user import User
@@ -9,7 +10,7 @@
from ..subsonic import Subsonic
-class PlayQueue:
+class PlayQueue(Model):
"""Representation of all the data related to a play queue in Subsonic."""
def __init__(
@@ -22,15 +23,15 @@ def __init__(
changed: str | None = None,
changedBy: str | None = None,
) -> None:
- self.__subsonic = subsonic
- self.current = Song(self.__subsonic, current) if current else None
+
+ super().__init__(subsonic)
+
+ self.current = Song(self._subsonic, current) if current else None
self.position = position
- self.user = User(self.__subsonic, username) if username else None
+ self.user = User(self._subsonic, username) if username else None
self.changed = parser.parse(changed) if changed else None
self.changed_by = changedBy
- self.songs = (
- [Song(self.__subsonic, **song) for song in entry] if entry else None
- )
+ self.songs = [Song(self._subsonic, **song) for song in entry] if entry else None
def generate(self) -> "PlayQueue":
"""Return a new play queue with all the data updated from the API,
@@ -43,7 +44,7 @@ def generate(self) -> "PlayQueue":
:rtype: PlayQueue
"""
- get_play_queue = self.__subsonic.bookmarks.get_play_queue()
+ get_play_queue = self._subsonic.bookmarks.get_play_queue()
return get_play_queue
@@ -58,7 +59,7 @@ def save(self) -> Self:
song_ids: list[str] = [song.id for song in self.songs] if self.songs else []
- self.__subsonic.bookmarks.save_play_queue(
+ self._subsonic.bookmarks.save_play_queue(
song_ids, self.current.id if self.current else None, self.position
)
diff --git a/src/knuckles/models/playlist.py b/src/knuckles/models/playlist.py
index 05fa1bb..0d4add5 100644
--- a/src/knuckles/models/playlist.py
+++ b/src/knuckles/models/playlist.py
@@ -2,6 +2,7 @@
from dateutil import parser
+from .model import Model
from .song import CoverArt, Song
from .user import User
@@ -9,14 +10,12 @@
from ..subsonic import Subsonic
-class Playlist:
+class Playlist(Model):
"""Representation of all the data related to a playlist in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
id: str,
name: str | None = None,
songCount: int | None = None,
@@ -61,7 +60,8 @@ def __init__(
:type entry: list[dict[str, Any]] | None, optional
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.id = id
self.name = name
self.song_count = songCount
@@ -69,17 +69,15 @@ def __init__(
self.created = parser.parse(created) if created else None
self.changed = parser.parse(changed) if changed else None
self.comment = comment
- self.owner = User(self.__subsonic, owner) if owner else None
+ self.owner = User(self._subsonic, owner) if owner else None
self.public = public
- self.cover_art = CoverArt(coverArt) if coverArt else None
+ self.cover_art = CoverArt(self._subsonic, coverArt) if coverArt else None
self.allowed_users = (
- [User(self.__subsonic, username) for username in allowedUser]
+ [User(self._subsonic, username) for username in allowedUser]
if allowedUser
else None
)
- self.songs = (
- [Song(self.__subsonic, **song) for song in entry] if entry else None
- )
+ self.songs = [Song(self._subsonic, **song) for song in entry] if entry else None
def generate(self) -> "Playlist":
"""Return a new playlist with all the data updated from the API,
@@ -89,7 +87,7 @@ def generate(self) -> "Playlist":
:rtype: Playlist
"""
- return self.__subsonic.playlists.get_playlist(self.id)
+ return self._subsonic.playlists.get_playlist(self.id)
def create(self) -> "Playlist":
"""Calls the "createPlaylist" endpoint of the API.
@@ -103,7 +101,7 @@ def create(self) -> "Playlist":
# Create a list of Song IDs if songs is not None
songs_ids = [song.id for song in self.songs] if self.songs else None
- new_playlist = self.__subsonic.playlists.create_playlist(
+ new_playlist = self._subsonic.playlists.create_playlist(
# Ignore the None type error as the server
# should return an Error Code 10 in response
self.name, # type: ignore[arg-type]
@@ -126,7 +124,7 @@ def update(self) -> Self:
:return: The object itself to allow method chaining.
:rtype: Self
"""
- self.__subsonic.playlists.update_playlist(
+ self._subsonic.playlists.update_playlist(
self.id, self.name, self.comment, self.public
)
@@ -140,7 +138,7 @@ def delete(self) -> Self:
:return: The object itself to allow method chaining.
:rtype: Self
"""
- self.__subsonic.playlists.delete_playlist(self.id)
+ self._subsonic.playlists.delete_playlist(self.id)
return self
@@ -154,13 +152,13 @@ def add_songs(self, song_ids: list[str]) -> Self:
:rtype: Self
"""
- self.__subsonic.playlists.update_playlist(self.id, song_ids_to_add=song_ids)
+ self._subsonic.playlists.update_playlist(self.id, song_ids_to_add=song_ids)
if not self.songs:
self.songs = []
for id_ in song_ids:
- self.songs.append(Song(self.__subsonic, id_))
+ self.songs.append(Song(self._subsonic, id_))
if not self.song_count:
self.song_count = 0
@@ -179,7 +177,7 @@ def remove_songs(self, songs_indexes: list[int]) -> Self:
:rtype: Self
"""
- self.__subsonic.playlists.update_playlist(
+ self._subsonic.playlists.update_playlist(
self.id, song_indexes_to_remove=songs_indexes
)
diff --git a/src/knuckles/models/podcast.py b/src/knuckles/models/podcast.py
index 9db3bc0..970fba3 100644
--- a/src/knuckles/models/podcast.py
+++ b/src/knuckles/models/podcast.py
@@ -2,6 +2,7 @@
from ..exceptions import ResourceNotFound
from .cover_art import CoverArt
+from .model import Model
if TYPE_CHECKING:
from ..subsonic import Subsonic
@@ -9,14 +10,12 @@
from dateutil import parser
-class Episode:
+class Episode(Model):
"""Representation of all the data related to a podcast episode in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
id: str,
streamId: str | None = None,
channelId: str | None = None,
@@ -79,10 +78,11 @@ def __init__(
:type path: str | None, optional
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.id = id
self.stream_id = streamId
- self.channel = Channel(self.__subsonic, channelId) if channelId else None
+ self.channel = Channel(self._subsonic, channelId) if channelId else None
self.title = title
self.description = description
self.publish_date = parser.parse(publishDate) if publishDate else None
@@ -91,7 +91,7 @@ def __init__(
self.is_dir = isDir
self.year = year
self.genre = genre
- self.cover_art = CoverArt(coverArt) if coverArt else None
+ self.cover_art = CoverArt(self._subsonic, coverArt) if coverArt else None
self.size = size
self.content_type = contentType
self.suffix = suffix
@@ -110,7 +110,7 @@ def generate(self) -> "Episode":
:rtype: Episode
"""
- get_episode = self.__subsonic.podcast.get_episode(self.id)
+ get_episode = self._subsonic.podcast.get_episode(self.id)
if get_episode is None:
raise ResourceNotFound(
@@ -126,7 +126,7 @@ def download(self) -> Self:
:rtype: Self
"""
- self.__subsonic.podcast.download_podcast_episode(self.id)
+ self._subsonic.podcast.download_podcast_episode(self.id)
return self
@@ -137,19 +137,17 @@ def delete(self) -> Self:
:rtype: Self
"""
- self.__subsonic.podcast.delete_podcast_episode(self.id)
+ self._subsonic.podcast.delete_podcast_episode(self.id)
return self
-class Channel:
+class Channel(Model):
"""Representation of all the data related to a podcast channel in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
id: str,
url: str | None = None,
title: str | None = None,
@@ -182,16 +180,17 @@ def __init__(
:type episode: list[dict[str, Any]] | None, optional
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.id = id
self.url = url
self.title = title
self.description = description
- self.cover_art = CoverArt(coverArt) if coverArt else None
+ self.cover_art = CoverArt(self._subsonic, coverArt) if coverArt else None
self.original_image_url = originalImageUrl
self.status = status
self.episodes = (
- [Episode(self.__subsonic, **episode_data) for episode_data in episode]
+ [Episode(self._subsonic, **episode_data) for episode_data in episode]
if episode
else None
)
@@ -207,7 +206,7 @@ def generate(self) -> "Channel":
:rtype: Channel
"""
- return self.__subsonic.podcast.get_podcast(self.id)
+ return self._subsonic.podcast.get_podcast(self.id)
def create(self) -> Self:
"""Calls the "createPodcastChannel" endpoint of the API.
@@ -218,7 +217,7 @@ def create(self) -> Self:
# Ignore the None type error as the server
# should return an Error Code 10 in response
- self.__subsonic.podcast.create_podcast_channel(
+ self._subsonic.podcast.create_podcast_channel(
self.url # type: ignore[arg-type]
)
@@ -231,6 +230,6 @@ def delete(self) -> Self:
:rtype: Self
"""
- self.__subsonic.podcast.delete_podcast_channel(self.id)
+ self._subsonic.podcast.delete_podcast_channel(self.id)
return self
diff --git a/src/knuckles/models/scan_status.py b/src/knuckles/models/scan_status.py
index 574549e..545bf6d 100644
--- a/src/knuckles/models/scan_status.py
+++ b/src/knuckles/models/scan_status.py
@@ -1,9 +1,17 @@
-class ScanStatus:
+from typing import TYPE_CHECKING
+
+from .model import Model
+
+if TYPE_CHECKING:
+ from ..subsonic import Subsonic
+
+
+class ScanStatus(Model):
"""Representation of all the data related to the status
of a library scan in Subsonic.
"""
- def __init__(self, scanning: bool, count: int) -> None:
+ def __init__(self, subsonic: "Subsonic", scanning: bool, count: int) -> None:
"""Representation of all the data related to the status
of a library scan in Subsonic.
@@ -13,5 +21,7 @@ def __init__(self, scanning: bool, count: int) -> None:
:type count: int
"""
+ super().__init__(subsonic)
+
self.scanning: bool = scanning
self.count: int = count
diff --git a/src/knuckles/models/share.py b/src/knuckles/models/share.py
index 210a01d..c94230a 100644
--- a/src/knuckles/models/share.py
+++ b/src/knuckles/models/share.py
@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING, Any, Self
from ..exceptions import ResourceNotFound, ShareInvalidSongList
+from .model import Model
from .song import Song
from .user import User
@@ -10,14 +11,12 @@
from dateutil import parser
-class Share:
+class Share(Model):
"""Representation of all the data related to a share in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
id: str,
url: str | None = None,
description: str | None = None,
@@ -54,18 +53,17 @@ def __init__(
:type entry: list[dict[str, Any]] | None, optional
"""
- self.__subsonic = subsonic
+ super().__init__(subsonic)
+
self.id = id
self.url = url
self.description = description
- self.user = User(self.__subsonic, username) if username else None
+ self.user = User(self._subsonic, username) if username else None
self.created = parser.parse(created) if created else None
self.expires = parser.parse(expires) if expires else None
self.last_visited = parser.parse(lastVisited) if lastVisited else None
self.visit_count = visitCount
- self.songs = (
- [Song(self.__subsonic, **song) for song in entry] if entry else None
- )
+ self.songs = [Song(self._subsonic, **song) for song in entry] if entry else None
def generate(self) -> "Share | None":
"""Return a new share with all the data updated from the API,
@@ -78,12 +76,10 @@ def generate(self) -> "Share | None":
:rtype: Share
"""
- get_share = self.__subsonic.sharing.get_share(self.id)
+ get_share = self._subsonic.sharing.get_share(self.id)
if get_share is None:
- raise ResourceNotFound(
- "Unable to generate share as it does not exist in the server"
- )
+ raise ResourceNotFound
return get_share
@@ -109,7 +105,7 @@ def create(self) -> "Share":
songs_ids = [song.id for song in self.songs]
- new_share = self.__subsonic.sharing.create_share(
+ new_share = self._subsonic.sharing.create_share(
songs_ids, self.description, self.expires
)
@@ -125,7 +121,7 @@ def update(self) -> Self:
:rtype: Self
"""
- self.__subsonic.sharing.update_share(self.id, self.description, self.expires)
+ self._subsonic.sharing.update_share(self.id, self.description, self.expires)
return self
@@ -138,6 +134,6 @@ def delete(self) -> Self:
:rtype: Self
"""
- self.__subsonic.sharing.delete_share(self.id)
+ self._subsonic.sharing.delete_share(self.id)
return self
diff --git a/src/knuckles/models/song.py b/src/knuckles/models/song.py
index d1e4b65..166a4d2 100644
--- a/src/knuckles/models/song.py
+++ b/src/knuckles/models/song.py
@@ -3,10 +3,10 @@
# Avoid circular import error
from knuckles.models.genre import Genre, ItemGenre
-from ..exceptions import AlbumOrArtistArgumentsInSong, VideoArgumentsInSong
from .album import Album
from .artist import Artist
from .cover_art import CoverArt
+from .model import Model
if TYPE_CHECKING:
from ..subsonic import Subsonic
@@ -16,27 +16,35 @@
from dateutil import parser
-class Contributor:
+class Contributor(Model):
def __init__(
self,
+ subsonic: "Subsonic",
role: str,
artist: Artist,
subRole: str | None = None,
) -> None:
+
+ super().__init__(subsonic)
+
self.role = role
self.subrole = subRole
self.artist = artist
-class ReplayGain:
+class ReplayGain(Model):
def __init__(
self,
+ subsonic: "Subsonic",
trackGain: str | None = None,
albumGain: str | None = None,
trackPeak: str | None = None,
albumPeak: str | None = None,
baseGain: str | None = None,
) -> None:
+
+ super().__init__(subsonic)
+
self.track_gain = trackGain
self.album_gain = albumGain
self.track_peak = trackPeak
@@ -44,7 +52,7 @@ def __init__(
self.base_gain = baseGain
-class Song:
+class Song(Model):
"""Representation of all the data related to a song in Subsonic."""
def __init__(
@@ -175,26 +183,14 @@ def __init__(
for albums or artists are passed in.
"""
- if isVideo or originalWidth is not None or originalHeight is not None:
- raise VideoArgumentsInSong(
- (
- "A song shouldn't contain values valid for videos."
- + "Did you mean: Video()?"
- )
- )
-
- if isDir:
- raise AlbumOrArtistArgumentsInSong(
- "'isDir' shouldn't be True. Did you mean: Album() or Artist()?"
- )
+ super().__init__(subsonic)
- self.__subsonic = subsonic
self.id: str = id
self.title: str | None = title
self.parent: str | None = parent
self.track: int | None = track
self.year: int | None = year
- self.genre = Genre(self.__subsonic, genre) if genre else None
+ self.genre = Genre(self._subsonic, genre) if genre else None
self.size: int | None = size
self.content_type: str | None = contentType
self.suffix: str | None = suffix
@@ -209,9 +205,9 @@ def __init__(
self.disc_number: int | None = discNumber
self.type: str | None = type
self.bookmark_position: int | None = bookmarkPosition
- self.album = Album(self.__subsonic, albumId, name=album) if albumId else None
- self.artist = Artist(self.__subsonic, artistId, artist) if artistId else None
- self.cover_art = CoverArt(coverArt) if coverArt else None
+ self.album = Album(self._subsonic, albumId, name=album) if albumId else None
+ self.artist = Artist(self._subsonic, artistId, artist) if artistId else None
+ self.cover_art = CoverArt(self._subsonic, coverArt) if coverArt else None
self.created = parser.parse(created) if created else None
self.starred = parser.parse(starred) if starred else None
self.played = parser.parse(played) if played else None
@@ -219,27 +215,31 @@ def __init__(
self.comment = comment
self.sort_name = sortName
self.music_brainz_id = musicBrainzId
- self.genres = [ItemGenre(**genre) for genre in genres] if genres else None
+ self.genres = (
+ [ItemGenre(self._subsonic, **genre) for genre in genres] if genres else None
+ )
self.artists = (
- [Artist(self.__subsonic, **artist) for artist in artists]
+ [Artist(self._subsonic, **artist) for artist in artists]
if artists
else None
)
self.display_artist = displayArtist
self.album_artists = (
- [Artist(self.__subsonic, **artist) for artist in albumArtists]
+ [Artist(self._subsonic, **artist) for artist in albumArtists]
if albumArtists
else None
)
self.display_album_artist = displayAlbumArtist
self.contributors = (
- [Contributor(**contributor) for contributor in contributors]
+ [Contributor(self._subsonic, **contributor) for contributor in contributors]
if contributors
else None
)
self.display_composer = displayComposer
self.moods = moods
- self.replay_gain = ReplayGain(**replayGain) if replayGain else None
+ self.replay_gain = (
+ ReplayGain(self._subsonic, **replayGain) if replayGain else None
+ )
def generate(self) -> "Song":
"""Return a new song with all the data updated from the API,
@@ -252,7 +252,7 @@ def generate(self) -> "Song":
:rtype: Song
"""
- return self.__subsonic.browsing.get_song(self.id)
+ return self._subsonic.browsing.get_song(self.id)
def star(self) -> Self:
"""Calls the "star" endpoint of the API.
@@ -261,7 +261,7 @@ def star(self) -> Self:
:rtype: Self
"""
- self.__subsonic.media_annotation.star_song(self.id)
+ self._subsonic.media_annotation.star_song(self.id)
return self
@@ -272,7 +272,7 @@ def unstar(self) -> Self:
:rtype: Self
"""
- self.__subsonic.media_annotation.unstar_song(self.id)
+ self._subsonic.media_annotation.unstar_song(self.id)
return self
@@ -285,7 +285,7 @@ def set_rating(self, rating: int) -> Self:
:rtype: Self
"""
- self.__subsonic.media_annotation.set_rating(self.id, rating)
+ self._subsonic.media_annotation.set_rating(self.id, rating)
return self
@@ -296,7 +296,7 @@ def remove_rating(self) -> Self:
:rtype: Self
"""
- self.__subsonic.media_annotation.remove_rating(self.id)
+ self._subsonic.media_annotation.remove_rating(self.id)
return self
@@ -307,6 +307,6 @@ def scrobble(self, time: datetime, submission: bool = True) -> Self:
:rtype: Self
"""
- self.__subsonic.media_annotation.scrobble([self.id], [time], submission)
+ self._subsonic.media_annotation.scrobble([self.id], [time], submission)
return self
diff --git a/src/knuckles/models/system.py b/src/knuckles/models/system.py
index 80d1b55..41871a4 100644
--- a/src/knuckles/models/system.py
+++ b/src/knuckles/models/system.py
@@ -1,15 +1,21 @@
from datetime import datetime
+from typing import TYPE_CHECKING
from dateutil import parser
+from knuckles.models.model import Model
+if TYPE_CHECKING:
+ from ..subsonic import Subsonic
-class SubsonicResponse:
+
+class SubsonicResponse(Model):
"""Representation of the generic successful response data
in a request to the API.
"""
def __init__(
self,
+ subsonic: "Subsonic",
status: str,
version: str,
type: str | None = None,
@@ -34,6 +40,8 @@ def __init__(
:type openSubsonic: bool, optional
"""
+ super().__init__(subsonic)
+
self.status: str = status
self.version: str = version
self.type: str | None = type
@@ -41,11 +49,12 @@ def __init__(
self.open_subsonic: bool = openSubsonic
-class License:
+class License(Model):
"""Representation of the license related data in Subsonic."""
def __init__(
self,
+ subsonic: "Subsonic",
valid: bool,
email: str | None = None,
licenseExpires: str | None = None,
@@ -63,6 +72,8 @@ def __init__(
:type trialExpires: str | None, optional
"""
+ super().__init__(subsonic)
+
self.valid: bool = valid
self.email: str | None = email
diff --git a/src/knuckles/models/user.py b/src/knuckles/models/user.py
index 1e85836..a44e5d0 100644
--- a/src/knuckles/models/user.py
+++ b/src/knuckles/models/user.py
@@ -1,19 +1,19 @@
from typing import TYPE_CHECKING, Self
+from .model import Model
+
if TYPE_CHECKING:
from ..subsonic import Subsonic
-from ..exceptions import NoApiAccess
+from ..exceptions import MissingRequiredProperty
-class User:
+class User(Model):
"""Representation of all the data related to a user in Subsonic."""
def __init__(
self,
- # Internal
subsonic: "Subsonic",
- # Subsonic fields
username: str,
password: str | None = None,
email: str | None = None,
@@ -33,7 +33,9 @@ def __init__(
music_folder_id: list[str] | None = None,
max_bit_rate: int | None = None,
) -> None:
- self.subsonic = subsonic
+
+ super().__init__(subsonic)
+
self.username = username
self.password = password
self.email = email
@@ -53,20 +55,6 @@ def __init__(
self.music_folder_id = music_folder_id
self.max_bit_rate = max_bit_rate
- def __check_api_access(self) -> None:
- """Check if the object has a valid subsonic property
-
- :raises NoApiAccess: _description_
- """
-
- if self.subsonic is None:
- raise NoApiAccess(
- (
- "This user isn't associated with a Subsonic object."
- + "A non None value in the subsonic property is required"
- )
- )
-
def generate(self) -> "User":
"""Returns the function to the same user with the maximum possible
information from the Subsonic API.
@@ -79,9 +67,7 @@ def generate(self) -> "User":
:rtype: User
"""
- self.__check_api_access()
-
- return self.subsonic.user_management.get_user(self.username)
+ return self._subsonic.user_management.get_user(self.username)
def create(self) -> Self:
"""Calls the "createUser" endpoint of the API.
@@ -91,13 +77,17 @@ def create(self) -> Self:
:rtype: Self
"""
- self.__check_api_access()
+ if not self.email:
+ raise MissingRequiredProperty(
+ "You must provide an email in the email property of the model"
+ )
- #! TODO This is bad
- if not self.password or not self.email:
- raise NoApiAccess()
+ if not self.password:
+ raise MissingRequiredProperty(
+ "You must provide an password in the password property of the model"
+ )
- self.subsonic.user_management.create_user(
+ self._subsonic.user_management.create_user(
self.username,
self.password,
self.email,
@@ -131,9 +121,7 @@ def update(self) -> Self:
:rtype: Self
"""
- self.__check_api_access()
-
- self.subsonic.user_management.update_user(
+ self._subsonic.user_management.update_user(
self.username,
self.password,
self.email,
@@ -164,9 +152,7 @@ def delete(self) -> Self:
:rtype: Self
"""
- self.__check_api_access()
-
- self.subsonic.user_management.delete_user(self.username)
+ self._subsonic.user_management.delete_user(self.username)
return self
@@ -182,9 +168,6 @@ def change_password(self, new_password: str) -> Self:
:return: The object itself to allow method chaining.
:rtype: Self
"""
-
- self.__check_api_access()
-
- self.subsonic.user_management.change_password(self.username, new_password)
+ self._subsonic.user_management.change_password(self.username, new_password)
return self
diff --git a/src/knuckles/subsonic.py b/src/knuckles/subsonic.py
index 9d2276d..3030ff2 100644
--- a/src/knuckles/subsonic.py
+++ b/src/knuckles/subsonic.py
@@ -49,7 +49,7 @@ def __init__(
self.api = Api(
url, user, password, client, use_https, use_token, request_method
)
- self.system = System(self.api)
+ self.system = System(self.api, self)
self.browsing = Browsing(self.api, self)
self.lists = None # !!
self.searching = Searching(self.api, self)
@@ -63,5 +63,4 @@ def __init__(
self.chat = Chat(self.api, self)
self.user_management = UserManagement(self.api, self)
self.bookmarks = Bookmarks(self.api, self)
- self.media_library_scanning = MediaLibraryScanning(self.api)
- self.media_library_scanning = MediaLibraryScanning(self.api)
+ self.media_library_scanning = MediaLibraryScanning(self.api, self)
diff --git a/src/knuckles/system.py b/src/knuckles/system.py
index e80bd37..a8706a6 100644
--- a/src/knuckles/system.py
+++ b/src/knuckles/system.py
@@ -1,8 +1,11 @@
-from typing import NamedTuple
+from typing import TYPE_CHECKING, NamedTuple
from .api import Api
from .models.system import License, SubsonicResponse
+if TYPE_CHECKING:
+ from .subsonic import Subsonic
+
class OpenSubsonicExtension(NamedTuple):
name: str
@@ -15,9 +18,12 @@ class System:
"""
- def __init__(self, api: Api) -> None:
+ def __init__(self, api: Api, subsonic: "Subsonic") -> None:
self.api = api
+ # Only to pass it to the models
+ self.subsonic = subsonic
+
def ping(self) -> SubsonicResponse:
"""Calls to the "ping" endpoint of the API.
@@ -29,7 +35,7 @@ def ping(self) -> SubsonicResponse:
response = self.api.json_request("ping")
- return SubsonicResponse(**response)
+ return SubsonicResponse(self.subsonic, **response)
def get_license(self) -> License:
"""Calls to the "getLicense" endpoint of the API.
@@ -40,7 +46,7 @@ def get_license(self) -> License:
response = self.api.json_request("getLicense")["license"]
- return License(**response)
+ return License(self.subsonic, **response)
def get_open_subsonic_extensions(self) -> list[OpenSubsonicExtension]:
response = self.api.json_request("getOpenSubsonicExtensions")[
diff --git a/tests/models/test_share.py b/tests/models/test_share.py
index 01890c6..d60b3be 100644
--- a/tests/models/test_share.py
+++ b/tests/models/test_share.py
@@ -38,7 +38,7 @@ def test_generate_nonexistent_genre(
with pytest.raises(
ResourceNotFound,
- match="Unable to generate share as it does not exist in the server",
+ match="Unable to generate the model as it does not exist in the server",
):
nonexistent_share.generate()