Generated API objects
This commit is contained in:
@@ -7,7 +7,7 @@ represents those API objects in Python.
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from typing import cast, Dict, DefaultDict, Set, Match, Tuple, List, Union
|
||||
from typing import cast, Dict, DefaultDict, Set, Match, Tuple, List
|
||||
import sys
|
||||
|
||||
from graphviz import Digraph
|
||||
@@ -16,6 +16,13 @@ from lxml import etree
|
||||
# Global variables.
|
||||
tag_type_re = re.compile(r'\{.*\}(.*)')
|
||||
element_type_re = re.compile(r'.*:(.*)')
|
||||
primitive_translation_map = {
|
||||
'string': 'str',
|
||||
'double': 'float',
|
||||
'boolean': 'bool',
|
||||
'long': 'int',
|
||||
'dateTime': 'datetime',
|
||||
}
|
||||
|
||||
|
||||
def render_digraph(graph, filename):
|
||||
@@ -32,8 +39,14 @@ def render_digraph(graph, filename):
|
||||
g.render()
|
||||
|
||||
|
||||
def primitive_translate(type_str):
|
||||
# Translate the primitive values, but default to the actual value.
|
||||
return primitive_translation_map.get(type_str, type_str)
|
||||
|
||||
|
||||
def extract_type(type_str):
|
||||
return cast(Match, element_type_re.match(type_str)).group(1)
|
||||
return primitive_translate(
|
||||
cast(Match, element_type_re.match(type_str)).group(1))
|
||||
|
||||
|
||||
def extract_tag_type(tag_type_str):
|
||||
@@ -71,15 +84,15 @@ def get_dependencies(xs_el) -> Tuple[Set[str], Dict[str, str]]:
|
||||
|
||||
restriction = xs_el.getchildren()[0]
|
||||
restriction_type = extract_type(restriction.attrib['base'])
|
||||
if restriction_type == 'string':
|
||||
if restriction_type == 'str':
|
||||
restriction_children = restriction.getchildren()
|
||||
if extract_tag_type(restriction_children[0].tag) == 'enumeration':
|
||||
type_fields['__inherits__'] = 'Enum'
|
||||
for rc in restriction_children:
|
||||
rc_type = rc.attrib['value']
|
||||
rc_type = primitive_translate(rc.attrib['value'])
|
||||
type_fields[rc_type] = rc_type
|
||||
else:
|
||||
type_fields['__inherits__'] = 'string'
|
||||
type_fields['__inherits__'] = 'str'
|
||||
else:
|
||||
type_fields['__inherits__'] = restriction_type
|
||||
|
||||
@@ -139,7 +152,7 @@ def get_dependencies(xs_el) -> Tuple[Set[str], Dict[str, str]]:
|
||||
else:
|
||||
raise Exception(f'Unknown tag type {tag_type}.')
|
||||
|
||||
depends_on -= {'boolean', 'int', 'string', 'float', 'long', 'dateTime'}
|
||||
depends_on -= {'bool', 'int', 'str', 'float', 'datetime'}
|
||||
return depends_on, type_fields
|
||||
|
||||
|
||||
@@ -151,7 +164,7 @@ if len(sys.argv) < 3:
|
||||
|
||||
schema_file, output_file = sys.argv[1:]
|
||||
|
||||
# First pass, determine who depends on what.
|
||||
# Determine who depends on what and determine what fields are on each object.
|
||||
# =============================================================================
|
||||
with open(schema_file) as f:
|
||||
tree = etree.parse(f)
|
||||
@@ -204,22 +217,26 @@ dfs(dependency_graph, 'subsonic-response')
|
||||
output_order = [x[0] for x in sorted(end_times, key=lambda x: x[1])]
|
||||
output_order.remove('subsonic-response')
|
||||
|
||||
# Second pass, determine the fields on each of the elements and create the code
|
||||
# accordingly.
|
||||
# Create the code according to the spec that was generated earlier.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def generate_class_for_type(type_name):
|
||||
# print(type_name, type_fields[type_name])
|
||||
fields = type_fields[type_name]
|
||||
is_enum = 'Enum' in fields.get('__inherits__', '')
|
||||
|
||||
code = ['', '']
|
||||
inherits_from = ['APIObject']
|
||||
|
||||
for inherit in map(str.strip, fields.get('__inherits__', '').split(',')):
|
||||
if inherit != '':
|
||||
inherits_from.append(inherit)
|
||||
inherits = fields.get('__inherits__', '')
|
||||
is_enum = 'Enum' in inherits
|
||||
|
||||
if inherits:
|
||||
if inherits in primitive_translation_map.values() or is_enum:
|
||||
inherits_from.append(inherits)
|
||||
else:
|
||||
# Add the fields, we can't directly inherit due to the Diamond
|
||||
# Problem.
|
||||
fields.update(type_fields[inherits])
|
||||
|
||||
format_str = ' ' + ("{} = '{}'" if is_enum else '{}: {}')
|
||||
|
||||
@@ -229,7 +246,9 @@ def generate_class_for_type(type_name):
|
||||
if key.startswith('__'):
|
||||
continue
|
||||
|
||||
# Uppercase the key if an Enum.
|
||||
key = key.upper() if is_enum else key
|
||||
|
||||
code.append(format_str.format(key, value))
|
||||
has_properties = True
|
||||
|
||||
@@ -251,6 +270,6 @@ with open(output_file, 'w+') as outfile:
|
||||
'from datetime import datetime',
|
||||
'from typing import List',
|
||||
'from enum import Enum',
|
||||
'from .api_object import APIObject',
|
||||
'from libremsonic.server.api_object import APIObject',
|
||||
*map(generate_class_for_type, output_order),
|
||||
]) + '\n')
|
||||
|
@@ -13,6 +13,7 @@ def main():
|
||||
server = Server('ohea', 'https://airsonic.the-evans.family', 'sumner',
|
||||
'O}/UieSb[nzZ~l[X1S&zzX1Hi')
|
||||
|
||||
print(server.ping())
|
||||
print(server.get_license())
|
||||
# app = LibremsonicApp()
|
||||
# app.run(sys.argv)
|
||||
|
@@ -25,19 +25,17 @@ def from_json(cls, data):
|
||||
|
||||
annotations: Dict[str, Type] = getattr(cls, '__annotations__', {})
|
||||
|
||||
print(type(cls))
|
||||
|
||||
# Handle primitive of objects
|
||||
if data is None:
|
||||
instance = None
|
||||
elif cls == str:
|
||||
elif cls == str or issubclass(cls, str):
|
||||
instance = data
|
||||
elif cls == int:
|
||||
elif cls == int or issubclass(cls, int):
|
||||
instance = int(data)
|
||||
elif cls == bool:
|
||||
elif cls == bool or issubclass(cls, bool):
|
||||
instance = bool(data)
|
||||
elif type(cls) == EnumMeta:
|
||||
instance = cls[data]
|
||||
instance = cls(data)
|
||||
elif cls == datetime:
|
||||
instance = parser.parse(data)
|
||||
|
||||
|
@@ -19,11 +19,14 @@ class APIObject:
|
||||
def __repr__(self):
|
||||
if isinstance(self, Enum):
|
||||
return super().__repr__()
|
||||
if isinstance(self, str):
|
||||
return self
|
||||
|
||||
annotations: Dict[str, Any] = self.get('__annotations__', {})
|
||||
typename = type(self).__name__
|
||||
fieldstr = ' '.join([
|
||||
f'{field}={getattr(self, field)!r}'
|
||||
for field in annotations.keys() if hasattr(self, field)
|
||||
for field in annotations.keys()
|
||||
if hasattr(self, field) and getattr(self, field) is not None
|
||||
])
|
||||
return f'<{typename} {fieldstr}>'
|
||||
|
@@ -1,31 +1,37 @@
|
||||
"""
|
||||
WARNING: AUTOGENERATED FILE
|
||||
This file was generated by the api_object_generator.py script. Do
|
||||
not modify this file directly, rather modify the script or run it on
|
||||
a new API version.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from enum import Enum
|
||||
|
||||
from .api_object import APIObject
|
||||
from libremsonic.server.api_object import APIObject
|
||||
|
||||
|
||||
class SubsonicError(APIObject):
|
||||
code: int
|
||||
message: str
|
||||
|
||||
def as_exception(self):
|
||||
return Exception(f'{self.code}: {self.message}')
|
||||
class AlbumInfo(APIObject):
|
||||
notes: List[str]
|
||||
musicBrainzId: List[str]
|
||||
lastFmUrl: List[str]
|
||||
smallImageUrl: List[str]
|
||||
mediumImageUrl: List[str]
|
||||
largeImageUrl: List[str]
|
||||
|
||||
|
||||
class License(APIObject):
|
||||
valid: bool
|
||||
email: str
|
||||
licenseExpires: datetime
|
||||
trialExpires: datetime
|
||||
|
||||
|
||||
class MusicFolder(APIObject):
|
||||
id: int
|
||||
name: str
|
||||
class AverageRating(APIObject, float):
|
||||
pass
|
||||
|
||||
|
||||
class MediaType(APIObject, Enum):
|
||||
MUSIC = 'music'
|
||||
PODCAST = 'podcast'
|
||||
AUDIOBOOK = 'audiobook'
|
||||
VIDEO = 'video'
|
||||
|
||||
|
||||
class UserRating(APIObject, int):
|
||||
pass
|
||||
|
||||
|
||||
@@ -63,19 +69,8 @@ class Child(APIObject):
|
||||
originalHeight: int
|
||||
|
||||
|
||||
class Album(APIObject):
|
||||
id: int
|
||||
name: str
|
||||
artist: str
|
||||
artistId: int
|
||||
coverArt: str
|
||||
songCount: int
|
||||
duration: int
|
||||
created: datetime
|
||||
year: str
|
||||
genre: str
|
||||
|
||||
song: List[Child]
|
||||
class AlbumList(APIObject):
|
||||
album: List[Child]
|
||||
|
||||
|
||||
class AlbumID3(APIObject):
|
||||
@@ -93,7 +88,12 @@ class AlbumID3(APIObject):
|
||||
genre: str
|
||||
|
||||
|
||||
class AlbumList2(APIObject):
|
||||
album: List[AlbumID3]
|
||||
|
||||
|
||||
class AlbumWithSongsID3(APIObject):
|
||||
song: List[Child]
|
||||
id: str
|
||||
name: str
|
||||
artist: str
|
||||
@@ -107,8 +107,6 @@ class AlbumWithSongsID3(APIObject):
|
||||
year: int
|
||||
genre: str
|
||||
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class Artist(APIObject):
|
||||
id: str
|
||||
@@ -119,6 +117,25 @@ class Artist(APIObject):
|
||||
averageRating: AverageRating
|
||||
|
||||
|
||||
class ArtistInfoBase(APIObject):
|
||||
biography: List[str]
|
||||
musicBrainzId: List[str]
|
||||
lastFmUrl: List[str]
|
||||
smallImageUrl: List[str]
|
||||
mediumImageUrl: List[str]
|
||||
largeImageUrl: List[str]
|
||||
|
||||
|
||||
class ArtistInfo(APIObject):
|
||||
similarArtist: List[Artist]
|
||||
biography: List[str]
|
||||
musicBrainzId: List[str]
|
||||
lastFmUrl: List[str]
|
||||
smallImageUrl: List[str]
|
||||
mediumImageUrl: List[str]
|
||||
largeImageUrl: List[str]
|
||||
|
||||
|
||||
class ArtistID3(APIObject):
|
||||
id: str
|
||||
name: str
|
||||
@@ -128,35 +145,61 @@ class ArtistID3(APIObject):
|
||||
starred: datetime
|
||||
|
||||
|
||||
class ArtistInfo2(APIObject):
|
||||
similarArtist: List[ArtistID3]
|
||||
biography: List[str]
|
||||
musicBrainzId: List[str]
|
||||
lastFmUrl: List[str]
|
||||
smallImageUrl: List[str]
|
||||
mediumImageUrl: List[str]
|
||||
largeImageUrl: List[str]
|
||||
|
||||
|
||||
class ArtistWithAlbumsID3(APIObject):
|
||||
album: List[AlbumID3]
|
||||
id: str
|
||||
name: str
|
||||
coverArt: str
|
||||
artistImageUrl: str
|
||||
albumCount: int
|
||||
starred: datetime
|
||||
album: List[AlbumID3]
|
||||
|
||||
|
||||
class Index(APIObject):
|
||||
name: str
|
||||
artist: List[Artist]
|
||||
|
||||
|
||||
class IndexID3(APIObject):
|
||||
name: str
|
||||
artist: List[ArtistID3]
|
||||
name: str
|
||||
|
||||
|
||||
class Indexes(APIObject):
|
||||
lastModified: int
|
||||
class ArtistsID3(APIObject):
|
||||
index: List[IndexID3]
|
||||
ignoredArticles: str
|
||||
index: List[Index]
|
||||
shortcut: List[Artist]
|
||||
child: List[Child]
|
||||
|
||||
|
||||
class Bookmark(APIObject):
|
||||
entry: List[Child]
|
||||
position: int
|
||||
username: str
|
||||
comment: str
|
||||
created: datetime
|
||||
changed: datetime
|
||||
|
||||
|
||||
class Bookmarks(APIObject):
|
||||
bookmark: List[Bookmark]
|
||||
|
||||
|
||||
class ChatMessage(APIObject):
|
||||
username: str
|
||||
time: int
|
||||
message: str
|
||||
|
||||
|
||||
class ChatMessages(APIObject):
|
||||
chatMessage: List[ChatMessage]
|
||||
|
||||
|
||||
class Directory(APIObject):
|
||||
child: List[Child]
|
||||
id: str
|
||||
parent: str
|
||||
name: str
|
||||
@@ -165,7 +208,10 @@ class Directory(APIObject):
|
||||
averageRating: AverageRating
|
||||
playCount: int
|
||||
|
||||
child: List[Child]
|
||||
|
||||
class Error(APIObject):
|
||||
code: int
|
||||
message: str
|
||||
|
||||
|
||||
class Genre(APIObject):
|
||||
@@ -173,28 +219,321 @@ class Genre(APIObject):
|
||||
albumCount: int
|
||||
|
||||
|
||||
class ArtistInfo(APIObject):
|
||||
biography: str
|
||||
musicBrainzId: str
|
||||
lastFmUrl: str
|
||||
smallImageUrl: str
|
||||
mediumImageUrl: str
|
||||
largeImageUrl: str
|
||||
similarArtist: List[Artist]
|
||||
class Genres(APIObject):
|
||||
genre: List[Genre]
|
||||
|
||||
|
||||
class AlbumInfo(APIObject):
|
||||
notes: str
|
||||
musicBrainzId: str
|
||||
lastFmUrl: str
|
||||
smallImageUrl: str
|
||||
mediumImageUrl: str
|
||||
largeImageUrl: str
|
||||
class Index(APIObject):
|
||||
artist: List[Artist]
|
||||
name: str
|
||||
|
||||
|
||||
class Captions(APIObject):
|
||||
class Indexes(APIObject):
|
||||
shortcut: List[Artist]
|
||||
index: List[Index]
|
||||
child: List[Child]
|
||||
lastModified: int
|
||||
ignoredArticles: str
|
||||
|
||||
|
||||
class InternetRadioStation(APIObject):
|
||||
id: str
|
||||
name: str
|
||||
streamUrl: str
|
||||
homePageUrl: str
|
||||
|
||||
|
||||
class InternetRadioStations(APIObject):
|
||||
internetRadioStation: List[InternetRadioStation]
|
||||
|
||||
|
||||
class JukeboxStatus(APIObject):
|
||||
currentIndex: int
|
||||
playing: bool
|
||||
gain: float
|
||||
position: int
|
||||
|
||||
|
||||
class JukeboxPlaylist(APIObject):
|
||||
entry: List[Child]
|
||||
currentIndex: int
|
||||
playing: bool
|
||||
gain: float
|
||||
position: int
|
||||
|
||||
|
||||
class License(APIObject):
|
||||
valid: bool
|
||||
email: str
|
||||
licenseExpires: datetime
|
||||
trialExpires: datetime
|
||||
|
||||
|
||||
class Lyrics(APIObject):
|
||||
artist: str
|
||||
title: str
|
||||
|
||||
|
||||
class MusicFolder(APIObject):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
class MusicFolders(APIObject):
|
||||
musicFolder: List[MusicFolder]
|
||||
|
||||
|
||||
class PodcastStatus(APIObject, Enum):
|
||||
NEW = 'new'
|
||||
DOWNLOADING = 'downloading'
|
||||
COMPLETED = 'completed'
|
||||
ERROR = 'error'
|
||||
DELETED = 'deleted'
|
||||
SKIPPED = 'skipped'
|
||||
|
||||
|
||||
class PodcastEpisode(APIObject):
|
||||
streamId: str
|
||||
channelId: str
|
||||
description: str
|
||||
status: PodcastStatus
|
||||
publishDate: datetime
|
||||
id: str
|
||||
parent: str
|
||||
isDir: bool
|
||||
title: str
|
||||
album: str
|
||||
artist: str
|
||||
track: int
|
||||
year: int
|
||||
genre: str
|
||||
coverArt: str
|
||||
size: int
|
||||
contentType: str
|
||||
suffix: str
|
||||
transcodedContentType: str
|
||||
transcodedSuffix: str
|
||||
duration: int
|
||||
bitRate: int
|
||||
path: str
|
||||
isVideo: bool
|
||||
userRating: UserRating
|
||||
averageRating: AverageRating
|
||||
playCount: int
|
||||
discNumber: int
|
||||
created: datetime
|
||||
starred: datetime
|
||||
albumId: str
|
||||
artistId: str
|
||||
type: MediaType
|
||||
bookmarkPosition: int
|
||||
originalWidth: int
|
||||
originalHeight: int
|
||||
|
||||
|
||||
class NewestPodcasts(APIObject):
|
||||
episode: List[PodcastEpisode]
|
||||
|
||||
|
||||
class NowPlayingEntry(APIObject):
|
||||
username: str
|
||||
minutesAgo: int
|
||||
playerId: int
|
||||
playerName: str
|
||||
id: str
|
||||
parent: str
|
||||
isDir: bool
|
||||
title: str
|
||||
album: str
|
||||
artist: str
|
||||
track: int
|
||||
year: int
|
||||
genre: str
|
||||
coverArt: str
|
||||
size: int
|
||||
contentType: str
|
||||
suffix: str
|
||||
transcodedContentType: str
|
||||
transcodedSuffix: str
|
||||
duration: int
|
||||
bitRate: int
|
||||
path: str
|
||||
isVideo: bool
|
||||
userRating: UserRating
|
||||
averageRating: AverageRating
|
||||
playCount: int
|
||||
discNumber: int
|
||||
created: datetime
|
||||
starred: datetime
|
||||
albumId: str
|
||||
artistId: str
|
||||
type: MediaType
|
||||
bookmarkPosition: int
|
||||
originalWidth: int
|
||||
originalHeight: int
|
||||
|
||||
|
||||
class NowPlaying(APIObject):
|
||||
entry: List[NowPlayingEntry]
|
||||
|
||||
|
||||
class PlayQueue(APIObject):
|
||||
entry: List[Child]
|
||||
current: int
|
||||
position: int
|
||||
username: str
|
||||
changed: datetime
|
||||
changedBy: str
|
||||
|
||||
|
||||
class Playlist(APIObject):
|
||||
allowedUser: List[str]
|
||||
id: str
|
||||
name: str
|
||||
comment: str
|
||||
owner: str
|
||||
public: bool
|
||||
songCount: int
|
||||
duration: int
|
||||
created: datetime
|
||||
changed: datetime
|
||||
coverArt: str
|
||||
|
||||
|
||||
class PlaylistWithSongs(APIObject):
|
||||
entry: List[Child]
|
||||
allowedUser: List[str]
|
||||
id: str
|
||||
name: str
|
||||
comment: str
|
||||
owner: str
|
||||
public: bool
|
||||
songCount: int
|
||||
duration: int
|
||||
created: datetime
|
||||
changed: datetime
|
||||
coverArt: str
|
||||
|
||||
|
||||
class Playlists(APIObject):
|
||||
playlist: List[Playlist]
|
||||
|
||||
|
||||
class PodcastChannel(APIObject):
|
||||
episode: List[PodcastEpisode]
|
||||
id: str
|
||||
url: str
|
||||
title: str
|
||||
description: str
|
||||
coverArt: str
|
||||
originalImageUrl: str
|
||||
status: PodcastStatus
|
||||
errorMessage: str
|
||||
|
||||
|
||||
class Podcasts(APIObject):
|
||||
channel: List[PodcastChannel]
|
||||
|
||||
|
||||
class ResponseStatus(APIObject, Enum):
|
||||
OK = 'ok'
|
||||
FAILED = 'failed'
|
||||
|
||||
|
||||
class ScanStatus(APIObject):
|
||||
scanning: bool
|
||||
count: int
|
||||
|
||||
|
||||
class SearchResult(APIObject):
|
||||
match: List[Child]
|
||||
offset: int
|
||||
totalHits: int
|
||||
|
||||
|
||||
class SearchResult2(APIObject):
|
||||
artist: List[Artist]
|
||||
album: List[Child]
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class SearchResult3(APIObject):
|
||||
artist: List[ArtistID3]
|
||||
album: List[AlbumID3]
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class Share(APIObject):
|
||||
entry: List[Child]
|
||||
id: str
|
||||
url: str
|
||||
description: str
|
||||
username: str
|
||||
created: datetime
|
||||
expires: datetime
|
||||
lastVisited: datetime
|
||||
visitCount: int
|
||||
|
||||
|
||||
class Shares(APIObject):
|
||||
share: List[Share]
|
||||
|
||||
|
||||
class SimilarSongs(APIObject):
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class SimilarSongs2(APIObject):
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class Songs(APIObject):
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class Starred(APIObject):
|
||||
artist: List[Artist]
|
||||
album: List[Child]
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class Starred2(APIObject):
|
||||
artist: List[ArtistID3]
|
||||
album: List[AlbumID3]
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class TopSongs(APIObject):
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class User(APIObject):
|
||||
folder: List[int]
|
||||
username: str
|
||||
email: str
|
||||
scrobblingEnabled: bool
|
||||
maxBitRate: int
|
||||
adminRole: bool
|
||||
settingsRole: bool
|
||||
downloadRole: bool
|
||||
uploadRole: bool
|
||||
playlistRole: bool
|
||||
coverArtRole: bool
|
||||
commentRole: bool
|
||||
podcastRole: bool
|
||||
streamRole: bool
|
||||
jukeboxRole: bool
|
||||
shareRole: bool
|
||||
videoConversionRole: bool
|
||||
avatarLastChanged: datetime
|
||||
|
||||
|
||||
class Users(APIObject):
|
||||
user: List[User]
|
||||
|
||||
|
||||
class Version(APIObject, str):
|
||||
pass
|
||||
|
||||
|
||||
class AudioTrack(APIObject):
|
||||
@@ -203,6 +542,11 @@ class AudioTrack(APIObject):
|
||||
languageCode: str
|
||||
|
||||
|
||||
class Captions(APIObject):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
|
||||
class VideoConversion(APIObject):
|
||||
id: str
|
||||
bitRate: int
|
||||
@@ -210,96 +554,59 @@ class VideoConversion(APIObject):
|
||||
|
||||
|
||||
class VideoInfo(APIObject):
|
||||
id: str
|
||||
captions: List[Captions]
|
||||
audioTrack: List[AudioTrack]
|
||||
conversion: List[VideoConversion]
|
||||
|
||||
|
||||
class ArtistsID3(APIObject):
|
||||
ignoredArticles: str
|
||||
index: List[IndexID3]
|
||||
|
||||
|
||||
class MusicFolders(APIObject):
|
||||
musicFolder: List[MusicFolder]
|
||||
|
||||
|
||||
class Genres(APIObject):
|
||||
genre: List[Genre]
|
||||
|
||||
|
||||
class Artists(APIObject):
|
||||
index: List[Index]
|
||||
id: str
|
||||
|
||||
|
||||
class Videos(APIObject):
|
||||
video: List[Child]
|
||||
|
||||
|
||||
class SimilarSongs(APIObject):
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class TopSongs(APIObject):
|
||||
song: List[Child]
|
||||
|
||||
|
||||
class AlbumList(APIObject):
|
||||
album: List[Album]
|
||||
|
||||
|
||||
class ResponseStatus(APIObject, Enum):
|
||||
ok = "ok"
|
||||
failed = "failed"
|
||||
|
||||
|
||||
class SubsonicResponse(APIObject):
|
||||
# On every Subsonic Response
|
||||
status: ResponseStatus
|
||||
version: str
|
||||
|
||||
# One of these will exist on each SubsonicResponse
|
||||
album: AlbumWithSongsID3
|
||||
albumInfo: AlbumInfo
|
||||
albumList: AlbumList
|
||||
albumList2: AlbumList2
|
||||
artist: ArtistWithAlbumsID3
|
||||
artistInfo: ArtistInfo
|
||||
artistInfo2: ArtistInfo2
|
||||
artists: ArtistsID3
|
||||
bookmarks: Bookmarks
|
||||
chatMessages: ChatMessages
|
||||
directory: Directory
|
||||
error: Error
|
||||
genres: Genres
|
||||
indexes: Indexes
|
||||
internetRadioStations: InternetRadioStations
|
||||
jukeboxPlaylist: JukeboxPlaylist
|
||||
jukeboxStatus: JukeboxStatus
|
||||
license: License
|
||||
lyrics: Lyrics
|
||||
class Response(APIObject):
|
||||
musicFolders: MusicFolders
|
||||
newestPodcasts: NewestPodcasts
|
||||
indexes: Indexes
|
||||
directory: Directory
|
||||
genres: Genres
|
||||
artists: ArtistsID3
|
||||
artist: ArtistWithAlbumsID3
|
||||
album: AlbumWithSongsID3
|
||||
song: Child
|
||||
videos: Videos
|
||||
videoInfo: VideoInfo
|
||||
nowPlaying: NowPlaying
|
||||
playlist: PlaylistWithSongs
|
||||
playlists: Playlists
|
||||
playQueue: PlayQueue
|
||||
podcasts: Podcasts
|
||||
randomSongs: Songs
|
||||
scanStatus: ScanStatus
|
||||
searchResult: SearchResult
|
||||
searchResult2: SearchResult2
|
||||
searchResult3: SearchResult3
|
||||
shares: Shares
|
||||
similarSongs: SimilarSongs
|
||||
similarSongs2: SimilarSongs2
|
||||
song: Child
|
||||
playlists: Playlists
|
||||
playlist: PlaylistWithSongs
|
||||
jukeboxStatus: JukeboxStatus
|
||||
jukeboxPlaylist: JukeboxPlaylist
|
||||
license: License
|
||||
users: Users
|
||||
user: User
|
||||
chatMessages: ChatMessages
|
||||
albumList: AlbumList
|
||||
albumList2: AlbumList2
|
||||
randomSongs: Songs
|
||||
songsByGenre: Songs
|
||||
lyrics: Lyrics
|
||||
podcasts: Podcasts
|
||||
newestPodcasts: NewestPodcasts
|
||||
internetRadioStations: InternetRadioStations
|
||||
bookmarks: Bookmarks
|
||||
playQueue: PlayQueue
|
||||
shares: Shares
|
||||
starred: Starred
|
||||
starred2: Starred2
|
||||
albumInfo: AlbumInfo
|
||||
artistInfo: ArtistInfo
|
||||
artistInfo2: ArtistInfo2
|
||||
similarSongs: SimilarSongs
|
||||
similarSongs2: SimilarSongs2
|
||||
topSongs: TopSongs
|
||||
user: User
|
||||
users: Users
|
||||
videos: Videos
|
||||
videoInfo: VideoInfo
|
||||
scanStatus: ScanStatus
|
||||
error: Error
|
||||
status: ResponseStatus
|
||||
version: Version
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import requests
|
||||
from typing import List, Optional, Dict
|
||||
|
||||
from .api_objects import (SubsonicResponse, License, MusicFolder, Indexes,
|
||||
AlbumInfo, ArtistInfo, VideoInfo, Child, Album,
|
||||
Artist, Artists, Directory, Genre)
|
||||
from .api_objects import (Response, License, MusicFolder, Indexes, AlbumInfo,
|
||||
ArtistInfo, VideoInfo, Child, AlbumID3, Artist,
|
||||
ArtistsID3, Directory, Genre)
|
||||
|
||||
|
||||
class Server:
|
||||
@@ -29,12 +29,12 @@ class Server:
|
||||
def _make_url(self, endpoint: str) -> str:
|
||||
return f'{self.hostname}/rest/{endpoint}.view'
|
||||
|
||||
def _post(self, url, **params) -> SubsonicResponse:
|
||||
def _post(self, url, **params) -> Response:
|
||||
"""
|
||||
Make a post to a *Sonic REST API. Handle all types of errors including
|
||||
*Sonic ``<error>`` responses.
|
||||
|
||||
:returns: a SubsonicResponse containing all of the data of the
|
||||
:returns: a Response containing all of the data of the
|
||||
response, deserialized
|
||||
:raises Exception: needs some work TODO
|
||||
"""
|
||||
@@ -54,7 +54,7 @@ class Server:
|
||||
# TODO: logging
|
||||
print(subsonic_response)
|
||||
|
||||
response = SubsonicResponse.from_json(subsonic_response)
|
||||
response = Response.from_json(subsonic_response)
|
||||
|
||||
# Check for an error and if it exists, raise it.
|
||||
if response.get('error'):
|
||||
@@ -62,7 +62,7 @@ class Server:
|
||||
|
||||
return response
|
||||
|
||||
def ping(self) -> SubsonicResponse:
|
||||
def ping(self) -> Response:
|
||||
"""
|
||||
Used to test connectivity with the server.
|
||||
"""
|
||||
@@ -73,7 +73,6 @@ class Server:
|
||||
Get details about the software license.
|
||||
"""
|
||||
result = self._post(self._make_url('getLicense'))
|
||||
print(result)
|
||||
return result.license
|
||||
|
||||
def get_music_folders(self) -> List[MusicFolder]:
|
||||
@@ -119,7 +118,7 @@ class Server:
|
||||
result = self._post(self._make_url('getGenres'))
|
||||
return result.genres.genre
|
||||
|
||||
def get_artists(self, music_folder_id: int = None) -> Artists:
|
||||
def get_artists(self, music_folder_id: int = None) -> ArtistsID3:
|
||||
"""
|
||||
Similar to getIndexes, but organizes music according to ID3 tags.
|
||||
|
||||
@@ -140,7 +139,7 @@ class Server:
|
||||
result = self._post(self._make_url('getArtist'), id=artist_id)
|
||||
return result.artist
|
||||
|
||||
def get_album(self, album_id: int) -> Album:
|
||||
def get_album(self, album_id: int) -> AlbumID3:
|
||||
"""
|
||||
Returns details for an album, including a list of songs. This method
|
||||
organizes music according to ID3 tags.
|
||||
|
Reference in New Issue
Block a user