Merge branch 'release-0.6'
This commit is contained in:
commit
f75f25001d
60 changed files with 2672 additions and 1170 deletions
4
.coveragerc
Normal file
4
.coveragerc
Normal file
|
@ -0,0 +1,4 @@
|
|||
# .coveragerc to control coverage.py
|
||||
[run]
|
||||
# Capture branch coverage
|
||||
branch = True
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,3 +6,5 @@ dist
|
|||
tests/tokens.py
|
||||
tests.log
|
||||
.tox
|
||||
.coverage
|
||||
htmlcov
|
||||
|
|
14
.travis.yml
14
.travis.yml
|
@ -1,15 +1,23 @@
|
|||
language: python
|
||||
|
||||
install:
|
||||
# Install test dependencies
|
||||
- pip install tox --use-mirrors
|
||||
- .travis/install_pylint
|
||||
- pip install coveralls --use-mirrors
|
||||
|
||||
script: tox
|
||||
|
||||
after_success:
|
||||
# Send coverage results to coveralls.io
|
||||
- coveralls
|
||||
|
||||
after_script:
|
||||
# Install dependencies for Pylint
|
||||
- pip install requests requests-oauthlib
|
||||
- pip install pylint-patcher --use-mirrors
|
||||
- pip install requests --use-mirrors
|
||||
- pip install requests-oauthlib --use-mirrors
|
||||
|
||||
# Run Pylint
|
||||
# Uses pylint-patcher to allow exceptions to be stored in a patchfile
|
||||
# (for information only, any errors don't affect the Travis result)
|
||||
- pylint --use-ignore-patch=y trovebox
|
||||
- pylint-patcher trovebox
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
# Until the --use-ignore-patch makes it into pylint upstream, we need to
|
||||
# download and install from sneakypete81's pylint fork
|
||||
|
||||
wget https://bitbucket.org/sneakypete81/pylint/get/use_ignore_patch.zip
|
||||
unzip use_ignore_patch.zip
|
||||
cd sneakypete81-pylint-*
|
||||
python setup.py install
|
||||
|
||||
cd ..
|
||||
rm -r sneakypete81-pylint-*
|
|
@ -2,6 +2,12 @@
|
|||
Trovebox Python Library Changelog
|
||||
=================================
|
||||
|
||||
v0.6
|
||||
======
|
||||
* Support for many additional API endpoints (#56, #65)
|
||||
* Code coverage reporting (#57)
|
||||
* Unit test improvements (#58, #63, #64)
|
||||
|
||||
v0.5.1
|
||||
======
|
||||
* Use httpretty v0.6.5 for unit tests (#60)
|
||||
|
|
|
@ -7,6 +7,13 @@ Trovebox Python Library
|
|||
:alt: Build Status
|
||||
:target: https://travis-ci.org/photo/openphoto-python
|
||||
|
||||
..
|
||||
(commented out until master is on coveralls.io)
|
||||
.. image:: https://coveralls.io/repos/photo/openphoto-python/badge.png?branch=master
|
||||
:alt: Coverage Status
|
||||
:target: https://coveralls.io/r/photo/openphoto-python?branch=master
|
||||
..
|
||||
|
||||
.. image:: https://pypip.in/v/trovebox/badge.png
|
||||
:alt: Python Package Index (PyPI)
|
||||
:target: https://pypi.python.org/pypi/trovebox
|
||||
|
|
16
pylintrc
Normal file
16
pylintrc
Normal file
|
@ -0,0 +1,16 @@
|
|||
[MESSAGES CONTROL]
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time. See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=locally-disabled
|
|
@ -9,26 +9,18 @@ tput setaf 3
|
|||
echo
|
||||
echo "Testing latest self-hosted site..."
|
||||
tput sgr0
|
||||
sleep 1
|
||||
export TROVEBOX_TEST_CONFIG=test
|
||||
unset TROVEBOX_TEST_SERVER_API
|
||||
python -m unittest discover --catch tests/functional
|
||||
|
||||
# Test server running APIv1 Trovebox instance
|
||||
# Install from photo/frontend commit 660b2ab
|
||||
tput setaf 3
|
||||
echo
|
||||
echo "Testing APIv1 self-hosted site..."
|
||||
tput sgr0
|
||||
export TROVEBOX_TEST_CONFIG=test-apiv1
|
||||
export TROVEBOX_TEST_SERVER_API=1
|
||||
python -m unittest discover --catch tests/functional
|
||||
|
||||
# Test server running v3.0.8 Trovebox instance
|
||||
# Install from photo/frontend commit e9d81de57b
|
||||
tput setaf 3
|
||||
echo
|
||||
echo "Testing v3.0.8 self-hosted site..."
|
||||
tput sgr0
|
||||
sleep 1
|
||||
export TROVEBOX_TEST_CONFIG=test-3.0.8
|
||||
unset TROVEBOX_TEST_SERVER_API
|
||||
python -m unittest discover --catch tests/functional
|
||||
|
@ -38,7 +30,18 @@ tput setaf 3
|
|||
echo
|
||||
echo "Testing latest hosted site..."
|
||||
tput sgr0
|
||||
sleep 1
|
||||
export TROVEBOX_TEST_CONFIG=test-hosted
|
||||
unset TROVEBOX_TEST_SERVER_API
|
||||
python -m unittest discover --catch tests/functional
|
||||
|
||||
# Test account on hosted trovebox.com site over HTTPS
|
||||
tput setaf 3
|
||||
echo
|
||||
echo "Testing latest hosted site over HTTPS..."
|
||||
tput sgr0
|
||||
sleep 1
|
||||
export TROVEBOX_TEST_CONFIG=test-hosted-https
|
||||
unset TROVEBOX_TEST_SERVER_API
|
||||
python -m unittest discover --catch tests/functional
|
||||
|
||||
|
|
3
setup.py
3
setup.py
|
@ -32,8 +32,7 @@ setup(name='trovebox',
|
|||
long_description=open("README.rst").read(),
|
||||
author='Pete Burgers, James Walker',
|
||||
url='https://github.com/photo/openphoto-python',
|
||||
packages=['trovebox'],
|
||||
data_files=['README.rst'],
|
||||
packages=['trovebox', 'trovebox.objects', 'trovebox.api'],
|
||||
keywords=['openphoto', 'pyopenphoto', 'openphoto-python',
|
||||
'trovebox', 'pytrovebox', 'trovebox-python'],
|
||||
classifiers=['Development Status :: 4 - Beta',
|
||||
|
|
|
@ -100,6 +100,6 @@ To use it, you must set up multiple Trovebox instances and create the following
|
|||
config files containing your credentials:
|
||||
|
||||
test : Latest self-hosted site (from photo/frontend master branch)
|
||||
test-apiv1 : APIv1 self-hosted site (from photo/frontend commit 660b2ab)
|
||||
test-3.0.8 : v3.0.8 self-hosted site (from photo/frontend commit e9d81de57b)
|
||||
test-hosted : Credentials for test account on trovebox.com
|
||||
test-hosted : Credentials for test account on http://<xxxx>.trovebox.com
|
||||
test-hosted-https : Same as test-hosted, but with https://
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# __init__.py
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# __init__.py
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
from tests.functional import test_activities, test_actions
|
||||
from tests.functional import test_albums, test_photos, test_tags
|
||||
|
||||
class TestActivitiesV1(test_activities.TestActivities):
|
||||
api_version = 1
|
||||
|
||||
class TestActionsV1(test_actions.TestActions):
|
||||
api_version = 1
|
||||
|
||||
class TestAlbumsV1(test_albums.TestAlbums):
|
||||
api_version = 1
|
||||
|
||||
|
|
|
@ -2,7 +2,19 @@ try:
|
|||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
from tests.functional import test_base, test_albums, test_photos, test_tags
|
||||
|
||||
from tests.functional import test_base, test_activities, test_actions
|
||||
from tests.functional import test_albums, test_photos, test_tags
|
||||
|
||||
@unittest.skipIf(test_base.get_test_server_api() < 2,
|
||||
"Don't test future API versions")
|
||||
class TestActivitiesV2(test_activities.TestActivities):
|
||||
api_version = 2
|
||||
|
||||
@unittest.skipIf(test_base.get_test_server_api() < 2,
|
||||
"Don't test future API versions")
|
||||
class TestActionsV2(test_actions.TestActions):
|
||||
api_version = 2
|
||||
|
||||
@unittest.skipIf(test_base.get_test_server_api() < 2,
|
||||
"Don't test future API versions")
|
||||
|
|
23
tests/functional/test_actions.py
Normal file
23
tests/functional/test_actions.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
import trovebox
|
||||
from tests.functional import test_base
|
||||
|
||||
class TestActions(test_base.TestBase):
|
||||
testcase_name = "action API"
|
||||
|
||||
def test_create_view_delete(self):
|
||||
""" Create an action on a photo, view it, then delete it """
|
||||
# Create and check that the action exists
|
||||
action = self.client.action.create(target=self.photos[0], type="comment", name="test")
|
||||
action_id = action.id
|
||||
self.assertEqual(self.client.action.view(action_id).name, "test")
|
||||
|
||||
# Delete and check that the action is gone
|
||||
action.delete()
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.action.view(action_id)
|
||||
|
84
tests/functional/test_activities.py
Normal file
84
tests/functional/test_activities.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
from tests.functional import test_base
|
||||
|
||||
@unittest.skipIf(test_base.get_test_server_api() == 1,
|
||||
("Activities never get deleted in v1, which makes "
|
||||
"these tests too hard to write"))
|
||||
class TestActivities(test_base.TestBase):
|
||||
testcase_name = "activity API"
|
||||
|
||||
def test_list(self):
|
||||
"""
|
||||
Upload three photos, and check that three corresponding activities
|
||||
are created.
|
||||
"""
|
||||
self._delete_all()
|
||||
self._create_test_photos(tag=False)
|
||||
photos = self.client.photos.list()
|
||||
|
||||
# Check that each activity is for a valid test photo
|
||||
activities = self.client.activities.list()
|
||||
self.assertEqual(len(activities), len(photos))
|
||||
for activity in activities:
|
||||
self.assertIn(activity.data.id, [photo.id for photo in photos])
|
||||
|
||||
# Put the environment back the way we found it
|
||||
for photo in photos:
|
||||
photo.update(tags=self.TEST_TAG)
|
||||
|
||||
def test_list_option(self):
|
||||
"""
|
||||
Check that the activity list options parameter works correctly
|
||||
"""
|
||||
self._delete_all()
|
||||
self._create_test_photos(tag=False)
|
||||
photos = self.client.photos.list()
|
||||
|
||||
# Dummy photo update activity
|
||||
photos[0].update(tags=photos[0].tags)
|
||||
|
||||
# Check that the activities can be filtered
|
||||
upload_activities = self.client.activities.list(options={"type": "photo-upload"})
|
||||
update_activities = self.client.activities.list(options={"type": "photo-update"})
|
||||
self.assertEqual(len(upload_activities), len(photos))
|
||||
self.assertEqual(len(update_activities), 1)
|
||||
|
||||
# Put the environment back the way we found it
|
||||
for photo in photos:
|
||||
photo.update(tags=self.TEST_TAG)
|
||||
|
||||
# The purge endpoint currently reports a 500: Internal Server Error
|
||||
# PHP Fatal error:
|
||||
# Call to undefined method DatabaseMySql::postActivitiesPurge()
|
||||
# in /var/www/openphoto-master/src/libraries/models/Activity.php
|
||||
# on line 66
|
||||
# Tracked in frontend/#1368
|
||||
@unittest.expectedFailure
|
||||
def test_purge(self):
|
||||
""" Test that the purge endpoint deletes all activities """
|
||||
activities = self.client.activities.list()
|
||||
self.assertNotEqual(activities, [])
|
||||
self.client.activities.purge()
|
||||
activities = self.client.activities.list()
|
||||
self.assertEqual(activities, [])
|
||||
|
||||
def test_view(self):
|
||||
""" Test that the view endpoint is working correctly """
|
||||
activity = self.client.activities.list()[0]
|
||||
fields = activity.get_fields().copy()
|
||||
|
||||
# Check that the view method returns the same data as the list
|
||||
activity.view()
|
||||
self.assertEqual(fields, activity.get_fields())
|
||||
|
||||
# Check using the Trovebox class
|
||||
activity = self.client.activity.view(activity)
|
||||
self.assertEqual(fields, activity.get_fields())
|
||||
|
||||
# Check passing the activity ID to the Trovebox class
|
||||
activity = self.client.activity.view(activity.id)
|
||||
self.assertEqual(fields, activity.get_fields())
|
|
@ -1,4 +1,10 @@
|
|||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
from tests.functional import test_base
|
||||
from trovebox.objects.album import Album
|
||||
|
||||
class TestAlbums(test_base.TestBase):
|
||||
testcase_name = "album API"
|
||||
|
@ -53,27 +59,50 @@ class TestAlbums(test_base.TestBase):
|
|||
self.albums = self.client.albums.list()
|
||||
self.assertEqual(self.albums[0].name, self.TEST_ALBUM)
|
||||
|
||||
@unittest.skipIf(test_base.get_test_server_api() == 1,
|
||||
"update_cover was introduced in APIv2")
|
||||
def test_update_cover(self):
|
||||
""" Test that an album cover can be updated """
|
||||
self.albums[0].cover_update(self.photos[0])
|
||||
self.assertNotEqual(self.albums[0].cover.id, self.photos[1].id)
|
||||
self.albums[0].cover_update(self.photos[1])
|
||||
self.assertEqual(self.albums[0].cover.id, self.photos[1].id)
|
||||
|
||||
@unittest.skipIf(test_base.get_test_server_api() == 1,
|
||||
"includeElements was introduced in APIv2")
|
||||
def test_view(self):
|
||||
""" Test the album view """
|
||||
album = self.albums[0]
|
||||
# Do a view() with includeElements=False, using a fresh Album object
|
||||
album = Album(self.client, {"id": self.albums[0].id})
|
||||
album.view()
|
||||
# Make sure there are no photos reported
|
||||
self.assertEqual(album.photos, None)
|
||||
|
||||
# Get the photos in the album using the Album object directly
|
||||
# Get the photos with includeElements=True
|
||||
album.view(includeElements=True)
|
||||
# Make sure all photos are in the album
|
||||
for photo in self.photos:
|
||||
self.assertIn(photo.id, [p.id for p in album.photos])
|
||||
|
||||
def test_form(self):
|
||||
""" If album.form gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.form(None)
|
||||
def test_add_remove(self):
|
||||
""" Test that photos can be added and removed from an album """
|
||||
# Make sure all photos are in the album
|
||||
album = self.albums[0]
|
||||
album.view(includeElements=True)
|
||||
for photo in self.photos:
|
||||
self.assertIn(photo.id, [p.id for p in album.photos])
|
||||
|
||||
def test_add_photos(self):
|
||||
""" If album.add_photos gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.add_photos(None, None)
|
||||
# Remove two photos and check that they're gone
|
||||
album.remove(self.photos[:2])
|
||||
album.view(includeElements=True)
|
||||
self.assertEqual([p.id for p in album.photos], [self.photos[2].id])
|
||||
|
||||
def test_remove_photos(self):
|
||||
""" If album.remove_photos gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.remove_photos(None, None)
|
||||
# Add a photo and check that it's there
|
||||
album.add(self.photos[1])
|
||||
album.view(includeElements=True)
|
||||
self.assertNotIn(self.photos[0].id, [p.id for p in album.photos])
|
||||
self.assertIn(self.photos[1].id, [p.id for p in album.photos])
|
||||
self.assertIn(self.photos[2].id, [p.id for p in album.photos])
|
||||
|
||||
# Put the environment back the way we found it
|
||||
album.add(self.photos[0])
|
||||
|
|
|
@ -42,8 +42,8 @@ class TestBase(unittest.TestCase):
|
|||
else:
|
||||
print("\nTesting %s v%d" % (cls.testcase_name, cls.api_version))
|
||||
|
||||
cls.client = trovebox.Trovebox(config_file=cls.config_file,
|
||||
api_version=cls.api_version)
|
||||
cls.client = trovebox.Trovebox(config_file=cls.config_file)
|
||||
cls.client.configure(api_version=cls.api_version)
|
||||
|
||||
if cls.client.photos.list() != []:
|
||||
raise ValueError("The test server (%s) contains photos. "
|
||||
|
@ -124,7 +124,7 @@ class TestBase(unittest.TestCase):
|
|||
logging.info("Finished %s\n", self.id())
|
||||
|
||||
@classmethod
|
||||
def _create_test_photos(cls):
|
||||
def _create_test_photos(cls, tag=True):
|
||||
""" Upload three test photos """
|
||||
album = cls.client.album.create(cls.TEST_ALBUM)
|
||||
photos = [
|
||||
|
@ -139,6 +139,7 @@ class TestBase(unittest.TestCase):
|
|||
albums=album.id),
|
||||
]
|
||||
# Add the test tag, removing any autogenerated tags
|
||||
if tag:
|
||||
for photo in photos:
|
||||
photo.update(tags=cls.TEST_TAG)
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@ class TestFramework(test_base.TestBase):
|
|||
"""
|
||||
API v0 has a special hello world message
|
||||
"""
|
||||
client = trovebox.Trovebox(config_file=self.config_file,
|
||||
api_version=0)
|
||||
client = trovebox.Trovebox(config_file=self.config_file)
|
||||
client.configure(api_version=0)
|
||||
result = client.get("hello.json")
|
||||
self.assertEqual(result['message'],
|
||||
"Hello, world! This is version zero of the API!")
|
||||
|
@ -28,8 +28,8 @@ class TestFramework(test_base.TestBase):
|
|||
For all API versions >0, we get a generic hello world message
|
||||
"""
|
||||
for api_version in range(1, test_base.get_test_server_api() + 1):
|
||||
client = trovebox.Trovebox(config_file=self.config_file,
|
||||
api_version=api_version)
|
||||
client = trovebox.Trovebox(config_file=self.config_file)
|
||||
client.configure(api_version=api_version)
|
||||
result = client.get("hello.json")
|
||||
self.assertEqual(result['message'], "Hello, world!")
|
||||
self.assertEqual(result['result']['__route__'],
|
||||
|
@ -40,8 +40,7 @@ class TestFramework(test_base.TestBase):
|
|||
If the API version is unspecified,
|
||||
we get a generic hello world message.
|
||||
"""
|
||||
client = trovebox.Trovebox(config_file=self.config_file,
|
||||
api_version=None)
|
||||
client = trovebox.Trovebox(config_file=self.config_file)
|
||||
result = client.get("hello.json")
|
||||
self.assertEqual(result['message'], "Hello, world!")
|
||||
self.assertEqual(result['result']['__route__'], "/hello.json")
|
||||
|
@ -52,7 +51,7 @@ class TestFramework(test_base.TestBase):
|
|||
(ValueError, since the returned 404 HTML page is not valid JSON)
|
||||
"""
|
||||
version = trovebox.LATEST_API_VERSION + 1
|
||||
client = trovebox.Trovebox(config_file=self.config_file,
|
||||
api_version=version)
|
||||
client = trovebox.Trovebox(config_file=self.config_file)
|
||||
client.configure(api_version=version)
|
||||
with self.assertRaises(trovebox.Trovebox404Error):
|
||||
client.get("hello.json")
|
||||
|
|
|
@ -1,11 +1,39 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
import trovebox
|
||||
from tests.functional import test_base
|
||||
|
||||
class TestPhotos(test_base.TestBase):
|
||||
testcase_name = "photo API"
|
||||
|
||||
def test_list_option(self):
|
||||
"""
|
||||
Check that the photo list options parameter works correctly
|
||||
"""
|
||||
option_tag = "Filter"
|
||||
# Assign a photo with a new tag
|
||||
self.photos[0].update(tagsAdd=option_tag)
|
||||
|
||||
# Check that the photos can be filtered
|
||||
photos = self.client.photos.list(options={"tags": option_tag})
|
||||
self.assertEqual(len(photos), 1)
|
||||
self.assertEqual(photos[0].id, self.photos[0].id)
|
||||
|
||||
# Put the environment back the way we found it
|
||||
photos[0].update(tagsRemove=option_tag)
|
||||
|
||||
# Photo share endpoint is currently not implemented
|
||||
@unittest.expectedFailure
|
||||
def test_share(self):
|
||||
""" Test photo sharing (currently not implemented) """
|
||||
self.client.photos.share()
|
||||
|
||||
def test_delete_upload(self):
|
||||
""" Test photo deletion and upload """
|
||||
# Delete one photo using the Trovebox class, passing in the id
|
||||
|
@ -18,7 +46,7 @@ class TestPhotos(test_base.TestBase):
|
|||
# Check that they're gone
|
||||
self.assertEqual(self.client.photos.list(), [])
|
||||
|
||||
# Re-upload the photos, one of them using Bas64 encoding
|
||||
# Re-upload the photos, one of them using Base64 encoding
|
||||
ret_val = self.client.photo.upload("tests/data/test_photo1.jpg",
|
||||
title=self.TEST_TITLE)
|
||||
self.client.photo.upload("tests/data/test_photo2.jpg",
|
||||
|
@ -47,15 +75,22 @@ class TestPhotos(test_base.TestBase):
|
|||
self._delete_all()
|
||||
self._create_test_photos()
|
||||
|
||||
def test_edit(self):
|
||||
""" Check that the edit request returns an HTML form """
|
||||
# Test using the Trovebox class
|
||||
html = self.client.photo.edit(self.photos[0])
|
||||
self.assertIn("<form", html.lower())
|
||||
def test_delete_source(self):
|
||||
""" Test that photo source files can be deleted """
|
||||
# Upload a new (duplicate) public photo
|
||||
photo = self.client.photo.upload("tests/data/test_photo1.jpg",
|
||||
allowDuplicate=True,
|
||||
permission=True)
|
||||
# Check that the photo can be downloaded
|
||||
self.assertEqual(requests.get(photo.pathOriginal).status_code, 200)
|
||||
|
||||
# And the Photo object directly
|
||||
html = self.photos[0].edit()
|
||||
self.assertIn("<form", html.lower())
|
||||
# Delete the source and check that the source file no longer exists
|
||||
photo.delete_source()
|
||||
self.assertIn(requests.get(photo.pathOriginal).status_code,
|
||||
[403, 404])
|
||||
|
||||
# Put the environment back the way we found it
|
||||
photo.delete()
|
||||
|
||||
def test_upload_duplicate(self):
|
||||
""" Ensure that duplicate photos are rejected """
|
||||
|
@ -68,6 +103,23 @@ class TestPhotos(test_base.TestBase):
|
|||
self.photos = self.client.photos.list()
|
||||
self.assertEqual(len(self.photos), 3)
|
||||
|
||||
def test_upload_from_url(self):
|
||||
""" Ensure that a photo can be imported from a URL """
|
||||
# Make an existing photo public
|
||||
self.photos[0].update(permission=True)
|
||||
# Upload a duplicate of an existing photo
|
||||
self.client.photo.upload_from_url(self.photos[0].pathOriginal,
|
||||
allowDuplicate=True)
|
||||
# Check there are now four photos
|
||||
photos = self.client.photos.list()
|
||||
self.assertEqual(len(photos), 4)
|
||||
# Check that the new one is a duplicate
|
||||
self.assertEqual(photos[0].hash, photos[1].hash)
|
||||
|
||||
# Put the environment back the way we found it
|
||||
photos[1].delete()
|
||||
self.photos[0].update(permission=False)
|
||||
|
||||
def test_update(self):
|
||||
""" Update a photo by editing the title """
|
||||
title = "\xfcmlaut" # umlauted umlaut
|
||||
|
@ -127,29 +179,29 @@ class TestPhotos(test_base.TestBase):
|
|||
|
||||
def test_next_previous(self):
|
||||
""" Test the next/previous links of the middle photo """
|
||||
next_prev = self.client.photo.next_previous(self.photos[1])
|
||||
next_prev = self.client.photo.next_previous(self.photos[1],
|
||||
sortBy="dateTaken,asc")
|
||||
self.assertEqual(next_prev["previous"][0].id, self.photos[0].id)
|
||||
self.assertEqual(next_prev["next"][0].id, self.photos[2].id)
|
||||
|
||||
# Do the same using the Photo object directly
|
||||
next_prev = self.photos[1].next_previous()
|
||||
next_prev = self.photos[1].next_previous(sortBy="dateTaken,asc")
|
||||
self.assertEqual(next_prev["previous"][0].id, self.photos[0].id)
|
||||
self.assertEqual(next_prev["next"][0].id, self.photos[2].id)
|
||||
|
||||
def test_replace(self):
|
||||
""" If photo.replace gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.replace(None, None)
|
||||
|
||||
def test_replace_encoded(self):
|
||||
""" If photo.replace_encoded gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.replace_encoded(None, None)
|
||||
|
||||
def test_dynamic_url(self):
|
||||
""" If photo.dynamic_url gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.dynamic_url(None)
|
||||
""" Test that a photo can be replaced with another """
|
||||
# Replace the first photo with a copy of the second
|
||||
original_hash = self.photos[0].hash
|
||||
self.assertNotEqual(original_hash, self.photos[1].hash)
|
||||
self.photos[0].replace("tests/data/test_photo2.jpg",
|
||||
allowDuplicate=True)
|
||||
# Check that its new hash is correct
|
||||
self.assertEqual(self.photos[0].hash, self.photos[1].hash)
|
||||
# Put it back using base64 encoding
|
||||
self.photos[0].replace_encoded("tests/data/test_photo1.jpg",
|
||||
allowDuplicate=True)
|
||||
self.assertEqual(self.photos[0].hash, original_hash)
|
||||
|
||||
def test_transform(self):
|
||||
""" Test photo rotation """
|
||||
|
|
31
tests/functional/test_system.py
Normal file
31
tests/functional/test_system.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
import logging
|
||||
import unittest
|
||||
|
||||
import trovebox
|
||||
from tests.functional import test_base
|
||||
|
||||
class TestSystem(test_base.TestBase):
|
||||
testcase_name = "system"
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Override the default setUp, since we don't need a populated database
|
||||
"""
|
||||
logging.info("\nRunning %s...", self.id())
|
||||
|
||||
def test_system_version(self):
|
||||
"""
|
||||
Check that the API version string is returned correctly
|
||||
"""
|
||||
client = trovebox.Trovebox(config_file=self.config_file)
|
||||
version = client.system.version()
|
||||
self.assertEqual(version["api"], "v%s" % trovebox.LATEST_API_VERSION)
|
||||
|
||||
@unittest.skip("Diagnostics don't work with the hosted site")
|
||||
def test_system_diagnostics(self):
|
||||
"""
|
||||
Check that the system diagnostics can be performed
|
||||
"""
|
||||
client = trovebox.Trovebox(config_file=self.config_file)
|
||||
diagnostics = client.system.diagnostics()
|
||||
self.assertIn(diagnostics, "database")
|
|
@ -89,7 +89,7 @@ class TestTags(test_base.TestBase):
|
|||
|
||||
# TODO: Un-skip this test once issue #919 is resolved -
|
||||
# tags with double-slashes cannot be deleted
|
||||
@unittest.expectedFailure
|
||||
@unittest.skip("Tags with double-slashed cannot be deleted")
|
||||
def test_tag_with_double_slashes(self):
|
||||
""" Run test_create_delete using a tag containing double-slashes """
|
||||
self.test_create_delete("tag//with//double//slashes")
|
||||
|
|
151
tests/unit/test_actions.py
Normal file
151
tests/unit/test_actions.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from __future__ import unicode_literals
|
||||
import mock
|
||||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
import trovebox
|
||||
|
||||
class TestActions(unittest.TestCase):
|
||||
test_host = "test.example.com"
|
||||
test_photos_dict = [{"id": "photo1"},
|
||||
{"id": "photo2"}]
|
||||
test_actions_dict = [{"id": "1",
|
||||
"target": test_photos_dict[0],
|
||||
"target_type": "photo",
|
||||
"type": "comment",
|
||||
"totalRows": 2},
|
||||
{"id": "2",
|
||||
"target": test_photos_dict[1],
|
||||
"target_type": "photo",
|
||||
"type": "comment",
|
||||
"totalRows": 2}]
|
||||
|
||||
def setUp(self):
|
||||
self.client = trovebox.Trovebox(host=self.test_host)
|
||||
self.test_photos = [trovebox.objects.photo.Photo(self.client, photo)
|
||||
for photo in self.test_photos_dict]
|
||||
self.test_actions = [trovebox.objects.action.Action(self.client, action)
|
||||
for action in self.test_actions_dict]
|
||||
|
||||
@staticmethod
|
||||
def _return_value(result, message="", code=200):
|
||||
return {"message": message, "code": code, "result": result}
|
||||
|
||||
class TestActionCreate(TestActions):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_action_create(self, mock_post):
|
||||
"""Check that an action can be created on a photo object"""
|
||||
mock_post.return_value = self._return_value(self.test_actions_dict[0])
|
||||
result = self.client.action.create(target=self.test_photos[0], type="comment", foo="bar")
|
||||
mock_post.assert_called_with("/action/%s/photo/create.json" %
|
||||
self.test_photos[0].id,
|
||||
type="comment",
|
||||
foo="bar")
|
||||
self.assertEqual(result.id, "1")
|
||||
self.assertEqual(result.target.id, "photo1")
|
||||
self.assertEqual(result.target_type, "photo")
|
||||
self.assertEqual(result.type, "comment")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_action_create_id(self, mock_post):
|
||||
"""Check that an action can be created using a photo id"""
|
||||
mock_post.return_value = self._return_value(self.test_actions_dict[0])
|
||||
result = self.client.action.create(target=self.test_photos[0].id,
|
||||
target_type="photo", type="comment",
|
||||
foo="bar")
|
||||
mock_post.assert_called_with("/action/%s/photo/create.json" %
|
||||
self.test_photos[0].id,
|
||||
type="comment",
|
||||
foo="bar")
|
||||
self.assertEqual(result.id, "1")
|
||||
self.assertEqual(result.target.id, "photo1")
|
||||
self.assertEqual(result.target_type, "photo")
|
||||
self.assertEqual(result.type, "comment")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_action_create_invalid_type(self, mock_post):
|
||||
"""
|
||||
Check that an exception is raised if an action is created on an
|
||||
invalid object.
|
||||
"""
|
||||
with self.assertRaises(AttributeError):
|
||||
self.client.action.create(target=object())
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_action_create_invalid_return_type(self, mock_post):
|
||||
"""Check that an exception is raised if an invalid object is returned"""
|
||||
mock_post.return_value = self._return_value({"target": "test",
|
||||
"target_type": "invalid"})
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.action.create(target=self.test_photos[0])
|
||||
|
||||
class TestActionDelete(TestActions):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_action_delete(self, mock_post):
|
||||
"""Check that an action can be deleted"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.action.delete(self.test_actions[0], foo="bar")
|
||||
mock_post.assert_called_with("/action/1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_action_delete_id(self, mock_post):
|
||||
"""Check that an action can be deleted using its ID"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.action.delete("1", foo="bar")
|
||||
mock_post.assert_called_with("/action/1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_action_object_delete(self, mock_post):
|
||||
"""Check that an action can be deleted using the action object directly"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
action = self.test_actions[0]
|
||||
result = action.delete(foo="bar")
|
||||
mock_post.assert_called_with("/action/1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
self.assertEqual(action.get_fields(), {})
|
||||
self.assertEqual(action.id, None)
|
||||
|
||||
class TestActionView(TestActions):
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_action_view(self, mock_get):
|
||||
"""Check that an action can be viewed"""
|
||||
mock_get.return_value = self._return_value(self.test_actions_dict[1])
|
||||
result = self.client.action.view(self.test_actions[0], name="Test")
|
||||
mock_get.assert_called_with("/action/1/view.json", name="Test")
|
||||
self.assertEqual(result.id, "2")
|
||||
self.assertEqual(result.target.id, "photo2")
|
||||
self.assertEqual(result.target_type, "photo")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_action_view_id(self, mock_get):
|
||||
"""Check that an action can be viewed using its ID"""
|
||||
mock_get.return_value = self._return_value(self.test_actions_dict[1])
|
||||
result = self.client.action.view("1", name="Test")
|
||||
mock_get.assert_called_with("/action/1/view.json", name="Test")
|
||||
self.assertEqual(result.id, "2")
|
||||
self.assertEqual(result.target.id, "photo2")
|
||||
self.assertEqual(result.target_type, "photo")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_action_object_view(self, mock_get):
|
||||
"""Check that an action can be viewed using the action object directly"""
|
||||
mock_get.return_value = self._return_value(self.test_actions_dict[1])
|
||||
action = self.test_actions[0]
|
||||
action.view(name="Test")
|
||||
mock_get.assert_called_with("/action/1/view.json", name="Test")
|
||||
self.assertEqual(action.id, "2")
|
||||
self.assertEqual(action.target.id, "photo2")
|
||||
self.assertEqual(action.target_type, "photo")
|
||||
|
||||
class TestActionMisc(TestActions):
|
||||
def test_update_fields_with_no_target(self):
|
||||
"""Check that an action object can be updated with no target"""
|
||||
action = self.test_actions[0]
|
||||
action.target = None
|
||||
action.target_type = None
|
||||
# Check that no exception is raised
|
||||
action._update_fields_with_objects()
|
142
tests/unit/test_activities.py
Normal file
142
tests/unit/test_activities.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
from __future__ import unicode_literals
|
||||
import json
|
||||
import mock
|
||||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
import trovebox
|
||||
|
||||
class TestActivities(unittest.TestCase):
|
||||
test_host = "test.example.com"
|
||||
test_photos_dict = [{"id": "photo1"},
|
||||
{"id": "photo2"}]
|
||||
test_activities_dict = [{"id": "1",
|
||||
"data": test_photos_dict[0],
|
||||
"type": "photo_upload"},
|
||||
{"id": "2",
|
||||
"data": test_photos_dict[1],
|
||||
"type": "photo_update"}]
|
||||
|
||||
def setUp(self):
|
||||
self.client = trovebox.Trovebox(host=self.test_host)
|
||||
self.test_photos = [trovebox.objects.photo.Photo(self.client, photo)
|
||||
for photo in self.test_photos_dict]
|
||||
self.test_activities = [trovebox.objects.activity.Activity(self.client, activity)
|
||||
for activity in self.test_activities_dict]
|
||||
|
||||
@staticmethod
|
||||
def _return_value(result, message="", code=200):
|
||||
return {"message": message, "code": code, "result": result}
|
||||
|
||||
@staticmethod
|
||||
def _view_wrapper(result):
|
||||
""" The view method returns data enclosed in a dict and JSON encoded """
|
||||
result["data"] = json.dumps(result["data"])
|
||||
return {"0": result}
|
||||
|
||||
class TestActivitiesList(TestActivities):
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_activities_list(self, mock_get):
|
||||
"""Check that the activity list is returned correctly"""
|
||||
mock_get.return_value = self._return_value(self.test_activities_dict)
|
||||
|
||||
result = self.client.activities.list(foo="bar")
|
||||
mock_get.assert_called_with("/activities/list.json", foo="bar")
|
||||
self.assertEqual(len(result), 2)
|
||||
self.assertEqual(result[0].id, "1")
|
||||
self.assertEqual(result[0].type, "photo_upload")
|
||||
self.assertEqual(result[0].data.id, "photo1")
|
||||
self.assertEqual(result[1].id, "2")
|
||||
self.assertEqual(result[1].type, "photo_update")
|
||||
self.assertEqual(result[1].data.id, "photo2")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_empty_result(self, mock_get):
|
||||
"""Check that an empty result is transformed into an empty list """
|
||||
mock_get.return_value = self._return_value("")
|
||||
result = self.client.activities.list(foo="bar")
|
||||
mock_get.assert_called_with("/activities/list.json", foo="bar")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_zero_rows(self, mock_get):
|
||||
"""Check that totalRows=0 is transformed into an empty list """
|
||||
mock_get.return_value = self._return_value([{"totalRows": 0}])
|
||||
result = self.client.activities.list(foo="bar")
|
||||
mock_get.assert_called_with("/activities/list.json", foo="bar")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_options(self, mock_get):
|
||||
"""Check that the activity list optionss are applied properly"""
|
||||
mock_get.return_value = self._return_value(self.test_activities_dict)
|
||||
self.client.activities.list(options={"foo": "bar",
|
||||
"test1": "test2"},
|
||||
foo="bar")
|
||||
# Dict element can be any order
|
||||
self.assertIn(mock_get.call_args[0],
|
||||
[("/activities/foo-bar/test1-test2/list.json",),
|
||||
("/activities/test1-test2/foo-bar/list.json",)])
|
||||
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
|
||||
|
||||
class TestActivitiesPurge(TestActivities):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_activity_purge(self, mock_get):
|
||||
"""Test activity purging """
|
||||
mock_get.return_value = self._return_value(True)
|
||||
|
||||
result = self.client.activities.purge(foo="bar")
|
||||
mock_get.assert_called_with("/activities/purge.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
class TestActivityView(TestActivities):
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_activity_view(self, mock_get):
|
||||
"""Check that a activity can be viewed"""
|
||||
mock_get.return_value = self._return_value(self._view_wrapper(
|
||||
self.test_activities_dict[1]))
|
||||
result = self.client.activity.view(self.test_activities[0],
|
||||
foo="bar")
|
||||
mock_get.assert_called_with("/activity/1/view.json", foo="bar")
|
||||
self.assertEqual(result.get_fields(), self.test_activities_dict[1])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_activity_view_id(self, mock_get):
|
||||
"""Check that a activity can be viewed using its ID"""
|
||||
mock_get.return_value = self._return_value(self._view_wrapper(
|
||||
self.test_activities_dict[1]))
|
||||
result = self.client.activity.view("1", foo="bar")
|
||||
mock_get.assert_called_with("/activity/1/view.json", foo="bar")
|
||||
self.assertEqual(result.get_fields(), self.test_activities_dict[1])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_activity_object_view(self, mock_get):
|
||||
"""
|
||||
Check that a activity can be viewed
|
||||
when using the activity object directly
|
||||
"""
|
||||
mock_get.return_value = self._return_value(self._view_wrapper(
|
||||
self.test_activities_dict[1]))
|
||||
activity = self.test_activities[0]
|
||||
activity.view(foo="bar")
|
||||
mock_get.assert_called_with("/activity/1/view.json", foo="bar")
|
||||
self.assertEqual(activity.get_fields(), self.test_activities_dict[1])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_activity_view_invalid_type(self, mock_get):
|
||||
"""Check that an invalid activity type raises an exception"""
|
||||
mock_get.return_value = self._return_value(self._view_wrapper(
|
||||
{"data": "", "type": "invalid"}))
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.activity.view(self.test_activities[0])
|
||||
|
||||
class TestActivityMisc(TestActivities):
|
||||
def test_update_fields_with_no_type(self):
|
||||
"""Check that an activity object can be updated with no type"""
|
||||
activity = self.test_activities[0]
|
||||
activity.type = None
|
||||
activity.data = None
|
||||
# Check that no exception is raised
|
||||
activity._update_fields_with_objects()
|
|
@ -9,17 +9,23 @@ import trovebox
|
|||
|
||||
class TestAlbums(unittest.TestCase):
|
||||
test_host = "test.example.com"
|
||||
test_photos_dict = [{"id": "1a", "tags": ["tag1", "tag2"]},
|
||||
{"id": "2b", "tags": ["tag3", "tag4"]}]
|
||||
test_albums_dict = [{"cover": {"id": "1a", "tags": ["tag1", "tag2"]},
|
||||
"id": "1",
|
||||
"name": "Album 1",
|
||||
"photos": [test_photos_dict[0]],
|
||||
"totalRows": 2},
|
||||
{"cover": {"id": "2b", "tags": ["tag3", "tag4"]},
|
||||
"id": "2",
|
||||
"name": "Album 2",
|
||||
"photos": [test_photos_dict[1]],
|
||||
"totalRows": 2}]
|
||||
def setUp(self):
|
||||
self.client = trovebox.Trovebox(host=self.test_host)
|
||||
self.test_albums = [trovebox.objects.Album(self.client, album)
|
||||
self.test_photos = [trovebox.objects.photo.Photo(self.client, photo)
|
||||
for photo in self.test_photos_dict]
|
||||
self.test_albums = [trovebox.objects.album.Album(self.client, album)
|
||||
for album in self.test_albums_dict]
|
||||
|
||||
@staticmethod
|
||||
|
@ -31,20 +37,36 @@ class TestAlbumsList(TestAlbums):
|
|||
def test_albums_list(self, mock_get):
|
||||
"""Check that the album list is returned correctly"""
|
||||
mock_get.return_value = self._return_value(self.test_albums_dict)
|
||||
result = self.client.albums.list()
|
||||
mock_get.assert_called_with("/albums/list.json")
|
||||
result = self.client.albums.list(foo="bar")
|
||||
mock_get.assert_called_with("/albums/list.json", foo="bar")
|
||||
self.assertEqual(len(result), 2)
|
||||
self.assertEqual(result[0].id, "1")
|
||||
self.assertEqual(result[0].name, "Album 1")
|
||||
self.assertEqual(result[1].id, "2")
|
||||
self.assertEqual(result[1].name, "Album 2")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_empty_result(self, mock_get):
|
||||
"""Check that an empty result is transformed into an empty list """
|
||||
mock_get.return_value = self._return_value("")
|
||||
result = self.client.albums.list(foo="bar")
|
||||
mock_get.assert_called_with("/albums/list.json", foo="bar")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_zero_rows(self, mock_get):
|
||||
"""Check that totalRows=0 is transformed into an empty list """
|
||||
mock_get.return_value = self._return_value([{"totalRows": 0}])
|
||||
result = self.client.albums.list(foo="bar")
|
||||
mock_get.assert_called_with("/albums/list.json", foo="bar")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_albums_list_returns_cover_photos(self, mock_get):
|
||||
"""Check that the album list returns cover photo objects"""
|
||||
mock_get.return_value = self._return_value(self.test_albums_dict)
|
||||
result = self.client.albums.list()
|
||||
mock_get.assert_called_with("/albums/list.json")
|
||||
result = self.client.albums.list(foo="bar")
|
||||
mock_get.assert_called_with("/albums/list.json", foo="bar")
|
||||
self.assertEqual(len(result), 2)
|
||||
self.assertEqual(result[0].id, "1")
|
||||
self.assertEqual(result[0].name, "Album 1")
|
||||
|
@ -55,6 +77,46 @@ class TestAlbumsList(TestAlbums):
|
|||
self.assertEqual(result[1].cover.id, "2b")
|
||||
self.assertEqual(result[1].cover.tags, ["tag3", "tag4"])
|
||||
|
||||
class TestAlbumUpdateCover(TestAlbums):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_cover_update(self, mock_post):
|
||||
"""Check that an album cover can be updated"""
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
result = self.client.album.cover_update(self.test_albums[0],
|
||||
self.test_photos[0],
|
||||
foo="bar")
|
||||
mock_post.assert_called_with("/album/1/cover/1a/update.json",
|
||||
foo="bar")
|
||||
self.assertEqual(result.id, "2")
|
||||
self.assertEqual(result.name, "Album 2")
|
||||
self.assertEqual(result.cover.id, "2b")
|
||||
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_cover_update_id(self, mock_post):
|
||||
"""Check that an album cover can be updated using IDs"""
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
result = self.client.album.cover_update("1", "1a", foo="bar")
|
||||
mock_post.assert_called_with("/album/1/cover/1a/update.json",
|
||||
foo="bar")
|
||||
self.assertEqual(result.id, "2")
|
||||
self.assertEqual(result.name, "Album 2")
|
||||
self.assertEqual(result.cover.id, "2b")
|
||||
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_object_cover_update(self, mock_post):
|
||||
"""Check that an album cover can be updated using the album object directly"""
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
album = self.test_albums[0]
|
||||
album.cover_update(self.test_photos[1], foo="bar")
|
||||
mock_post.assert_called_with("/album/1/cover/2b/update.json",
|
||||
foo="bar")
|
||||
self.assertEqual(album.id, "2")
|
||||
self.assertEqual(album.name, "Album 2")
|
||||
self.assertEqual(album.cover.id, "2b")
|
||||
self.assertEqual(album.cover.tags, ["tag3", "tag4"])
|
||||
|
||||
class TestAlbumCreate(TestAlbums):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_create(self, mock_post):
|
||||
|
@ -73,104 +135,153 @@ class TestAlbumDelete(TestAlbums):
|
|||
def test_album_delete(self, mock_post):
|
||||
"""Check that an album can be deleted"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.album.delete(self.test_albums[0])
|
||||
mock_post.assert_called_with("/album/1/delete.json")
|
||||
result = self.client.album.delete(self.test_albums[0], foo="bar")
|
||||
mock_post.assert_called_with("/album/1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_delete_id(self, mock_post):
|
||||
"""Check that an album can be deleted using its ID"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.album.delete("1")
|
||||
mock_post.assert_called_with("/album/1/delete.json")
|
||||
result = self.client.album.delete("1", foo="bar")
|
||||
mock_post.assert_called_with("/album/1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_delete_failure(self, mock_post):
|
||||
"""Check that an exception is raised if an album cannot be deleted"""
|
||||
mock_post.return_value = self._return_value(False)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.album.delete(self.test_albums[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_object_delete(self, mock_post):
|
||||
"""Check that an album can be deleted using the album object directly"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
album = self.test_albums[0]
|
||||
result = album.delete()
|
||||
mock_post.assert_called_with("/album/1/delete.json")
|
||||
result = album.delete(foo="bar")
|
||||
mock_post.assert_called_with("/album/1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
self.assertEqual(album.get_fields(), {})
|
||||
self.assertEqual(album.id, None)
|
||||
self.assertEqual(album.name, None)
|
||||
|
||||
class TestAlbumAdd(TestAlbums):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_object_delete_failure(self, mock_post):
|
||||
def test_album_add(self, mock_post):
|
||||
""" Check that photos can be added to an album """
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
result = self.client.album.add(self.test_albums[0], self.test_photos,
|
||||
foo="bar")
|
||||
mock_post.assert_called_with("/album/1/photo/add.json",
|
||||
ids=["1a", "2b"], foo="bar")
|
||||
self.assertEqual(result.id, self.test_albums[1].id)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_add_id(self, mock_post):
|
||||
""" Check that photos can be added to an album using IDs """
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
result = self.client.album.add(self.test_albums[0].id,
|
||||
objects=["1a", "2b"],
|
||||
object_type="photo",
|
||||
foo="bar")
|
||||
mock_post.assert_called_with("/album/1/photo/add.json",
|
||||
ids=["1a", "2b"], foo="bar")
|
||||
self.assertEqual(result.id, self.test_albums[1].id)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_object_add(self, mock_post):
|
||||
"""
|
||||
Check that an exception is raised if an album cannot be deleted
|
||||
when using the album object directly
|
||||
Check that photos can be added to an album using the
|
||||
album object directly
|
||||
"""
|
||||
mock_post.return_value = self._return_value(False)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.test_albums[0].delete()
|
||||
|
||||
class TestAlbumForm(TestAlbums):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_form(self, _):
|
||||
""" If album.form gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.form(self.test_albums[0])
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
album = self.test_albums[0]
|
||||
album.add(self.test_photos, foo="bar")
|
||||
mock_post.assert_called_with("/album/1/photo/add.json",
|
||||
ids=["1a", "2b"], foo="bar")
|
||||
self.assertEqual(album.id, self.test_albums[1].id)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_form_id(self, _):
|
||||
""" If album.form gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.form("1")
|
||||
def test_album_add_single(self, mock_post):
|
||||
""" Check that a single photo can be added to an album """
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
self.test_albums[0].add(self.test_photos[0], foo="bar")
|
||||
mock_post.assert_called_with("/album/1/photo/add.json",
|
||||
ids=["1a"], foo="bar")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_object_form(self, _):
|
||||
""" If album.form gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.test_albums[0].form()
|
||||
|
||||
class TestAlbumAddPhotos(TestAlbums):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_add_photos(self, _):
|
||||
""" If album.add_photos gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.add_photos(self.test_albums[0], ["Photo Objects"])
|
||||
def test_album_add_invalid_type(self, _):
|
||||
"""
|
||||
Check that an exception is raised if an invalid object is added
|
||||
to an album.
|
||||
"""
|
||||
with self.assertRaises(AttributeError):
|
||||
self.test_albums[0].add([object()])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_add_photos_id(self, _):
|
||||
""" If album.add_photos gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.add_photos("1", ["Photo Objects"])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_object_add_photos(self, _):
|
||||
""" If album.add_photos gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.test_albums[0].add_photos(["Photo Objects"])
|
||||
def test_album_add_multiple_types(self, _):
|
||||
"""
|
||||
Check that an exception is raised if multiple types are added
|
||||
to an album.
|
||||
"""
|
||||
with self.assertRaises(ValueError):
|
||||
self.test_albums[0].add(self.test_photos+self.test_albums)
|
||||
|
||||
class TestAlbumRemovePhotos(TestAlbums):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_remove_photos(self, _):
|
||||
""" If album.remove_photos gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.remove_photos(self.test_albums[0],
|
||||
["Photo Objects"])
|
||||
def test_album_remove(self, mock_post):
|
||||
""" Check that photos can be removed from an album """
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
result = self.client.album.remove(self.test_albums[0], self.test_photos,
|
||||
foo="bar")
|
||||
mock_post.assert_called_with("/album/1/photo/remove.json",
|
||||
ids=["1a", "2b"], foo="bar")
|
||||
self.assertEqual(result.id, self.test_albums[1].id)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_remove_photos_id(self, _):
|
||||
""" If album.remove_photos gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.album.remove_photos("1", ["Photo Objects"])
|
||||
def test_album_remove_id(self, mock_post):
|
||||
""" Check that photos can be removed from an album using IDs """
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
result = self.client.album.remove(self.test_albums[0].id,
|
||||
objects=["1a", "2b"],
|
||||
object_type="photo",
|
||||
foo="bar")
|
||||
mock_post.assert_called_with("/album/1/photo/remove.json",
|
||||
ids=["1a", "2b"], foo="bar")
|
||||
self.assertEqual(result.id, self.test_albums[1].id)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_object_remove_photos(self, _):
|
||||
""" If album.remove_photos gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.test_albums[0].remove_photos(["Photo Objects"])
|
||||
def test_album_object_remove(self, mock_post):
|
||||
"""
|
||||
Check that photos can be removed from an album using the
|
||||
album object directly
|
||||
"""
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
album = self.test_albums[0]
|
||||
album.remove(self.test_photos, foo="bar")
|
||||
mock_post.assert_called_with("/album/1/photo/remove.json",
|
||||
ids=["1a", "2b"], foo="bar")
|
||||
self.assertEqual(album.id, self.test_albums[1].id)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_remove_single(self, mock_post):
|
||||
""" Check that a single photo can be removed from an album """
|
||||
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||
self.test_albums[0].remove(self.test_photos[0], foo="bar")
|
||||
mock_post.assert_called_with("/album/1/photo/remove.json",
|
||||
ids=["1a"], foo="bar")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_remove_invalid_type(self, _):
|
||||
"""
|
||||
Check that an exception is raised if an invalid object is removed
|
||||
from an album.
|
||||
"""
|
||||
with self.assertRaises(AttributeError):
|
||||
self.test_albums[0].remove([object()])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_album_remove_multiple_types(self, _):
|
||||
"""
|
||||
Check that an exception is raised if multiple types are removed
|
||||
from an album.
|
||||
"""
|
||||
with self.assertRaises(ValueError):
|
||||
self.test_albums[0].remove(self.test_photos+self.test_albums)
|
||||
|
||||
class TestAlbumUpdate(TestAlbums):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
|
@ -212,33 +323,44 @@ class TestAlbumView(TestAlbums):
|
|||
def test_album_view(self, mock_get):
|
||||
"""Check that an album can be viewed"""
|
||||
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
||||
result = self.client.album.view(self.test_albums[0], name="Test")
|
||||
mock_get.assert_called_with("/album/1/view.json", name="Test")
|
||||
result = self.client.album.view(self.test_albums[0], includeElements=True)
|
||||
mock_get.assert_called_with("/album/1/view.json", includeElements=True)
|
||||
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.photos[0].id, self.test_photos[1].id)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_album_view_id(self, mock_get):
|
||||
"""Check that an album can be viewed using its ID"""
|
||||
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
||||
result = self.client.album.view("1", name="Test")
|
||||
mock_get.assert_called_with("/album/1/view.json", name="Test")
|
||||
result = self.client.album.view("1", includeElements=True)
|
||||
mock_get.assert_called_with("/album/1/view.json", includeElements=True)
|
||||
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.photos[0].id, self.test_photos[1].id)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_album_object_view(self, mock_get):
|
||||
"""Check that an album can be viewed using the album object directly"""
|
||||
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
||||
album = self.test_albums[0]
|
||||
album.view(name="Test")
|
||||
mock_get.assert_called_with("/album/1/view.json", name="Test")
|
||||
album.view(includeElements=True)
|
||||
mock_get.assert_called_with("/album/1/view.json", includeElements=True)
|
||||
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.photos[0].id, self.test_photos[1].id)
|
||||
|
||||
class TestAlbumMisc(TestAlbums):
|
||||
def test_update_fields_with_no_cover(self):
|
||||
"""Check that an album object can be updated with no cover"""
|
||||
album = self.test_albums[0]
|
||||
album.cover = None
|
||||
album.photos = None
|
||||
# Check that no exception is raised
|
||||
album._update_fields_with_objects()
|
||||
|
|
|
@ -31,6 +31,7 @@ class TestAuth(unittest.TestCase):
|
|||
def create_config(config_file, host):
|
||||
"""Create a dummy config file"""
|
||||
with open(os.path.join(CONFIG_PATH, config_file), "w") as conf:
|
||||
if host is not None:
|
||||
conf.write("host = %s\n" % host)
|
||||
conf.write("# Comment\n\n")
|
||||
conf.write("consumerKey = \"%s_consumer_key\"\n" % config_file)
|
||||
|
@ -97,4 +98,13 @@ class TestAuth(unittest.TestCase):
|
|||
with self.assertRaises(ValueError):
|
||||
Trovebox(config_file="custom", host="host_override")
|
||||
|
||||
|
||||
def test_partial_config_file(self):
|
||||
""" Test that an incomplete config file causes default values to be set """
|
||||
self.create_config("incomplete", host=None) # Don't write the host line
|
||||
client = Trovebox(config_file="incomplete")
|
||||
auth = client.auth
|
||||
self.assertEqual(auth.host, "localhost")
|
||||
self.assertEqual(auth.consumer_key, "incomplete_consumer_key")
|
||||
self.assertEqual(auth.consumer_secret, "incomplete_consumer_secret")
|
||||
self.assertEqual(auth.token, "incomplete_token")
|
||||
self.assertEqual(auth.token_secret, "incomplete_token_secret")
|
||||
|
|
|
@ -106,11 +106,21 @@ class TestCli(unittest.TestCase):
|
|||
|
||||
@mock.patch.object(trovebox.main.trovebox, "Trovebox")
|
||||
@mock.patch('sys.stdout', new_callable=io.StringIO)
|
||||
def test_verbose(self, mock_stdout, _):
|
||||
"""Check that the verbose option is working"""
|
||||
def test_verbose_without_params(self, mock_stdout, _):
|
||||
"""Check that the verbose option works with no parameters"""
|
||||
main(["-v"])
|
||||
self.assertIn("Method: GET", mock_stdout.getvalue())
|
||||
self.assertIn("Endpoint: /photos/list.json", mock_stdout.getvalue())
|
||||
self.assertNotIn("Fields:", mock_stdout.getvalue())
|
||||
|
||||
@mock.patch.object(trovebox.main.trovebox, "Trovebox")
|
||||
@mock.patch('sys.stdout', new_callable=io.StringIO)
|
||||
def test_verbose_with_params(self, mock_stdout, _):
|
||||
"""Check that the verbose option works with parameters"""
|
||||
main(["-v", "-F foo=bar"])
|
||||
self.assertIn("Method: GET", mock_stdout.getvalue())
|
||||
self.assertIn("Endpoint: /photos/list.json", mock_stdout.getvalue())
|
||||
self.assertIn("Fields:\n foo=bar", mock_stdout.getvalue())
|
||||
|
||||
@mock.patch.object(trovebox.main.trovebox, "Trovebox")
|
||||
@mock.patch('sys.stdout', new_callable=io.StringIO)
|
||||
|
@ -127,3 +137,8 @@ class TestCli(unittest.TestCase):
|
|||
main(["--version"])
|
||||
self.assertEqual(mock_stdout.getvalue(), trovebox.__version__ + "\n")
|
||||
|
||||
@mock.patch('sys.stdout', new_callable=io.StringIO)
|
||||
def test_help(self, mock_stdout):
|
||||
"""Check that the help string is correctly printed"""
|
||||
main(["--help"])
|
||||
self.assertIn("show this help message", mock_stdout.getvalue())
|
||||
|
|
|
@ -112,6 +112,22 @@ class TestHttp(unittest.TestCase):
|
|||
"https://test.example.com/%s" % self.test_endpoint)
|
||||
self.assertEqual(self.client.last_response.json(), self.test_data)
|
||||
|
||||
@httpretty.activate
|
||||
@data(GET, POST)
|
||||
def test_endpoint_leading_slash(self, method):
|
||||
"""Check that an endpoint with a leading slash is constructed correctly"""
|
||||
self._register_uri(method,
|
||||
uri="http://test.example.com/%s" % self.test_endpoint)
|
||||
|
||||
self.client = trovebox.Trovebox(host="http://test.example.com",
|
||||
**self.test_oauth)
|
||||
response = GetOrPost(self.client, method).call("/" + self.test_endpoint)
|
||||
self.assertIn("OAuth", self._last_request().headers["authorization"])
|
||||
self.assertEqual(response, self.test_data)
|
||||
self.assertEqual(self.client.last_url,
|
||||
"http://test.example.com/%s" % self.test_endpoint)
|
||||
self.assertEqual(self.client.last_response.json(), self.test_data)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_parameters(self):
|
||||
"""Check that the get method accepts parameters correctly"""
|
||||
|
@ -171,12 +187,13 @@ class TestHttp(unittest.TestCase):
|
|||
def test_get_parameter_processing(self):
|
||||
"""Check that the parameter processing function is working"""
|
||||
self._register_uri(httpretty.GET)
|
||||
photo = trovebox.objects.Photo(None, {"id": "photo_id"})
|
||||
album = trovebox.objects.Album(None, {"id": "album_id"})
|
||||
tag = trovebox.objects.Tag(None, {"id": "tag_id"})
|
||||
photo = trovebox.objects.photo.Photo(None, {"id": "photo_id"})
|
||||
album = trovebox.objects.album.Album(None, {"id": "album_id"})
|
||||
tag = trovebox.objects.tag.Tag(None, {"id": "tag_id"})
|
||||
self.client.get(self.test_endpoint,
|
||||
photo=photo, album=album, tag=tag,
|
||||
list_=[photo, album, tag],
|
||||
list2=["1", "2", "3"],
|
||||
boolean=True,
|
||||
unicode_="\xfcmlaut")
|
||||
params = self._last_request().querystring
|
||||
|
@ -184,6 +201,7 @@ class TestHttp(unittest.TestCase):
|
|||
self.assertEqual(params["album"], ["album_id"])
|
||||
self.assertEqual(params["tag"], ["tag_id"])
|
||||
self.assertEqual(params["list_"], ["photo_id,album_id,tag_id"])
|
||||
self.assertEqual(params["list2"], ["1,2,3"])
|
||||
self.assertEqual(params["boolean"], ["1"])
|
||||
self.assertIn(params["unicode_"], [["\xc3\xbcmlaut"], ["\xfcmlaut"]])
|
||||
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
from __future__ import unicode_literals
|
||||
import json
|
||||
import httpretty
|
||||
from httpretty import GET, POST
|
||||
from ddt import ddt, data
|
||||
|
||||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
from test_http import GetOrPost
|
||||
import trovebox
|
||||
|
||||
@ddt
|
||||
class TestHttpErrors(unittest.TestCase):
|
||||
test_host = "test.example.com"
|
||||
test_endpoint = "test.json"
|
||||
|
@ -38,146 +42,93 @@ class TestHttpErrors(unittest.TestCase):
|
|||
**kwds)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_error_status(self):
|
||||
@data(GET, POST)
|
||||
def test_error_status(self, method):
|
||||
"""
|
||||
Check that an error status causes the get method
|
||||
Check that an error status causes the get/post methods
|
||||
to raise an exception
|
||||
"""
|
||||
self._register_uri(httpretty.GET, status=500)
|
||||
self._register_uri(method, status=500)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.get(self.test_endpoint)
|
||||
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_with_error_status(self):
|
||||
@data(GET, POST)
|
||||
def test_404_status(self, method):
|
||||
"""
|
||||
Check that an error status causes the post method
|
||||
to raise an exception
|
||||
"""
|
||||
self._register_uri(httpretty.POST, status=500)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.post(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_404_status(self):
|
||||
"""
|
||||
Check that a 404 status causes the get method
|
||||
Check that a 404 status causes the get/post methods
|
||||
to raise a 404 exception
|
||||
"""
|
||||
self._register_uri(httpretty.GET, status=404)
|
||||
self._register_uri(method, status=404)
|
||||
with self.assertRaises(trovebox.Trovebox404Error):
|
||||
self.client.get(self.test_endpoint)
|
||||
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_with_404_status(self):
|
||||
@data(GET, POST)
|
||||
def test_with_invalid_json(self, method):
|
||||
"""
|
||||
Check that a 404 status causes the post method
|
||||
to raise a 404 exception
|
||||
"""
|
||||
self._register_uri(httpretty.POST, status=404)
|
||||
with self.assertRaises(trovebox.Trovebox404Error):
|
||||
self.client.post(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_invalid_json(self):
|
||||
"""
|
||||
Check that invalid JSON causes the get method to
|
||||
Check that invalid JSON causes the get/post methods to
|
||||
raise an exception
|
||||
"""
|
||||
self._register_uri(httpretty.GET, body="Invalid JSON")
|
||||
self._register_uri(method, body="Invalid JSON")
|
||||
with self.assertRaises(ValueError):
|
||||
self.client.get(self.test_endpoint)
|
||||
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_with_invalid_json(self):
|
||||
@data(GET, POST)
|
||||
def test_with_error_status_and_invalid_json(self, method):
|
||||
"""
|
||||
Check that invalid JSON causes the post method to
|
||||
raise an exception
|
||||
Check that invalid JSON causes the get/post methods to raise
|
||||
an exception, even with an error status is returned
|
||||
"""
|
||||
self._register_uri(httpretty.POST, body="Invalid JSON")
|
||||
with self.assertRaises(ValueError):
|
||||
self.client.post(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_error_status_and_invalid_json(self):
|
||||
"""
|
||||
Check that invalid JSON causes the get method to raise an exception,
|
||||
even with an error status is returned
|
||||
"""
|
||||
self._register_uri(httpretty.GET, body="Invalid JSON", status=500)
|
||||
self._register_uri(method, body="Invalid JSON", status=500)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.get(self.test_endpoint)
|
||||
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_with_error_status_and_invalid_json(self):
|
||||
@data(GET, POST)
|
||||
def test_with_404_status_and_invalid_json(self, method):
|
||||
"""
|
||||
Check that invalid JSON causes the post method to raise an exception,
|
||||
even with an error status is returned
|
||||
Check that invalid JSON causes the get/post methods to raise
|
||||
an exception, even with a 404 status is returned
|
||||
"""
|
||||
self._register_uri(httpretty.POST, body="Invalid JSON", status=500)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.post(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_404_status_and_invalid_json(self):
|
||||
"""
|
||||
Check that invalid JSON causes the get method to raise an exception,
|
||||
even with a 404 status is returned
|
||||
"""
|
||||
self._register_uri(httpretty.GET, body="Invalid JSON", status=404)
|
||||
self._register_uri(method, body="Invalid JSON", status=404)
|
||||
with self.assertRaises(trovebox.Trovebox404Error):
|
||||
self.client.get(self.test_endpoint)
|
||||
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_with_404_status_and_invalid_json(self):
|
||||
@data(GET, POST)
|
||||
def test_with_duplicate_status(self, method):
|
||||
"""
|
||||
Check that invalid JSON causes the post method to raise an exception,
|
||||
even with a 404 status is returned
|
||||
"""
|
||||
self._register_uri(httpretty.POST, body="Invalid JSON", status=404)
|
||||
with self.assertRaises(trovebox.Trovebox404Error):
|
||||
self.client.post(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_duplicate_status(self):
|
||||
"""
|
||||
Check that a get with a duplicate status
|
||||
Check that a get/post with a duplicate status
|
||||
raises a duplicate exception
|
||||
"""
|
||||
data = {"message": "This photo already exists", "code": 409}
|
||||
self._register_uri(httpretty.GET, data=data, status=409)
|
||||
self._register_uri(method, data=data, status=409)
|
||||
with self.assertRaises(trovebox.TroveboxDuplicateError):
|
||||
self.client.get(self.test_endpoint)
|
||||
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_with_duplicate_status(self):
|
||||
"""
|
||||
Check that a post with a duplicate status
|
||||
raises a duplicate exception
|
||||
"""
|
||||
data = {"message": "This photo already exists", "code": 409}
|
||||
self._register_uri(httpretty.POST, data=data, status=409)
|
||||
with self.assertRaises(trovebox.TroveboxDuplicateError):
|
||||
self.client.post(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_status_code_mismatch(self):
|
||||
@data(GET, POST)
|
||||
def test_with_status_code_mismatch(self, method):
|
||||
"""
|
||||
Check that a mismatched HTTP status code still returns the
|
||||
JSON status code for get requests.
|
||||
JSON status code.
|
||||
"""
|
||||
data = {"message": "Test Message", "code": 202}
|
||||
self._register_uri(httpretty.GET, data=data, status=200)
|
||||
response = self.client.get(self.test_endpoint)
|
||||
self._register_uri(method, data=data, status=200)
|
||||
response = GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
self.assertEqual(response["code"], 202)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_with_status_code_mismatch(self):
|
||||
@data(GET, POST)
|
||||
def test_http_error_with_no_response_processing(self, method):
|
||||
"""
|
||||
Check that a mismatched HTTP status code still returns the
|
||||
JSON status code for post requests.
|
||||
Check that get/post methods work with response processing disabled
|
||||
when an HTTP error code is returned.
|
||||
"""
|
||||
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)
|
||||
httpretty.register_uri(method, self.test_uri, status=500)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
response = GetOrPost(self.client, method).call(self.test_endpoint,
|
||||
process_response=False)
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class TestPhotos(unittest.TestCase):
|
|||
"totalPages": 1, "totalRows": 2}]
|
||||
def setUp(self):
|
||||
self.client = trovebox.Trovebox(host=self.test_host)
|
||||
self.test_photos = [trovebox.objects.Photo(self.client, photo)
|
||||
self.test_photos = [trovebox.objects.photo.Photo(self.client, photo)
|
||||
for photo in self.test_photos_dict]
|
||||
|
||||
@staticmethod
|
||||
|
@ -31,14 +31,55 @@ class TestPhotosList(TestPhotos):
|
|||
"""Check that the photo list is returned correctly"""
|
||||
mock_get.return_value = self._return_value(self.test_photos_dict)
|
||||
|
||||
result = self.client.photos.list()
|
||||
mock_get.assert_called_with("/photos/list.json")
|
||||
result = self.client.photos.list(foo="bar")
|
||||
mock_get.assert_called_with("/photos/list.json", foo="bar")
|
||||
self.assertEqual(len(result), 2)
|
||||
self.assertEqual(result[0].id, "1a")
|
||||
self.assertEqual(result[0].tags, ["tag1", "tag2"])
|
||||
self.assertEqual(result[1].id, "2b")
|
||||
self.assertEqual(result[1].tags, ["tag3", "tag4"])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_empty_result(self, mock_get):
|
||||
"""Check that an empty result is transformed into an empty list """
|
||||
mock_get.return_value = self._return_value("")
|
||||
result = self.client.photos.list(foo="bar")
|
||||
mock_get.assert_called_with("/photos/list.json", foo="bar")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_zero_rows(self, mock_get):
|
||||
"""Check that totalRows=0 is transformed into an empty list """
|
||||
mock_get.return_value = self._return_value([{"totalRows": 0}])
|
||||
result = self.client.photos.list(foo="bar")
|
||||
mock_get.assert_called_with("/photos/list.json", foo="bar")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_options(self, mock_get):
|
||||
"""Check that the activity list options are applied properly"""
|
||||
mock_get.return_value = self._return_value(self.test_photos_dict)
|
||||
self.client.photos.list(options={"foo": "bar",
|
||||
"test1": "test2"},
|
||||
foo="bar")
|
||||
# Dict element can be any order
|
||||
self.assertIn(mock_get.call_args[0],
|
||||
[("/photos/foo-bar/test1-test2/list.json",),
|
||||
("/photos/test1-test2/foo-bar/list.json",)])
|
||||
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
|
||||
|
||||
class TestPhotosShare(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photos_share(self, mock_post):
|
||||
self.client.photos.share(options={"foo": "bar",
|
||||
"test1": "test2"},
|
||||
foo="bar")
|
||||
# Dict element can be any order
|
||||
self.assertIn(mock_post.call_args[0],
|
||||
[("/photos/foo-bar/test1-test2/share.json",),
|
||||
("/photos/test1-test2/foo-bar/share.json",)])
|
||||
self.assertEqual(mock_post.call_args[1], {"foo": "bar"})
|
||||
|
||||
class TestPhotosUpdate(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photos_update(self, mock_post):
|
||||
|
@ -58,67 +99,42 @@ class TestPhotosUpdate(TestPhotos):
|
|||
ids=["1a", "2b"], title="Test")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photos_update_failure(self, mock_post):
|
||||
"""
|
||||
Check that an exception is raised if multiple photos
|
||||
cannot be updated
|
||||
"""
|
||||
mock_post.return_value = self._return_value(False)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.photos.update(self.test_photos, title="Test")
|
||||
|
||||
class TestPhotosDelete(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photos_delete(self, mock_post):
|
||||
"""Check that multiple photos can be deleted"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.photos.delete(self.test_photos)
|
||||
mock_post.assert_called_with("/photos/delete.json", ids=["1a", "2b"])
|
||||
result = self.client.photos.delete(self.test_photos, foo="bar")
|
||||
mock_post.assert_called_with("/photos/delete.json",
|
||||
ids=["1a", "2b"], foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photos_delete_ids(self, mock_post):
|
||||
"""Check that multiple photos can be deleted using their IDs"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.photos.delete(["1a", "2b"])
|
||||
mock_post.assert_called_with("/photos/delete.json", ids=["1a", "2b"])
|
||||
result = self.client.photos.delete(["1a", "2b"], foo="bar")
|
||||
mock_post.assert_called_with("/photos/delete.json",
|
||||
ids=["1a", "2b"], foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photos_delete_failure(self, mock_post):
|
||||
"""
|
||||
Check that an exception is raised if multiple photos
|
||||
cannot be deleted
|
||||
"""
|
||||
mock_post.return_value = self._return_value(False)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.photos.delete(self.test_photos)
|
||||
|
||||
class TestPhotoDelete(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_delete(self, mock_post):
|
||||
"""Check that a photo can be deleted"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.photo.delete(self.test_photos[0])
|
||||
mock_post.assert_called_with("/photo/1a/delete.json")
|
||||
result = self.client.photo.delete(self.test_photos[0], foo="bar")
|
||||
mock_post.assert_called_with("/photo/1a/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_delete_id(self, mock_post):
|
||||
"""Check that a photo can be deleted using its ID"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.photo.delete("1a")
|
||||
mock_post.assert_called_with("/photo/1a/delete.json")
|
||||
result = self.client.photo.delete("1a", foo="bar")
|
||||
mock_post.assert_called_with("/photo/1a/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_delete_failure(self, mock_post):
|
||||
"""Check that an exception is raised if a photo cannot be deleted"""
|
||||
mock_post.return_value = self._return_value(False)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.photo.delete(self.test_photos[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_delete(self, mock_post):
|
||||
"""
|
||||
|
@ -127,87 +143,189 @@ class TestPhotoDelete(TestPhotos):
|
|||
"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
photo = self.test_photos[0]
|
||||
result = photo.delete()
|
||||
mock_post.assert_called_with("/photo/1a/delete.json")
|
||||
result = photo.delete(foo="bar")
|
||||
mock_post.assert_called_with("/photo/1a/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
self.assertEqual(photo.get_fields(), {})
|
||||
self.assertEqual(photo.id, None)
|
||||
|
||||
class TestPhotoDeleteSource(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_delete_failure(self, mock_post):
|
||||
"""
|
||||
Check that an exception is raised if a photo cannot be deleted
|
||||
when using the photo object directly
|
||||
"""
|
||||
mock_post.return_value = self._return_value(False)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.test_photos[0].delete()
|
||||
def test_photo_delete_source(self, mock_post):
|
||||
"""Check that photo source files can be deleted"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.photo.delete_source(self.test_photos[0], foo="bar")
|
||||
mock_post.assert_called_with("/photo/1a/source/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
class TestPhotoEdit(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_photo_edit(self, mock_get):
|
||||
"""Check that a the photo edit endpoint is working"""
|
||||
mock_get.return_value = self._return_value({"markup": "<form/>"})
|
||||
result = self.client.photo.edit(self.test_photos[0])
|
||||
mock_get.assert_called_with("/photo/1a/edit.json")
|
||||
self.assertEqual(result, "<form/>")
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_delete_source_id(self, mock_post):
|
||||
"""Check that photo source files can be deleted using its ID"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.photo.delete_source("1a", foo="bar")
|
||||
mock_post.assert_called_with("/photo/1a/source/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_photo_edit_id(self, mock_get):
|
||||
"""Check that a the photo edit endpoint is working when using an ID"""
|
||||
mock_get.return_value = self._return_value({"markup": "<form/>"})
|
||||
result = self.client.photo.edit("1a")
|
||||
mock_get.assert_called_with("/photo/1a/edit.json")
|
||||
self.assertEqual(result, "<form/>")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_photo_object_edit(self, mock_get):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_delete_source(self, mock_post):
|
||||
"""
|
||||
Check that a the photo edit endpoint is working
|
||||
when using the photo object directly
|
||||
Check that photo source files can be deleted when using
|
||||
the photo object directly
|
||||
"""
|
||||
mock_get.return_value = self._return_value({"markup": "<form/>"})
|
||||
result = self.test_photos[0].edit()
|
||||
mock_get.assert_called_with("/photo/1a/edit.json")
|
||||
self.assertEqual(result, "<form/>")
|
||||
mock_post.return_value = self._return_value(True)
|
||||
photo = self.test_photos[0]
|
||||
result = photo.delete_source(foo="bar")
|
||||
mock_post.assert_called_with("/photo/1a/source/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
class TestPhotoReplace(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_replace(self, _):
|
||||
""" If photo.replace gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.replace(self.test_photos[0], self.test_file)
|
||||
def test_photo_replace(self, mock_post):
|
||||
"""Check that an existing photo can be replaced"""
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
result = self.client.photo.replace(self.test_photos[1],
|
||||
self.test_file, title="Test")
|
||||
# It's not possible to compare the file object,
|
||||
# so check each parameter individually
|
||||
endpoint = mock_post.call_args[0]
|
||||
title = mock_post.call_args[1]["title"]
|
||||
files = mock_post.call_args[1]["files"]
|
||||
self.assertEqual(endpoint,
|
||||
("/photo/%s/replace.json" % self.test_photos[1].id,))
|
||||
self.assertEqual(title, "Test")
|
||||
self.assertIn("photo", files)
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_replace_id(self, _):
|
||||
""" If photo.replace gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.replace("1a", self.test_file)
|
||||
def test_photo_replace_id(self, mock_post):
|
||||
"""Check that an existing photo can be replaced using its ID"""
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
result = self.client.photo.replace(self.test_photos[1].id,
|
||||
self.test_file, title="Test")
|
||||
# It's not possible to compare the file object,
|
||||
# so check each parameter individually
|
||||
endpoint = mock_post.call_args[0]
|
||||
title = mock_post.call_args[1]["title"]
|
||||
files = mock_post.call_args[1]["files"]
|
||||
self.assertEqual(endpoint,
|
||||
("/photo/%s/replace.json" % self.test_photos[1].id,))
|
||||
self.assertEqual(title, "Test")
|
||||
self.assertIn("photo", files)
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_replace(self, _):
|
||||
""" If photo.replace gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.test_photos[0].replace(self.test_file)
|
||||
def test_photo_object_replace(self, mock_post):
|
||||
"""
|
||||
Check that an existing photo can be replaced when using the
|
||||
Photo object directly.
|
||||
"""
|
||||
photo_id = self.test_photos[1].id
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
self.test_photos[1].replace(self.test_file, title="Test")
|
||||
# It's not possible to compare the file object,
|
||||
# so check each parameter individually
|
||||
endpoint = mock_post.call_args[0]
|
||||
title = mock_post.call_args[1]["title"]
|
||||
files = mock_post.call_args[1]["files"]
|
||||
self.assertEqual(endpoint, ("/photo/%s/replace.json" % photo_id,))
|
||||
self.assertEqual(title, "Test")
|
||||
self.assertIn("photo", files)
|
||||
self.assertEqual(self.test_photos[1].get_fields(),
|
||||
self.test_photos_dict[0])
|
||||
|
||||
class TestPhotoReplaceEncoded(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_replace_encoded(self, mock_post):
|
||||
"""
|
||||
Check that a photo can be uploaded using Base64 encoding to
|
||||
replace an existing photo.
|
||||
"""
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
result = self.client.photo.replace_encoded(self.test_photos[1],
|
||||
self.test_file, title="Test")
|
||||
with open(self.test_file, "rb") as in_file:
|
||||
encoded_file = base64.b64encode(in_file.read())
|
||||
mock_post.assert_called_with("/photo/%s/replace.json"
|
||||
% self.test_photos[1].id,
|
||||
photo=encoded_file, title="Test")
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_replace_encoded(self, _):
|
||||
""" If photo.replace_encoded gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.replace_encoded(self.test_photos[0],
|
||||
self.test_file)
|
||||
def test_photo_replace_encoded_id(self, mock_post):
|
||||
"""
|
||||
Check that a photo can be uploaded using Base64 encoding to
|
||||
replace an existing photo using its ID.
|
||||
"""
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
result = self.client.photo.replace_encoded(self.test_photos[1].id,
|
||||
self.test_file, title="Test")
|
||||
with open(self.test_file, "rb") as in_file:
|
||||
encoded_file = base64.b64encode(in_file.read())
|
||||
mock_post.assert_called_with("/photo/%s/replace.json"
|
||||
% self.test_photos[1].id,
|
||||
photo=encoded_file, title="Test")
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_replace_encoded_id(self, _):
|
||||
""" If photo.replace_encoded gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.replace_encoded("1a", self.test_file)
|
||||
def test_photo_object_replace_encoded(self, mock_post):
|
||||
"""
|
||||
Check that a photo can be uploaded using Base64 encoding to
|
||||
replace an existing photo when using the Photo object directly.
|
||||
"""
|
||||
photo_id = self.test_photos[1].id
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
self.test_photos[1].replace_encoded(self.test_file, title="Test")
|
||||
with open(self.test_file, "rb") as in_file:
|
||||
encoded_file = base64.b64encode(in_file.read())
|
||||
mock_post.assert_called_with("/photo/%s/replace.json"
|
||||
% photo_id,
|
||||
photo=encoded_file, title="Test")
|
||||
self.assertEqual(self.test_photos[1].get_fields(),
|
||||
self.test_photos_dict[0])
|
||||
|
||||
class TestPhotoReplaceFromUrl(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_replace_from_url(self, mock_post):
|
||||
"""
|
||||
Check that a photo can be imported from a url to
|
||||
replace an existing photo.
|
||||
"""
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
result = self.client.photo.replace_from_url(self.test_photos[1],
|
||||
"test_url", title="Test")
|
||||
mock_post.assert_called_with("/photo/%s/replace.json"
|
||||
% self.test_photos[1].id,
|
||||
photo="test_url", title="Test")
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_replace_encoded(self, _):
|
||||
""" If photo.replace_encoded gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.test_photos[0].replace_encoded(photo_file=self.test_file)
|
||||
def test_photo_id_replace_from_url(self, mock_post):
|
||||
"""
|
||||
Check that a photo can be imported from a url to
|
||||
replace an existing photo using its ID.
|
||||
"""
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
result = self.client.photo.replace_from_url(self.test_photos[1].id,
|
||||
"test_url", title="Test")
|
||||
mock_post.assert_called_with("/photo/%s/replace.json"
|
||||
% self.test_photos[1].id,
|
||||
photo="test_url", title="Test")
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_replace_from_url(self, mock_post):
|
||||
"""
|
||||
Check that a photo can be imported from a url to
|
||||
replace an existing photo when using the Photo object directly.
|
||||
"""
|
||||
photo_id = self.test_photos[1].id
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
self.test_photos[1].replace_from_url("test_url", title="Test")
|
||||
mock_post.assert_called_with("/photo/%s/replace.json"
|
||||
% photo_id,
|
||||
photo="test_url", title="Test")
|
||||
self.assertEqual(self.test_photos[1].get_fields(),
|
||||
self.test_photos_dict[0])
|
||||
|
||||
class TestPhotoUpdate(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
|
@ -244,16 +362,30 @@ class TestPhotoView(TestPhotos):
|
|||
"""Check that a photo can be viewed"""
|
||||
mock_get.return_value = self._return_value(self.test_photos_dict[1])
|
||||
result = self.client.photo.view(self.test_photos[0],
|
||||
options={"foo": "bar",
|
||||
"test1": "test2"},
|
||||
returnSizes="20x20")
|
||||
mock_get.assert_called_with("/photo/1a/view.json", returnSizes="20x20")
|
||||
# Dict elemet can be in any order
|
||||
self.assertIn(mock_get.call_args[0],
|
||||
[("/photo/1a/foo-bar/test1-test2/view.json",),
|
||||
("/photo/1a/test1-test2/foo-bar/view.json",)])
|
||||
self.assertEqual(mock_get.call_args[1], {"returnSizes": "20x20"})
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_photo_view_id(self, mock_get):
|
||||
"""Check that a photo can be viewed using its ID"""
|
||||
mock_get.return_value = self._return_value(self.test_photos_dict[1])
|
||||
result = self.client.photo.view("1a", returnSizes="20x20")
|
||||
mock_get.assert_called_with("/photo/1a/view.json", returnSizes="20x20")
|
||||
result = self.client.photo.view("1a",
|
||||
options={"foo": "bar",
|
||||
"test1": "test2"},
|
||||
returnSizes="20x20")
|
||||
|
||||
# Dict elemet can be in any order
|
||||
self.assertIn(mock_get.call_args[0],
|
||||
[("/photo/1a/foo-bar/test1-test2/view.json",),
|
||||
("/photo/1a/test1-test2/foo-bar/view.json",)])
|
||||
self.assertEqual(mock_get.call_args[1], {"returnSizes": "20x20"})
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
|
@ -264,8 +396,14 @@ class TestPhotoView(TestPhotos):
|
|||
"""
|
||||
mock_get.return_value = self._return_value(self.test_photos_dict[1])
|
||||
photo = self.test_photos[0]
|
||||
photo.view(returnSizes="20x20")
|
||||
mock_get.assert_called_with("/photo/1a/view.json", returnSizes="20x20")
|
||||
photo.view(returnSizes="20x20", options={"foo": "bar",
|
||||
"test1": "test2"})
|
||||
|
||||
# Dict elemet can be in any order
|
||||
self.assertIn(mock_get.call_args[0],
|
||||
[("/photo/1a/foo-bar/test1-test2/view.json",),
|
||||
("/photo/1a/test1-test2/foo-bar/view.json",)])
|
||||
self.assertEqual(mock_get.call_args[1], {"returnSizes": "20x20"})
|
||||
self.assertEqual(photo.get_fields(), self.test_photos_dict[1])
|
||||
|
||||
class TestPhotoUpload(TestPhotos):
|
||||
|
@ -284,6 +422,7 @@ class TestPhotoUpload(TestPhotos):
|
|||
self.assertIn("photo", files)
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
class TestPhotoUploadEncoded(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_upload_encoded(self, mock_post):
|
||||
"""Check that a photo can be uploaded using Base64 encoding"""
|
||||
|
@ -295,24 +434,17 @@ class TestPhotoUpload(TestPhotos):
|
|||
photo=encoded_file, title="Test")
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
class TestPhotoDynamicUrl(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_photo_dynamic_url(self, _):
|
||||
""" If photo.dynamic_url gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.dynamic_url(self.test_photos[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_photo_dynamic_url_id(self, _):
|
||||
""" If photo.dynamic_url gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.client.photo.dynamic_url("1a")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_photo_object_dynamic_url(self, _):
|
||||
""" If photo.dynamic_url gets implemented, write a test! """
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.test_photos[0].dynamic_url()
|
||||
class TestPhotoUploadFromUrl(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_upload_from_url(self, mock_post):
|
||||
"""
|
||||
Check that a photo can be imported from a url.
|
||||
"""
|
||||
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||
result = self.client.photo.upload_from_url("test_url", title="Test")
|
||||
mock_post.assert_called_with("/photo/upload.json",
|
||||
photo="test_url", title="Test")
|
||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||
|
||||
class TestPhotoNextPrevious(TestPhotos):
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
|
@ -321,8 +453,15 @@ class TestPhotoNextPrevious(TestPhotos):
|
|||
mock_get.return_value = self._return_value(
|
||||
{"next": [self.test_photos_dict[0]],
|
||||
"previous": [self.test_photos_dict[1]]})
|
||||
result = self.client.photo.next_previous(self.test_photos[0])
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
||||
result = self.client.photo.next_previous(self.test_photos[0],
|
||||
options={"foo": "bar",
|
||||
"test1": "test2"},
|
||||
foo="bar")
|
||||
# Dict elemet can be in any order
|
||||
self.assertIn(mock_get.call_args[0],
|
||||
[("/photo/1a/nextprevious/foo-bar/test1-test2.json",),
|
||||
("/photo/1a/nextprevious/test1-test2/foo-bar.json",)])
|
||||
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
|
||||
self.assertEqual(result["next"][0].get_fields(),
|
||||
self.test_photos_dict[0])
|
||||
self.assertEqual(result["previous"][0].get_fields(),
|
||||
|
@ -337,8 +476,15 @@ class TestPhotoNextPrevious(TestPhotos):
|
|||
mock_get.return_value = self._return_value(
|
||||
{"next": [self.test_photos_dict[0]],
|
||||
"previous": [self.test_photos_dict[1]]})
|
||||
result = self.client.photo.next_previous("1a")
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
||||
result = self.client.photo.next_previous("1a",
|
||||
options={"foo": "bar",
|
||||
"test1": "test2"},
|
||||
foo="bar")
|
||||
# Dict elemet can be in any order
|
||||
self.assertIn(mock_get.call_args[0],
|
||||
[("/photo/1a/nextprevious/foo-bar/test1-test2.json",),
|
||||
("/photo/1a/nextprevious/test1-test2/foo-bar.json",)])
|
||||
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
|
||||
self.assertEqual(result["next"][0].get_fields(),
|
||||
self.test_photos_dict[0])
|
||||
self.assertEqual(result["previous"][0].get_fields(),
|
||||
|
@ -353,8 +499,14 @@ class TestPhotoNextPrevious(TestPhotos):
|
|||
mock_get.return_value = self._return_value(
|
||||
{"next": [self.test_photos_dict[0]],
|
||||
"previous": [self.test_photos_dict[1]]})
|
||||
result = self.test_photos[0].next_previous()
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
||||
result = self.test_photos[0].next_previous(options={"foo": "bar",
|
||||
"test1": "test2"},
|
||||
foo="bar")
|
||||
# Dict elemet can be in any order
|
||||
self.assertIn(mock_get.call_args[0],
|
||||
[("/photo/1a/nextprevious/foo-bar/test1-test2.json",),
|
||||
("/photo/1a/nextprevious/test1-test2/foo-bar.json",)])
|
||||
self.assertEqual(mock_get.call_args[1], {"foo": "bar"})
|
||||
self.assertEqual(result["next"][0].get_fields(),
|
||||
self.test_photos_dict[0])
|
||||
self.assertEqual(result["previous"][0].get_fields(),
|
||||
|
@ -365,8 +517,10 @@ class TestPhotoNextPrevious(TestPhotos):
|
|||
"""Check that the next photos are returned"""
|
||||
mock_get.return_value = self._return_value(
|
||||
{"next": [self.test_photos_dict[0]]})
|
||||
result = self.client.photo.next_previous(self.test_photos[0])
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
||||
result = self.client.photo.next_previous(self.test_photos[0],
|
||||
foo="bar")
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json",
|
||||
foo="bar")
|
||||
self.assertEqual(result["next"][0].get_fields(),
|
||||
self.test_photos_dict[0])
|
||||
self.assertNotIn("previous", result)
|
||||
|
@ -376,8 +530,10 @@ class TestPhotoNextPrevious(TestPhotos):
|
|||
"""Check that the previous photos are returned"""
|
||||
mock_get.return_value = self._return_value(
|
||||
{"previous": [self.test_photos_dict[1]]})
|
||||
result = self.client.photo.next_previous(self.test_photos[0])
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
||||
result = self.client.photo.next_previous(self.test_photos[0],
|
||||
foo="bar")
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json",
|
||||
foo="bar")
|
||||
self.assertEqual(result["previous"][0].get_fields(),
|
||||
self.test_photos_dict[1])
|
||||
self.assertNotIn("next", result)
|
||||
|
@ -388,8 +544,10 @@ class TestPhotoNextPrevious(TestPhotos):
|
|||
mock_get.return_value = self._return_value(
|
||||
{"next": [self.test_photos_dict[0], self.test_photos_dict[0]],
|
||||
"previous": [self.test_photos_dict[1], self.test_photos_dict[1]]})
|
||||
result = self.client.photo.next_previous(self.test_photos[0])
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
||||
result = self.client.photo.next_previous(self.test_photos[0],
|
||||
foo="bar")
|
||||
mock_get.assert_called_with("/photo/1a/nextprevious.json",
|
||||
foo="bar")
|
||||
self.assertEqual(result["next"][0].get_fields(),
|
||||
self.test_photos_dict[0])
|
||||
self.assertEqual(result["next"][1].get_fields(),
|
||||
|
@ -427,3 +585,96 @@ class TestPhotoTransform(TestPhotos):
|
|||
photo.transform(rotate="90")
|
||||
mock_post.assert_called_with("/photo/1a/transform.json", rotate="90")
|
||||
self.assertEqual(photo.get_fields(), self.test_photos_dict[1])
|
||||
|
||||
class TestPhotoObject(TestPhotos):
|
||||
def test_photo_object_repr_without_id_or_name(self):
|
||||
"""
|
||||
Ensure the string representation on an object includes its class name
|
||||
if the ID and Name attributes don't exist.
|
||||
"""
|
||||
photo = trovebox.objects.photo.Photo(self.client, {})
|
||||
self.assertEqual(repr(photo), "<Photo>")
|
||||
|
||||
def test_photo_object_repr_with_id(self):
|
||||
""" Ensure the string representation on an object includes its id, if present """
|
||||
photo = trovebox.objects.photo.Photo(self.client, {"id": "Test ID"})
|
||||
self.assertEqual(repr(photo), "<Photo id='Test ID'>")
|
||||
|
||||
def test_photo_object_repr_with_id_and_name(self):
|
||||
""" Ensure the string representation on an object includes its name, if present """
|
||||
photo = trovebox.objects.photo.Photo(self.client, {"id": "Test ID",
|
||||
"name": "Test Name"})
|
||||
self.assertEqual(repr(photo), "<Photo name='Test Name'>")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_create_attribute(self, _):
|
||||
"""
|
||||
Check that attributes are created when creating a
|
||||
Photo object
|
||||
"""
|
||||
photo = trovebox.objects.photo.Photo(self.client, {"attribute": "test"})
|
||||
self.assertEqual(photo.attribute, "test")
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_delete_attribute(self, _):
|
||||
"""
|
||||
Check that attributes are deleted when creating a
|
||||
Photo object
|
||||
"""
|
||||
photo = trovebox.objects.photo.Photo(self.client, {"attribute": "test"})
|
||||
photo.delete()
|
||||
with self.assertRaises(AttributeError):
|
||||
value = photo.attribute
|
||||
self.assertEqual(photo.get_fields(), {})
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_update_attribute(self, mock_post):
|
||||
"""
|
||||
Check that attributes are updated when creating a
|
||||
Photo object
|
||||
"""
|
||||
photo = trovebox.objects.photo.Photo(self.client, {"attribute": "test"})
|
||||
mock_post.return_value = self._return_value({"attribute": "test2"})
|
||||
photo.update()
|
||||
self.assertEqual(photo.attribute, "test2")
|
||||
self.assertEqual(photo.get_fields(), {"attribute": "test2"})
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_create_illegal_attribute(self, _):
|
||||
"""
|
||||
Check that illegal attributes are ignored when creating a
|
||||
Photo object
|
||||
"""
|
||||
photo = trovebox.objects.photo.Photo(self.client, {"_illegal_attribute": "test"})
|
||||
# The object's attribute shouldn't be created
|
||||
with self.assertRaises(AttributeError):
|
||||
value = photo._illegal_attribute
|
||||
# The field dict gets created correctly, however.
|
||||
self.assertEqual(photo.get_fields(), {"_illegal_attribute": "test"})
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_delete_illegal_attribute(self, _):
|
||||
"""
|
||||
Check that illegal attributes are ignored when deleting a
|
||||
Photo object
|
||||
"""
|
||||
photo = trovebox.objects.photo.Photo(self.client, {"_illegal_attribute": "test"})
|
||||
photo.delete()
|
||||
with self.assertRaises(AttributeError):
|
||||
value = photo._illegal_attribute
|
||||
self.assertEqual(photo.get_fields(), {})
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_photo_object_update_illegal_attribute(self, mock_post):
|
||||
"""
|
||||
Check that illegal attributes are ignored when updating a
|
||||
Photo object
|
||||
"""
|
||||
photo = trovebox.objects.photo.Photo(self.client, {"_illegal_attribute": "test"})
|
||||
mock_post.return_value = self._return_value({"_illegal_attribute": "test2"})
|
||||
photo.update()
|
||||
# The object's attribute shouldn't be created
|
||||
with self.assertRaises(AttributeError):
|
||||
value = photo._illegal_attribute
|
||||
# The field dict gets updated correctly, however.
|
||||
self.assertEqual(photo.get_fields(), {"_illegal_attribute": "test2"})
|
||||
|
|
66
tests/unit/test_system.py
Normal file
66
tests/unit/test_system.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
from __future__ import unicode_literals
|
||||
import json
|
||||
import httpretty
|
||||
from httpretty import GET
|
||||
|
||||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
import trovebox
|
||||
|
||||
class TestSystem(unittest.TestCase):
|
||||
test_host = "test.example.com"
|
||||
|
||||
def setUp(self):
|
||||
self.client = trovebox.Trovebox(host=self.test_host)
|
||||
|
||||
@staticmethod
|
||||
def _return_value(result, message="", code=200):
|
||||
return json.dumps({"message": message, "code": code, "result": result})
|
||||
|
||||
class TestSystemVersion(TestSystem):
|
||||
test_result = {"api": "v2",
|
||||
"database": "2.0.0"}
|
||||
|
||||
@httpretty.activate
|
||||
def test_version(self):
|
||||
"""Check that the version dictionary is returned correctly"""
|
||||
httpretty.register_uri(GET, uri="http://test.example.com/system/version.json",
|
||||
body=self._return_value(self.test_result),
|
||||
status=200)
|
||||
response = self.client.system.version()
|
||||
|
||||
self.assertEqual(response, self.test_result)
|
||||
|
||||
class TestSystemDiagnostics(TestSystem):
|
||||
test_result = {'database': [{'label': 'failure',
|
||||
'message': 'Could not properly connect to the database.',
|
||||
'status': False}],
|
||||
}
|
||||
|
||||
@httpretty.activate
|
||||
def test_diagnostics_pass(self):
|
||||
"""Check that the diagnostics dictionary is returned correctly on success"""
|
||||
httpretty.register_uri(GET, uri="http://test.example.com/system/diagnostics.json",
|
||||
body=self._return_value(self.test_result),
|
||||
status=200)
|
||||
response = self.client.system.diagnostics()
|
||||
|
||||
self.assertEqual(response, self.test_result)
|
||||
|
||||
@httpretty.activate
|
||||
def test_diagnostics_fail(self):
|
||||
"""
|
||||
Check that the diagnostics dictionary is returned correctly on failure.
|
||||
Although the JSON code is 500, no exception should be raised.
|
||||
"""
|
||||
# On failure, the diagnostics endpoint returns a JSON code of 500
|
||||
# and a response status code of 200.
|
||||
httpretty.register_uri(GET, uri="http://test.example.com/system/diagnostics.json",
|
||||
body=self._return_value(self.test_result, code=500),
|
||||
status=200)
|
||||
response = self.client.system.diagnostics()
|
||||
|
||||
self.assertEqual(response, self.test_result)
|
|
@ -15,7 +15,7 @@ class TestTags(unittest.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.client = trovebox.Trovebox(host=self.test_host)
|
||||
self.test_tags = [trovebox.objects.Tag(self.client, tag)
|
||||
self.test_tags = [trovebox.objects.tag.Tag(self.client, tag)
|
||||
for tag in self.test_tags_dict]
|
||||
|
||||
@staticmethod
|
||||
|
@ -25,61 +25,70 @@ class TestTags(unittest.TestCase):
|
|||
class TestTagsList(TestTags):
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_tags_list(self, mock_get):
|
||||
"""Check that the the tag list is returned correctly"""
|
||||
"""Check that the tag list is returned correctly"""
|
||||
mock_get.return_value = self._return_value(self.test_tags_dict)
|
||||
result = self.client.tags.list()
|
||||
mock_get.assert_called_with("/tags/list.json")
|
||||
result = self.client.tags.list(foo="bar")
|
||||
mock_get.assert_called_with("/tags/list.json", foo="bar")
|
||||
self.assertEqual(len(result), 2)
|
||||
self.assertEqual(result[0].id, "tag1")
|
||||
self.assertEqual(result[0].count, 11)
|
||||
self.assertEqual(result[1].id, "tag2")
|
||||
self.assertEqual(result[1].count, 5)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_empty_result(self, mock_get):
|
||||
"""Check that an empty result is transformed into an empty list """
|
||||
mock_get.return_value = self._return_value("")
|
||||
result = self.client.tags.list(foo="bar")
|
||||
mock_get.assert_called_with("/tags/list.json", foo="bar")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||
def test_zero_rows(self, mock_get):
|
||||
"""Check that totalRows=0 is transformed into an empty list """
|
||||
mock_get.return_value = self._return_value([{"totalRows": 0}])
|
||||
result = self.client.tags.list(foo="bar")
|
||||
mock_get.assert_called_with("/tags/list.json", foo="bar")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
class TestTagCreate(TestTags):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_tag_create(self, mock_post):
|
||||
"""Check that a tag can be created"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.tag.create("test", foo="bar")
|
||||
mock_post.assert_called_with("/tag/create.json", tag="test",
|
||||
foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
class TestTagDelete(TestTags):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_tag_delete(self, mock_post):
|
||||
"""Check that a tag can be deleted"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.tag.delete(self.test_tags[0])
|
||||
mock_post.assert_called_with("/tag/tag1/delete.json")
|
||||
result = self.client.tag.delete(self.test_tags[0], foo="bar")
|
||||
mock_post.assert_called_with("/tag/tag1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_tag_delete_id(self, mock_post):
|
||||
"""Check that a tag can be deleted using its ID"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
result = self.client.tag.delete("tag1")
|
||||
mock_post.assert_called_with("/tag/tag1/delete.json")
|
||||
result = self.client.tag.delete("tag1", foo="bar")
|
||||
mock_post.assert_called_with("/tag/tag1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_tag_delete_failure(self, mock_post):
|
||||
"""Check that an exception is raised if a tag cannot be deleted"""
|
||||
mock_post.return_value = self._return_value(False)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.client.tag.delete(self.test_tags[0])
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_tag_object_delete(self, mock_post):
|
||||
"""Check that a tag can be deleted when using the tag object directly"""
|
||||
mock_post.return_value = self._return_value(True)
|
||||
tag = self.test_tags[0]
|
||||
result = tag.delete()
|
||||
mock_post.assert_called_with("/tag/tag1/delete.json")
|
||||
result = tag.delete(foo="bar")
|
||||
mock_post.assert_called_with("/tag/tag1/delete.json", foo="bar")
|
||||
self.assertEqual(result, True)
|
||||
self.assertEqual(tag.get_fields(), {})
|
||||
self.assertEqual(tag.id, None)
|
||||
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_tag_object_delete_failure(self, mock_post):
|
||||
"""
|
||||
Check that an exception is raised if a tag cannot be deleted
|
||||
when using the tag object directly
|
||||
"""
|
||||
mock_post.return_value = self._return_value(False)
|
||||
with self.assertRaises(trovebox.TroveboxError):
|
||||
self.test_tags[0].delete()
|
||||
|
||||
class TestTagUpdate(TestTags):
|
||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||
def test_tag_update(self, mock_post):
|
||||
|
|
11
tox.ini
11
tox.ini
|
@ -1,5 +1,5 @@
|
|||
[tox]
|
||||
envlist = py26, py27, py33
|
||||
envlist = py26, py27, py33, coverage
|
||||
|
||||
[testenv]
|
||||
commands = python -m unittest discover tests/unit
|
||||
|
@ -18,3 +18,12 @@ deps =
|
|||
ddt >= 0.3.0
|
||||
unittest2
|
||||
discover
|
||||
|
||||
[testenv:coverage]
|
||||
commands = coverage run --source trovebox setup.py test
|
||||
deps =
|
||||
mock >= 1.0.0
|
||||
# Hold httpretty at 0.6.5 until https://github.com/gabrielfalcao/HTTPretty/issues/114 is resolved
|
||||
httpretty == 0.6.5
|
||||
ddt >= 0.3.0
|
||||
coverage
|
||||
|
|
181
trovebox/.pylint-disable.patch
Normal file
181
trovebox/.pylint-disable.patch
Normal file
|
@ -0,0 +1,181 @@
|
|||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/api/api_activity.py patched/api/api_activity.py
|
||||
--- original/api/api_activity.py
|
||||
+++ patched/api/api_activity.py
|
||||
@@ -32,7 +32,7 @@
|
||||
"""
|
||||
return self._client.post("/activities/purge.json", **kwds)["result"]
|
||||
|
||||
-class ApiActivity(ApiBase):
|
||||
+class ApiActivity(ApiBase): # pylint: disable=too-few-public-methods
|
||||
""" Definitions of /activity/ API endpoints """
|
||||
def view(self, activity, **kwds):
|
||||
"""
|
||||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/api/api_album.py patched/api/api_album.py
|
||||
--- original/api/api_album.py
|
||||
+++ patched/api/api_album.py
|
||||
@@ -7,7 +7,7 @@
|
||||
from trovebox.objects.album import Album
|
||||
from .api_base import ApiBase
|
||||
|
||||
-class ApiAlbums(ApiBase):
|
||||
+class ApiAlbums(ApiBase): # pylint: disable=too-few-public-methods
|
||||
""" Definitions of /albums/ API endpoints """
|
||||
def list(self, **kwds):
|
||||
"""
|
||||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/api/api_base.py patched/api/api_base.py
|
||||
--- original/api/api_base.py
|
||||
+++ patched/api/api_base.py
|
||||
@@ -2,7 +2,7 @@
|
||||
api_base.py: Base class for all API classes
|
||||
"""
|
||||
|
||||
-class ApiBase(object):
|
||||
+class ApiBase(object): # pylint: disable=too-few-public-methods
|
||||
""" Base class for all API objects """
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/api/api_tag.py patched/api/api_tag.py
|
||||
--- original/api/api_tag.py
|
||||
+++ patched/api/api_tag.py
|
||||
@@ -2,14 +2,14 @@
|
||||
api_tag.py : Trovebox Tag API Classes
|
||||
"""
|
||||
try:
|
||||
- from urllib.parse import quote # Python3
|
||||
+ from urllib.parse import quote # Python3 # pylint: disable=import-error,no-name-in-module
|
||||
except ImportError:
|
||||
from urllib import quote # Python2
|
||||
|
||||
from trovebox.objects.tag import Tag
|
||||
from .api_base import ApiBase
|
||||
|
||||
-class ApiTags(ApiBase):
|
||||
+class ApiTags(ApiBase): # pylint: disable=too-few-public-methods
|
||||
""" Definitions of /tags/ API endpoints """
|
||||
def list(self, **kwds):
|
||||
"""
|
||||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/auth.py patched/auth.py
|
||||
--- original/auth.py
|
||||
+++ patched/auth.py
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
try:
|
||||
- from configparser import ConfigParser # Python3
|
||||
+ from configparser import ConfigParser # Python3 # pylint: disable=import-error
|
||||
except ImportError:
|
||||
from ConfigParser import SafeConfigParser as ConfigParser # Python2
|
||||
try:
|
||||
@@ -12,9 +12,9 @@
|
||||
except ImportError: # pragma: no cover
|
||||
import StringIO as io # Python2
|
||||
|
||||
-class Auth(object):
|
||||
+class Auth(object): # pylint: disable=too-few-public-methods
|
||||
"""OAuth secrets"""
|
||||
- def __init__(self, config_file, host,
|
||||
+ def __init__(self, config_file, host, # pylint: disable=too-many-arguments
|
||||
consumer_key, consumer_secret,
|
||||
token, token_secret):
|
||||
if host is None:
|
||||
@@ -69,7 +69,7 @@
|
||||
parser = ConfigParser()
|
||||
parser.optionxform = str # Case-sensitive options
|
||||
try:
|
||||
- parser.read_file(buf) # Python3
|
||||
+ parser.read_file(buf) # Python3 # pylint: disable=maybe-no-member
|
||||
except AttributeError:
|
||||
parser.readfp(buf) # Python2
|
||||
|
||||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/http.py patched/http.py
|
||||
--- original/http.py
|
||||
+++ patched/http.py
|
||||
@@ -7,7 +7,7 @@
|
||||
import requests_oauthlib
|
||||
import logging
|
||||
try:
|
||||
- from urllib.parse import urlparse, urlunparse # Python3
|
||||
+ from urllib.parse import urlparse, urlunparse # Python3 # pylint: disable=import-error,no-name-in-module
|
||||
except ImportError:
|
||||
from urlparse import urlparse, urlunparse # Python2
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
from .auth import Auth
|
||||
|
||||
if sys.version < '3':
|
||||
- TEXT_TYPE = unicode
|
||||
+ TEXT_TYPE = unicode # pylint: disable=invalid-name
|
||||
else: # pragma: no cover
|
||||
- TEXT_TYPE = str
|
||||
+ TEXT_TYPE = str # pylint: disable=invalid-name
|
||||
|
||||
DUPLICATE_RESPONSE = {"code": 409,
|
||||
"message": "This photo already exists"}
|
||||
@@ -37,7 +37,7 @@
|
||||
"ssl_verify" : True,
|
||||
}
|
||||
|
||||
- def __init__(self, config_file=None, host=None,
|
||||
+ def __init__(self, config_file=None, host=None, # pylint: disable=too-many-arguments
|
||||
consumer_key='', consumer_secret='',
|
||||
token='', token_secret='', api_version=None):
|
||||
|
||||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/__init__.py patched/__init__.py
|
||||
--- original/__init__.py
|
||||
+++ patched/__init__.py
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
LATEST_API_VERSION = 2
|
||||
|
||||
-class Trovebox(Http):
|
||||
+class Trovebox(Http): # pylint: disable=too-many-instance-attributes
|
||||
"""
|
||||
Client library for Trovebox
|
||||
If no parameters are specified, config is loaded from the default
|
||||
@@ -25,7 +25,7 @@
|
||||
This should be used to ensure that your application will continue to work
|
||||
even if the Trovebox API is updated to a new revision.
|
||||
"""
|
||||
- def __init__(self, config_file=None, host=None,
|
||||
+ def __init__(self, config_file=None, host=None, # pylint: disable=too-many-arguments
|
||||
consumer_key='', consumer_secret='',
|
||||
token='', token_secret='',
|
||||
api_version=None):
|
||||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/main.py patched/main.py
|
||||
--- original/main.py
|
||||
+++ patched/main.py
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#################################################################
|
||||
|
||||
-def main(args=sys.argv[1:]):
|
||||
+def main(args=sys.argv[1:]): # pylint: disable=too-many-branches
|
||||
"""Run the commandline script"""
|
||||
usage = "%prog --help"
|
||||
parser = OptionParser(usage, add_help_option=False)
|
||||
@@ -85,11 +85,11 @@
|
||||
sys.exit(1)
|
||||
|
||||
if options.method == "GET":
|
||||
- result = client.get(options.endpoint, process_response=False,
|
||||
+ result = client.get(options.endpoint, process_response=False, # pylint: disable=star-args
|
||||
**params)
|
||||
else:
|
||||
params, files = extract_files(params)
|
||||
- result = client.post(options.endpoint, process_response=False,
|
||||
+ result = client.post(options.endpoint, process_response=False, # pylint: disable=star-args
|
||||
files=files, **params)
|
||||
for file_ in files:
|
||||
files[file_].close()
|
||||
diff --unified --recursive '--exclude=.pylint-disable.patch' original/objects/trovebox_object.py patched/objects/trovebox_object.py
|
||||
--- original/objects/trovebox_object.py
|
||||
+++ patched/objects/trovebox_object.py
|
||||
@@ -5,7 +5,7 @@
|
||||
""" Base object supporting the storage of custom fields as attributes """
|
||||
_type = "None"
|
||||
def __init__(self, client, json_dict):
|
||||
- self.id = None
|
||||
+ self.id = None # pylint: disable=invalid-name
|
||||
self.name = None
|
||||
self._client = client
|
||||
self._json_dict = json_dict
|
|
@ -1,238 +0,0 @@
|
|||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/api_album.py patched/api_album.py
|
||||
--- original/api_album.py 2013-08-16 18:12:30.434212000 +0100
|
||||
+++ patched/api_album.py 2013-08-16 18:13:29.678506001 +0100
|
||||
@@ -3,7 +3,7 @@
|
||||
"""
|
||||
from .objects import Album
|
||||
|
||||
-class ApiAlbums(object):
|
||||
+class ApiAlbums(object): # pylint: disable=R0903,C0111
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
results = self._client.get("/albums/list.json", **kwds)["result"]
|
||||
return [Album(self._client, album) for album in results]
|
||||
|
||||
-class ApiAlbum(object):
|
||||
+class ApiAlbum(object): # pylint: disable=C0111
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/api_photo.py patched/api_photo.py
|
||||
--- original/api_photo.py 2013-08-16 18:12:30.434212000 +0100
|
||||
+++ patched/api_photo.py 2013-08-16 18:13:29.678506001 +0100
|
||||
@@ -20,7 +20,7 @@
|
||||
ids.append(photo)
|
||||
return ids
|
||||
|
||||
-class ApiPhotos(object):
|
||||
+class ApiPhotos(object): # pylint: disable=C0111
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
raise TroveboxError("Delete response returned False")
|
||||
return True
|
||||
|
||||
-class ApiPhoto(object):
|
||||
+class ApiPhoto(object): # pylint: disable=C0111
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/api_tag.py patched/api_tag.py
|
||||
--- original/api_tag.py 2013-08-16 18:12:30.434212000 +0100
|
||||
+++ patched/api_tag.py 2013-08-16 18:13:29.678506001 +0100
|
||||
@@ -3,7 +3,7 @@
|
||||
"""
|
||||
from .objects import Tag
|
||||
|
||||
-class ApiTags(object):
|
||||
+class ApiTags(object): # pylint: disable=R0903,C0111
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
results = self._client.get("/tags/list.json", **kwds)["result"]
|
||||
return [Tag(self._client, tag) for tag in results]
|
||||
|
||||
-class ApiTag(object):
|
||||
+class ApiTag(object): # pylint: disable=C0111
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/auth.py patched/auth.py
|
||||
--- original/auth.py 2013-08-16 18:13:24.966482000 +0100
|
||||
+++ patched/auth.py 2013-08-16 18:13:51.766615537 +0100
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
try:
|
||||
- from configparser import ConfigParser # Python3
|
||||
+ from configparser import ConfigParser # Python3 # pylint: disable=F0401
|
||||
except ImportError:
|
||||
from ConfigParser import SafeConfigParser as ConfigParser # Python2
|
||||
try:
|
||||
@@ -12,9 +12,9 @@
|
||||
except ImportError:
|
||||
import StringIO as io # Python2
|
||||
|
||||
-class Auth(object):
|
||||
+class Auth(object): # pylint: disable=R0903
|
||||
"""OAuth secrets"""
|
||||
- def __init__(self, config_file, host,
|
||||
+ def __init__(self, config_file, host, # pylint: disable=R0913
|
||||
consumer_key, consumer_secret,
|
||||
token, token_secret):
|
||||
if host is None:
|
||||
@@ -69,7 +69,7 @@
|
||||
parser = ConfigParser()
|
||||
parser.optionxform = str # Case-sensitive options
|
||||
try:
|
||||
- parser.read_file(buf) # Python3
|
||||
+ parser.read_file(buf) # Python3 # pylint: disable=E1103
|
||||
except AttributeError:
|
||||
parser.readfp(buf) # Python2
|
||||
|
||||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/http.py patched/http.py
|
||||
--- original/http.py 2013-08-16 17:54:30.688858000 +0100
|
||||
+++ patched/http.py 2013-08-16 18:14:14.106726301 +0100
|
||||
@@ -7,18 +7,18 @@
|
||||
import requests_oauthlib
|
||||
import logging
|
||||
try:
|
||||
- from urllib.parse import urlparse, urlunparse # Python3
|
||||
+ from urllib.parse import urlparse, urlunparse # Python3 # pylint: disable=F0401,E0611
|
||||
except ImportError:
|
||||
from urlparse import urlparse, urlunparse # Python2
|
||||
|
||||
from .objects import TroveboxObject
|
||||
-from .errors import *
|
||||
+from .errors import * # pylint: disable=W0401
|
||||
from .auth import Auth
|
||||
|
||||
if sys.version < '3':
|
||||
- TEXT_TYPE = unicode
|
||||
+ TEXT_TYPE = unicode # pylint: disable=C0103
|
||||
else:
|
||||
- TEXT_TYPE = str
|
||||
+ TEXT_TYPE = str # pylint: disable=C0103
|
||||
|
||||
DUPLICATE_RESPONSE = {"code": 409,
|
||||
"message": "This photo already exists"}
|
||||
@@ -37,7 +37,7 @@
|
||||
"ssl_verify" : True,
|
||||
}
|
||||
|
||||
- def __init__(self, config_file=None, host=None,
|
||||
+ def __init__(self, config_file=None, host=None, # pylint: disable=R0913
|
||||
consumer_key='', consumer_secret='',
|
||||
token='', token_secret='', api_version=None):
|
||||
|
||||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/__init__.py patched/__init__.py
|
||||
--- original/__init__.py 2013-08-16 18:12:30.438212000 +0100
|
||||
+++ patched/__init__.py 2013-08-16 18:13:29.678506001 +0100
|
||||
@@ -2,7 +2,7 @@
|
||||
__init__.py : Trovebox package top level
|
||||
"""
|
||||
from .http import Http
|
||||
-from .errors import *
|
||||
+from .errors import * # pylint: disable=W0401
|
||||
from ._version import __version__
|
||||
from . import api_photo
|
||||
from . import api_tag
|
||||
@@ -22,7 +22,7 @@
|
||||
This should be used to ensure that your application will continue to work
|
||||
even if the Trovebox API is updated to a new revision.
|
||||
"""
|
||||
- def __init__(self, config_file=None, host=None,
|
||||
+ def __init__(self, config_file=None, host=None, # pylint: disable=R0913
|
||||
consumer_key='', consumer_secret='',
|
||||
token='', token_secret='',
|
||||
api_version=None):
|
||||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/main.py patched/main.py
|
||||
--- original/main.py 2013-08-16 18:12:30.438212000 +0100
|
||||
+++ patched/main.py 2013-08-16 18:13:29.678506001 +0100
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#################################################################
|
||||
|
||||
-def main(args=sys.argv[1:]):
|
||||
+def main(args=sys.argv[1:]): # pylint: disable=R0912,C0111
|
||||
usage = "%prog --help"
|
||||
parser = OptionParser(usage, add_help_option=False)
|
||||
parser.add_option('-c', '--config', help="Configuration file to use",
|
||||
@@ -84,13 +84,13 @@
|
||||
sys.exit(1)
|
||||
|
||||
if options.method == "GET":
|
||||
- result = client.get(options.endpoint, process_response=False,
|
||||
+ result = client.get(options.endpoint, process_response=False, # pylint: disable=W0142
|
||||
**params)
|
||||
else:
|
||||
params, files = extract_files(params)
|
||||
- result = client.post(options.endpoint, process_response=False,
|
||||
+ result = client.post(options.endpoint, process_response=False, # pylint: disable=W0142
|
||||
files=files, **params)
|
||||
- for f in files:
|
||||
+ for f in files: # pylint: disable=C0103
|
||||
files[f].close()
|
||||
|
||||
if options.verbose:
|
||||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/objects.py patched/objects.py
|
||||
--- original/objects.py 2013-08-16 18:12:30.438212000 +0100
|
||||
+++ patched/objects.py 2013-08-16 18:13:29.682506021 +0100
|
||||
@@ -2,16 +2,16 @@
|
||||
objects.py : Basic Trovebox API Objects
|
||||
"""
|
||||
try:
|
||||
- from urllib.parse import quote # Python3
|
||||
+ from urllib.parse import quote # Python3 # pylint: disable=F0401,E0611
|
||||
except ImportError:
|
||||
from urllib import quote # Python2
|
||||
|
||||
from .errors import TroveboxError
|
||||
|
||||
-class TroveboxObject(object):
|
||||
+class TroveboxObject(object): # pylint: disable=R0903
|
||||
""" Base object supporting the storage of custom fields as attributes """
|
||||
def __init__(self, trovebox, json_dict):
|
||||
- self.id = None
|
||||
+ self.id = None # pylint: disable=C0103
|
||||
self.name = None
|
||||
self._trovebox = trovebox
|
||||
self._json_dict = json_dict
|
||||
@@ -57,7 +57,7 @@
|
||||
return self._json_dict
|
||||
|
||||
|
||||
-class Photo(TroveboxObject):
|
||||
+class Photo(TroveboxObject): # pylint: disable=C0111
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Delete this photo.
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
self._replace_fields(new_dict)
|
||||
|
||||
-class Tag(TroveboxObject):
|
||||
+class Tag(TroveboxObject): # pylint: disable=C0111
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Delete this tag.
|
||||
@@ -168,7 +168,7 @@
|
||||
self._replace_fields(new_dict)
|
||||
|
||||
|
||||
-class Album(TroveboxObject):
|
||||
+class Album(TroveboxObject): # pylint: disable=C0111
|
||||
def __init__(self, trovebox, json_dict):
|
||||
self.photos = None
|
||||
self.cover = None
|
||||
diff --unified --recursive '--exclude=.pylint-ignores.patch' original/_version.py patched/_version.py
|
||||
--- original/_version.py 2013-08-16 18:12:30.438212000 +0100
|
||||
+++ patched/_version.py 2013-08-16 18:13:29.682506021 +0100
|
||||
@@ -1,2 +1,2 @@
|
||||
-
|
||||
+ # pylint: disable=C0111
|
||||
__version__ = "0.4"
|
|
@ -2,11 +2,14 @@
|
|||
__init__.py : Trovebox package top level
|
||||
"""
|
||||
from .http import Http
|
||||
from .errors import *
|
||||
from .errors import TroveboxError, TroveboxDuplicateError, Trovebox404Error
|
||||
from ._version import __version__
|
||||
from . import api_photo
|
||||
from . import api_tag
|
||||
from . import api_album
|
||||
from trovebox.api import api_photo
|
||||
from trovebox.api import api_tag
|
||||
from trovebox.api import api_album
|
||||
from trovebox.api import api_action
|
||||
from trovebox.api import api_activity
|
||||
from trovebox.api import api_system
|
||||
|
||||
LATEST_API_VERSION = 2
|
||||
|
||||
|
@ -36,3 +39,7 @@ class Trovebox(Http):
|
|||
self.tag = api_tag.ApiTag(self)
|
||||
self.albums = api_album.ApiAlbums(self)
|
||||
self.album = api_album.ApiAlbum(self)
|
||||
self.action = api_action.ApiAction(self)
|
||||
self.activities = api_activity.ApiActivities(self)
|
||||
self.activity = api_activity.ApiActivity(self)
|
||||
self.system = api_system.ApiSystem(self)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
|
||||
__version__ = "0.5.1"
|
||||
"""Current version string"""
|
||||
__version__ = "0.6"
|
||||
|
|
4
trovebox/api/__init__.py
Normal file
4
trovebox/api/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
trovebox.api Package
|
||||
Definitions for each of the Trovebox API endpoints
|
||||
"""
|
55
trovebox/api/api_action.py
Normal file
55
trovebox/api/api_action.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
api_action.py : Trovebox Action API Classes
|
||||
"""
|
||||
from trovebox.objects.action import Action
|
||||
from .api_base import ApiBase
|
||||
|
||||
class ApiAction(ApiBase):
|
||||
""" Definitions of /action/ API endpoints """
|
||||
def create(self, target, target_type=None, **kwds):
|
||||
"""
|
||||
Endpoint: /action/<target_id>/<target_type>/create.json
|
||||
|
||||
Creates a new action and returns it.
|
||||
The target parameter can either be an id or a Trovebox object.
|
||||
If a Trovebox object is used, the target type is inferred
|
||||
automatically.
|
||||
"""
|
||||
# Extract the target type
|
||||
if target_type is None:
|
||||
target_type = target.get_type()
|
||||
|
||||
# Extract the target ID
|
||||
try:
|
||||
target_id = target.id
|
||||
except AttributeError:
|
||||
target_id = target
|
||||
|
||||
result = self._client.post("/action/%s/%s/create.json" %
|
||||
(target_id, target_type),
|
||||
**kwds)["result"]
|
||||
return Action(self._client, result)
|
||||
|
||||
def delete(self, action, **kwds):
|
||||
"""
|
||||
Endpoint: /action/<id>/delete.json
|
||||
|
||||
Deletes an action.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
return self._client.post("/action/%s/delete.json" %
|
||||
self._extract_id(action),
|
||||
**kwds)["result"]
|
||||
|
||||
def view(self, action, **kwds):
|
||||
"""
|
||||
Endpoint: /action/<id>/view.json
|
||||
|
||||
Requests all properties of an action.
|
||||
Returns the requested action object.
|
||||
"""
|
||||
result = self._client.get("/action/%s/view.json" %
|
||||
self._extract_id(action),
|
||||
**kwds)["result"]
|
||||
return Action(self._client, result)
|
51
trovebox/api/api_activity.py
Normal file
51
trovebox/api/api_activity.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
"""
|
||||
api_activity.py : Trovebox Activity API Classes
|
||||
"""
|
||||
import json
|
||||
from trovebox.objects.activity import Activity
|
||||
from .api_base import ApiBase
|
||||
|
||||
class ApiActivities(ApiBase):
|
||||
""" Definitions of /activities/ API endpoints """
|
||||
def list(self, options=None, **kwds):
|
||||
"""
|
||||
Endpoint: /activities[/<options>]/list.json
|
||||
|
||||
Returns a list of Activity objects.
|
||||
The options parameter can be used to narrow down the activities.
|
||||
Eg: options={"type": "photo-upload"}
|
||||
"""
|
||||
option_string = self._build_option_string(options)
|
||||
activities = self._client.get("/activities%s/list.json" % option_string,
|
||||
**kwds)["result"]
|
||||
activities = self._result_to_list(activities)
|
||||
return [Activity(self._client, activity) for activity in activities]
|
||||
|
||||
def purge(self, **kwds):
|
||||
"""
|
||||
Endpoint: /activities/purge.json
|
||||
|
||||
Purges all activities.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
Currently not working due to frontend issue #1368.
|
||||
"""
|
||||
return self._client.post("/activities/purge.json", **kwds)["result"]
|
||||
|
||||
class ApiActivity(ApiBase):
|
||||
""" Definitions of /activity/ API endpoints """
|
||||
def view(self, activity, **kwds):
|
||||
"""
|
||||
Endpoint: /activity/<id>/view.json
|
||||
|
||||
Requests all properties of an activity.
|
||||
Returns the requested activity object.
|
||||
"""
|
||||
result = self._client.get("/activity/%s/view.json" %
|
||||
self._extract_id(activity),
|
||||
**kwds)["result"]
|
||||
|
||||
# TBD: Why is the result enclosed/encoded like this?
|
||||
result = result["0"]
|
||||
result["data"] = json.loads(result["data"])
|
||||
return Activity(self._client, result)
|
152
trovebox/api/api_album.py
Normal file
152
trovebox/api/api_album.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
"""
|
||||
api_album.py : Trovebox Album API Classes
|
||||
"""
|
||||
import collections
|
||||
|
||||
from trovebox.objects.trovebox_object import TroveboxObject
|
||||
from trovebox.objects.album import Album
|
||||
from .api_base import ApiBase
|
||||
|
||||
class ApiAlbums(ApiBase):
|
||||
""" Definitions of /albums/ API endpoints """
|
||||
def list(self, **kwds):
|
||||
"""
|
||||
Endpoint: /albums/list.json
|
||||
|
||||
Returns a list of Album objects.
|
||||
"""
|
||||
albums = self._client.get("/albums/list.json", **kwds)["result"]
|
||||
albums = self._result_to_list(albums)
|
||||
return [Album(self._client, album) for album in albums]
|
||||
|
||||
class ApiAlbum(ApiBase):
|
||||
""" Definitions of /album/ API endpoints """
|
||||
def cover_update(self, album, photo, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<album_id>/cover/<photo_id>/update.json
|
||||
|
||||
Update the cover photo of an album.
|
||||
Returns the updated album object.
|
||||
"""
|
||||
result = self._client.post("/album/%s/cover/%s/update.json" %
|
||||
(self._extract_id(album),
|
||||
self._extract_id(photo)),
|
||||
**kwds)["result"]
|
||||
|
||||
# API currently doesn't return the updated album
|
||||
# (frontend issue #1369)
|
||||
if isinstance(result, bool): # pragma: no cover
|
||||
result = self._client.get("/album/%s/view.json" %
|
||||
self._extract_id(album))["result"]
|
||||
|
||||
return Album(self._client, result)
|
||||
|
||||
def create(self, name, **kwds):
|
||||
"""
|
||||
Endpoint: /album/create.json
|
||||
|
||||
Creates a new album and returns it.
|
||||
"""
|
||||
result = self._client.post("/album/create.json",
|
||||
name=name, **kwds)["result"]
|
||||
return Album(self._client, result)
|
||||
|
||||
def delete(self, album, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/delete.json
|
||||
|
||||
Deletes an album.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
return self._client.post("/album/%s/delete.json" %
|
||||
self._extract_id(album),
|
||||
**kwds)["result"]
|
||||
|
||||
def add(self, album, objects, object_type=None, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/<type>/add.json
|
||||
|
||||
Add objects (eg. Photos) to an album.
|
||||
The objects are a list of either IDs or Trovebox objects.
|
||||
If Trovebox objects are used, the object type is inferred
|
||||
automatically.
|
||||
Returns the updated album object.
|
||||
"""
|
||||
return self._add_remove("add", album, objects, object_type,
|
||||
**kwds)
|
||||
|
||||
def remove(self, album, objects, object_type=None, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/<type>/remove.json
|
||||
|
||||
Remove objects (eg. Photos) to an album.
|
||||
The objects are a list of either IDs or Trovebox objects.
|
||||
If Trovebox objects are used, the object type is inferred
|
||||
automatically.
|
||||
Returns the updated album object.
|
||||
"""
|
||||
return self._add_remove("remove", album, objects, object_type,
|
||||
**kwds)
|
||||
|
||||
def _add_remove(self, action, album, objects, object_type=None,
|
||||
**kwds):
|
||||
"""Common code for the add and remove endpoints."""
|
||||
# Ensure we have an iterable of objects
|
||||
if not isinstance(objects, collections.Iterable):
|
||||
objects = [objects]
|
||||
|
||||
# Extract the type of the objects
|
||||
if object_type is None:
|
||||
object_type = objects[0].get_type()
|
||||
|
||||
for i, obj in enumerate(objects):
|
||||
if isinstance(obj, TroveboxObject):
|
||||
# Ensure all objects are the same type
|
||||
if obj.get_type() != object_type:
|
||||
raise ValueError("Not all objects are of type '%s'"
|
||||
% object_type)
|
||||
# Extract the ids of the objects
|
||||
objects[i] = obj.id
|
||||
|
||||
result = self._client.post("/album/%s/%s/%s.json" %
|
||||
(self._extract_id(album),
|
||||
object_type, action),
|
||||
ids=objects, **kwds)["result"]
|
||||
|
||||
# API currently doesn't return the updated album
|
||||
# (frontend issue #1369)
|
||||
if isinstance(result, bool): # pragma: no cover
|
||||
result = self._client.get("/album/%s/view.json" %
|
||||
self._extract_id(album))["result"]
|
||||
return Album(self._client, result)
|
||||
|
||||
def update(self, album, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/update.json
|
||||
|
||||
Updates an album with the specified parameters.
|
||||
Returns the updated album object.
|
||||
"""
|
||||
result = self._client.post("/album/%s/update.json" %
|
||||
self._extract_id(album),
|
||||
**kwds)["result"]
|
||||
|
||||
# APIv1 doesn't return the updated album (frontend issue #937)
|
||||
if isinstance(result, bool): # pragma: no cover
|
||||
result = self._client.get("/album/%s/view.json" %
|
||||
self._extract_id(album))["result"]
|
||||
|
||||
return Album(self._client, result)
|
||||
|
||||
def view(self, album, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/view.json
|
||||
|
||||
Requests all properties of an album.
|
||||
Returns the requested album object.
|
||||
"""
|
||||
result = self._client.get("/album/%s/view.json" %
|
||||
self._extract_id(album),
|
||||
**kwds)["result"]
|
||||
return Album(self._client, result)
|
38
trovebox/api/api_base.py
Normal file
38
trovebox/api/api_base.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
"""
|
||||
api_base.py: Base class for all API classes
|
||||
"""
|
||||
|
||||
class ApiBase(object):
|
||||
""" Base class for all API objects """
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
@staticmethod
|
||||
def _build_option_string(options):
|
||||
"""
|
||||
:param options: dictionary containing the options
|
||||
:returns: option_string formatted for an API endpoint
|
||||
"""
|
||||
option_string = ""
|
||||
if options is not None:
|
||||
for key in options:
|
||||
option_string += "/%s-%s" % (key, options[key])
|
||||
return option_string
|
||||
|
||||
@staticmethod
|
||||
def _extract_id(obj):
|
||||
""" Return obj.id, or obj if the object doesn't have an ID """
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def _result_to_list(result):
|
||||
""" Handle the case where the result contains no items """
|
||||
if not result:
|
||||
return []
|
||||
if "totalRows" in result[0] and result[0]["totalRows"] == 0:
|
||||
return []
|
||||
else:
|
||||
return result
|
241
trovebox/api/api_photo.py
Normal file
241
trovebox/api/api_photo.py
Normal file
|
@ -0,0 +1,241 @@
|
|||
"""
|
||||
api_photo.py : Trovebox Photo API Classes
|
||||
"""
|
||||
import base64
|
||||
|
||||
from trovebox.objects.photo import Photo
|
||||
from .api_base import ApiBase
|
||||
|
||||
class ApiPhotos(ApiBase):
|
||||
""" Definitions of /photos/ API endpoints """
|
||||
def list(self, options=None, **kwds):
|
||||
"""
|
||||
Endpoint: /photos[/<options>]/list.json
|
||||
|
||||
Returns a list of Photo objects.
|
||||
The options parameter can be used to narrow down the list.
|
||||
Eg: options={"album": <album_id>}
|
||||
"""
|
||||
option_string = self._build_option_string(options)
|
||||
photos = self._client.get("/photos%s/list.json" % option_string,
|
||||
**kwds)["result"]
|
||||
photos = self._result_to_list(photos)
|
||||
return [Photo(self._client, photo) for photo in photos]
|
||||
|
||||
def share(self, options=None, **kwds):
|
||||
"""
|
||||
Endpoint: /photos[/<options>/share.json
|
||||
|
||||
Not currently implemented.
|
||||
"""
|
||||
option_string = self._build_option_string(options)
|
||||
return self._client.post("/photos%s/share.json" % option_string,
|
||||
**kwds)["result"]
|
||||
|
||||
def delete(self, photos, **kwds):
|
||||
"""
|
||||
Endpoint: /photos/delete.json
|
||||
|
||||
Deletes a list of photos.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
ids = [self._extract_id(photo) for photo in photos]
|
||||
return self._client.post("/photos/delete.json", ids=ids,
|
||||
**kwds)["result"]
|
||||
|
||||
def update(self, photos, **kwds):
|
||||
"""
|
||||
Endpoint: /photos/<id>/update.json
|
||||
|
||||
Updates a list of photos with the specified parameters.
|
||||
Returns True if successful.
|
||||
Raises TroveboxError if not.
|
||||
"""
|
||||
ids = [self._extract_id(photo) for photo in photos]
|
||||
return self._client.post("/photos/update.json", ids=ids,
|
||||
**kwds)["result"]
|
||||
|
||||
class ApiPhoto(ApiBase):
|
||||
""" Definitions of /photo/ API endpoints """
|
||||
def delete(self, photo, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/delete.json
|
||||
|
||||
Deletes a photo.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
return self._client.post("/photo/%s/delete.json" %
|
||||
self._extract_id(photo),
|
||||
**kwds)["result"]
|
||||
|
||||
def delete_source(self, photo, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/source/delete.json
|
||||
|
||||
Delete the source files of a photo.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
return self._client.post("/photo/%s/source/delete.json" %
|
||||
self._extract_id(photo),
|
||||
**kwds)["result"]
|
||||
|
||||
def replace(self, photo, photo_file, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/replace.json
|
||||
|
||||
Uploads the specified photo file to replace an existing photo.
|
||||
"""
|
||||
with open(photo_file, 'rb') as in_file:
|
||||
result = self._client.post("/photo/%s/replace.json" %
|
||||
self._extract_id(photo),
|
||||
files={'photo': in_file},
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def replace_encoded(self, photo, photo_file, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/replace.json
|
||||
|
||||
Base64-encodes and uploads the specified photo filename to
|
||||
replace an existing photo.
|
||||
"""
|
||||
with open(photo_file, "rb") as in_file:
|
||||
encoded_photo = base64.b64encode(in_file.read())
|
||||
result = self._client.post("/photo/%s/replace.json" %
|
||||
self._extract_id(photo),
|
||||
photo=encoded_photo,
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def replace_from_url(self, photo, url, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>replace.json
|
||||
|
||||
Import a photo from the specified URL to replace an existing
|
||||
photo.
|
||||
"""
|
||||
result = self._client.post("/photo/%s/replace.json" %
|
||||
self._extract_id(photo),
|
||||
photo=url,
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
|
||||
def update(self, photo, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/update.json
|
||||
|
||||
Updates a photo with the specified parameters.
|
||||
Returns the updated photo object.
|
||||
"""
|
||||
result = self._client.post("/photo/%s/update.json" %
|
||||
self._extract_id(photo),
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def view(self, photo, options=None, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>[/<options>]/view.json
|
||||
|
||||
Requests all properties of a photo.
|
||||
Can be used to obtain URLs for the photo at a particular size,
|
||||
by using the "returnSizes" parameter.
|
||||
Returns the requested photo object.
|
||||
The options parameter can be used to pass in additional options.
|
||||
Eg: options={"token": <token_data>}
|
||||
"""
|
||||
option_string = self._build_option_string(options)
|
||||
result = self._client.get("/photo/%s%s/view.json" %
|
||||
(self._extract_id(photo), option_string),
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def upload(self, photo_file, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/upload.json
|
||||
|
||||
Uploads the specified photo filename.
|
||||
"""
|
||||
with open(photo_file, 'rb') as in_file:
|
||||
result = self._client.post("/photo/upload.json",
|
||||
files={'photo': in_file},
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def upload_encoded(self, photo_file, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/upload.json
|
||||
|
||||
Base64-encodes and uploads the specified photo filename.
|
||||
"""
|
||||
with open(photo_file, "rb") as in_file:
|
||||
encoded_photo = base64.b64encode(in_file.read())
|
||||
result = self._client.post("/photo/upload.json", photo=encoded_photo,
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def upload_from_url(self, url, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/upload.json
|
||||
|
||||
Import a photo from the specified URL
|
||||
"""
|
||||
result = self._client.post("/photo/upload.json", photo=url,
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def next_previous(self, photo, options=None, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/nextprevious[/<options>].json
|
||||
|
||||
Returns a dict containing the next and previous photo lists
|
||||
(there may be more than one next/previous photo returned).
|
||||
The options parameter can be used to narrow down the photos
|
||||
Eg: options={"album": <album_id>}
|
||||
"""
|
||||
option_string = self._build_option_string(options)
|
||||
result = self._client.get("/photo/%s/nextprevious%s.json" %
|
||||
(self._extract_id(photo), option_string),
|
||||
**kwds)["result"]
|
||||
value = {}
|
||||
if "next" in result:
|
||||
# Workaround for APIv1
|
||||
if not isinstance(result["next"], list): # pragma: no cover
|
||||
result["next"] = [result["next"]]
|
||||
|
||||
value["next"] = []
|
||||
for photo in result["next"]:
|
||||
value["next"].append(Photo(self._client, photo))
|
||||
|
||||
if "previous" in result:
|
||||
# Workaround for APIv1
|
||||
if not isinstance(result["previous"], list): # pragma: no cover
|
||||
result["previous"] = [result["previous"]]
|
||||
|
||||
value["previous"] = []
|
||||
for photo in result["previous"]:
|
||||
value["previous"].append(Photo(self._client, photo))
|
||||
|
||||
return value
|
||||
|
||||
def transform(self, photo, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/transform.json
|
||||
|
||||
Performs the specified transformations.
|
||||
eg. transform(photo, rotate=90)
|
||||
Returns the transformed photo.
|
||||
"""
|
||||
result = self._client.post("/photo/%s/transform.json" %
|
||||
self._extract_id(photo),
|
||||
**kwds)["result"]
|
||||
|
||||
# APIv1 doesn't return the transformed photo (frontend issue #955)
|
||||
if isinstance(result, bool): # pragma: no cover
|
||||
result = self._client.get("/photo/%s/view.json" %
|
||||
self._extract_id(photo))["result"]
|
||||
|
||||
return Photo(self._client, result)
|
27
trovebox/api/api_system.py
Normal file
27
trovebox/api/api_system.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
api_system.py : Trovebox System API Classes
|
||||
"""
|
||||
from .api_base import ApiBase
|
||||
|
||||
class ApiSystem(ApiBase):
|
||||
""" Definitions of /system/ API endpoints """
|
||||
def version(self, **kwds):
|
||||
"""
|
||||
Endpoint: /system/version.json
|
||||
|
||||
Returns a dictionary containing the various server version strings
|
||||
"""
|
||||
return self._client.get("/system/version.json", **kwds)["result"]
|
||||
|
||||
def diagnostics(self, **kwds):
|
||||
"""
|
||||
Endpoint: /system/diagnostics.json
|
||||
|
||||
Runs a set of diagnostic tests on the server.
|
||||
Returns a dictionary containing the results.
|
||||
"""
|
||||
# Don't process the result automatically, since this raises an exception
|
||||
# on failure, which doesn't provide the cause of the failure
|
||||
self._client.get("/system/diagnostics.json", process_response=False,
|
||||
**kwds)
|
||||
return self._client.last_response.json()["result"]
|
60
trovebox/api/api_tag.py
Normal file
60
trovebox/api/api_tag.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
api_tag.py : Trovebox Tag API Classes
|
||||
"""
|
||||
try:
|
||||
from urllib.parse import quote # Python3
|
||||
except ImportError:
|
||||
from urllib import quote # Python2
|
||||
|
||||
from trovebox.objects.tag import Tag
|
||||
from .api_base import ApiBase
|
||||
|
||||
class ApiTags(ApiBase):
|
||||
""" Definitions of /tags/ API endpoints """
|
||||
def list(self, **kwds):
|
||||
"""
|
||||
Endpoint: /tags/list.json
|
||||
|
||||
Returns a list of Tag objects.
|
||||
"""
|
||||
tags = self._client.get("/tags/list.json", **kwds)["result"]
|
||||
tags = self._result_to_list(tags)
|
||||
return [Tag(self._client, tag) for tag in tags]
|
||||
|
||||
class ApiTag(ApiBase):
|
||||
""" Definitions of /tag/ API endpoints """
|
||||
def create(self, tag, **kwds):
|
||||
"""
|
||||
Endpoint: /tag/create.json
|
||||
|
||||
Creates a new tag.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
return self._client.post("/tag/create.json", tag=tag, **kwds)["result"]
|
||||
|
||||
def delete(self, tag, **kwds):
|
||||
"""
|
||||
Endpoint: /tag/<id>/delete.json
|
||||
|
||||
Deletes a tag.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
return self._client.post("/tag/%s/delete.json" %
|
||||
quote(self._extract_id(tag)),
|
||||
**kwds)["result"]
|
||||
|
||||
def update(self, tag, **kwds):
|
||||
"""
|
||||
Endpoint: /tag/<id>/update.json
|
||||
|
||||
Updates a tag with the specified parameters.
|
||||
Returns the updated tag object.
|
||||
"""
|
||||
result = self._client.post("/tag/%s/update.json" %
|
||||
quote(self._extract_id(tag)),
|
||||
**kwds)["result"]
|
||||
return Tag(self._client, result)
|
||||
|
||||
# def view(self, tag, **kwds):
|
|
@ -1,62 +0,0 @@
|
|||
"""
|
||||
api_album.py : Trovebox Album API Classes
|
||||
"""
|
||||
from .objects import Album
|
||||
|
||||
class ApiAlbums(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def list(self, **kwds):
|
||||
""" Return a list of Album objects """
|
||||
results = self._client.get("/albums/list.json", **kwds)["result"]
|
||||
return [Album(self._client, album) for album in results]
|
||||
|
||||
class ApiAlbum(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def create(self, name, **kwds):
|
||||
""" Create a new album and return it"""
|
||||
result = self._client.post("/album/create.json",
|
||||
name=name, **kwds)["result"]
|
||||
return Album(self._client, result)
|
||||
|
||||
def delete(self, album, **kwds):
|
||||
"""
|
||||
Delete an album.
|
||||
Returns True if successful.
|
||||
Raises an TroveboxError if not.
|
||||
"""
|
||||
if not isinstance(album, Album):
|
||||
album = Album(self._client, {"id": album})
|
||||
return album.delete(**kwds)
|
||||
|
||||
def form(self, album, **kwds):
|
||||
""" Not yet implemented """
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_photos(self, album, photos, **kwds):
|
||||
""" Not yet implemented """
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_photos(self, album, photos, **kwds):
|
||||
""" Not yet implemented """
|
||||
raise NotImplementedError()
|
||||
|
||||
def update(self, album, **kwds):
|
||||
""" Update an album """
|
||||
if not isinstance(album, Album):
|
||||
album = Album(self._client, {"id": album})
|
||||
album.update(**kwds)
|
||||
return album
|
||||
|
||||
def view(self, album, **kwds):
|
||||
"""
|
||||
View an album's contents.
|
||||
Returns the requested album object.
|
||||
"""
|
||||
if not isinstance(album, Album):
|
||||
album = Album(self._client, {"id": album})
|
||||
album.view(**kwds)
|
||||
return album
|
|
@ -1,142 +0,0 @@
|
|||
"""
|
||||
api_photo.py : Trovebox Photo API Classes
|
||||
"""
|
||||
import base64
|
||||
|
||||
from .errors import TroveboxError
|
||||
from . import http
|
||||
from .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(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def list(self, **kwds):
|
||||
""" Returns a list of Photo objects """
|
||||
photos = self._client.get("/photos/list.json", **kwds)["result"]
|
||||
photos = http.result_to_list(photos)
|
||||
return [Photo(self._client, photo) for photo in photos]
|
||||
|
||||
def update(self, photos, **kwds):
|
||||
"""
|
||||
Updates a list of photos.
|
||||
Returns True if successful.
|
||||
Raises TroveboxError if not.
|
||||
"""
|
||||
ids = extract_ids(photos)
|
||||
if not self._client.post("/photos/update.json", ids=ids,
|
||||
**kwds)["result"]:
|
||||
raise TroveboxError("Update response returned False")
|
||||
return True
|
||||
|
||||
def delete(self, photos, **kwds):
|
||||
"""
|
||||
Deletes a list of photos.
|
||||
Returns True if successful.
|
||||
Raises TroveboxError if not.
|
||||
"""
|
||||
ids = extract_ids(photos)
|
||||
if not self._client.post("/photos/delete.json", ids=ids,
|
||||
**kwds)["result"]:
|
||||
raise TroveboxError("Delete response returned False")
|
||||
return True
|
||||
|
||||
class ApiPhoto(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def delete(self, photo, **kwds):
|
||||
"""
|
||||
Delete a photo.
|
||||
Returns True if successful.
|
||||
Raises an TroveboxError if not.
|
||||
"""
|
||||
if not isinstance(photo, Photo):
|
||||
photo = Photo(self._client, {"id": photo})
|
||||
return photo.delete(**kwds)
|
||||
|
||||
def edit(self, photo, **kwds):
|
||||
""" Returns an HTML form to edit a photo """
|
||||
if not isinstance(photo, Photo):
|
||||
photo = Photo(self._client, {"id": photo})
|
||||
return photo.edit(**kwds)
|
||||
|
||||
def replace(self, photo, photo_file, **kwds):
|
||||
""" Not yet implemented """
|
||||
raise NotImplementedError()
|
||||
|
||||
def replace_encoded(self, photo, photo_file, **kwds):
|
||||
""" Not yet implemented """
|
||||
raise NotImplementedError()
|
||||
|
||||
def update(self, photo, **kwds):
|
||||
"""
|
||||
Update a photo with the specified parameters.
|
||||
Returns the updated photo object
|
||||
"""
|
||||
if not isinstance(photo, Photo):
|
||||
photo = Photo(self._client, {"id": photo})
|
||||
photo.update(**kwds)
|
||||
return photo
|
||||
|
||||
def view(self, photo, **kwds):
|
||||
"""
|
||||
Used to view the photo at a particular size.
|
||||
Returns the requested photo object
|
||||
"""
|
||||
if not isinstance(photo, Photo):
|
||||
photo = Photo(self._client, {"id": photo})
|
||||
photo.view(**kwds)
|
||||
return photo
|
||||
|
||||
def upload(self, photo_file, **kwds):
|
||||
""" Uploads the specified file to the server """
|
||||
with open(photo_file, 'rb') as in_file:
|
||||
result = self._client.post("/photo/upload.json",
|
||||
files={'photo': in_file},
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def upload_encoded(self, photo_file, **kwds):
|
||||
""" Base64-encodes and uploads the specified file """
|
||||
with open(photo_file, "rb") as in_file:
|
||||
encoded_photo = base64.b64encode(in_file.read())
|
||||
result = self._client.post("/photo/upload.json", photo=encoded_photo,
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def dynamic_url(self, photo, **kwds):
|
||||
""" Not yet implemented """
|
||||
raise NotImplementedError()
|
||||
|
||||
def next_previous(self, photo, **kwds):
|
||||
"""
|
||||
Returns a dict containing the next and previous photo lists
|
||||
(there may be more than one next/previous photo returned).
|
||||
"""
|
||||
if not isinstance(photo, Photo):
|
||||
photo = Photo(self._client, {"id": photo})
|
||||
return photo.next_previous(**kwds)
|
||||
|
||||
def transform(self, photo, **kwds):
|
||||
"""
|
||||
Performs transformation specified in **kwds
|
||||
Example: transform(photo, rotate=90)
|
||||
"""
|
||||
if not isinstance(photo, Photo):
|
||||
photo = Photo(self._client, {"id": photo})
|
||||
photo.transform(**kwds)
|
||||
return photo
|
|
@ -1,41 +0,0 @@
|
|||
"""
|
||||
api_tag.py : Trovebox Tag API Classes
|
||||
"""
|
||||
from .objects import Tag
|
||||
|
||||
class ApiTags(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def list(self, **kwds):
|
||||
""" Returns a list of Tag objects """
|
||||
results = self._client.get("/tags/list.json", **kwds)["result"]
|
||||
return [Tag(self._client, tag) for tag in results]
|
||||
|
||||
class ApiTag(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def create(self, tag, **kwds):
|
||||
"""
|
||||
Create a new tag.
|
||||
The API returns true if the tag was sucessfully created
|
||||
"""
|
||||
return self._client.post("/tag/create.json", tag=tag, **kwds)["result"]
|
||||
|
||||
def delete(self, tag, **kwds):
|
||||
"""
|
||||
Delete a tag.
|
||||
Returns True if successful.
|
||||
Raises an TroveboxError if not.
|
||||
"""
|
||||
if not isinstance(tag, Tag):
|
||||
tag = Tag(self._client, {"id": tag})
|
||||
return tag.delete(**kwds)
|
||||
|
||||
def update(self, tag, **kwds):
|
||||
""" Update a tag """
|
||||
if not isinstance(tag, Tag):
|
||||
tag = Tag(self._client, {"id": tag})
|
||||
tag.update(**kwds)
|
||||
return tag
|
|
@ -9,7 +9,7 @@ except ImportError:
|
|||
from ConfigParser import SafeConfigParser as ConfigParser # Python2
|
||||
try:
|
||||
import io # Python3
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
import StringIO as io # Python2
|
||||
|
||||
class Auth(object):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
errors.py : Trovebox Error Classes
|
||||
"""
|
||||
class TroveboxError(Exception):
|
||||
""" Indicates that an Trovebox operation failed """
|
||||
""" Indicates that a Trovebox operation failed """
|
||||
pass
|
||||
|
||||
class TroveboxDuplicateError(TroveboxError):
|
||||
|
|
|
@ -11,13 +11,13 @@ try:
|
|||
except ImportError:
|
||||
from urlparse import urlparse, urlunparse # Python2
|
||||
|
||||
from .objects import TroveboxObject
|
||||
from .errors import *
|
||||
from trovebox.objects.trovebox_object import TroveboxObject
|
||||
from .errors import TroveboxError, Trovebox404Error, TroveboxDuplicateError
|
||||
from .auth import Auth
|
||||
|
||||
if sys.version < '3':
|
||||
TEXT_TYPE = unicode
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
TEXT_TYPE = str
|
||||
|
||||
DUPLICATE_RESPONSE = {"code": 409,
|
||||
|
@ -25,7 +25,7 @@ DUPLICATE_RESPONSE = {"code": 409,
|
|||
|
||||
class Http(object):
|
||||
"""
|
||||
Base class to handle HTTP requests to an Trovebox server.
|
||||
Base class to handle HTTP requests to a Trovebox server.
|
||||
If no parameters are specified, auth config is loaded from the
|
||||
default location (~/.config/trovebox/default).
|
||||
The config_file parameter is used to specify an alternate config file.
|
||||
|
@ -43,7 +43,7 @@ class Http(object):
|
|||
|
||||
self.config = dict(self._CONFIG_DEFAULTS)
|
||||
|
||||
if api_version is not None:
|
||||
if api_version is not None: # pragma: no cover
|
||||
print("Deprecation Warning: api_version should be set by "
|
||||
"calling the configure function")
|
||||
self.config["api_version"] = api_version
|
||||
|
@ -104,7 +104,9 @@ class Http(object):
|
|||
self._logger.info("============================")
|
||||
self._logger.info("GET %s" % url)
|
||||
self._logger.info("---")
|
||||
self._logger.info(response.text)
|
||||
self._logger.info(response.text[:1000])
|
||||
if len(response.text) > 1000: # pragma: no cover
|
||||
self._logger.info("[Response truncated to 1000 characters]")
|
||||
|
||||
self.last_url = url
|
||||
self.last_params = params
|
||||
|
@ -113,7 +115,11 @@ class Http(object):
|
|||
if process_response:
|
||||
return self._process_response(response)
|
||||
else:
|
||||
if 200 <= response.status_code < 300:
|
||||
return response.text
|
||||
else:
|
||||
raise TroveboxError("HTTP Error %d: %s" %
|
||||
(response.status_code, response.reason))
|
||||
|
||||
def post(self, endpoint, process_response=True, files=None, **params):
|
||||
"""
|
||||
|
@ -154,7 +160,9 @@ class Http(object):
|
|||
if files:
|
||||
self._logger.info("files: %s" % repr(files))
|
||||
self._logger.info("---")
|
||||
self._logger.info(response.text)
|
||||
self._logger.info(response.text[:1000])
|
||||
if len(response.text) > 1000: # pragma: no cover
|
||||
self._logger.info("[Response truncated to 1000 characters]")
|
||||
|
||||
self.last_url = url
|
||||
self.last_params = params
|
||||
|
@ -163,7 +171,11 @@ class Http(object):
|
|||
if process_response:
|
||||
return self._process_response(response)
|
||||
else:
|
||||
if 200 <= response.status_code < 300:
|
||||
return response.text
|
||||
else:
|
||||
raise TroveboxError("HTTP Error %d: %s" %
|
||||
(response.status_code, response.reason))
|
||||
|
||||
def _construct_url(self, endpoint):
|
||||
"""Return the full URL to the specified endpoint"""
|
||||
|
@ -241,12 +253,3 @@ class Http(object):
|
|||
raise TroveboxDuplicateError("Code %d: %s" % (code, message))
|
||||
else:
|
||||
raise TroveboxError("Code %d: %s" % (code, message))
|
||||
|
||||
def result_to_list(result):
|
||||
""" Handle the case where the result contains no items """
|
||||
if not result:
|
||||
return []
|
||||
if result[0]["totalRows"] == 0:
|
||||
return []
|
||||
else:
|
||||
return result
|
||||
|
|
|
@ -27,6 +27,7 @@ To get your credentials:
|
|||
#################################################################
|
||||
|
||||
def main(args=sys.argv[1:]):
|
||||
"""Run the commandline script"""
|
||||
usage = "%prog --help"
|
||||
parser = OptionParser(usage, add_help_option=False)
|
||||
parser.add_option('-c', '--config', help="Configuration file to use",
|
||||
|
@ -90,8 +91,8 @@ def main(args=sys.argv[1:]):
|
|||
params, files = extract_files(params)
|
||||
result = client.post(options.endpoint, process_response=False,
|
||||
files=files, **params)
|
||||
for f in files:
|
||||
files[f].close()
|
||||
for file_ in files:
|
||||
files[file_].close()
|
||||
|
||||
if options.verbose:
|
||||
print("==========\nMethod: %s\nHost: %s\nEndpoint: %s" %
|
||||
|
@ -129,5 +130,5 @@ def extract_files(params):
|
|||
|
||||
return updated_params, files
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
|
|
|
@ -1,236 +0,0 @@
|
|||
"""
|
||||
objects.py : Basic Trovebox API Objects
|
||||
"""
|
||||
try:
|
||||
from urllib.parse import quote # Python3
|
||||
except ImportError:
|
||||
from urllib import quote # Python2
|
||||
|
||||
from .errors import TroveboxError
|
||||
|
||||
class TroveboxObject(object):
|
||||
""" Base object supporting the storage of custom fields as attributes """
|
||||
def __init__(self, trovebox, json_dict):
|
||||
self.id = None
|
||||
self.name = None
|
||||
self._trovebox = trovebox
|
||||
self._json_dict = json_dict
|
||||
self._set_fields(json_dict)
|
||||
|
||||
def _set_fields(self, json_dict):
|
||||
""" Set this object's attributes specified in json_dict """
|
||||
for key, value in json_dict.items():
|
||||
if not key.startswith("_"):
|
||||
setattr(self, key, value)
|
||||
|
||||
def _replace_fields(self, json_dict):
|
||||
"""
|
||||
Delete this object's attributes, and replace with
|
||||
those in json_dict.
|
||||
"""
|
||||
for key in self._json_dict.keys():
|
||||
if not key.startswith("_"):
|
||||
delattr(self, key)
|
||||
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():
|
||||
if not key.startswith("_"):
|
||||
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)
|
||||
elif self.id is not None:
|
||||
return "<%s id='%s'>" % (self.__class__, self.id)
|
||||
else:
|
||||
return "<%s>" % (self.__class__)
|
||||
|
||||
def get_fields(self):
|
||||
""" Returns this object's attributes """
|
||||
return self._json_dict
|
||||
|
||||
|
||||
class Photo(TroveboxObject):
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Delete this photo.
|
||||
Returns True if successful.
|
||||
Raises an TroveboxError if not.
|
||||
"""
|
||||
result = self._trovebox.post("/photo/%s/delete.json" %
|
||||
self.id, **kwds)["result"]
|
||||
if not result:
|
||||
raise TroveboxError("Delete response returned False")
|
||||
self._delete_fields()
|
||||
return result
|
||||
|
||||
def edit(self, **kwds):
|
||||
""" Returns an HTML form to edit the photo """
|
||||
result = self._trovebox.get("/photo/%s/edit.json" %
|
||||
self.id, **kwds)["result"]
|
||||
return result["markup"]
|
||||
|
||||
def replace(self, photo_file, **kwds):
|
||||
""" Not implemented yet """
|
||||
raise NotImplementedError()
|
||||
|
||||
def replace_encoded(self, photo_file, **kwds):
|
||||
""" Not implemented yet """
|
||||
raise NotImplementedError()
|
||||
|
||||
def update(self, **kwds):
|
||||
""" Update this photo with the specified parameters """
|
||||
new_dict = self._trovebox.post("/photo/%s/update.json" %
|
||||
self.id, **kwds)["result"]
|
||||
self._replace_fields(new_dict)
|
||||
|
||||
def view(self, **kwds):
|
||||
"""
|
||||
Used to view the photo at a particular size.
|
||||
Updates the photo's fields with the response.
|
||||
"""
|
||||
new_dict = self._trovebox.get("/photo/%s/view.json" %
|
||||
self.id, **kwds)["result"]
|
||||
self._replace_fields(new_dict)
|
||||
|
||||
def dynamic_url(self, **kwds):
|
||||
""" Not implemented yet """
|
||||
raise NotImplementedError()
|
||||
|
||||
def next_previous(self, **kwds):
|
||||
"""
|
||||
Returns a dict containing the next and previous photo lists
|
||||
(there may be more than one next/previous photo returned).
|
||||
"""
|
||||
result = self._trovebox.get("/photo/%s/nextprevious.json" %
|
||||
self.id, **kwds)["result"]
|
||||
value = {}
|
||||
if "next" in result:
|
||||
# Workaround for APIv1
|
||||
if not isinstance(result["next"], list):
|
||||
result["next"] = [result["next"]]
|
||||
|
||||
value["next"] = []
|
||||
for photo in result["next"]:
|
||||
value["next"].append(Photo(self._trovebox, photo))
|
||||
|
||||
if "previous" in result:
|
||||
# Workaround for APIv1
|
||||
if not isinstance(result["previous"], list):
|
||||
result["previous"] = [result["previous"]]
|
||||
|
||||
value["previous"] = []
|
||||
for photo in result["previous"]:
|
||||
value["previous"].append(Photo(self._trovebox, photo))
|
||||
|
||||
return value
|
||||
|
||||
def transform(self, **kwds):
|
||||
"""
|
||||
Performs transformation specified in **kwds
|
||||
Example: transform(rotate=90)
|
||||
"""
|
||||
new_dict = self._trovebox.post("/photo/%s/transform.json" %
|
||||
self.id, **kwds)["result"]
|
||||
|
||||
# APIv1 doesn't return the transformed photo (frontend issue #955)
|
||||
if isinstance(new_dict, bool):
|
||||
new_dict = self._trovebox.get("/photo/%s/view.json" %
|
||||
self.id)["result"]
|
||||
|
||||
self._replace_fields(new_dict)
|
||||
|
||||
class Tag(TroveboxObject):
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Delete this tag.
|
||||
Returns True if successful.
|
||||
Raises an TroveboxError if not.
|
||||
"""
|
||||
result = self._trovebox.post("/tag/%s/delete.json" %
|
||||
quote(self.id), **kwds)["result"]
|
||||
if not result:
|
||||
raise TroveboxError("Delete response returned False")
|
||||
self._delete_fields()
|
||||
return result
|
||||
|
||||
def update(self, **kwds):
|
||||
""" Update this tag with the specified parameters """
|
||||
new_dict = self._trovebox.post("/tag/%s/update.json" % quote(self.id),
|
||||
**kwds)["result"]
|
||||
self._replace_fields(new_dict)
|
||||
|
||||
|
||||
class Album(TroveboxObject):
|
||||
def __init__(self, trovebox, json_dict):
|
||||
self.photos = None
|
||||
self.cover = None
|
||||
TroveboxObject.__init__(self, trovebox, json_dict)
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def _update_fields_with_objects(self):
|
||||
""" Convert dict fields into objects, where appropriate """
|
||||
# Update the cover with a photo object
|
||||
if isinstance(self.cover, dict):
|
||||
self.cover = Photo(self._trovebox, self.cover)
|
||||
# Update the photo list with photo objects
|
||||
if isinstance(self.photos, list):
|
||||
for i, photo in enumerate(self.photos):
|
||||
if isinstance(photo, dict):
|
||||
self.photos[i] = Photo(self._trovebox, photo)
|
||||
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Delete this album.
|
||||
Returns True if successful.
|
||||
Raises an TroveboxError if not.
|
||||
"""
|
||||
result = self._trovebox.post("/album/%s/delete.json" %
|
||||
self.id, **kwds)["result"]
|
||||
if not result:
|
||||
raise TroveboxError("Delete response returned False")
|
||||
self._delete_fields()
|
||||
return result
|
||||
|
||||
def form(self, **kwds):
|
||||
""" Not implemented yet """
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_photos(self, photos, **kwds):
|
||||
""" Not implemented yet """
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_photos(self, photos, **kwds):
|
||||
""" Not implemented yet """
|
||||
raise NotImplementedError()
|
||||
|
||||
def update(self, **kwds):
|
||||
""" Update this album with the specified parameters """
|
||||
new_dict = self._trovebox.post("/album/%s/update.json" %
|
||||
self.id, **kwds)["result"]
|
||||
|
||||
# APIv1 doesn't return the updated album (frontend issue #937)
|
||||
if isinstance(new_dict, bool):
|
||||
new_dict = self._trovebox.get("/album/%s/view.json" %
|
||||
self.id)["result"]
|
||||
|
||||
self._replace_fields(new_dict)
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def view(self, **kwds):
|
||||
"""
|
||||
Requests the full contents of the album.
|
||||
Updates the album's fields with the response.
|
||||
"""
|
||||
result = self._trovebox.get("/album/%s/view.json" %
|
||||
self.id, **kwds)["result"]
|
||||
self._replace_fields(result)
|
||||
self._update_fields_with_objects()
|
4
trovebox/objects/__init__.py
Normal file
4
trovebox/objects/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
trovebox.objects Package
|
||||
Object classes returned by the API.
|
||||
"""
|
48
trovebox/objects/action.py
Normal file
48
trovebox/objects/action.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
"""
|
||||
Representation of an Action object
|
||||
"""
|
||||
from .trovebox_object import TroveboxObject
|
||||
from .photo import Photo
|
||||
|
||||
class Action(TroveboxObject):
|
||||
""" Representation of an Action object """
|
||||
_type = "action"
|
||||
|
||||
def __init__(self, client, json_dict):
|
||||
self.target = None
|
||||
self.target_type = None
|
||||
TroveboxObject.__init__(self, client, json_dict)
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def _update_fields_with_objects(self):
|
||||
""" Convert dict fields into objects, where appropriate """
|
||||
# Update the photo target with photo objects
|
||||
if self.target is not None:
|
||||
if self.target_type == "photo":
|
||||
self.target = Photo(self._client, self.target)
|
||||
else:
|
||||
raise NotImplementedError("Actions can only be assigned to "
|
||||
"Photos")
|
||||
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Endpoint: /action/<id>/delete.json
|
||||
|
||||
Deletes this action.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
result = self._client.action.delete(self, **kwds)
|
||||
self._delete_fields()
|
||||
return result
|
||||
|
||||
def view(self, **kwds):
|
||||
"""
|
||||
Endpoint: /action/<id>/view.json
|
||||
|
||||
Requests the full contents of the action.
|
||||
Updates the action object's fields with the response.
|
||||
"""
|
||||
result = self._client.action.view(self, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
self._update_fields_with_objects()
|
37
trovebox/objects/activity.py
Normal file
37
trovebox/objects/activity.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
Representation of an Activity object
|
||||
"""
|
||||
from .trovebox_object import TroveboxObject
|
||||
from .photo import Photo
|
||||
|
||||
class Activity(TroveboxObject):
|
||||
""" Representation of an Activity object """
|
||||
_type = "activity"
|
||||
|
||||
def __init__(self, client, json_dict):
|
||||
self.data = None
|
||||
self.type = None
|
||||
TroveboxObject.__init__(self, client, json_dict)
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def _update_fields_with_objects(self):
|
||||
""" Convert dict fields into objects, where appropriate """
|
||||
# Update the data with photo objects
|
||||
if self.type is not None:
|
||||
if self.type.startswith("photo"):
|
||||
self.data = Photo(self._client, self.data)
|
||||
else:
|
||||
raise NotImplementedError("Unrecognised activity type: %s"
|
||||
% self.type)
|
||||
|
||||
def view(self, **kwds):
|
||||
"""
|
||||
Endpoint: /activity/<id>/view.json
|
||||
|
||||
Requests the full contents of the activity.
|
||||
Updates the activity's fields with the response.
|
||||
"""
|
||||
result = self._client.activity.view(self, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
self._update_fields_with_objects()
|
||||
|
101
trovebox/objects/album.py
Normal file
101
trovebox/objects/album.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
Representation of an Album object
|
||||
"""
|
||||
from .trovebox_object import TroveboxObject
|
||||
from .photo import Photo
|
||||
|
||||
class Album(TroveboxObject):
|
||||
""" Representation of an Album object """
|
||||
_type = "album"
|
||||
|
||||
def __init__(self, client, json_dict):
|
||||
self.photos = None
|
||||
self.cover = None
|
||||
TroveboxObject.__init__(self, client, json_dict)
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def _update_fields_with_objects(self):
|
||||
""" Convert dict fields into objects, where appropriate """
|
||||
# Update the cover with a photo object
|
||||
if isinstance(self.cover, dict):
|
||||
self.cover = Photo(self._client, self.cover)
|
||||
|
||||
# Update the photo list with photo objects
|
||||
try:
|
||||
for i, photo in enumerate(self.photos):
|
||||
if isinstance(photo, dict):
|
||||
self.photos[i] = Photo(self._client, photo)
|
||||
except (AttributeError, TypeError):
|
||||
pass # No photos, or not a list
|
||||
|
||||
def cover_update(self, photo, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<album_id>/cover/<photo_id>/update.json
|
||||
|
||||
Update the cover photo of this album.
|
||||
"""
|
||||
result = self._client.album.cover_update(self, photo, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/delete.json
|
||||
|
||||
Deletes this album.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
result = self._client.album.delete(self, **kwds)
|
||||
self._delete_fields()
|
||||
return result
|
||||
|
||||
def add(self, objects, object_type=None, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/<type>/add.json
|
||||
|
||||
Add objects (eg. Photos) to this album.
|
||||
The objects are a list of either IDs or Trovebox objects.
|
||||
If Trovebox objects are used, the object type is inferred
|
||||
automatically.
|
||||
Updates the album's fields with the response.
|
||||
"""
|
||||
result = self._client.album.add(self, objects, object_type, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def remove(self, objects, object_type=None, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/<type>/remove.json
|
||||
|
||||
Remove objects (eg. Photos) from this album.
|
||||
The objects are a list of either IDs or Trovebox objects.
|
||||
If Trovebox objects are used, the object type is inferred
|
||||
automatically.
|
||||
Updates the album's fields with the response.
|
||||
"""
|
||||
result = self._client.album.remove(self, objects, object_type,
|
||||
**kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def update(self, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/update.json
|
||||
|
||||
Updates this album with the specified parameters.
|
||||
"""
|
||||
result = self._client.album.update(self, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
self._update_fields_with_objects()
|
||||
|
||||
def view(self, **kwds):
|
||||
"""
|
||||
Endpoint: /album/<id>/view.json
|
||||
|
||||
Requests all properties of an album.
|
||||
Updates the album's fields with the response.
|
||||
"""
|
||||
result = self._client.album.view(self, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
self._update_fields_with_objects()
|
102
trovebox/objects/photo.py
Normal file
102
trovebox/objects/photo.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
"""
|
||||
Representation of a Photo object
|
||||
"""
|
||||
from .trovebox_object import TroveboxObject
|
||||
|
||||
class Photo(TroveboxObject):
|
||||
""" Representation of a Photo object """
|
||||
_type = "photo"
|
||||
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/delete.json
|
||||
|
||||
Deletes this photo.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
result = self._client.photo.delete(self, **kwds)
|
||||
self._delete_fields()
|
||||
return result
|
||||
|
||||
def delete_source(self, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/source/delete.json
|
||||
|
||||
Deletes the source files of this photo.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
return self._client.photo.delete_source(self, **kwds)
|
||||
|
||||
def replace(self, photo_file, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/replace.json
|
||||
|
||||
Uploads the specified photo file to replace this photo.
|
||||
"""
|
||||
result = self._client.photo.replace(self, photo_file, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
|
||||
def replace_encoded(self, photo_file, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/replace.json
|
||||
|
||||
Base64-encodes and uploads the specified photo file to
|
||||
replace this photo.
|
||||
"""
|
||||
result = self._client.photo.replace_encoded(self, photo_file,
|
||||
**kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
|
||||
def replace_from_url(self, url, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>replace.json
|
||||
|
||||
Import a photo from the specified URL to replace this photo.
|
||||
"""
|
||||
result = self._client.photo.replace_from_url(self, url, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
|
||||
def update(self, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/update.json
|
||||
|
||||
Updates this photo with the specified parameters.
|
||||
"""
|
||||
result = self._client.photo.update(self, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
|
||||
def view(self, options=None, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>[/<options>]/view.json
|
||||
|
||||
Requests all properties of this photo.
|
||||
Can be used to obtain URLs for the photo at a particular size,
|
||||
by using the "returnSizes" parameter.
|
||||
Updates the photo's fields with the response.
|
||||
The options parameter can be used to pass in additional options.
|
||||
Eg: options={"token": <token_data>}
|
||||
"""
|
||||
result = self._client.photo.view(self, options, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
|
||||
def next_previous(self, options=None, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/nextprevious[/<options>].json
|
||||
|
||||
Returns a dict containing the next and previous photo lists
|
||||
(there may be more than one next/previous photo returned).
|
||||
"""
|
||||
return self._client.photo.next_previous(self, options, **kwds)
|
||||
|
||||
def transform(self, **kwds):
|
||||
"""
|
||||
Endpoint: /photo/<id>/transform.json
|
||||
|
||||
Performs the specified transformations.
|
||||
eg. transform(photo, rotate=90)
|
||||
Updates the photo's fields with the response.
|
||||
"""
|
||||
result = self._client.photo.transform(self, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
32
trovebox/objects/tag.py
Normal file
32
trovebox/objects/tag.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
"""
|
||||
Representation of a Tag object
|
||||
"""
|
||||
from .trovebox_object import TroveboxObject
|
||||
|
||||
class Tag(TroveboxObject):
|
||||
""" Representation of a Tag object """
|
||||
_type = "tag"
|
||||
|
||||
def delete(self, **kwds):
|
||||
"""
|
||||
Endpoint: /tag/<id>/delete.json
|
||||
|
||||
Deletes this tag.
|
||||
Returns True if successful.
|
||||
Raises a TroveboxError if not.
|
||||
"""
|
||||
result = self._client.tag.delete(self, **kwds)
|
||||
self._delete_fields()
|
||||
return result
|
||||
|
||||
def update(self, **kwds):
|
||||
"""
|
||||
Endpoint: /tag/<id>/update.json
|
||||
|
||||
Updates this tag with the specified parameters.
|
||||
Returns the updated tag object.
|
||||
"""
|
||||
result = self._client.tag.update(self, **kwds)
|
||||
self._replace_fields(result.get_fields())
|
||||
|
||||
# def view(self, **kwds):
|
56
trovebox/objects/trovebox_object.py
Normal file
56
trovebox/objects/trovebox_object.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
"""
|
||||
Base object supporting the storage of custom fields as attributes
|
||||
"""
|
||||
class TroveboxObject(object):
|
||||
""" Base object supporting the storage of custom fields as attributes """
|
||||
_type = "None"
|
||||
def __init__(self, client, json_dict):
|
||||
self.id = None
|
||||
self.name = None
|
||||
self._client = client
|
||||
self._json_dict = json_dict
|
||||
self._set_fields(json_dict)
|
||||
|
||||
def _set_fields(self, json_dict):
|
||||
""" Set this object's attributes specified in json_dict """
|
||||
for key, value in json_dict.items():
|
||||
if not key.startswith("_"):
|
||||
setattr(self, key, value)
|
||||
|
||||
def _replace_fields(self, json_dict):
|
||||
"""
|
||||
Delete this object's attributes, and replace with
|
||||
those in json_dict.
|
||||
"""
|
||||
for key in self._json_dict.keys():
|
||||
if not key.startswith("_"):
|
||||
delattr(self, key)
|
||||
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():
|
||||
if not key.startswith("_"):
|
||||
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__.__name__, self.name)
|
||||
elif self.id is not None:
|
||||
return "<%s id='%s'>" % (self.__class__.__name__, self.id)
|
||||
else:
|
||||
return "<%s>" % (self.__class__.__name__)
|
||||
|
||||
def get_fields(self):
|
||||
""" Returns this object's attributes """
|
||||
return self._json_dict
|
||||
|
||||
def get_type(self):
|
||||
""" Return this object's type (eg. "photo") """
|
||||
return self._type
|
Loading…
Add table
Add a link
Reference in a new issue