Merge branch 'consistency_fixes' into development

This commit is contained in:
sneakypete81 2013-07-02 20:51:29 +01:00
commit 0de30e196d
7 changed files with 76 additions and 98 deletions

View file

@ -4,6 +4,19 @@ from openphoto.errors import OpenPhotoError
import openphoto.openphoto_http
from openphoto.objects import Photo
def extract_ids(photos):
"""
Given a list of objects, extract the photo id for each Photo
object.
"""
ids = []
for photo in photos:
if isinstance(photo, Photo):
ids.append(photo.id)
else:
ids.append(photo)
return ids
class ApiPhotos:
def __init__(self, client):
self._client = client
@ -20,7 +33,8 @@ class ApiPhotos:
Returns True if successful.
Raises OpenPhotoError if not.
"""
if not self._client.post("/photos/update.json", ids=photos,
ids = extract_ids(photos)
if not self._client.post("/photos/update.json", ids=ids,
**kwds)["result"]:
raise OpenPhotoError("Update response returned False")
return True
@ -31,7 +45,8 @@ class ApiPhotos:
Returns True if successful.
Raises OpenPhotoError if not.
"""
if not self._client.post("/photos/delete.json", ids=photos,
ids = extract_ids(photos)
if not self._client.post("/photos/delete.json", ids=ids,
**kwds)["result"]:
raise OpenPhotoError("Delete response returned False")
return True

View file

@ -3,6 +3,8 @@ try:
except ImportError:
from urllib import quote # Python2
from openphoto.errors import OpenPhotoError
class OpenPhotoObject:
""" Base object supporting the storage of custom fields as attributes """
def __init__(self, openphoto, json_dict):
@ -29,6 +31,16 @@ class OpenPhotoObject:
self._json_dict = json_dict
self._set_fields(json_dict)
def _delete_fields(self):
"""
Delete this object's attributes, including name and id
"""
for key in self._json_dict.keys():
delattr(self, key)
self._json_dict = {}
self.id = None
self.name = None
def __repr__(self):
if self.name is not None:
return "<%s name='%s'>" % (self.__class__, self.name)
@ -51,7 +63,9 @@ class Photo(OpenPhotoObject):
"""
result = self._openphoto.post("/photo/%s/delete.json" %
self.id, **kwds)["result"]
self._replace_fields({})
if not result:
raise OpenPhotoError("Delete response returned False")
self._delete_fields()
return result
def edit(self, **kwds):
@ -63,7 +77,7 @@ class Photo(OpenPhotoObject):
def replace(self, photo_file, **kwds):
raise NotImplementedError()
def replace_encoded(self, encoded_photo, **kwds):
def replace_encoded(self, photo_file, **kwds):
raise NotImplementedError()
def update(self, **kwds):
@ -136,7 +150,9 @@ class Tag(OpenPhotoObject):
"""
result = self._openphoto.post("/tag/%s/delete.json" %
quote(self.id), **kwds)["result"]
self._replace_fields({})
if not result:
raise OpenPhotoError("Delete response returned False")
self._delete_fields()
return result
def update(self, **kwds):
@ -148,9 +164,9 @@ class Tag(OpenPhotoObject):
class Album(OpenPhotoObject):
def __init__(self, openphoto, json_dict):
OpenPhotoObject.__init__(self, openphoto, json_dict)
self.photos = None
self.cover = None
OpenPhotoObject.__init__(self, openphoto, json_dict)
self._update_fields_with_objects()
def _update_fields_with_objects(self):
@ -172,16 +188,18 @@ class Album(OpenPhotoObject):
"""
result = self._openphoto.post("/album/%s/delete.json" %
self.id, **kwds)["result"]
self._replace_fields({})
if not result:
raise OpenPhotoError("Delete response returned False")
self._delete_fields()
return result
def form(self, **kwds):
raise NotImplementedError()
def add_photos(self, **kwds):
def add_photos(self, photos, **kwds):
raise NotImplementedError()
def remove_photos(self, **kwds):
def remove_photos(self, photos, **kwds):
raise NotImplementedError()
def update(self, **kwds):

View file

@ -183,6 +183,9 @@ class OpenPhotoHttp:
Decodes the JSON response, returning a dict.
Raises an exception if an invalid response code is received.
"""
if response.status_code == 404:
raise OpenPhoto404Error("HTTP Error %d: %s" %
(response.status_code, response.reason))
try:
json_response = response.json()
code = json_response["code"]
@ -192,9 +195,6 @@ class OpenPhotoHttp:
if 200 <= response.status_code < 300:
# Status code was valid, so just reraise the exception
raise
elif response.status_code == 404:
raise OpenPhoto404Error("HTTP Error %d: %s" %
(response.status_code, response.reason))
else:
raise OpenPhotoError("HTTP Error %d: %s" %
(response.status_code, response.reason))

View file

@ -39,8 +39,6 @@ class TestAlbumsList(TestAlbums):
self.assertEqual(result[1].id, "2")
self.assertEqual(result[1].name, "Album 2")
# TODO: cover should be updated to Photo object
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_albums_list_returns_cover_photos(self, mock_get):
"""Check that the album list returns cover photo objects"""
@ -53,12 +51,11 @@ class TestAlbumsList(TestAlbums):
self.assertEqual(result[0].cover.id, "1a")
self.assertEqual(result[0].cover.tags, ["tag1", "tag2"])
self.assertEqual(result[1].id, "2")
self.assertEqual(result[0].name, "Album 2")
self.assertEqual(result[1].name, "Album 2")
self.assertEqual(result[1].cover.id, "2b")
self.assertEqual(result[1].cover.tags, ["tag3", "tag4"])
class TestAlbumCreate(TestAlbums):
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_create(self, mock_post):
"""Check that an album can be created"""
@ -68,8 +65,8 @@ class TestAlbumCreate(TestAlbums):
foo="bar")
self.assertEqual(result.id, "1")
self.assertEqual(result.name, "Album 1")
# self.assertEqual(result.cover.id, "1a")
# self.assertEqual(result.cover.tags, ["tag1", "tag2"])
self.assertEqual(result.cover.id, "1a")
self.assertEqual(result.cover.tags, ["tag1", "tag2"])
class TestAlbumDelete(TestAlbums):
@mock.patch.object(openphoto.OpenPhoto, 'post')
@ -88,8 +85,6 @@ class TestAlbumDelete(TestAlbums):
mock_post.assert_called_with("/album/1/delete.json")
self.assertEqual(result, True)
# TODO: album.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_delete_failure(self, mock_post):
"""Check that an exception is raised if an album cannot be deleted"""
@ -97,7 +92,6 @@ class TestAlbumDelete(TestAlbums):
with self.assertRaises(openphoto.OpenPhotoError):
self.client.album.delete(self.test_albums[0])
# TODO: after deleting object fields, name and id should be set to None
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_delete(self, mock_post):
"""Check that an album can be deleted using the album object directly"""
@ -107,11 +101,9 @@ class TestAlbumDelete(TestAlbums):
mock_post.assert_called_with("/album/1/delete.json")
self.assertEqual(result, True)
self.assertEqual(album.get_fields(), {})
# self.assertEqual(album.id, None)
# self.assertEqual(album.name, None)
self.assertEqual(album.id, None)
self.assertEqual(album.name, None)
# TODO: album.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_delete_failure(self, mock_post):
"""
@ -154,8 +146,6 @@ class TestAlbumAddPhotos(TestAlbums):
with self.assertRaises(NotImplementedError):
self.client.album.add_photos("1", ["Photo Objects"])
# TODO: object.add_photos should accept photos list as first parameter
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_add_photos(self, _):
""" If album.add_photos gets implemented, write a test! """
@ -176,8 +166,6 @@ class TestAlbumRemovePhotos(TestAlbums):
with self.assertRaises(NotImplementedError):
self.client.album.remove_photos("1", ["Photo Objects"])
# TODO: object.remove_photos should accept photos list as first parameter
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_remove_photos(self, _):
""" If album.remove_photos gets implemented, write a test! """
@ -185,7 +173,6 @@ class TestAlbumRemovePhotos(TestAlbums):
self.test_albums[0].remove_photos(["Photo Objects"])
class TestAlbumUpdate(TestAlbums):
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_update(self, mock_post):
"""Check that an album can be updated"""
@ -194,10 +181,9 @@ class TestAlbumUpdate(TestAlbums):
mock_post.assert_called_with("/album/1/update.json", name="Test")
self.assertEqual(result.id, "2")
self.assertEqual(result.name, "Album 2")
# self.assertEqual(result.cover.id, "2b")
# self.assertEqual(result.cover.tags, ["tag3", "tag4"])
self.assertEqual(result.cover.id, "2b")
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_update_id(self, mock_post):
"""Check that an album can be updated using its ID"""
@ -206,10 +192,9 @@ class TestAlbumUpdate(TestAlbums):
mock_post.assert_called_with("/album/1/update.json", name="Test")
self.assertEqual(result.id, "2")
self.assertEqual(result.name, "Album 2")
# self.assertEqual(result.cover.id, "2b")
# self.assertEqual(result.cover.tags, ["tag3", "tag4"])
self.assertEqual(result.cover.id, "2b")
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_update(self, mock_post):
"""Check that an album can be updated using the album object directly"""
@ -219,11 +204,10 @@ class TestAlbumUpdate(TestAlbums):
mock_post.assert_called_with("/album/1/update.json", name="Test")
self.assertEqual(album.id, "2")
self.assertEqual(album.name, "Album 2")
# self.assertEqual(album.cover.id, "2b")
# self.assertEqual(album.cover.tags, ["tag3", "tag4"])
self.assertEqual(album.cover.id, "2b")
self.assertEqual(album.cover.tags, ["tag3", "tag4"])
class TestAlbumView(TestAlbums):
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_album_view(self, mock_get):
"""Check that an album can be viewed"""
@ -232,10 +216,9 @@ class TestAlbumView(TestAlbums):
mock_get.assert_called_with("/album/1/view.json", name="Test")
self.assertEqual(result.id, "2")
self.assertEqual(result.name, "Album 2")
# self.assertEqual(result.cover.id, "2b")
# self.assertEqual(result.cover.tags, ["tag3", "tag4"])
self.assertEqual(result.cover.id, "2b")
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_album_view_id(self, mock_get):
"""Check that an album can be viewed using its ID"""
@ -244,10 +227,9 @@ class TestAlbumView(TestAlbums):
mock_get.assert_called_with("/album/1/view.json", name="Test")
self.assertEqual(result.id, "2")
self.assertEqual(result.name, "Album 2")
# self.assertEqual(result.cover.id, "2b")
# self.assertEqual(result.cover.tags, ["tag3", "tag4"])
self.assertEqual(result.cover.id, "2b")
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_album_object_view(self, mock_get):
"""Check that an album can be viewed using the album object directly"""
@ -257,6 +239,6 @@ class TestAlbumView(TestAlbums):
mock_get.assert_called_with("/album/1/view.json", name="Test")
self.assertEqual(album.id, "2")
self.assertEqual(album.name, "Album 2")
# self.assertEqual(album.cover.id, "2b")
# self.assertEqual(album.cover.tags, ["tag3", "tag4"])
self.assertEqual(album.cover.id, "2b")
self.assertEqual(album.cover.tags, ["tag3", "tag4"])

View file

@ -61,8 +61,6 @@ class TestHttpErrors(unittest.TestCase):
with self.assertRaises(openphoto.OpenPhotoError):
self.client.post(self.test_endpoint)
# TODO: 404 status should raise 404 error, even if JSON is valid
@unittest.expectedFailure
@httpretty.activate
def test_get_with_404_status(self):
"""
@ -73,8 +71,6 @@ class TestHttpErrors(unittest.TestCase):
with self.assertRaises(openphoto.OpenPhoto404Error):
self.client.get(self.test_endpoint)
# TODO: 404 status should raise 404 error, even if JSON is valid
@unittest.expectedFailure
@httpretty.activate
def test_post_with_404_status(self):
"""
@ -167,29 +163,25 @@ class TestHttpErrors(unittest.TestCase):
with self.assertRaises(openphoto.OpenPhotoDuplicateError):
self.client.post(self.test_endpoint)
# TODO: Status code mismatch should raise an exception
@unittest.expectedFailure
@httpretty.activate
def test_get_with_status_code_mismatch(self):
"""
Check that an exception is raised if a get returns a
status code that doesn't match the JSON code
Check that a mismatched HTTP status code still returns the
JSON status code for get requests.
"""
data = {"message": "Test Message", "code": 200}
self._register_uri(httpretty.GET, data=data, status=202)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.get(self.test_endpoint)
data = {"message": "Test Message", "code": 202}
self._register_uri(httpretty.GET, data=data, status=200)
response = self.client.get(self.test_endpoint)
self.assertEqual(response["code"], 202)
# TODO: Status code mismatch should raise an exception
@unittest.expectedFailure
@httpretty.activate
def test_post_with_status_code_mismatch(self):
"""
Check that an exception is raised if a post returns a
status code that doesn't match the JSON code
Check that a mismatched HTTP status code still returns the
JSON status code for post requests.
"""
data = {"message": "Test Message", "code": 200}
self._register_uri(httpretty.POST, data=data, status=202)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.post(self.test_endpoint)
data = {"message": "Test Message", "code": 202}
self._register_uri(httpretty.POST, data=data, status=200)
response = self.client.post(self.test_endpoint)
self.assertEqual(response["code"], 202)

View file

@ -40,8 +40,6 @@ class TestPhotosList(TestPhotos):
self.assertEqual(result[1].tags, ["tag3", "tag4"])
class TestPhotosUpdate(TestPhotos):
# TODO: photos.update should accept a list of Photo objects
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photos_update(self, mock_post):
"""Check that multiple photos can be updated"""
@ -71,8 +69,6 @@ class TestPhotosUpdate(TestPhotos):
self.client.photos.update(self.test_photos, title="Test")
class TestPhotosDelete(TestPhotos):
# TODO: photos.delete should accept a list of Photo objects
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photos_delete(self, mock_post):
"""Check that multiple photos can be deleted"""
@ -116,8 +112,6 @@ class TestPhotoDelete(TestPhotos):
mock_post.assert_called_with("/photo/1a/delete.json")
self.assertEqual(result, True)
# TODO: photo.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_delete_failure(self, mock_post):
"""Check that an exception is raised if a photo cannot be deleted"""
@ -125,7 +119,6 @@ class TestPhotoDelete(TestPhotos):
with self.assertRaises(openphoto.OpenPhotoError):
self.client.photo.delete(self.test_photos[0])
# TODO: after deleting object fields, name and id should be set to None
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_delete(self, mock_post):
"""
@ -138,10 +131,8 @@ class TestPhotoDelete(TestPhotos):
mock_post.assert_called_with("/photo/1a/delete.json")
self.assertEqual(result, True)
self.assertEqual(photo.get_fields(), {})
# self.assertEqual(photo.id, None)
self.assertEqual(photo.id, None)
# TODO: photo.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_delete_failure(self, mock_post):
"""
@ -212,9 +203,6 @@ class TestPhotoReplace(TestPhotos):
with self.assertRaises(NotImplementedError):
self.client.photo.replace_encoded("1a", self.test_file)
# TODO: replace_encoded parameter should be called photo_file,
# not encoded_photo
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_replace_encoded(self, _):
""" If photo.replace_encoded gets implemented, write a test! """

View file

@ -35,18 +35,6 @@ class TestTagsList(TestTags):
self.assertEqual(result[1].id, "tag2")
self.assertEqual(result[1].count, 5)
class TestTagCreate(TestTags):
# TODO: should return a tag object, not a result dict
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_create(self, mock_post):
"""Check that a tag can be created"""
mock_post.return_value = self._return_value(self.test_tags_dict[0])
result = self.client.tag.create(tag="Test", foo="bar")
mock_post.assert_called_with("/tag/create.json", tag="Test", foo="bar")
self.assertEqual(result.id, "tag1")
self.assertEqual(result.count, 11)
class TestTagDelete(TestTags):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_delete(self, mock_post):
@ -64,8 +52,6 @@ class TestTagDelete(TestTags):
mock_post.assert_called_with("/tag/tag1/delete.json")
self.assertEqual(result, True)
# TODO: tag.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_delete_failure(self, mock_post):
"""Check that an exception is raised if a tag cannot be deleted"""
@ -73,7 +59,6 @@ class TestTagDelete(TestTags):
with self.assertRaises(openphoto.OpenPhotoError):
self.client.tag.delete(self.test_tags[0])
# TODO: after deleting object fields, id should be set to None
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_object_delete(self, mock_post):
"""Check that a tag can be deleted when using the tag object directly"""
@ -83,10 +68,8 @@ class TestTagDelete(TestTags):
mock_post.assert_called_with("/tag/tag1/delete.json")
self.assertEqual(result, True)
self.assertEqual(tag.get_fields(), {})
# self.assertEqual(tag.id, None)
self.assertEqual(tag.id, None)
# TODO: tag.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_object_delete_failure(self, mock_post):
"""