Merge branch 'feature/model-serialization' into develop
This commit is contained in:
commit
f82d3fca39
@ -1,5 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
|
||||
class ImmutableObject(object):
|
||||
"""
|
||||
@ -12,7 +14,7 @@ class ImmutableObject(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
if not hasattr(self, key):
|
||||
if not hasattr(self, key) or callable(getattr(self, key)):
|
||||
raise TypeError(
|
||||
'__init__() got an unexpected keyword argument "%s"' %
|
||||
key)
|
||||
@ -78,6 +80,7 @@ class ImmutableObject(object):
|
||||
|
||||
def serialize(self):
|
||||
data = {}
|
||||
data['__model__'] = self.__class__.__name__
|
||||
for key in self.__dict__.keys():
|
||||
public_key = key.lstrip('_')
|
||||
value = self.__dict__[key]
|
||||
@ -90,6 +93,44 @@ class ImmutableObject(object):
|
||||
return data
|
||||
|
||||
|
||||
class ModelJSONEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Automatically serialize Mopidy models to JSON.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> import json
|
||||
>>> json.dumps({'a_track': Track(name='name')}, cls=ModelJSONEncoder)
|
||||
'{"a_track": {"__model__": "Track", "name": "name"}}'
|
||||
|
||||
"""
|
||||
def default(self, obj):
|
||||
if isinstance(obj, ImmutableObject):
|
||||
return obj.serialize()
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
def model_json_decoder(dct):
|
||||
"""
|
||||
Automatically deserialize Mopidy models from JSON.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> import json
|
||||
>>> json.loads(
|
||||
... '{"a_track": {"__model__": "Track", "name": "name"}}',
|
||||
... object_hook=model_json_decoder)
|
||||
{u'a_track': Track(artists=[], name=u'name')}
|
||||
|
||||
"""
|
||||
if '__model__' in dct:
|
||||
model_name = dct.pop('__model__')
|
||||
cls = globals().get(model_name, None)
|
||||
if issubclass(cls, ImmutableObject):
|
||||
return cls(**dct)
|
||||
return dct
|
||||
|
||||
|
||||
class Artist(ImmutableObject):
|
||||
"""
|
||||
:param uri: artist URI
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from mopidy.models import Artist, Album, TlTrack, Track, Playlist
|
||||
from mopidy.models import (
|
||||
Artist, Album, TlTrack, Track, Playlist,
|
||||
ModelJSONEncoder, model_json_decoder)
|
||||
|
||||
from tests import unittest
|
||||
|
||||
@ -76,6 +79,13 @@ class ArtistTest(unittest.TestCase):
|
||||
test = lambda: Artist(foo='baz')
|
||||
self.assertRaises(TypeError, test)
|
||||
|
||||
def test_invalid_kwarg_with_name_matching_method(self):
|
||||
test = lambda: Artist(copy='baz')
|
||||
self.assertRaises(TypeError, test)
|
||||
|
||||
test = lambda: Artist(serialize='baz')
|
||||
self.assertRaises(TypeError, test)
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEquals(
|
||||
"Artist(name=u'name', uri=u'uri')",
|
||||
@ -83,9 +93,36 @@ class ArtistTest(unittest.TestCase):
|
||||
|
||||
def test_serialize(self):
|
||||
self.assertDictEqual(
|
||||
{'uri': 'uri', 'name': 'name'},
|
||||
{'__model__': 'Artist', 'uri': 'uri', 'name': 'name'},
|
||||
Artist(uri='uri', name='name').serialize())
|
||||
|
||||
def test_to_json_and_back(self):
|
||||
artist1 = Artist(uri='uri', name='name')
|
||||
serialized = json.dumps(artist1, cls=ModelJSONEncoder)
|
||||
artist2 = json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertEqual(artist1, artist2)
|
||||
|
||||
def test_to_json_and_back_with_unknown_field(self):
|
||||
artist = Artist(uri='uri', name='name').serialize()
|
||||
artist['foo'] = 'foo'
|
||||
serialized = json.dumps(artist)
|
||||
test = lambda: json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertRaises(TypeError, test)
|
||||
|
||||
def test_to_json_and_back_with_field_matching_method(self):
|
||||
artist = Artist(uri='uri', name='name').serialize()
|
||||
artist['copy'] = 'foo'
|
||||
serialized = json.dumps(artist)
|
||||
test = lambda: json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertRaises(TypeError, test)
|
||||
|
||||
def test_to_json_and_back_with_field_matching_internal_field(self):
|
||||
artist = Artist(uri='uri', name='name').serialize()
|
||||
artist['__mro__'] = 'foo'
|
||||
serialized = json.dumps(artist)
|
||||
test = lambda: json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertRaises(TypeError, test)
|
||||
|
||||
def test_eq_name(self):
|
||||
artist1 = Artist(name='name')
|
||||
artist2 = Artist(name='name')
|
||||
@ -195,15 +232,22 @@ class AlbumTest(unittest.TestCase):
|
||||
|
||||
def test_serialize_without_artists(self):
|
||||
self.assertDictEqual(
|
||||
{'uri': 'uri', 'name': 'name'},
|
||||
{'__model__': 'Album', 'uri': 'uri', 'name': 'name'},
|
||||
Album(uri='uri', name='name').serialize())
|
||||
|
||||
def test_serialize_with_artists(self):
|
||||
artist = Artist(name='foo')
|
||||
self.assertDictEqual(
|
||||
{'uri': 'uri', 'name': 'name', 'artists': [artist.serialize()]},
|
||||
{'__model__': 'Album', 'uri': 'uri', 'name': 'name',
|
||||
'artists': [artist.serialize()]},
|
||||
Album(uri='uri', name='name', artists=[artist]).serialize())
|
||||
|
||||
def test_to_json_and_back(self):
|
||||
album1 = Album(uri='uri', name='name', artists=[Artist(name='foo')])
|
||||
serialized = json.dumps(album1, cls=ModelJSONEncoder)
|
||||
album2 = json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertEqual(album1, album2)
|
||||
|
||||
def test_eq_name(self):
|
||||
album1 = Album(name='name')
|
||||
album2 = Album(name='name')
|
||||
@ -386,21 +430,31 @@ class TrackTest(unittest.TestCase):
|
||||
|
||||
def test_serialize_without_artists(self):
|
||||
self.assertDictEqual(
|
||||
{'uri': 'uri', 'name': 'name'},
|
||||
{'__model__': 'Track', 'uri': 'uri', 'name': 'name'},
|
||||
Track(uri='uri', name='name').serialize())
|
||||
|
||||
def test_serialize_with_artists(self):
|
||||
artist = Artist(name='foo')
|
||||
self.assertDictEqual(
|
||||
{'uri': 'uri', 'name': 'name', 'artists': [artist.serialize()]},
|
||||
{'__model__': 'Track', 'uri': 'uri', 'name': 'name',
|
||||
'artists': [artist.serialize()]},
|
||||
Track(uri='uri', name='name', artists=[artist]).serialize())
|
||||
|
||||
def test_serialize_with_album(self):
|
||||
album = Album(name='foo')
|
||||
self.assertDictEqual(
|
||||
{'uri': 'uri', 'name': 'name', 'album': album.serialize()},
|
||||
{'__model__': 'Track', 'uri': 'uri', 'name': 'name',
|
||||
'album': album.serialize()},
|
||||
Track(uri='uri', name='name', album=album).serialize())
|
||||
|
||||
def test_to_json_and_back(self):
|
||||
track1 = Track(
|
||||
uri='uri', name='name', album=Album(name='foo'),
|
||||
artists=[Artist(name='foo')])
|
||||
serialized = json.dumps(track1, cls=ModelJSONEncoder)
|
||||
track2 = json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertEqual(track1, track2)
|
||||
|
||||
def test_eq_uri(self):
|
||||
track1 = Track(uri='uri1')
|
||||
track2 = Track(uri='uri1')
|
||||
@ -590,9 +644,16 @@ class TlTrackTest(unittest.TestCase):
|
||||
repr(TlTrack(tlid=123, track=Track(uri='uri'))))
|
||||
|
||||
def test_serialize(self):
|
||||
track = Track(uri='uri', name='name')
|
||||
self.assertDictEqual(
|
||||
{'tlid': 123, 'track': {'uri': 'uri', 'name': 'name'}},
|
||||
TlTrack(tlid=123, track=Track(uri='uri', name='name')).serialize())
|
||||
{'__model__': 'TlTrack', 'tlid': 123, 'track': track.serialize()},
|
||||
TlTrack(tlid=123, track=track).serialize())
|
||||
|
||||
def test_to_json_and_back(self):
|
||||
tl_track1 = TlTrack(tlid=123, track=Track(uri='uri', name='name'))
|
||||
serialized = json.dumps(tl_track1, cls=ModelJSONEncoder)
|
||||
tl_track2 = json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertEqual(tl_track1, tl_track2)
|
||||
|
||||
def test_eq(self):
|
||||
tlid = 123
|
||||
@ -719,15 +780,22 @@ class PlaylistTest(unittest.TestCase):
|
||||
|
||||
def test_serialize_without_tracks(self):
|
||||
self.assertDictEqual(
|
||||
{'uri': 'uri', 'name': 'name'},
|
||||
{'__model__': 'Playlist', 'uri': 'uri', 'name': 'name'},
|
||||
Playlist(uri='uri', name='name').serialize())
|
||||
|
||||
def test_serialize_with_tracks(self):
|
||||
track = Track(name='foo')
|
||||
self.assertDictEqual(
|
||||
{'uri': 'uri', 'name': 'name', 'tracks': [track.serialize()]},
|
||||
{'__model__': 'Playlist', 'uri': 'uri', 'name': 'name',
|
||||
'tracks': [track.serialize()]},
|
||||
Playlist(uri='uri', name='name', tracks=[track]).serialize())
|
||||
|
||||
def test_to_json_and_back(self):
|
||||
playlist1 = Playlist(uri='uri', name='name')
|
||||
serialized = json.dumps(playlist1, cls=ModelJSONEncoder)
|
||||
playlist2 = json.loads(serialized, object_hook=model_json_decoder)
|
||||
self.assertEqual(playlist1, playlist2)
|
||||
|
||||
def test_eq_name(self):
|
||||
playlist1 = Playlist(name='name')
|
||||
playlist2 = Playlist(name='name')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user