Python3 support
This commit is contained in:
parent
6c75abc9a8
commit
0805f032fb
14 changed files with 131 additions and 96 deletions
|
@ -1,8 +1,8 @@
|
|||
from openphoto_http import OpenPhotoHttp
|
||||
from errors import *
|
||||
import api_photo
|
||||
import api_tag
|
||||
import api_album
|
||||
from .openphoto_http import OpenPhotoHttp
|
||||
from .errors import *
|
||||
from . import api_photo
|
||||
from . import api_tag
|
||||
from . import api_album
|
||||
|
||||
LATEST_API_VERSION = 2
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from errors import *
|
||||
from objects import Album
|
||||
from .errors import *
|
||||
from .objects import Album
|
||||
|
||||
class ApiAlbums:
|
||||
def __init__(self, client):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import base64
|
||||
|
||||
from errors import *
|
||||
from objects import Photo
|
||||
from .errors import *
|
||||
from .objects import Photo
|
||||
|
||||
class ApiPhotos:
|
||||
def __init__(self, client):
|
||||
|
@ -80,14 +80,16 @@ class ApiPhoto:
|
|||
return photo
|
||||
|
||||
def upload(self, photo_file, **kwds):
|
||||
result = self._client.post("/photo/upload.json",
|
||||
files={'photo': open(photo_file, 'rb')},
|
||||
**kwds)["result"]
|
||||
with open(photo_file, 'rb') as f:
|
||||
result = self._client.post("/photo/upload.json",
|
||||
files={'photo': f},
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
||||
def upload_encoded(self, photo_file, **kwds):
|
||||
""" Base64-encodes and uploads the specified file """
|
||||
encoded_photo = base64.b64encode(open(photo_file, "rb").read())
|
||||
with open(photo_file, "rb") as f:
|
||||
encoded_photo = base64.b64encode(f.read())
|
||||
result = self._client.post("/photo/upload.json", photo=encoded_photo,
|
||||
**kwds)["result"]
|
||||
return Photo(self._client, result)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from errors import *
|
||||
from objects import Tag
|
||||
from .errors import *
|
||||
from .objects import Tag
|
||||
|
||||
class ApiTags:
|
||||
def __init__(self, client):
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import os
|
||||
import sys
|
||||
import string
|
||||
import urllib
|
||||
from optparse import OptionParser
|
||||
|
||||
try:
|
||||
|
@ -56,22 +55,22 @@ def main(args=sys.argv[1:]):
|
|||
try:
|
||||
client = OpenPhoto(config_file=options.config_file)
|
||||
except IOError as error:
|
||||
print error
|
||||
print
|
||||
print "You must create a configuration file with the following contents:"
|
||||
print " host = your.host.com"
|
||||
print " consumerKey = your_consumer_key"
|
||||
print " consumerSecret = your_consumer_secret"
|
||||
print " token = your_access_token"
|
||||
print " tokenSecret = your_access_token_secret"
|
||||
print
|
||||
print "To get your credentials:"
|
||||
print " * Log into your Trovebox site"
|
||||
print " * Click the arrow on the top-right and select 'Settings'."
|
||||
print " * Click the 'Create a new app' button."
|
||||
print " * Click the 'View' link beside the newly created app."
|
||||
print
|
||||
print error
|
||||
print(error)
|
||||
print()
|
||||
print("You must create a configuration file with the following contents:")
|
||||
print(" host = your.host.com")
|
||||
print(" consumerKey = your_consumer_key")
|
||||
print(" consumerSecret = your_consumer_secret")
|
||||
print(" token = your_access_token")
|
||||
print(" tokenSecret = your_access_token_secret")
|
||||
print()
|
||||
print("To get your credentials:")
|
||||
print(" * Log into your Trovebox site")
|
||||
print(" * Click the arrow on the top-right and select 'Settings'.")
|
||||
print(" * Click the 'Create a new app' button.")
|
||||
print(" * Click the 'View' link beside the newly created app.")
|
||||
print()
|
||||
print(error)
|
||||
sys.exit(1)
|
||||
|
||||
if options.method == "GET":
|
||||
|
@ -81,17 +80,17 @@ def main(args=sys.argv[1:]):
|
|||
result = client.post(options.endpoint, process_response=False, files=files, **params)
|
||||
|
||||
if options.verbose:
|
||||
print "==========\nMethod: %s\nHost: %s\nEndpoint: %s" % (options.method, config['host'], options.endpoint)
|
||||
print("==========\nMethod: %s\nHost: %s\nEndpoint: %s" % (options.method, config['host'], options.endpoint))
|
||||
if len( params ) > 0:
|
||||
print "Fields:"
|
||||
for kv in params.iteritems():
|
||||
print " %s=%s" % kv
|
||||
print "==========\n"
|
||||
print("Fields:")
|
||||
for kv in params.items():
|
||||
print(" %s=%s" % kv)
|
||||
print("==========\n")
|
||||
|
||||
if options.pretty:
|
||||
print json.dumps(json.loads(result), sort_keys=True, indent=4, separators=(',',':'))
|
||||
print(json.dumps(json.loads(result), sort_keys=True, indent=4, separators=(',',':')))
|
||||
else:
|
||||
print result
|
||||
print(result)
|
||||
|
||||
def extract_files(params):
|
||||
"""
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import urllib
|
||||
from errors import *
|
||||
try:
|
||||
from urllib.parse import quote # Python3
|
||||
except ImportError:
|
||||
from urllib import quote # Python2
|
||||
from .errors import *
|
||||
|
||||
class OpenPhotoObject:
|
||||
""" Base object supporting the storage of custom fields as attributes """
|
||||
|
@ -128,13 +131,13 @@ class Tag(OpenPhotoObject):
|
|||
Returns True if successful.
|
||||
Raises an OpenPhotoError if not.
|
||||
"""
|
||||
result = self._openphoto.post("/tag/%s/delete.json" % urllib.quote(self.id), **kwds)["result"]
|
||||
result = self._openphoto.post("/tag/%s/delete.json" % quote(self.id), **kwds)["result"]
|
||||
self._replace_fields({})
|
||||
return result
|
||||
|
||||
def update(self, **kwds):
|
||||
""" Update this tag with the specified parameters """
|
||||
new_dict = self._openphoto.post("/tag/%s/update.json" % urllib.quote(self.id),
|
||||
new_dict = self._openphoto.post("/tag/%s/update.json" % quote(self.id),
|
||||
**kwds)["result"]
|
||||
self._replace_fields(new_dict)
|
||||
|
||||
|
|
|
@ -1,18 +1,36 @@
|
|||
from __future__ import unicode_literals
|
||||
import sys
|
||||
import os
|
||||
import urlparse
|
||||
import urllib
|
||||
try:
|
||||
from urllib.parse import urlunparse # Python3
|
||||
except ImportError:
|
||||
from urlparse import urlunparse # Python2
|
||||
import requests
|
||||
import requests_oauthlib
|
||||
import logging
|
||||
import StringIO
|
||||
import ConfigParser
|
||||
try:
|
||||
import json
|
||||
import io # Python3
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
import StringIO as io # Python2
|
||||
try:
|
||||
from configparser import ConfigParser # Python3
|
||||
except ImportError:
|
||||
from ConfigParser import SafeConfigParser as ConfigParser # Python2
|
||||
|
||||
from objects import OpenPhotoObject
|
||||
from errors import *
|
||||
if sys.version < '3':
|
||||
text_type = unicode # Python2
|
||||
else:
|
||||
text_type = str # Python3
|
||||
|
||||
from .objects import OpenPhotoObject
|
||||
from .errors import *
|
||||
|
||||
if sys.version < '3':
|
||||
# requests_oauth needs to decode to ascii for Python2
|
||||
_oauth_decoding = "utf-8"
|
||||
else:
|
||||
# requests_oauth needs to use (unicode) strings for Python3
|
||||
_oauth_decoding = None # Python3
|
||||
|
||||
DUPLICATE_RESPONSE = {"code": 409,
|
||||
"message": "This photo already exists"}
|
||||
|
@ -75,15 +93,17 @@ class OpenPhotoHttp:
|
|||
endpoint = "/" + endpoint
|
||||
if self._api_version is not None:
|
||||
endpoint = "/v%d%s" % (self._api_version, endpoint)
|
||||
url = urlparse.urlunparse(('http', self._host, endpoint, '', '', ''))
|
||||
url = urlunparse(('http', self._host, endpoint, '', '', ''))
|
||||
|
||||
if self._consumer_key:
|
||||
auth = requests_oauthlib.OAuth1(self._consumer_key, self._consumer_secret,
|
||||
self._token, self._token_secret)
|
||||
self._token, self._token_secret,
|
||||
decoding=_oauth_decoding)
|
||||
else:
|
||||
auth = None
|
||||
|
||||
response = requests.get(url, params=params, auth=auth)
|
||||
with requests.Session() as s:
|
||||
response = s.get(url, params=params, auth=auth)
|
||||
|
||||
self._logger.info("============================")
|
||||
self._logger.info("GET %s" % url)
|
||||
|
@ -115,20 +135,22 @@ class OpenPhotoHttp:
|
|||
endpoint = "/" + endpoint
|
||||
if self._api_version is not None:
|
||||
endpoint = "/v%d%s" % (self._api_version, endpoint)
|
||||
url = urlparse.urlunparse(('http', self._host, endpoint, '', '', ''))
|
||||
url = urlunparse(('http', self._host, endpoint, '', '', ''))
|
||||
|
||||
if not self._consumer_key:
|
||||
raise OpenPhotoError("Cannot issue POST without OAuth tokens")
|
||||
|
||||
auth = requests_oauthlib.OAuth1(self._consumer_key, self._consumer_secret,
|
||||
self._token, self._token_secret)
|
||||
if files:
|
||||
# Need to pass parameters as URL query, so they get OAuth signed
|
||||
response = requests.post(url, params=params, files=files, auth=auth)
|
||||
else:
|
||||
# Passing parameters as URL query doesn't work if there are no files to send.
|
||||
# Send them as form data instead.
|
||||
response = requests.post(url, data=params, auth=auth)
|
||||
self._token, self._token_secret,
|
||||
decoding=_oauth_decoding)
|
||||
with requests.Session() as s:
|
||||
if files:
|
||||
# Need to pass parameters as URL query, so they get OAuth signed
|
||||
response = s.post(url, params=params, files=files, auth=auth)
|
||||
else:
|
||||
# Passing parameters as URL query doesn't work if there are no files to send.
|
||||
# Send them as form data instead.
|
||||
response = s.post(url, data=params, auth=auth)
|
||||
|
||||
self._logger.info("============================")
|
||||
self._logger.info("POST %s" % url)
|
||||
|
@ -156,9 +178,9 @@ class OpenPhotoHttp:
|
|||
if isinstance(value, OpenPhotoObject):
|
||||
value = value.id
|
||||
|
||||
# Use UTF-8 encoding
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode('utf-8')
|
||||
# Ensure value is UTF-8 encoded
|
||||
if isinstance(value, text_type):
|
||||
value = value.encode("utf-8")
|
||||
|
||||
# Handle lists
|
||||
if isinstance(value, list):
|
||||
|
@ -168,8 +190,8 @@ class OpenPhotoHttp:
|
|||
for i, item in enumerate(new_list):
|
||||
if isinstance(item, OpenPhotoObject):
|
||||
new_list[i] = item.id
|
||||
# Convert list to unicode string
|
||||
value = u','.join([unicode(item) for item in new_list])
|
||||
# Convert list to string
|
||||
value = ','.join([str(item) for item in new_list])
|
||||
|
||||
# Handle booleans
|
||||
if isinstance(value, bool):
|
||||
|
@ -188,7 +210,7 @@ class OpenPhotoHttp:
|
|||
json_response = response.json()
|
||||
code = json_response["code"]
|
||||
message = json_response["message"]
|
||||
except ValueError, KeyError:
|
||||
except (ValueError, KeyError):
|
||||
# Response wasn't OpenPhoto JSON - check the HTTP status code
|
||||
if 200 <= response.status_code < 300:
|
||||
# Status code was valid, so just reraise the exception
|
||||
|
@ -236,14 +258,18 @@ class OpenPhotoHttp:
|
|||
'token': '', 'tokenSecret':'',
|
||||
}
|
||||
# Insert an section header at the start of the config file, so ConfigParser can understand it
|
||||
buf = StringIO.StringIO()
|
||||
buf = io.StringIO()
|
||||
buf.write('[%s]\n' % section)
|
||||
buf.write(open(config_file).read())
|
||||
with io.open(config_file, "r") as f:
|
||||
buf.write(f.read())
|
||||
|
||||
buf.seek(0, os.SEEK_SET)
|
||||
parser = ConfigParser.SafeConfigParser()
|
||||
parser = ConfigParser()
|
||||
parser.optionxform = str # Case-sensitive options
|
||||
parser.readfp(buf)
|
||||
try:
|
||||
parser.read_file(buf) # Python3
|
||||
except AttributeError:
|
||||
parser.readfp(buf) # Python2
|
||||
|
||||
# Trim quotes
|
||||
config = parser.items(section)
|
||||
|
|
|
@ -3,7 +3,7 @@ try:
|
|||
except ImportError:
|
||||
import unittest
|
||||
import openphoto
|
||||
import test_base
|
||||
from . import test_base
|
||||
|
||||
class TestAlbums(test_base.TestBase):
|
||||
testcase_name = "album API"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from __future__ import print_function
|
||||
import sys
|
||||
import os
|
||||
try:
|
||||
|
@ -35,9 +36,9 @@ class TestBase(unittest.TestCase):
|
|||
""" Ensure there is nothing on the server before running any tests """
|
||||
if cls.debug:
|
||||
if cls.api_version is None:
|
||||
print "\nTesting Latest %s" % cls.testcase_name
|
||||
print("\nTesting Latest %s" % cls.testcase_name)
|
||||
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 = openphoto.OpenPhoto(config_file=cls.config_file,
|
||||
api_version=cls.api_version)
|
||||
|
@ -71,9 +72,9 @@ class TestBase(unittest.TestCase):
|
|||
self.photos = self.client.photos.list()
|
||||
if len(self.photos) != 3:
|
||||
if self.debug:
|
||||
print "[Regenerating Photos]"
|
||||
print("[Regenerating Photos]")
|
||||
else:
|
||||
print " ",
|
||||
print(" ", end='')
|
||||
sys.stdout.flush()
|
||||
if len(self.photos) > 0:
|
||||
self._delete_all()
|
||||
|
@ -85,16 +86,16 @@ class TestBase(unittest.TestCase):
|
|||
self.tags[0].id != self.TEST_TAG or
|
||||
str(self.tags[0].count) != "3"):
|
||||
if self.debug:
|
||||
print "[Regenerating Tags]"
|
||||
print("[Regenerating Tags]")
|
||||
else:
|
||||
print " ",
|
||||
print(" ", end='')
|
||||
sys.stdout.flush()
|
||||
self._delete_all()
|
||||
self._create_test_photos()
|
||||
self.photos = self.client.photos.list()
|
||||
self.tags = self.client.tags.list()
|
||||
if len(self.tags) != 1:
|
||||
print "Tags: %s" % self.tags
|
||||
print("Tags: %s" % self.tags)
|
||||
raise Exception("Tag creation failed")
|
||||
|
||||
self.albums = self.client.albums.list()
|
||||
|
@ -102,9 +103,9 @@ class TestBase(unittest.TestCase):
|
|||
self.albums[0].name != self.TEST_ALBUM or
|
||||
self.albums[0].count != "3"):
|
||||
if self.debug:
|
||||
print "[Regenerating Albums]"
|
||||
print("[Regenerating Albums]")
|
||||
else:
|
||||
print " ",
|
||||
print(" ", end='')
|
||||
sys.stdout.flush()
|
||||
self._delete_all()
|
||||
self._create_test_photos()
|
||||
|
@ -112,7 +113,7 @@ class TestBase(unittest.TestCase):
|
|||
self.tags = self.client.tags.list()
|
||||
self.albums = self.client.albums.list()
|
||||
if len(self.albums) != 1:
|
||||
print "Albums: %s" % self.albums
|
||||
print("Albums: %s" % self.albums)
|
||||
raise Exception("Album creation failed")
|
||||
|
||||
logging.info("\nRunning %s..." % self.id())
|
||||
|
|
|
@ -27,13 +27,13 @@ class TestConfig(unittest.TestCase):
|
|||
shutil.rmtree(CONFIG_HOME_PATH, ignore_errors=True)
|
||||
|
||||
def create_config(self, config_file, host):
|
||||
f = open(os.path.join(CONFIG_PATH, config_file), "w")
|
||||
f.write("host = %s\n" % host)
|
||||
f.write("# Comment\n\n")
|
||||
f.write("consumerKey = \"%s_consumer_key\"\n" % config_file)
|
||||
f.write("\"consumerSecret\" = %s_consumer_secret\n" % config_file)
|
||||
f.write("'token'=%s_token\n" % config_file)
|
||||
f.write("tokenSecret = '%s_token_secret'\n" % config_file)
|
||||
with open(os.path.join(CONFIG_PATH, config_file), "w") as f:
|
||||
f.write("host = %s\n" % host)
|
||||
f.write("# Comment\n\n")
|
||||
f.write("consumerKey = \"%s_consumer_key\"\n" % config_file)
|
||||
f.write("\"consumerSecret\" = %s_consumer_secret\n" % config_file)
|
||||
f.write("'token'=%s_token\n" % config_file)
|
||||
f.write("tokenSecret = '%s_token_secret'\n" % config_file)
|
||||
|
||||
def test_default_config(self):
|
||||
""" Ensure the default config is loaded """
|
||||
|
|
|
@ -4,7 +4,7 @@ except ImportError:
|
|||
import unittest
|
||||
import logging
|
||||
import openphoto
|
||||
import test_base
|
||||
from . import test_base
|
||||
|
||||
class TestFramework(test_base.TestBase):
|
||||
testcase_name = "framework"
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from __future__ import unicode_literals
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
import openphoto
|
||||
import test_base
|
||||
from . import test_base
|
||||
|
||||
class TestPhotos(test_base.TestBase):
|
||||
testcase_name = "photo API"
|
||||
|
@ -72,7 +73,7 @@ class TestPhotos(test_base.TestBase):
|
|||
|
||||
def test_update(self):
|
||||
""" Update a photo by editing the title """
|
||||
title = u"\xfcmlaut" # umlauted umlaut
|
||||
title = "\xfcmlaut" # umlauted umlaut
|
||||
# Get a photo and check that it doesn't have the magic title
|
||||
photo = self.photos[0]
|
||||
self.assertNotEqual(photo.title, title)
|
||||
|
|
|
@ -3,7 +3,7 @@ try:
|
|||
except ImportError:
|
||||
import unittest
|
||||
import openphoto
|
||||
import test_base
|
||||
from . import test_base
|
||||
|
||||
@unittest.skipIf(test_base.get_test_server_api() == 1,
|
||||
"The tag API didn't work at v1 - see frontend issue #927")
|
||||
|
|
5
tox.ini
5
tox.ini
|
@ -1,7 +1,10 @@
|
|||
[tox]
|
||||
envlist = py27,py26
|
||||
envlist = py26, py27, py33
|
||||
|
||||
[testenv]
|
||||
commands = python -m unittest discover --catch
|
||||
|
||||
[testenv:py26]
|
||||
commands = unit2 discover --catch
|
||||
deps =
|
||||
unittest2
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue