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