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/tokens.py
|
||||||
tests.log
|
tests.log
|
||||||
.tox
|
.tox
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
|
14
.travis.yml
14
.travis.yml
|
@ -1,15 +1,23 @@
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
# Install test dependencies
|
||||||
- pip install tox --use-mirrors
|
- pip install tox --use-mirrors
|
||||||
- .travis/install_pylint
|
- pip install coveralls --use-mirrors
|
||||||
|
|
||||||
script: tox
|
script: tox
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
# Send coverage results to coveralls.io
|
||||||
|
- coveralls
|
||||||
|
|
||||||
after_script:
|
after_script:
|
||||||
# Install dependencies for Pylint
|
# 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
|
# 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)
|
# (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
|
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
|
v0.5.1
|
||||||
======
|
======
|
||||||
* Use httpretty v0.6.5 for unit tests (#60)
|
* Use httpretty v0.6.5 for unit tests (#60)
|
||||||
|
|
|
@ -7,6 +7,13 @@ Trovebox Python Library
|
||||||
:alt: Build Status
|
:alt: Build Status
|
||||||
:target: https://travis-ci.org/photo/openphoto-python
|
: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
|
.. image:: https://pypip.in/v/trovebox/badge.png
|
||||||
:alt: Python Package Index (PyPI)
|
:alt: Python Package Index (PyPI)
|
||||||
:target: https://pypi.python.org/pypi/trovebox
|
: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
|
||||||
echo "Testing latest self-hosted site..."
|
echo "Testing latest self-hosted site..."
|
||||||
tput sgr0
|
tput sgr0
|
||||||
|
sleep 1
|
||||||
export TROVEBOX_TEST_CONFIG=test
|
export TROVEBOX_TEST_CONFIG=test
|
||||||
unset TROVEBOX_TEST_SERVER_API
|
unset TROVEBOX_TEST_SERVER_API
|
||||||
python -m unittest discover --catch tests/functional
|
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
|
# Test server running v3.0.8 Trovebox instance
|
||||||
# Install from photo/frontend commit e9d81de57b
|
# Install from photo/frontend commit e9d81de57b
|
||||||
tput setaf 3
|
tput setaf 3
|
||||||
echo
|
echo
|
||||||
echo "Testing v3.0.8 self-hosted site..."
|
echo "Testing v3.0.8 self-hosted site..."
|
||||||
tput sgr0
|
tput sgr0
|
||||||
|
sleep 1
|
||||||
export TROVEBOX_TEST_CONFIG=test-3.0.8
|
export TROVEBOX_TEST_CONFIG=test-3.0.8
|
||||||
unset TROVEBOX_TEST_SERVER_API
|
unset TROVEBOX_TEST_SERVER_API
|
||||||
python -m unittest discover --catch tests/functional
|
python -m unittest discover --catch tests/functional
|
||||||
|
@ -38,7 +30,18 @@ tput setaf 3
|
||||||
echo
|
echo
|
||||||
echo "Testing latest hosted site..."
|
echo "Testing latest hosted site..."
|
||||||
tput sgr0
|
tput sgr0
|
||||||
|
sleep 1
|
||||||
export TROVEBOX_TEST_CONFIG=test-hosted
|
export TROVEBOX_TEST_CONFIG=test-hosted
|
||||||
unset TROVEBOX_TEST_SERVER_API
|
unset TROVEBOX_TEST_SERVER_API
|
||||||
python -m unittest discover --catch tests/functional
|
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(),
|
long_description=open("README.rst").read(),
|
||||||
author='Pete Burgers, James Walker',
|
author='Pete Burgers, James Walker',
|
||||||
url='https://github.com/photo/openphoto-python',
|
url='https://github.com/photo/openphoto-python',
|
||||||
packages=['trovebox'],
|
packages=['trovebox', 'trovebox.objects', 'trovebox.api'],
|
||||||
data_files=['README.rst'],
|
|
||||||
keywords=['openphoto', 'pyopenphoto', 'openphoto-python',
|
keywords=['openphoto', 'pyopenphoto', 'openphoto-python',
|
||||||
'trovebox', 'pytrovebox', 'trovebox-python'],
|
'trovebox', 'pytrovebox', 'trovebox-python'],
|
||||||
classifiers=['Development Status :: 4 - Beta',
|
classifiers=['Development Status :: 4 - Beta',
|
||||||
|
|
|
@ -99,7 +99,7 @@ all supported API versions.
|
||||||
To use it, you must set up multiple Trovebox instances and create the following
|
To use it, you must set up multiple Trovebox instances and create the following
|
||||||
config files containing your credentials:
|
config files containing your credentials:
|
||||||
|
|
||||||
test : Latest self-hosted site (from photo/frontend master branch)
|
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-3.0.8 : v3.0.8 self-hosted site (from photo/frontend commit e9d81de57b)
|
test-hosted : Credentials for test account on http://<xxxx>.trovebox.com
|
||||||
test-hosted : Credentials for test account on 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
|
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):
|
class TestAlbumsV1(test_albums.TestAlbums):
|
||||||
api_version = 1
|
api_version = 1
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,19 @@ try:
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import unittest
|
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,
|
@unittest.skipIf(test_base.get_test_server_api() < 2,
|
||||||
"Don't test future API versions")
|
"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 tests.functional import test_base
|
||||||
|
from trovebox.objects.album import Album
|
||||||
|
|
||||||
class TestAlbums(test_base.TestBase):
|
class TestAlbums(test_base.TestBase):
|
||||||
testcase_name = "album API"
|
testcase_name = "album API"
|
||||||
|
@ -53,27 +59,50 @@ class TestAlbums(test_base.TestBase):
|
||||||
self.albums = self.client.albums.list()
|
self.albums = self.client.albums.list()
|
||||||
self.assertEqual(self.albums[0].name, self.TEST_ALBUM)
|
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):
|
def test_view(self):
|
||||||
""" Test the album view """
|
""" 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)
|
album.view(includeElements=True)
|
||||||
# Make sure all photos are in the album
|
# Make sure all photos are in the album
|
||||||
for photo in self.photos:
|
for photo in self.photos:
|
||||||
self.assertIn(photo.id, [p.id for p in album.photos])
|
self.assertIn(photo.id, [p.id for p in album.photos])
|
||||||
|
|
||||||
def test_form(self):
|
def test_add_remove(self):
|
||||||
""" If album.form gets implemented, write a test! """
|
""" Test that photos can be added and removed from an album """
|
||||||
with self.assertRaises(NotImplementedError):
|
# Make sure all photos are in the album
|
||||||
self.client.album.form(None)
|
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):
|
# Remove two photos and check that they're gone
|
||||||
""" If album.add_photos gets implemented, write a test! """
|
album.remove(self.photos[:2])
|
||||||
with self.assertRaises(NotImplementedError):
|
album.view(includeElements=True)
|
||||||
self.client.album.add_photos(None, None)
|
self.assertEqual([p.id for p in album.photos], [self.photos[2].id])
|
||||||
|
|
||||||
def test_remove_photos(self):
|
# Add a photo and check that it's there
|
||||||
""" If album.remove_photos gets implemented, write a test! """
|
album.add(self.photos[1])
|
||||||
with self.assertRaises(NotImplementedError):
|
album.view(includeElements=True)
|
||||||
self.client.album.remove_photos(None, None)
|
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:
|
else:
|
||||||
print("\nTesting %s v%d" % (cls.testcase_name, cls.api_version))
|
print("\nTesting %s v%d" % (cls.testcase_name, cls.api_version))
|
||||||
|
|
||||||
cls.client = trovebox.Trovebox(config_file=cls.config_file,
|
cls.client = trovebox.Trovebox(config_file=cls.config_file)
|
||||||
api_version=cls.api_version)
|
cls.client.configure(api_version=cls.api_version)
|
||||||
|
|
||||||
if cls.client.photos.list() != []:
|
if cls.client.photos.list() != []:
|
||||||
raise ValueError("The test server (%s) contains photos. "
|
raise ValueError("The test server (%s) contains photos. "
|
||||||
|
@ -124,7 +124,7 @@ class TestBase(unittest.TestCase):
|
||||||
logging.info("Finished %s\n", self.id())
|
logging.info("Finished %s\n", self.id())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create_test_photos(cls):
|
def _create_test_photos(cls, tag=True):
|
||||||
""" Upload three test photos """
|
""" Upload three test photos """
|
||||||
album = cls.client.album.create(cls.TEST_ALBUM)
|
album = cls.client.album.create(cls.TEST_ALBUM)
|
||||||
photos = [
|
photos = [
|
||||||
|
@ -139,8 +139,9 @@ class TestBase(unittest.TestCase):
|
||||||
albums=album.id),
|
albums=album.id),
|
||||||
]
|
]
|
||||||
# Add the test tag, removing any autogenerated tags
|
# Add the test tag, removing any autogenerated tags
|
||||||
for photo in photos:
|
if tag:
|
||||||
photo.update(tags=cls.TEST_TAG)
|
for photo in photos:
|
||||||
|
photo.update(tags=cls.TEST_TAG)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _delete_all(cls):
|
def _delete_all(cls):
|
||||||
|
|
|
@ -16,8 +16,8 @@ class TestFramework(test_base.TestBase):
|
||||||
"""
|
"""
|
||||||
API v0 has a special hello world message
|
API v0 has a special hello world message
|
||||||
"""
|
"""
|
||||||
client = trovebox.Trovebox(config_file=self.config_file,
|
client = trovebox.Trovebox(config_file=self.config_file)
|
||||||
api_version=0)
|
client.configure(api_version=0)
|
||||||
result = client.get("hello.json")
|
result = client.get("hello.json")
|
||||||
self.assertEqual(result['message'],
|
self.assertEqual(result['message'],
|
||||||
"Hello, world! This is version zero of the API!")
|
"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 all API versions >0, we get a generic hello world message
|
||||||
"""
|
"""
|
||||||
for api_version in range(1, test_base.get_test_server_api() + 1):
|
for api_version in range(1, test_base.get_test_server_api() + 1):
|
||||||
client = trovebox.Trovebox(config_file=self.config_file,
|
client = trovebox.Trovebox(config_file=self.config_file)
|
||||||
api_version=api_version)
|
client.configure(api_version=api_version)
|
||||||
result = client.get("hello.json")
|
result = client.get("hello.json")
|
||||||
self.assertEqual(result['message'], "Hello, world!")
|
self.assertEqual(result['message'], "Hello, world!")
|
||||||
self.assertEqual(result['result']['__route__'],
|
self.assertEqual(result['result']['__route__'],
|
||||||
|
@ -40,8 +40,7 @@ class TestFramework(test_base.TestBase):
|
||||||
If the API version is unspecified,
|
If the API version is unspecified,
|
||||||
we get a generic hello world message.
|
we get a generic hello world message.
|
||||||
"""
|
"""
|
||||||
client = trovebox.Trovebox(config_file=self.config_file,
|
client = trovebox.Trovebox(config_file=self.config_file)
|
||||||
api_version=None)
|
|
||||||
result = client.get("hello.json")
|
result = client.get("hello.json")
|
||||||
self.assertEqual(result['message'], "Hello, world!")
|
self.assertEqual(result['message'], "Hello, world!")
|
||||||
self.assertEqual(result['result']['__route__'], "/hello.json")
|
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)
|
(ValueError, since the returned 404 HTML page is not valid JSON)
|
||||||
"""
|
"""
|
||||||
version = trovebox.LATEST_API_VERSION + 1
|
version = trovebox.LATEST_API_VERSION + 1
|
||||||
client = trovebox.Trovebox(config_file=self.config_file,
|
client = trovebox.Trovebox(config_file=self.config_file)
|
||||||
api_version=version)
|
client.configure(api_version=version)
|
||||||
with self.assertRaises(trovebox.Trovebox404Error):
|
with self.assertRaises(trovebox.Trovebox404Error):
|
||||||
client.get("hello.json")
|
client.get("hello.json")
|
||||||
|
|
|
@ -1,11 +1,39 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
try:
|
||||||
|
import unittest2 as unittest # Python2.6
|
||||||
|
except ImportError:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import requests
|
||||||
import trovebox
|
import trovebox
|
||||||
from tests.functional import test_base
|
from tests.functional import test_base
|
||||||
|
|
||||||
class TestPhotos(test_base.TestBase):
|
class TestPhotos(test_base.TestBase):
|
||||||
testcase_name = "photo API"
|
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):
|
def test_delete_upload(self):
|
||||||
""" Test photo deletion and upload """
|
""" Test photo deletion and upload """
|
||||||
# Delete one photo using the Trovebox class, passing in the id
|
# 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
|
# Check that they're gone
|
||||||
self.assertEqual(self.client.photos.list(), [])
|
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",
|
ret_val = self.client.photo.upload("tests/data/test_photo1.jpg",
|
||||||
title=self.TEST_TITLE)
|
title=self.TEST_TITLE)
|
||||||
self.client.photo.upload("tests/data/test_photo2.jpg",
|
self.client.photo.upload("tests/data/test_photo2.jpg",
|
||||||
|
@ -47,15 +75,22 @@ class TestPhotos(test_base.TestBase):
|
||||||
self._delete_all()
|
self._delete_all()
|
||||||
self._create_test_photos()
|
self._create_test_photos()
|
||||||
|
|
||||||
def test_edit(self):
|
def test_delete_source(self):
|
||||||
""" Check that the edit request returns an HTML form """
|
""" Test that photo source files can be deleted """
|
||||||
# Test using the Trovebox class
|
# Upload a new (duplicate) public photo
|
||||||
html = self.client.photo.edit(self.photos[0])
|
photo = self.client.photo.upload("tests/data/test_photo1.jpg",
|
||||||
self.assertIn("<form", html.lower())
|
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
|
# Delete the source and check that the source file no longer exists
|
||||||
html = self.photos[0].edit()
|
photo.delete_source()
|
||||||
self.assertIn("<form", html.lower())
|
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):
|
def test_upload_duplicate(self):
|
||||||
""" Ensure that duplicate photos are rejected """
|
""" Ensure that duplicate photos are rejected """
|
||||||
|
@ -68,6 +103,23 @@ class TestPhotos(test_base.TestBase):
|
||||||
self.photos = self.client.photos.list()
|
self.photos = self.client.photos.list()
|
||||||
self.assertEqual(len(self.photos), 3)
|
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):
|
def test_update(self):
|
||||||
""" Update a photo by editing the title """
|
""" Update a photo by editing the title """
|
||||||
title = "\xfcmlaut" # umlauted umlaut
|
title = "\xfcmlaut" # umlauted umlaut
|
||||||
|
@ -127,29 +179,29 @@ class TestPhotos(test_base.TestBase):
|
||||||
|
|
||||||
def test_next_previous(self):
|
def test_next_previous(self):
|
||||||
""" Test the next/previous links of the middle photo """
|
""" 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["previous"][0].id, self.photos[0].id)
|
||||||
self.assertEqual(next_prev["next"][0].id, self.photos[2].id)
|
self.assertEqual(next_prev["next"][0].id, self.photos[2].id)
|
||||||
|
|
||||||
# Do the same using the Photo object directly
|
# 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["previous"][0].id, self.photos[0].id)
|
||||||
self.assertEqual(next_prev["next"][0].id, self.photos[2].id)
|
self.assertEqual(next_prev["next"][0].id, self.photos[2].id)
|
||||||
|
|
||||||
def test_replace(self):
|
def test_replace(self):
|
||||||
""" If photo.replace gets implemented, write a test! """
|
""" Test that a photo can be replaced with another """
|
||||||
with self.assertRaises(NotImplementedError):
|
# Replace the first photo with a copy of the second
|
||||||
self.client.photo.replace(None, None)
|
original_hash = self.photos[0].hash
|
||||||
|
self.assertNotEqual(original_hash, self.photos[1].hash)
|
||||||
def test_replace_encoded(self):
|
self.photos[0].replace("tests/data/test_photo2.jpg",
|
||||||
""" If photo.replace_encoded gets implemented, write a test! """
|
allowDuplicate=True)
|
||||||
with self.assertRaises(NotImplementedError):
|
# Check that its new hash is correct
|
||||||
self.client.photo.replace_encoded(None, None)
|
self.assertEqual(self.photos[0].hash, self.photos[1].hash)
|
||||||
|
# Put it back using base64 encoding
|
||||||
def test_dynamic_url(self):
|
self.photos[0].replace_encoded("tests/data/test_photo1.jpg",
|
||||||
""" If photo.dynamic_url gets implemented, write a test! """
|
allowDuplicate=True)
|
||||||
with self.assertRaises(NotImplementedError):
|
self.assertEqual(self.photos[0].hash, original_hash)
|
||||||
self.client.photo.dynamic_url(None)
|
|
||||||
|
|
||||||
def test_transform(self):
|
def test_transform(self):
|
||||||
""" Test photo rotation """
|
""" 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 -
|
# TODO: Un-skip this test once issue #919 is resolved -
|
||||||
# tags with double-slashes cannot be deleted
|
# 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):
|
def test_tag_with_double_slashes(self):
|
||||||
""" Run test_create_delete using a tag containing double-slashes """
|
""" Run test_create_delete using a tag containing double-slashes """
|
||||||
self.test_create_delete("tag//with//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):
|
class TestAlbums(unittest.TestCase):
|
||||||
test_host = "test.example.com"
|
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"]},
|
test_albums_dict = [{"cover": {"id": "1a", "tags": ["tag1", "tag2"]},
|
||||||
"id": "1",
|
"id": "1",
|
||||||
"name": "Album 1",
|
"name": "Album 1",
|
||||||
|
"photos": [test_photos_dict[0]],
|
||||||
"totalRows": 2},
|
"totalRows": 2},
|
||||||
{"cover": {"id": "2b", "tags": ["tag3", "tag4"]},
|
{"cover": {"id": "2b", "tags": ["tag3", "tag4"]},
|
||||||
"id": "2",
|
"id": "2",
|
||||||
"name": "Album 2",
|
"name": "Album 2",
|
||||||
|
"photos": [test_photos_dict[1]],
|
||||||
"totalRows": 2}]
|
"totalRows": 2}]
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = trovebox.Trovebox(host=self.test_host)
|
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]
|
for album in self.test_albums_dict]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -31,20 +37,36 @@ class TestAlbumsList(TestAlbums):
|
||||||
def test_albums_list(self, mock_get):
|
def test_albums_list(self, mock_get):
|
||||||
"""Check that the album list is returned correctly"""
|
"""Check that the album list is returned correctly"""
|
||||||
mock_get.return_value = self._return_value(self.test_albums_dict)
|
mock_get.return_value = self._return_value(self.test_albums_dict)
|
||||||
result = self.client.albums.list()
|
result = self.client.albums.list(foo="bar")
|
||||||
mock_get.assert_called_with("/albums/list.json")
|
mock_get.assert_called_with("/albums/list.json", foo="bar")
|
||||||
self.assertEqual(len(result), 2)
|
self.assertEqual(len(result), 2)
|
||||||
self.assertEqual(result[0].id, "1")
|
self.assertEqual(result[0].id, "1")
|
||||||
self.assertEqual(result[0].name, "Album 1")
|
self.assertEqual(result[0].name, "Album 1")
|
||||||
self.assertEqual(result[1].id, "2")
|
self.assertEqual(result[1].id, "2")
|
||||||
self.assertEqual(result[1].name, "Album 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')
|
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||||
def test_albums_list_returns_cover_photos(self, mock_get):
|
def test_albums_list_returns_cover_photos(self, mock_get):
|
||||||
"""Check that the album list returns cover photo objects"""
|
"""Check that the album list returns cover photo objects"""
|
||||||
mock_get.return_value = self._return_value(self.test_albums_dict)
|
mock_get.return_value = self._return_value(self.test_albums_dict)
|
||||||
result = self.client.albums.list()
|
result = self.client.albums.list(foo="bar")
|
||||||
mock_get.assert_called_with("/albums/list.json")
|
mock_get.assert_called_with("/albums/list.json", foo="bar")
|
||||||
self.assertEqual(len(result), 2)
|
self.assertEqual(len(result), 2)
|
||||||
self.assertEqual(result[0].id, "1")
|
self.assertEqual(result[0].id, "1")
|
||||||
self.assertEqual(result[0].name, "Album 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.id, "2b")
|
||||||
self.assertEqual(result[1].cover.tags, ["tag3", "tag4"])
|
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):
|
class TestAlbumCreate(TestAlbums):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_create(self, mock_post):
|
def test_album_create(self, mock_post):
|
||||||
|
@ -73,104 +135,153 @@ class TestAlbumDelete(TestAlbums):
|
||||||
def test_album_delete(self, mock_post):
|
def test_album_delete(self, mock_post):
|
||||||
"""Check that an album can be deleted"""
|
"""Check that an album can be deleted"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.client.album.delete(self.test_albums[0])
|
result = self.client.album.delete(self.test_albums[0], foo="bar")
|
||||||
mock_post.assert_called_with("/album/1/delete.json")
|
mock_post.assert_called_with("/album/1/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
self.assertEqual(result, True)
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_delete_id(self, mock_post):
|
def test_album_delete_id(self, mock_post):
|
||||||
"""Check that an album can be deleted using its ID"""
|
"""Check that an album can be deleted using its ID"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.client.album.delete("1")
|
result = self.client.album.delete("1", foo="bar")
|
||||||
mock_post.assert_called_with("/album/1/delete.json")
|
mock_post.assert_called_with("/album/1/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_object_delete(self, mock_post):
|
def test_album_object_delete(self, mock_post):
|
||||||
"""Check that an album can be deleted using the album object directly"""
|
"""Check that an album can be deleted using the album object directly"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
album = self.test_albums[0]
|
album = self.test_albums[0]
|
||||||
result = album.delete()
|
result = album.delete(foo="bar")
|
||||||
mock_post.assert_called_with("/album/1/delete.json")
|
mock_post.assert_called_with("/album/1/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
self.assertEqual(result, True)
|
||||||
self.assertEqual(album.get_fields(), {})
|
self.assertEqual(album.get_fields(), {})
|
||||||
self.assertEqual(album.id, None)
|
self.assertEqual(album.id, None)
|
||||||
self.assertEqual(album.name, None)
|
self.assertEqual(album.name, None)
|
||||||
|
|
||||||
|
class TestAlbumAdd(TestAlbums):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@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
|
Check that photos can be added to an album using the
|
||||||
when using the album object directly
|
album object directly
|
||||||
"""
|
"""
|
||||||
mock_post.return_value = self._return_value(False)
|
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||||
with self.assertRaises(trovebox.TroveboxError):
|
album = self.test_albums[0]
|
||||||
self.test_albums[0].delete()
|
album.add(self.test_photos, foo="bar")
|
||||||
|
mock_post.assert_called_with("/album/1/photo/add.json",
|
||||||
class TestAlbumForm(TestAlbums):
|
ids=["1a", "2b"], foo="bar")
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
self.assertEqual(album.id, self.test_albums[1].id)
|
||||||
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.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_form_id(self, _):
|
def test_album_add_single(self, mock_post):
|
||||||
""" If album.form gets implemented, write a test! """
|
""" Check that a single photo can be added to an album """
|
||||||
with self.assertRaises(NotImplementedError):
|
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||||
self.client.album.form("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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_object_form(self, _):
|
def test_album_add_invalid_type(self, _):
|
||||||
""" If album.form gets implemented, write a test! """
|
"""
|
||||||
with self.assertRaises(NotImplementedError):
|
Check that an exception is raised if an invalid object is added
|
||||||
self.test_albums[0].form()
|
to an album.
|
||||||
|
"""
|
||||||
class TestAlbumAddPhotos(TestAlbums):
|
with self.assertRaises(AttributeError):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
self.test_albums[0].add([object()])
|
||||||
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"])
|
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_add_photos_id(self, _):
|
def test_album_add_multiple_types(self, _):
|
||||||
""" If album.add_photos gets implemented, write a test! """
|
"""
|
||||||
with self.assertRaises(NotImplementedError):
|
Check that an exception is raised if multiple types are added
|
||||||
self.client.album.add_photos("1", ["Photo Objects"])
|
to an album.
|
||||||
|
"""
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
with self.assertRaises(ValueError):
|
||||||
def test_album_object_add_photos(self, _):
|
self.test_albums[0].add(self.test_photos+self.test_albums)
|
||||||
""" If album.add_photos gets implemented, write a test! """
|
|
||||||
with self.assertRaises(NotImplementedError):
|
|
||||||
self.test_albums[0].add_photos(["Photo Objects"])
|
|
||||||
|
|
||||||
class TestAlbumRemovePhotos(TestAlbums):
|
class TestAlbumRemovePhotos(TestAlbums):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_remove_photos(self, _):
|
def test_album_remove(self, mock_post):
|
||||||
""" If album.remove_photos gets implemented, write a test! """
|
""" Check that photos can be removed from an album """
|
||||||
with self.assertRaises(NotImplementedError):
|
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||||
self.client.album.remove_photos(self.test_albums[0],
|
result = self.client.album.remove(self.test_albums[0], self.test_photos,
|
||||||
["Photo Objects"])
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_remove_photos_id(self, _):
|
def test_album_remove_id(self, mock_post):
|
||||||
""" If album.remove_photos gets implemented, write a test! """
|
""" Check that photos can be removed from an album using IDs """
|
||||||
with self.assertRaises(NotImplementedError):
|
mock_post.return_value = self._return_value(self.test_albums_dict[1])
|
||||||
self.client.album.remove_photos("1", ["Photo Objects"])
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_album_object_remove_photos(self, _):
|
def test_album_object_remove(self, mock_post):
|
||||||
""" If album.remove_photos gets implemented, write a test! """
|
"""
|
||||||
with self.assertRaises(NotImplementedError):
|
Check that photos can be removed from an album using the
|
||||||
self.test_albums[0].remove_photos(["Photo Objects"])
|
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):
|
class TestAlbumUpdate(TestAlbums):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
|
@ -212,33 +323,44 @@ class TestAlbumView(TestAlbums):
|
||||||
def test_album_view(self, mock_get):
|
def test_album_view(self, mock_get):
|
||||||
"""Check that an album can be viewed"""
|
"""Check that an album can be viewed"""
|
||||||
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
||||||
result = self.client.album.view(self.test_albums[0], name="Test")
|
result = self.client.album.view(self.test_albums[0], includeElements=True)
|
||||||
mock_get.assert_called_with("/album/1/view.json", name="Test")
|
mock_get.assert_called_with("/album/1/view.json", includeElements=True)
|
||||||
self.assertEqual(result.id, "2")
|
self.assertEqual(result.id, "2")
|
||||||
self.assertEqual(result.name, "Album 2")
|
self.assertEqual(result.name, "Album 2")
|
||||||
self.assertEqual(result.cover.id, "2b")
|
self.assertEqual(result.cover.id, "2b")
|
||||||
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
|
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
|
||||||
|
self.assertEqual(result.photos[0].id, self.test_photos[1].id)
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||||
def test_album_view_id(self, mock_get):
|
def test_album_view_id(self, mock_get):
|
||||||
"""Check that an album can be viewed using its ID"""
|
"""Check that an album can be viewed using its ID"""
|
||||||
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
||||||
result = self.client.album.view("1", name="Test")
|
result = self.client.album.view("1", includeElements=True)
|
||||||
mock_get.assert_called_with("/album/1/view.json", name="Test")
|
mock_get.assert_called_with("/album/1/view.json", includeElements=True)
|
||||||
self.assertEqual(result.id, "2")
|
self.assertEqual(result.id, "2")
|
||||||
self.assertEqual(result.name, "Album 2")
|
self.assertEqual(result.name, "Album 2")
|
||||||
self.assertEqual(result.cover.id, "2b")
|
self.assertEqual(result.cover.id, "2b")
|
||||||
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
|
self.assertEqual(result.cover.tags, ["tag3", "tag4"])
|
||||||
|
self.assertEqual(result.photos[0].id, self.test_photos[1].id)
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||||
def test_album_object_view(self, mock_get):
|
def test_album_object_view(self, mock_get):
|
||||||
"""Check that an album can be viewed using the album object directly"""
|
"""Check that an album can be viewed using the album object directly"""
|
||||||
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
mock_get.return_value = self._return_value(self.test_albums_dict[1])
|
||||||
album = self.test_albums[0]
|
album = self.test_albums[0]
|
||||||
album.view(name="Test")
|
album.view(includeElements=True)
|
||||||
mock_get.assert_called_with("/album/1/view.json", name="Test")
|
mock_get.assert_called_with("/album/1/view.json", includeElements=True)
|
||||||
self.assertEqual(album.id, "2")
|
self.assertEqual(album.id, "2")
|
||||||
self.assertEqual(album.name, "Album 2")
|
self.assertEqual(album.name, "Album 2")
|
||||||
self.assertEqual(album.cover.id, "2b")
|
self.assertEqual(album.cover.id, "2b")
|
||||||
self.assertEqual(album.cover.tags, ["tag3", "tag4"])
|
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,7 +31,8 @@ class TestAuth(unittest.TestCase):
|
||||||
def create_config(config_file, host):
|
def create_config(config_file, host):
|
||||||
"""Create a dummy config file"""
|
"""Create a dummy config file"""
|
||||||
with open(os.path.join(CONFIG_PATH, config_file), "w") as conf:
|
with open(os.path.join(CONFIG_PATH, config_file), "w") as conf:
|
||||||
conf.write("host = %s\n" % host)
|
if host is not None:
|
||||||
|
conf.write("host = %s\n" % host)
|
||||||
conf.write("# Comment\n\n")
|
conf.write("# Comment\n\n")
|
||||||
conf.write("consumerKey = \"%s_consumer_key\"\n" % config_file)
|
conf.write("consumerKey = \"%s_consumer_key\"\n" % config_file)
|
||||||
conf.write("\"consumerSecret\"= %s_consumer_secret\n" % config_file)
|
conf.write("\"consumerSecret\"= %s_consumer_secret\n" % config_file)
|
||||||
|
@ -97,4 +98,13 @@ class TestAuth(unittest.TestCase):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Trovebox(config_file="custom", host="host_override")
|
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.object(trovebox.main.trovebox, "Trovebox")
|
||||||
@mock.patch('sys.stdout', new_callable=io.StringIO)
|
@mock.patch('sys.stdout', new_callable=io.StringIO)
|
||||||
def test_verbose(self, mock_stdout, _):
|
def test_verbose_without_params(self, mock_stdout, _):
|
||||||
"""Check that the verbose option is working"""
|
"""Check that the verbose option works with no parameters"""
|
||||||
main(["-v"])
|
main(["-v"])
|
||||||
self.assertIn("Method: GET", mock_stdout.getvalue())
|
self.assertIn("Method: GET", mock_stdout.getvalue())
|
||||||
self.assertIn("Endpoint: /photos/list.json", 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.object(trovebox.main.trovebox, "Trovebox")
|
||||||
@mock.patch('sys.stdout', new_callable=io.StringIO)
|
@mock.patch('sys.stdout', new_callable=io.StringIO)
|
||||||
|
@ -127,3 +137,8 @@ class TestCli(unittest.TestCase):
|
||||||
main(["--version"])
|
main(["--version"])
|
||||||
self.assertEqual(mock_stdout.getvalue(), trovebox.__version__ + "\n")
|
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)
|
"https://test.example.com/%s" % self.test_endpoint)
|
||||||
self.assertEqual(self.client.last_response.json(), self.test_data)
|
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
|
@httpretty.activate
|
||||||
def test_get_with_parameters(self):
|
def test_get_with_parameters(self):
|
||||||
"""Check that the get method accepts parameters correctly"""
|
"""Check that the get method accepts parameters correctly"""
|
||||||
|
@ -171,12 +187,13 @@ class TestHttp(unittest.TestCase):
|
||||||
def test_get_parameter_processing(self):
|
def test_get_parameter_processing(self):
|
||||||
"""Check that the parameter processing function is working"""
|
"""Check that the parameter processing function is working"""
|
||||||
self._register_uri(httpretty.GET)
|
self._register_uri(httpretty.GET)
|
||||||
photo = trovebox.objects.Photo(None, {"id": "photo_id"})
|
photo = trovebox.objects.photo.Photo(None, {"id": "photo_id"})
|
||||||
album = trovebox.objects.Album(None, {"id": "album_id"})
|
album = trovebox.objects.album.Album(None, {"id": "album_id"})
|
||||||
tag = trovebox.objects.Tag(None, {"id": "tag_id"})
|
tag = trovebox.objects.tag.Tag(None, {"id": "tag_id"})
|
||||||
self.client.get(self.test_endpoint,
|
self.client.get(self.test_endpoint,
|
||||||
photo=photo, album=album, tag=tag,
|
photo=photo, album=album, tag=tag,
|
||||||
list_=[photo, album, tag],
|
list_=[photo, album, tag],
|
||||||
|
list2=["1", "2", "3"],
|
||||||
boolean=True,
|
boolean=True,
|
||||||
unicode_="\xfcmlaut")
|
unicode_="\xfcmlaut")
|
||||||
params = self._last_request().querystring
|
params = self._last_request().querystring
|
||||||
|
@ -184,6 +201,7 @@ class TestHttp(unittest.TestCase):
|
||||||
self.assertEqual(params["album"], ["album_id"])
|
self.assertEqual(params["album"], ["album_id"])
|
||||||
self.assertEqual(params["tag"], ["tag_id"])
|
self.assertEqual(params["tag"], ["tag_id"])
|
||||||
self.assertEqual(params["list_"], ["photo_id,album_id,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.assertEqual(params["boolean"], ["1"])
|
||||||
self.assertIn(params["unicode_"], [["\xc3\xbcmlaut"], ["\xfcmlaut"]])
|
self.assertIn(params["unicode_"], [["\xc3\xbcmlaut"], ["\xfcmlaut"]])
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import json
|
import json
|
||||||
import httpretty
|
import httpretty
|
||||||
|
from httpretty import GET, POST
|
||||||
|
from ddt import ddt, data
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import unittest2 as unittest # Python2.6
|
import unittest2 as unittest # Python2.6
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from test_http import GetOrPost
|
||||||
import trovebox
|
import trovebox
|
||||||
|
|
||||||
|
@ddt
|
||||||
class TestHttpErrors(unittest.TestCase):
|
class TestHttpErrors(unittest.TestCase):
|
||||||
test_host = "test.example.com"
|
test_host = "test.example.com"
|
||||||
test_endpoint = "test.json"
|
test_endpoint = "test.json"
|
||||||
|
@ -38,146 +42,93 @@ class TestHttpErrors(unittest.TestCase):
|
||||||
**kwds)
|
**kwds)
|
||||||
|
|
||||||
@httpretty.activate
|
@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
|
to raise an exception
|
||||||
"""
|
"""
|
||||||
self._register_uri(httpretty.GET, status=500)
|
self._register_uri(method, status=500)
|
||||||
with self.assertRaises(trovebox.TroveboxError):
|
with self.assertRaises(trovebox.TroveboxError):
|
||||||
self.client.get(self.test_endpoint)
|
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||||
|
|
||||||
@httpretty.activate
|
@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
|
Check that a 404 status causes the get/post methods
|
||||||
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
|
|
||||||
to raise a 404 exception
|
to raise a 404 exception
|
||||||
"""
|
"""
|
||||||
self._register_uri(httpretty.GET, status=404)
|
self._register_uri(method, status=404)
|
||||||
with self.assertRaises(trovebox.Trovebox404Error):
|
with self.assertRaises(trovebox.Trovebox404Error):
|
||||||
self.client.get(self.test_endpoint)
|
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||||
|
|
||||||
@httpretty.activate
|
@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
|
Check that invalid JSON causes the get/post methods to
|
||||||
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
|
|
||||||
raise an exception
|
raise an exception
|
||||||
"""
|
"""
|
||||||
self._register_uri(httpretty.GET, body="Invalid JSON")
|
self._register_uri(method, body="Invalid JSON")
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
self.client.get(self.test_endpoint)
|
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||||
|
|
||||||
@httpretty.activate
|
@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
|
Check that invalid JSON causes the get/post methods to raise
|
||||||
raise an exception
|
an exception, even with an error status is returned
|
||||||
"""
|
"""
|
||||||
self._register_uri(httpretty.POST, body="Invalid JSON")
|
self._register_uri(method, body="Invalid JSON", status=500)
|
||||||
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)
|
|
||||||
with self.assertRaises(trovebox.TroveboxError):
|
with self.assertRaises(trovebox.TroveboxError):
|
||||||
self.client.get(self.test_endpoint)
|
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||||
|
|
||||||
@httpretty.activate
|
@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,
|
Check that invalid JSON causes the get/post methods to raise
|
||||||
even with an error status is returned
|
an exception, even with a 404 status is returned
|
||||||
"""
|
"""
|
||||||
self._register_uri(httpretty.POST, body="Invalid JSON", status=500)
|
self._register_uri(method, body="Invalid JSON", status=404)
|
||||||
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)
|
|
||||||
with self.assertRaises(trovebox.Trovebox404Error):
|
with self.assertRaises(trovebox.Trovebox404Error):
|
||||||
self.client.get(self.test_endpoint)
|
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||||
|
|
||||||
@httpretty.activate
|
@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,
|
Check that a get/post with a duplicate status
|
||||||
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
|
|
||||||
raises a duplicate exception
|
raises a duplicate exception
|
||||||
"""
|
"""
|
||||||
data = {"message": "This photo already exists", "code": 409}
|
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):
|
with self.assertRaises(trovebox.TroveboxDuplicateError):
|
||||||
self.client.get(self.test_endpoint)
|
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||||
|
|
||||||
@httpretty.activate
|
@httpretty.activate
|
||||||
def test_post_with_duplicate_status(self):
|
@data(GET, POST)
|
||||||
"""
|
def test_with_status_code_mismatch(self, method):
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Check that a mismatched HTTP status code still returns the
|
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}
|
data = {"message": "Test Message", "code": 202}
|
||||||
self._register_uri(httpretty.GET, data=data, status=200)
|
self._register_uri(method, data=data, status=200)
|
||||||
response = self.client.get(self.test_endpoint)
|
response = GetOrPost(self.client, method).call(self.test_endpoint)
|
||||||
self.assertEqual(response["code"], 202)
|
self.assertEqual(response["code"], 202)
|
||||||
|
|
||||||
@httpretty.activate
|
@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
|
Check that get/post methods work with response processing disabled
|
||||||
JSON status code for post requests.
|
when an HTTP error code is returned.
|
||||||
"""
|
"""
|
||||||
data = {"message": "Test Message", "code": 202}
|
httpretty.register_uri(method, self.test_uri, status=500)
|
||||||
self._register_uri(httpretty.POST, data=data, status=200)
|
with self.assertRaises(trovebox.TroveboxError):
|
||||||
response = self.client.post(self.test_endpoint)
|
response = GetOrPost(self.client, method).call(self.test_endpoint,
|
||||||
self.assertEqual(response["code"], 202)
|
process_response=False)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ class TestPhotos(unittest.TestCase):
|
||||||
"totalPages": 1, "totalRows": 2}]
|
"totalPages": 1, "totalRows": 2}]
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = trovebox.Trovebox(host=self.test_host)
|
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]
|
for photo in self.test_photos_dict]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -31,14 +31,55 @@ class TestPhotosList(TestPhotos):
|
||||||
"""Check that the photo list is returned correctly"""
|
"""Check that the photo list is returned correctly"""
|
||||||
mock_get.return_value = self._return_value(self.test_photos_dict)
|
mock_get.return_value = self._return_value(self.test_photos_dict)
|
||||||
|
|
||||||
result = self.client.photos.list()
|
result = self.client.photos.list(foo="bar")
|
||||||
mock_get.assert_called_with("/photos/list.json")
|
mock_get.assert_called_with("/photos/list.json", foo="bar")
|
||||||
self.assertEqual(len(result), 2)
|
self.assertEqual(len(result), 2)
|
||||||
self.assertEqual(result[0].id, "1a")
|
self.assertEqual(result[0].id, "1a")
|
||||||
self.assertEqual(result[0].tags, ["tag1", "tag2"])
|
self.assertEqual(result[0].tags, ["tag1", "tag2"])
|
||||||
self.assertEqual(result[1].id, "2b")
|
self.assertEqual(result[1].id, "2b")
|
||||||
self.assertEqual(result[1].tags, ["tag3", "tag4"])
|
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):
|
class TestPhotosUpdate(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photos_update(self, mock_post):
|
def test_photos_update(self, mock_post):
|
||||||
|
@ -58,67 +99,42 @@ class TestPhotosUpdate(TestPhotos):
|
||||||
ids=["1a", "2b"], title="Test")
|
ids=["1a", "2b"], title="Test")
|
||||||
self.assertEqual(result, True)
|
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):
|
class TestPhotosDelete(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photos_delete(self, mock_post):
|
def test_photos_delete(self, mock_post):
|
||||||
"""Check that multiple photos can be deleted"""
|
"""Check that multiple photos can be deleted"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.client.photos.delete(self.test_photos)
|
result = self.client.photos.delete(self.test_photos, foo="bar")
|
||||||
mock_post.assert_called_with("/photos/delete.json", ids=["1a", "2b"])
|
mock_post.assert_called_with("/photos/delete.json",
|
||||||
|
ids=["1a", "2b"], foo="bar")
|
||||||
self.assertEqual(result, True)
|
self.assertEqual(result, True)
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photos_delete_ids(self, mock_post):
|
def test_photos_delete_ids(self, mock_post):
|
||||||
"""Check that multiple photos can be deleted using their IDs"""
|
"""Check that multiple photos can be deleted using their IDs"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.client.photos.delete(["1a", "2b"])
|
result = self.client.photos.delete(["1a", "2b"], foo="bar")
|
||||||
mock_post.assert_called_with("/photos/delete.json", ids=["1a", "2b"])
|
mock_post.assert_called_with("/photos/delete.json",
|
||||||
|
ids=["1a", "2b"], foo="bar")
|
||||||
self.assertEqual(result, True)
|
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):
|
class TestPhotoDelete(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_delete(self, mock_post):
|
def test_photo_delete(self, mock_post):
|
||||||
"""Check that a photo can be deleted"""
|
"""Check that a photo can be deleted"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.client.photo.delete(self.test_photos[0])
|
result = self.client.photo.delete(self.test_photos[0], foo="bar")
|
||||||
mock_post.assert_called_with("/photo/1a/delete.json")
|
mock_post.assert_called_with("/photo/1a/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
self.assertEqual(result, True)
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_delete_id(self, mock_post):
|
def test_photo_delete_id(self, mock_post):
|
||||||
"""Check that a photo can be deleted using its ID"""
|
"""Check that a photo can be deleted using its ID"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.client.photo.delete("1a")
|
result = self.client.photo.delete("1a", foo="bar")
|
||||||
mock_post.assert_called_with("/photo/1a/delete.json")
|
mock_post.assert_called_with("/photo/1a/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_object_delete(self, mock_post):
|
def test_photo_object_delete(self, mock_post):
|
||||||
"""
|
"""
|
||||||
|
@ -127,87 +143,189 @@ class TestPhotoDelete(TestPhotos):
|
||||||
"""
|
"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
photo = self.test_photos[0]
|
photo = self.test_photos[0]
|
||||||
result = photo.delete()
|
result = photo.delete(foo="bar")
|
||||||
mock_post.assert_called_with("/photo/1a/delete.json")
|
mock_post.assert_called_with("/photo/1a/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
self.assertEqual(result, True)
|
||||||
self.assertEqual(photo.get_fields(), {})
|
self.assertEqual(photo.get_fields(), {})
|
||||||
self.assertEqual(photo.id, None)
|
self.assertEqual(photo.id, None)
|
||||||
|
|
||||||
|
class TestPhotoDeleteSource(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_object_delete_failure(self, mock_post):
|
def test_photo_delete_source(self, mock_post):
|
||||||
"""
|
"""Check that photo source files can be deleted"""
|
||||||
Check that an exception is raised if a photo cannot be deleted
|
mock_post.return_value = self._return_value(True)
|
||||||
when using the photo object directly
|
result = self.client.photo.delete_source(self.test_photos[0], foo="bar")
|
||||||
"""
|
mock_post.assert_called_with("/photo/1a/source/delete.json", foo="bar")
|
||||||
mock_post.return_value = self._return_value(False)
|
self.assertEqual(result, True)
|
||||||
with self.assertRaises(trovebox.TroveboxError):
|
|
||||||
self.test_photos[0].delete()
|
|
||||||
|
|
||||||
class TestPhotoEdit(TestPhotos):
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
def test_photo_delete_source_id(self, mock_post):
|
||||||
def test_photo_edit(self, mock_get):
|
"""Check that photo source files can be deleted using its ID"""
|
||||||
"""Check that a the photo edit endpoint is working"""
|
mock_post.return_value = self._return_value(True)
|
||||||
mock_get.return_value = self._return_value({"markup": "<form/>"})
|
result = self.client.photo.delete_source("1a", foo="bar")
|
||||||
result = self.client.photo.edit(self.test_photos[0])
|
mock_post.assert_called_with("/photo/1a/source/delete.json", foo="bar")
|
||||||
mock_get.assert_called_with("/photo/1a/edit.json")
|
self.assertEqual(result, True)
|
||||||
self.assertEqual(result, "<form/>")
|
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_edit_id(self, mock_get):
|
def test_photo_object_delete_source(self, mock_post):
|
||||||
"""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):
|
|
||||||
"""
|
"""
|
||||||
Check that a the photo edit endpoint is working
|
Check that photo source files can be deleted when using
|
||||||
when using the photo object directly
|
the photo object directly
|
||||||
"""
|
"""
|
||||||
mock_get.return_value = self._return_value({"markup": "<form/>"})
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.test_photos[0].edit()
|
photo = self.test_photos[0]
|
||||||
mock_get.assert_called_with("/photo/1a/edit.json")
|
result = photo.delete_source(foo="bar")
|
||||||
self.assertEqual(result, "<form/>")
|
mock_post.assert_called_with("/photo/1a/source/delete.json", foo="bar")
|
||||||
|
self.assertEqual(result, True)
|
||||||
|
|
||||||
class TestPhotoReplace(TestPhotos):
|
class TestPhotoReplace(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_replace(self, _):
|
def test_photo_replace(self, mock_post):
|
||||||
""" If photo.replace gets implemented, write a test! """
|
"""Check that an existing photo can be replaced"""
|
||||||
with self.assertRaises(NotImplementedError):
|
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||||
self.client.photo.replace(self.test_photos[0], self.test_file)
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_replace_id(self, _):
|
def test_photo_replace_id(self, mock_post):
|
||||||
""" If photo.replace gets implemented, write a test! """
|
"""Check that an existing photo can be replaced using its ID"""
|
||||||
with self.assertRaises(NotImplementedError):
|
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||||
self.client.photo.replace("1a", self.test_file)
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_object_replace(self, _):
|
def test_photo_object_replace(self, mock_post):
|
||||||
""" If photo.replace gets implemented, write a test! """
|
"""
|
||||||
with self.assertRaises(NotImplementedError):
|
Check that an existing photo can be replaced when using the
|
||||||
self.test_photos[0].replace(self.test_file)
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_replace_encoded(self, _):
|
def test_photo_replace_encoded_id(self, mock_post):
|
||||||
""" If photo.replace_encoded gets implemented, write a test! """
|
"""
|
||||||
with self.assertRaises(NotImplementedError):
|
Check that a photo can be uploaded using Base64 encoding to
|
||||||
self.client.photo.replace_encoded(self.test_photos[0],
|
replace an existing photo using its ID.
|
||||||
self.test_file)
|
"""
|
||||||
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_replace_encoded_id(self, _):
|
def test_photo_object_replace_encoded(self, mock_post):
|
||||||
""" If photo.replace_encoded gets implemented, write a test! """
|
"""
|
||||||
with self.assertRaises(NotImplementedError):
|
Check that a photo can be uploaded using Base64 encoding to
|
||||||
self.client.photo.replace_encoded("1a", self.test_file)
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_object_replace_encoded(self, _):
|
def test_photo_id_replace_from_url(self, mock_post):
|
||||||
""" If photo.replace_encoded gets implemented, write a test! """
|
"""
|
||||||
with self.assertRaises(NotImplementedError):
|
Check that a photo can be imported from a url to
|
||||||
self.test_photos[0].replace_encoded(photo_file=self.test_file)
|
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):
|
class TestPhotoUpdate(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
|
@ -244,16 +362,30 @@ class TestPhotoView(TestPhotos):
|
||||||
"""Check that a photo can be viewed"""
|
"""Check that a photo can be viewed"""
|
||||||
mock_get.return_value = self._return_value(self.test_photos_dict[1])
|
mock_get.return_value = self._return_value(self.test_photos_dict[1])
|
||||||
result = self.client.photo.view(self.test_photos[0],
|
result = self.client.photo.view(self.test_photos[0],
|
||||||
|
options={"foo": "bar",
|
||||||
|
"test1": "test2"},
|
||||||
returnSizes="20x20")
|
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])
|
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||||
def test_photo_view_id(self, mock_get):
|
def test_photo_view_id(self, mock_get):
|
||||||
"""Check that a photo can be viewed using its ID"""
|
"""Check that a photo can be viewed using its ID"""
|
||||||
mock_get.return_value = self._return_value(self.test_photos_dict[1])
|
mock_get.return_value = self._return_value(self.test_photos_dict[1])
|
||||||
result = self.client.photo.view("1a", returnSizes="20x20")
|
result = self.client.photo.view("1a",
|
||||||
mock_get.assert_called_with("/photo/1a/view.json", returnSizes="20x20")
|
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])
|
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
@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])
|
mock_get.return_value = self._return_value(self.test_photos_dict[1])
|
||||||
photo = self.test_photos[0]
|
photo = self.test_photos[0]
|
||||||
photo.view(returnSizes="20x20")
|
photo.view(returnSizes="20x20", options={"foo": "bar",
|
||||||
mock_get.assert_called_with("/photo/1a/view.json", returnSizes="20x20")
|
"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])
|
self.assertEqual(photo.get_fields(), self.test_photos_dict[1])
|
||||||
|
|
||||||
class TestPhotoUpload(TestPhotos):
|
class TestPhotoUpload(TestPhotos):
|
||||||
|
@ -284,6 +422,7 @@ class TestPhotoUpload(TestPhotos):
|
||||||
self.assertIn("photo", files)
|
self.assertIn("photo", files)
|
||||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||||
|
|
||||||
|
class TestPhotoUploadEncoded(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_upload_encoded(self, mock_post):
|
def test_photo_upload_encoded(self, mock_post):
|
||||||
"""Check that a photo can be uploaded using Base64 encoding"""
|
"""Check that a photo can be uploaded using Base64 encoding"""
|
||||||
|
@ -295,24 +434,17 @@ class TestPhotoUpload(TestPhotos):
|
||||||
photo=encoded_file, title="Test")
|
photo=encoded_file, title="Test")
|
||||||
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||||
|
|
||||||
class TestPhotoDynamicUrl(TestPhotos):
|
class TestPhotoUploadFromUrl(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_photo_dynamic_url(self, _):
|
def test_photo_upload_from_url(self, mock_post):
|
||||||
""" If photo.dynamic_url gets implemented, write a test! """
|
"""
|
||||||
with self.assertRaises(NotImplementedError):
|
Check that a photo can be imported from a url.
|
||||||
self.client.photo.dynamic_url(self.test_photos[0])
|
"""
|
||||||
|
mock_post.return_value = self._return_value(self.test_photos_dict[0])
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
result = self.client.photo.upload_from_url("test_url", title="Test")
|
||||||
def test_photo_dynamic_url_id(self, _):
|
mock_post.assert_called_with("/photo/upload.json",
|
||||||
""" If photo.dynamic_url gets implemented, write a test! """
|
photo="test_url", title="Test")
|
||||||
with self.assertRaises(NotImplementedError):
|
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
|
||||||
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 TestPhotoNextPrevious(TestPhotos):
|
class TestPhotoNextPrevious(TestPhotos):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||||
|
@ -321,8 +453,15 @@ class TestPhotoNextPrevious(TestPhotos):
|
||||||
mock_get.return_value = self._return_value(
|
mock_get.return_value = self._return_value(
|
||||||
{"next": [self.test_photos_dict[0]],
|
{"next": [self.test_photos_dict[0]],
|
||||||
"previous": [self.test_photos_dict[1]]})
|
"previous": [self.test_photos_dict[1]]})
|
||||||
result = self.client.photo.next_previous(self.test_photos[0])
|
result = self.client.photo.next_previous(self.test_photos[0],
|
||||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
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.assertEqual(result["next"][0].get_fields(),
|
||||||
self.test_photos_dict[0])
|
self.test_photos_dict[0])
|
||||||
self.assertEqual(result["previous"][0].get_fields(),
|
self.assertEqual(result["previous"][0].get_fields(),
|
||||||
|
@ -337,8 +476,15 @@ class TestPhotoNextPrevious(TestPhotos):
|
||||||
mock_get.return_value = self._return_value(
|
mock_get.return_value = self._return_value(
|
||||||
{"next": [self.test_photos_dict[0]],
|
{"next": [self.test_photos_dict[0]],
|
||||||
"previous": [self.test_photos_dict[1]]})
|
"previous": [self.test_photos_dict[1]]})
|
||||||
result = self.client.photo.next_previous("1a")
|
result = self.client.photo.next_previous("1a",
|
||||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
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.assertEqual(result["next"][0].get_fields(),
|
||||||
self.test_photos_dict[0])
|
self.test_photos_dict[0])
|
||||||
self.assertEqual(result["previous"][0].get_fields(),
|
self.assertEqual(result["previous"][0].get_fields(),
|
||||||
|
@ -353,8 +499,14 @@ class TestPhotoNextPrevious(TestPhotos):
|
||||||
mock_get.return_value = self._return_value(
|
mock_get.return_value = self._return_value(
|
||||||
{"next": [self.test_photos_dict[0]],
|
{"next": [self.test_photos_dict[0]],
|
||||||
"previous": [self.test_photos_dict[1]]})
|
"previous": [self.test_photos_dict[1]]})
|
||||||
result = self.test_photos[0].next_previous()
|
result = self.test_photos[0].next_previous(options={"foo": "bar",
|
||||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
"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.assertEqual(result["next"][0].get_fields(),
|
||||||
self.test_photos_dict[0])
|
self.test_photos_dict[0])
|
||||||
self.assertEqual(result["previous"][0].get_fields(),
|
self.assertEqual(result["previous"][0].get_fields(),
|
||||||
|
@ -365,8 +517,10 @@ class TestPhotoNextPrevious(TestPhotos):
|
||||||
"""Check that the next photos are returned"""
|
"""Check that the next photos are returned"""
|
||||||
mock_get.return_value = self._return_value(
|
mock_get.return_value = self._return_value(
|
||||||
{"next": [self.test_photos_dict[0]]})
|
{"next": [self.test_photos_dict[0]]})
|
||||||
result = self.client.photo.next_previous(self.test_photos[0])
|
result = self.client.photo.next_previous(self.test_photos[0],
|
||||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
foo="bar")
|
||||||
|
mock_get.assert_called_with("/photo/1a/nextprevious.json",
|
||||||
|
foo="bar")
|
||||||
self.assertEqual(result["next"][0].get_fields(),
|
self.assertEqual(result["next"][0].get_fields(),
|
||||||
self.test_photos_dict[0])
|
self.test_photos_dict[0])
|
||||||
self.assertNotIn("previous", result)
|
self.assertNotIn("previous", result)
|
||||||
|
@ -376,8 +530,10 @@ class TestPhotoNextPrevious(TestPhotos):
|
||||||
"""Check that the previous photos are returned"""
|
"""Check that the previous photos are returned"""
|
||||||
mock_get.return_value = self._return_value(
|
mock_get.return_value = self._return_value(
|
||||||
{"previous": [self.test_photos_dict[1]]})
|
{"previous": [self.test_photos_dict[1]]})
|
||||||
result = self.client.photo.next_previous(self.test_photos[0])
|
result = self.client.photo.next_previous(self.test_photos[0],
|
||||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
foo="bar")
|
||||||
|
mock_get.assert_called_with("/photo/1a/nextprevious.json",
|
||||||
|
foo="bar")
|
||||||
self.assertEqual(result["previous"][0].get_fields(),
|
self.assertEqual(result["previous"][0].get_fields(),
|
||||||
self.test_photos_dict[1])
|
self.test_photos_dict[1])
|
||||||
self.assertNotIn("next", result)
|
self.assertNotIn("next", result)
|
||||||
|
@ -388,8 +544,10 @@ class TestPhotoNextPrevious(TestPhotos):
|
||||||
mock_get.return_value = self._return_value(
|
mock_get.return_value = self._return_value(
|
||||||
{"next": [self.test_photos_dict[0], self.test_photos_dict[0]],
|
{"next": [self.test_photos_dict[0], self.test_photos_dict[0]],
|
||||||
"previous": [self.test_photos_dict[1], self.test_photos_dict[1]]})
|
"previous": [self.test_photos_dict[1], self.test_photos_dict[1]]})
|
||||||
result = self.client.photo.next_previous(self.test_photos[0])
|
result = self.client.photo.next_previous(self.test_photos[0],
|
||||||
mock_get.assert_called_with("/photo/1a/nextprevious.json")
|
foo="bar")
|
||||||
|
mock_get.assert_called_with("/photo/1a/nextprevious.json",
|
||||||
|
foo="bar")
|
||||||
self.assertEqual(result["next"][0].get_fields(),
|
self.assertEqual(result["next"][0].get_fields(),
|
||||||
self.test_photos_dict[0])
|
self.test_photos_dict[0])
|
||||||
self.assertEqual(result["next"][1].get_fields(),
|
self.assertEqual(result["next"][1].get_fields(),
|
||||||
|
@ -427,3 +585,96 @@ class TestPhotoTransform(TestPhotos):
|
||||||
photo.transform(rotate="90")
|
photo.transform(rotate="90")
|
||||||
mock_post.assert_called_with("/photo/1a/transform.json", rotate="90")
|
mock_post.assert_called_with("/photo/1a/transform.json", rotate="90")
|
||||||
self.assertEqual(photo.get_fields(), self.test_photos_dict[1])
|
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):
|
def setUp(self):
|
||||||
self.client = trovebox.Trovebox(host=self.test_host)
|
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]
|
for tag in self.test_tags_dict]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -25,61 +25,70 @@ class TestTags(unittest.TestCase):
|
||||||
class TestTagsList(TestTags):
|
class TestTagsList(TestTags):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'get')
|
@mock.patch.object(trovebox.Trovebox, 'get')
|
||||||
def test_tags_list(self, mock_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)
|
mock_get.return_value = self._return_value(self.test_tags_dict)
|
||||||
result = self.client.tags.list()
|
result = self.client.tags.list(foo="bar")
|
||||||
mock_get.assert_called_with("/tags/list.json")
|
mock_get.assert_called_with("/tags/list.json", foo="bar")
|
||||||
self.assertEqual(len(result), 2)
|
self.assertEqual(len(result), 2)
|
||||||
self.assertEqual(result[0].id, "tag1")
|
self.assertEqual(result[0].id, "tag1")
|
||||||
self.assertEqual(result[0].count, 11)
|
self.assertEqual(result[0].count, 11)
|
||||||
self.assertEqual(result[1].id, "tag2")
|
self.assertEqual(result[1].id, "tag2")
|
||||||
self.assertEqual(result[1].count, 5)
|
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):
|
class TestTagDelete(TestTags):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_tag_delete(self, mock_post):
|
def test_tag_delete(self, mock_post):
|
||||||
"""Check that a tag can be deleted"""
|
"""Check that a tag can be deleted"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.client.tag.delete(self.test_tags[0])
|
result = self.client.tag.delete(self.test_tags[0], foo="bar")
|
||||||
mock_post.assert_called_with("/tag/tag1/delete.json")
|
mock_post.assert_called_with("/tag/tag1/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
self.assertEqual(result, True)
|
||||||
|
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_tag_delete_id(self, mock_post):
|
def test_tag_delete_id(self, mock_post):
|
||||||
"""Check that a tag can be deleted using its ID"""
|
"""Check that a tag can be deleted using its ID"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
result = self.client.tag.delete("tag1")
|
result = self.client.tag.delete("tag1", foo="bar")
|
||||||
mock_post.assert_called_with("/tag/tag1/delete.json")
|
mock_post.assert_called_with("/tag/tag1/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
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')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_tag_object_delete(self, mock_post):
|
def test_tag_object_delete(self, mock_post):
|
||||||
"""Check that a tag can be deleted when using the tag object directly"""
|
"""Check that a tag can be deleted when using the tag object directly"""
|
||||||
mock_post.return_value = self._return_value(True)
|
mock_post.return_value = self._return_value(True)
|
||||||
tag = self.test_tags[0]
|
tag = self.test_tags[0]
|
||||||
result = tag.delete()
|
result = tag.delete(foo="bar")
|
||||||
mock_post.assert_called_with("/tag/tag1/delete.json")
|
mock_post.assert_called_with("/tag/tag1/delete.json", foo="bar")
|
||||||
self.assertEqual(result, True)
|
self.assertEqual(result, True)
|
||||||
self.assertEqual(tag.get_fields(), {})
|
self.assertEqual(tag.get_fields(), {})
|
||||||
self.assertEqual(tag.id, None)
|
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):
|
class TestTagUpdate(TestTags):
|
||||||
@mock.patch.object(trovebox.Trovebox, 'post')
|
@mock.patch.object(trovebox.Trovebox, 'post')
|
||||||
def test_tag_update(self, mock_post):
|
def test_tag_update(self, mock_post):
|
||||||
|
|
11
tox.ini
11
tox.ini
|
@ -1,5 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py26, py27, py33
|
envlist = py26, py27, py33, coverage
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands = python -m unittest discover tests/unit
|
commands = python -m unittest discover tests/unit
|
||||||
|
@ -18,3 +18,12 @@ deps =
|
||||||
ddt >= 0.3.0
|
ddt >= 0.3.0
|
||||||
unittest2
|
unittest2
|
||||||
discover
|
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
|
__init__.py : Trovebox package top level
|
||||||
"""
|
"""
|
||||||
from .http import Http
|
from .http import Http
|
||||||
from .errors import *
|
from .errors import TroveboxError, TroveboxDuplicateError, Trovebox404Error
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
from . import api_photo
|
from trovebox.api import api_photo
|
||||||
from . import api_tag
|
from trovebox.api import api_tag
|
||||||
from . import api_album
|
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
|
LATEST_API_VERSION = 2
|
||||||
|
|
||||||
|
@ -36,3 +39,7 @@ class Trovebox(Http):
|
||||||
self.tag = api_tag.ApiTag(self)
|
self.tag = api_tag.ApiTag(self)
|
||||||
self.albums = api_album.ApiAlbums(self)
|
self.albums = api_album.ApiAlbums(self)
|
||||||
self.album = api_album.ApiAlbum(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 @@
|
||||||
|
"""Current version string"""
|
||||||
__version__ = "0.5.1"
|
__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
|
from ConfigParser import SafeConfigParser as ConfigParser # Python2
|
||||||
try:
|
try:
|
||||||
import io # Python3
|
import io # Python3
|
||||||
except ImportError:
|
except ImportError: # pragma: no cover
|
||||||
import StringIO as io # Python2
|
import StringIO as io # Python2
|
||||||
|
|
||||||
class Auth(object):
|
class Auth(object):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
errors.py : Trovebox Error Classes
|
errors.py : Trovebox Error Classes
|
||||||
"""
|
"""
|
||||||
class TroveboxError(Exception):
|
class TroveboxError(Exception):
|
||||||
""" Indicates that an Trovebox operation failed """
|
""" Indicates that a Trovebox operation failed """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class TroveboxDuplicateError(TroveboxError):
|
class TroveboxDuplicateError(TroveboxError):
|
||||||
|
|
|
@ -11,13 +11,13 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urlparse import urlparse, urlunparse # Python2
|
from urlparse import urlparse, urlunparse # Python2
|
||||||
|
|
||||||
from .objects import TroveboxObject
|
from trovebox.objects.trovebox_object import TroveboxObject
|
||||||
from .errors import *
|
from .errors import TroveboxError, Trovebox404Error, TroveboxDuplicateError
|
||||||
from .auth import Auth
|
from .auth import Auth
|
||||||
|
|
||||||
if sys.version < '3':
|
if sys.version < '3':
|
||||||
TEXT_TYPE = unicode
|
TEXT_TYPE = unicode
|
||||||
else:
|
else: # pragma: no cover
|
||||||
TEXT_TYPE = str
|
TEXT_TYPE = str
|
||||||
|
|
||||||
DUPLICATE_RESPONSE = {"code": 409,
|
DUPLICATE_RESPONSE = {"code": 409,
|
||||||
|
@ -25,7 +25,7 @@ DUPLICATE_RESPONSE = {"code": 409,
|
||||||
|
|
||||||
class Http(object):
|
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
|
If no parameters are specified, auth config is loaded from the
|
||||||
default location (~/.config/trovebox/default).
|
default location (~/.config/trovebox/default).
|
||||||
The config_file parameter is used to specify an alternate config file.
|
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)
|
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 "
|
print("Deprecation Warning: api_version should be set by "
|
||||||
"calling the configure function")
|
"calling the configure function")
|
||||||
self.config["api_version"] = api_version
|
self.config["api_version"] = api_version
|
||||||
|
@ -104,7 +104,9 @@ class Http(object):
|
||||||
self._logger.info("============================")
|
self._logger.info("============================")
|
||||||
self._logger.info("GET %s" % url)
|
self._logger.info("GET %s" % url)
|
||||||
self._logger.info("---")
|
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_url = url
|
||||||
self.last_params = params
|
self.last_params = params
|
||||||
|
@ -113,7 +115,11 @@ class Http(object):
|
||||||
if process_response:
|
if process_response:
|
||||||
return self._process_response(response)
|
return self._process_response(response)
|
||||||
else:
|
else:
|
||||||
return response.text
|
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):
|
def post(self, endpoint, process_response=True, files=None, **params):
|
||||||
"""
|
"""
|
||||||
|
@ -154,7 +160,9 @@ class Http(object):
|
||||||
if files:
|
if files:
|
||||||
self._logger.info("files: %s" % repr(files))
|
self._logger.info("files: %s" % repr(files))
|
||||||
self._logger.info("---")
|
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_url = url
|
||||||
self.last_params = params
|
self.last_params = params
|
||||||
|
@ -163,7 +171,11 @@ class Http(object):
|
||||||
if process_response:
|
if process_response:
|
||||||
return self._process_response(response)
|
return self._process_response(response)
|
||||||
else:
|
else:
|
||||||
return response.text
|
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):
|
def _construct_url(self, endpoint):
|
||||||
"""Return the full URL to the specified endpoint"""
|
"""Return the full URL to the specified endpoint"""
|
||||||
|
@ -241,12 +253,3 @@ class Http(object):
|
||||||
raise TroveboxDuplicateError("Code %d: %s" % (code, message))
|
raise TroveboxDuplicateError("Code %d: %s" % (code, message))
|
||||||
else:
|
else:
|
||||||
raise TroveboxError("Code %d: %s" % (code, message))
|
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:]):
|
def main(args=sys.argv[1:]):
|
||||||
|
"""Run the commandline script"""
|
||||||
usage = "%prog --help"
|
usage = "%prog --help"
|
||||||
parser = OptionParser(usage, add_help_option=False)
|
parser = OptionParser(usage, add_help_option=False)
|
||||||
parser.add_option('-c', '--config', help="Configuration file to use",
|
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)
|
params, files = extract_files(params)
|
||||||
result = client.post(options.endpoint, process_response=False,
|
result = client.post(options.endpoint, process_response=False,
|
||||||
files=files, **params)
|
files=files, **params)
|
||||||
for f in files:
|
for file_ in files:
|
||||||
files[f].close()
|
files[file_].close()
|
||||||
|
|
||||||
if options.verbose:
|
if options.verbose:
|
||||||
print("==========\nMethod: %s\nHost: %s\nEndpoint: %s" %
|
print("==========\nMethod: %s\nHost: %s\nEndpoint: %s" %
|
||||||
|
@ -129,5 +130,5 @@ def extract_files(params):
|
||||||
|
|
||||||
return updated_params, files
|
return updated_params, files
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # pragma: no cover
|
||||||
main()
|
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