models: Convert all models to using fields.

This commit is contained in:
Thomas Adamcik 2015-04-04 15:18:56 +02:00
parent 07912e1091
commit 4faf4de7aa

View File

@ -128,12 +128,15 @@ class ImmutableObject(object):
""" """
Superclass for immutable objects whose fields can only be modified via the Superclass for immutable objects whose fields can only be modified via the
constructor. constructor. Fields should be :class:`Field` instances to ensure type
safety in our models.
:param kwargs: kwargs to set as fields on the object :param kwargs: kwargs to set as fields on the object
:type kwargs: any :type kwargs: any
""" """
__metaclass__ = FieldOwner
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
for key, value in kwargs.items(): for key, value in kwargs.items():
if not hasattr(self, key) or callable(getattr(self, key)): if not hasattr(self, key) or callable(getattr(self, key)):
@ -142,7 +145,7 @@ class ImmutableObject(object):
key) key)
if value == getattr(self, key): if value == getattr(self, key):
continue # Don't explicitly set default values continue # Don't explicitly set default values
self.__dict__[key] = value super(ImmutableObject, self).__setattr__(key, value)
def __setattr__(self, name, value): def __setattr__(self, name, value):
if name.startswith('_'): if name.startswith('_'):
@ -192,7 +195,7 @@ class ImmutableObject(object):
:type values: dict :type values: dict
:rtype: new instance of the model being copied :rtype: new instance of the model being copied
""" """
data = {} data = {} # TODO: do we need public key handling now?
for key in self.__dict__.keys(): for key in self.__dict__.keys():
public_key = key.lstrip('_') public_key = key.lstrip('_')
value = values.pop(public_key, self.__dict__[key]) value = values.pop(public_key, self.__dict__[key])
@ -207,7 +210,7 @@ class ImmutableObject(object):
return self.__class__(**data) return self.__class__(**data)
def serialize(self): def serialize(self):
data = {} data = {} # TODO: do we need public key handling now?
data['__model__'] = self.__class__.__name__ data['__model__'] = self.__class__.__name__
for key in self.__dict__.keys(): for key in self.__dict__.keys():
public_key = key.lstrip('_') public_key = key.lstrip('_')
@ -282,14 +285,10 @@ class Ref(ImmutableObject):
""" """
#: The object URI. Read-only. #: The object URI. Read-only.
uri = None uri = String()
#: The object name. Read-only. #: The object name. Read-only.
name = None name = String()
#: The object type, e.g. "artist", "album", "track", "playlist",
#: "directory". Read-only.
type = None
#: Constant used for comparison with the :attr:`type` field. #: Constant used for comparison with the :attr:`type` field.
ALBUM = 'album' ALBUM = 'album'
@ -306,6 +305,10 @@ class Ref(ImmutableObject):
#: Constant used for comparison with the :attr:`type` field. #: Constant used for comparison with the :attr:`type` field.
TRACK = 'track' TRACK = 'track'
#: The object type, e.g. "artist", "album", "track", "playlist",
#: "directory". Read-only.
type = Field(choices=(ALBUM, ARTIST, DIRECTORY, PLAYLIST, TRACK))
@classmethod @classmethod
def album(cls, **kwargs): def album(cls, **kwargs):
"""Create a :class:`Ref` with ``type`` :attr:`ALBUM`.""" """Create a :class:`Ref` with ``type`` :attr:`ALBUM`."""
@ -346,13 +349,13 @@ class Image(ImmutableObject):
""" """
#: The image URI. Read-only. #: The image URI. Read-only.
uri = None uri = String()
#: Optional width of the image or :class:`None`. Read-only. #: Optional width of the image or :class:`None`. Read-only.
width = None width = Integer(min=0)
#: Optional height of the image or :class:`None`. Read-only. #: Optional height of the image or :class:`None`. Read-only.
height = None height = Integer(min=0)
class Artist(ImmutableObject): class Artist(ImmutableObject):
@ -367,13 +370,13 @@ class Artist(ImmutableObject):
""" """
#: The artist URI. Read-only. #: The artist URI. Read-only.
uri = None uri = String()
#: The artist name. Read-only. #: The artist name. Read-only.
name = None name = String()
#: The MusicBrainz ID of the artist. Read-only. #: The MusicBrainz ID of the artist. Read-only.
musicbrainz_id = None musicbrainz_id = String()
class Album(ImmutableObject): class Album(ImmutableObject):
@ -398,37 +401,32 @@ class Album(ImmutableObject):
""" """
#: The album URI. Read-only. #: The album URI. Read-only.
uri = None uri = String()
#: The album name. Read-only. #: The album name. Read-only.
name = None name = String()
#: A set of album artists. Read-only. #: A set of album artists. Read-only.
artists = frozenset() artists = Collection(type=Artist, container=frozenset)
#: The number of tracks in the album. Read-only. #: The number of tracks in the album. Read-only.
num_tracks = None num_tracks = Integer(min=0)
#: The number of discs in the album. Read-only. #: The number of discs in the album. Read-only.
num_discs = None num_discs = Integer(min=0)
#: The album release date. Read-only. #: The album release date. Read-only.
date = None date = String() # TODO: add date type
#: The MusicBrainz ID of the album. Read-only. #: The MusicBrainz ID of the album. Read-only.
musicbrainz_id = None musicbrainz_id = String()
#: The album image URIs. Read-only. #: The album image URIs. Read-only.
images = frozenset() images = Collection(type=basestring, container=frozenset)
# XXX If we want to keep the order of images we shouldn't use frozenset() # XXX If we want to keep the order of images we shouldn't use frozenset()
# as it doesn't preserve order. I'm deferring this issue until we got # as it doesn't preserve order. I'm deferring this issue until we got
# actual usage of this field with more than one image. # actual usage of this field with more than one image.
def __init__(self, *args, **kwargs):
self.__dict__['artists'] = frozenset(kwargs.pop('artists', None) or [])
self.__dict__['images'] = frozenset(kwargs.pop('images', None) or [])
super(Album, self).__init__(*args, **kwargs)
class Track(ImmutableObject): class Track(ImmutableObject):
@ -466,61 +464,52 @@ class Track(ImmutableObject):
""" """
#: The track URI. Read-only. #: The track URI. Read-only.
uri = None uri = String()
#: The track name. Read-only. #: The track name. Read-only.
name = None name = String()
#: A set of track artists. Read-only. #: A set of track artists. Read-only.
artists = frozenset() artists = Collection(type=Artist, container=frozenset)
#: The track :class:`Album`. Read-only. #: The track :class:`Album`. Read-only.
album = None album = Field(type=Album)
#: A set of track composers. Read-only. #: A set of track composers. Read-only.
composers = frozenset() composers = Collection(type=Artist, container=frozenset)
#: A set of track performers`. Read-only. #: A set of track performers`. Read-only.
performers = frozenset() performers = Collection(type=Artist, container=frozenset)
#: The track genre. Read-only. #: The track genre. Read-only.
genre = None genre = String()
#: The track number in the album. Read-only. #: The track number in the album. Read-only.
track_no = None track_no = Integer(min=0)
#: The disc number in the album. Read-only. #: The disc number in the album. Read-only.
disc_no = None disc_no = Integer(min=0)
#: The track release date. Read-only. #: The track release date. Read-only.
date = None date = String() # TODO: add date type
#: The track length in milliseconds. Read-only. #: The track length in milliseconds. Read-only.
length = None length = Integer(min=0)
#: The track's bitrate in kbit/s. Read-only. #: The track's bitrate in kbit/s. Read-only.
bitrate = None bitrate = Integer(min=0)
#: The track comment. Read-only. #: The track comment. Read-only.
comment = None comment = String()
#: The MusicBrainz ID of the track. Read-only. #: The MusicBrainz ID of the track. Read-only.
musicbrainz_id = None musicbrainz_id = String()
#: Integer representing when the track was last modified. Exact meaning #: Integer representing when the track was last modified. Exact meaning
#: depends on source of track. For local files this is the modification #: depends on source of track. For local files this is the modification
#: time in milliseconds since Unix epoch. For other backends it could be an #: time in milliseconds since Unix epoch. For other backends it could be an
#: equivalent timestamp or simply a version counter. #: equivalent timestamp or simply a version counter.
last_modified = None last_modified = Integer(min=0)
def __init__(self, *args, **kwargs):
def get(key):
return frozenset(kwargs.pop(key, None) or [])
self.__dict__['artists'] = get('artists')
self.__dict__['composers'] = get('composers')
self.__dict__['performers'] = get('performers')
super(Track, self).__init__(*args, **kwargs)
class TlTrack(ImmutableObject): class TlTrack(ImmutableObject):
@ -546,10 +535,10 @@ class TlTrack(ImmutableObject):
""" """
#: The tracklist ID. Read-only. #: The tracklist ID. Read-only.
tlid = None tlid = Integer(min=0)
#: The track. Read-only. #: The track. Read-only.
track = None track = Field(type=Track)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if len(args) == 2 and len(kwargs) == 0: if len(args) == 2 and len(kwargs) == 0:
@ -577,23 +566,19 @@ class Playlist(ImmutableObject):
""" """
#: The playlist URI. Read-only. #: The playlist URI. Read-only.
uri = None uri = String()
#: The playlist name. Read-only. #: The playlist name. Read-only.
name = None name = String()
#: The playlist's tracks. Read-only. #: The playlist's tracks. Read-only.
tracks = tuple() tracks = Collection(type=Track, container=tuple)
#: The playlist modification time in milliseconds since Unix epoch. #: The playlist modification time in milliseconds since Unix epoch.
#: Read-only. #: Read-only.
#: #:
#: Integer, or :class:`None` if unknown. #: Integer, or :class:`None` if unknown.
last_modified = None last_modified = Integer(min=0)
def __init__(self, *args, **kwargs):
self.__dict__['tracks'] = tuple(kwargs.pop('tracks', None) or [])
super(Playlist, self).__init__(*args, **kwargs)
# TODO: def insert(self, pos, track): ... ? # TODO: def insert(self, pos, track): ... ?
@ -617,19 +602,13 @@ class SearchResult(ImmutableObject):
""" """
# The search result URI. Read-only. # The search result URI. Read-only.
uri = None uri = String()
# The tracks matching the search query. Read-only. # The tracks matching the search query. Read-only.
tracks = tuple() tracks = Collection(type=Track, container=tuple)
# The artists matching the search query. Read-only. # The artists matching the search query. Read-only.
artists = tuple() artists = Collection(type=Artist, container=tuple)
# The albums matching the search query. Read-only. # The albums matching the search query. Read-only.
albums = tuple() albums = Collection(type=Album, container=tuple)
def __init__(self, *args, **kwargs):
self.__dict__['tracks'] = tuple(kwargs.pop('tracks', None) or [])
self.__dict__['artists'] = tuple(kwargs.pop('artists', None) or [])
self.__dict__['albums'] = tuple(kwargs.pop('albums', None) or [])
super(SearchResult, self).__init__(*args, **kwargs)