Merge branch 'release-0.5'
This commit is contained in:
commit
ed5cc0ac11
20 changed files with 523 additions and 107 deletions
|
@ -7,8 +7,9 @@ install:
|
|||
script: tox
|
||||
|
||||
after_script:
|
||||
# Install dependencies for Pylint
|
||||
- pip install requests requests-oauthlib
|
||||
|
||||
# Run Pylint
|
||||
# (for information only, any errors don't affect the Travis result)
|
||||
- pylint --use-ignore-patch=y openphoto
|
||||
- pylint --use-ignore-patch=y tests
|
||||
|
||||
- pylint --use-ignore-patch=y trovebox
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# Until the --use-ignore-patch makes it into pylint upstream, we need to
|
||||
# download and install from sneakypete81's pylint fork
|
||||
HG_HASH=16de8b9518be
|
||||
|
||||
wget https://bitbucket.org/sneakypete81/pylint/get/$HG_HASH.zip
|
||||
unzip $HG_HASH.zip
|
||||
cd sneakypete81-pylint-$HG_HASH
|
||||
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-$HG_HASH
|
||||
rm -r sneakypete81-pylint-*
|
||||
|
|
25
CHANGELOG
Normal file
25
CHANGELOG
Normal file
|
@ -0,0 +1,25 @@
|
|||
=================================
|
||||
Trovebox Python Library Changelog
|
||||
=================================
|
||||
|
||||
v0.5
|
||||
====
|
||||
* Pylint improvements - using .pylint-ignores to waive warnings (#49)
|
||||
* Add support for https URLs (#51)
|
||||
* Configuration improvements (#53)
|
||||
* Allow https SSL verification bypass (#50)
|
||||
* Test improvements (#54)
|
||||
|
||||
v0.4
|
||||
====
|
||||
|
||||
First release
|
||||
|
||||
* Added more unit tests (#44, #45)
|
||||
* Fixed consistency problems found with unit tests (#46)
|
||||
* Renamed to Trovebox (#48)
|
||||
* Packaged for PyPI:
|
||||
- Updated metadata
|
||||
- Store the version number inside the package, and add --version CLI option
|
||||
- Update README and convert to ReStructuredText, as required by PyPI
|
||||
|
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
|
@ -0,0 +1 @@
|
|||
include README.rst
|
13
README.rst
13
README.rst
|
@ -3,7 +3,7 @@ Trovebox Python Library
|
|||
=======================
|
||||
(Previously known as openphoto-python)
|
||||
|
||||
.. image:: https://api.travis-ci.org/photo/openphoto-python.png
|
||||
.. image:: https://travis-ci.org/photo/openphoto-python.png?branch=master
|
||||
:alt: Build Status
|
||||
:target: https://travis-ci.org/photo/openphoto-python
|
||||
|
||||
|
@ -77,11 +77,16 @@ API Versioning
|
|||
==============
|
||||
It may be useful to lock your application to a particular version of the Trovebox API.
|
||||
This ensures that future API updates won't cause unexpected breakages.
|
||||
To do this, configure your Trovebox client as follows::
|
||||
|
||||
To do this, add the optional ``api_version`` parameter when creating the client object::
|
||||
client.configure(api_version=2)
|
||||
|
||||
from trovebox import Trovebox
|
||||
client = Trovebox(api_version=2)
|
||||
SSL Verification
|
||||
================
|
||||
If you connect to your Trovebox server over HTTPS, its SSL certificate is automatically verified.
|
||||
You can configure your Trovebox client to bypass this verification step::
|
||||
|
||||
client.configure(ssl_verify=False)
|
||||
|
||||
Commandline Tool
|
||||
================
|
||||
|
|
|
@ -12,6 +12,7 @@ They run very quickly and don't require any external test hosts.
|
|||
#### Requirements
|
||||
* mock >= 1.0.0
|
||||
* httpretty >= 0.6.1
|
||||
* ddt >= 0.3.0
|
||||
* tox (optional)
|
||||
|
||||
#### Running the Unit Tests
|
||||
|
|
|
@ -10,7 +10,7 @@ from trovebox import Trovebox
|
|||
CONFIG_HOME_PATH = os.path.join("tests", "config")
|
||||
CONFIG_PATH = os.path.join(CONFIG_HOME_PATH, "trovebox")
|
||||
|
||||
class TestConfig(unittest.TestCase):
|
||||
class TestAuth(unittest.TestCase):
|
||||
def setUp(self):
|
||||
""" Override XDG_CONFIG_HOME env var, to use test configs """
|
||||
try:
|
||||
|
@ -42,47 +42,47 @@ class TestConfig(unittest.TestCase):
|
|||
""" Ensure the default config is loaded """
|
||||
self.create_config("default", "Test Default Host")
|
||||
client = Trovebox()
|
||||
config = client.config
|
||||
auth = client.auth
|
||||
self.assertEqual(client.host, "Test Default Host")
|
||||
self.assertEqual(config.consumer_key, "default_consumer_key")
|
||||
self.assertEqual(config.consumer_secret, "default_consumer_secret")
|
||||
self.assertEqual(config.token, "default_token")
|
||||
self.assertEqual(config.token_secret, "default_token_secret")
|
||||
self.assertEqual(auth.consumer_key, "default_consumer_key")
|
||||
self.assertEqual(auth.consumer_secret, "default_consumer_secret")
|
||||
self.assertEqual(auth.token, "default_token")
|
||||
self.assertEqual(auth.token_secret, "default_token_secret")
|
||||
|
||||
def test_custom_config(self):
|
||||
""" Ensure a custom config can be loaded """
|
||||
self.create_config("default", "Test Default Host")
|
||||
self.create_config("custom", "Test Custom Host")
|
||||
client = Trovebox(config_file="custom")
|
||||
config = client.config
|
||||
auth = client.auth
|
||||
self.assertEqual(client.host, "Test Custom Host")
|
||||
self.assertEqual(config.consumer_key, "custom_consumer_key")
|
||||
self.assertEqual(config.consumer_secret, "custom_consumer_secret")
|
||||
self.assertEqual(config.token, "custom_token")
|
||||
self.assertEqual(config.token_secret, "custom_token_secret")
|
||||
self.assertEqual(auth.consumer_key, "custom_consumer_key")
|
||||
self.assertEqual(auth.consumer_secret, "custom_consumer_secret")
|
||||
self.assertEqual(auth.token, "custom_token")
|
||||
self.assertEqual(auth.token_secret, "custom_token_secret")
|
||||
|
||||
def test_full_config_path(self):
|
||||
""" Ensure a full custom config path can be loaded """
|
||||
self.create_config("path", "Test Path Host")
|
||||
full_path = os.path.abspath(CONFIG_PATH)
|
||||
client = Trovebox(config_file=os.path.join(full_path, "path"))
|
||||
config = client.config
|
||||
auth = client.auth
|
||||
self.assertEqual(client.host, "Test Path Host")
|
||||
self.assertEqual(config.consumer_key, "path_consumer_key")
|
||||
self.assertEqual(config.consumer_secret, "path_consumer_secret")
|
||||
self.assertEqual(config.token, "path_token")
|
||||
self.assertEqual(config.token_secret, "path_token_secret")
|
||||
self.assertEqual(auth.consumer_key, "path_consumer_key")
|
||||
self.assertEqual(auth.consumer_secret, "path_consumer_secret")
|
||||
self.assertEqual(auth.token, "path_token")
|
||||
self.assertEqual(auth.token_secret, "path_token_secret")
|
||||
|
||||
def test_host_override(self):
|
||||
""" Ensure that specifying a host overrides the default config """
|
||||
self.create_config("default", "Test Default Host")
|
||||
client = Trovebox(host="host_override")
|
||||
config = client.config
|
||||
self.assertEqual(config.host, "host_override")
|
||||
self.assertEqual(config.consumer_key, "")
|
||||
self.assertEqual(config.consumer_secret, "")
|
||||
self.assertEqual(config.token, "")
|
||||
self.assertEqual(config.token_secret, "")
|
||||
auth = client.auth
|
||||
self.assertEqual(auth.host, "host_override")
|
||||
self.assertEqual(auth.consumer_key, "")
|
||||
self.assertEqual(auth.consumer_secret, "")
|
||||
self.assertEqual(auth.token, "")
|
||||
self.assertEqual(auth.token_secret, "")
|
||||
|
||||
def test_missing_config_files(self):
|
||||
""" Ensure that missing config files raise exceptions """
|
|
@ -1,7 +1,10 @@
|
|||
from __future__ import unicode_literals
|
||||
import os
|
||||
import json
|
||||
import mock
|
||||
import httpretty
|
||||
from httpretty import GET, POST
|
||||
from ddt import ddt, data
|
||||
try:
|
||||
import unittest2 as unittest # Python2.6
|
||||
except ImportError:
|
||||
|
@ -9,6 +12,21 @@ except ImportError:
|
|||
|
||||
import trovebox
|
||||
|
||||
class GetOrPost(object):
|
||||
"""Helper class to call the correct (GET/POST) method"""
|
||||
def __init__(self, client, method):
|
||||
self.client = client
|
||||
self.method = method
|
||||
|
||||
def call(self, *args, **kwds):
|
||||
if self.method == GET:
|
||||
return self.client.get(*args, **kwds)
|
||||
elif self.method == POST:
|
||||
return self.client.post(*args, **kwds)
|
||||
else:
|
||||
raise ValueError("unknown method: %s" % self.method)
|
||||
|
||||
@ddt
|
||||
class TestHttp(unittest.TestCase):
|
||||
test_host = "test.example.com"
|
||||
test_endpoint = "test.json"
|
||||
|
@ -44,7 +62,55 @@ class TestHttp(unittest.TestCase):
|
|||
def test_attributes(self):
|
||||
"""Check that the host attribute has been set correctly"""
|
||||
self.assertEqual(self.client.host, self.test_host)
|
||||
self.assertEqual(self.client.config.host, self.test_host)
|
||||
self.assertEqual(self.client.auth.host, self.test_host)
|
||||
|
||||
@httpretty.activate
|
||||
@data(GET, POST)
|
||||
def test_http_scheme(self, method):
|
||||
"""Check that we can access hosts starting with 'http://'"""
|
||||
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
|
||||
@data(GET, POST)
|
||||
def test_no_scheme(self, method):
|
||||
"""Check that we can access hosts without a 'http://' prefix"""
|
||||
self._register_uri(method,
|
||||
uri="http://test.example.com/%s" % self.test_endpoint)
|
||||
|
||||
self.client = trovebox.Trovebox(host="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
|
||||
@data(GET, POST)
|
||||
def test_https_scheme(self, method):
|
||||
"""Check that we can access hosts starting with 'https://'"""
|
||||
self._register_uri(method,
|
||||
uri="https://test.example.com/%s" % self.test_endpoint)
|
||||
|
||||
self.client = trovebox.Trovebox(host="https://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,
|
||||
"https://test.example.com/%s" % self.test_endpoint)
|
||||
self.assertEqual(self.client.last_response.json(), self.test_data)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_parameters(self):
|
||||
|
@ -93,17 +159,12 @@ class TestHttp(unittest.TestCase):
|
|||
self.client.post(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_without_response_processing(self):
|
||||
"""Check that the get method works with response processing disabled"""
|
||||
self._register_uri(httpretty.GET)
|
||||
response = self.client.get(self.test_endpoint, process_response=False)
|
||||
self.assertEqual(response, json.dumps(self.test_data))
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_without_response_processing(self):
|
||||
"""Check that the post method works with response processing disabled"""
|
||||
self._register_uri(httpretty.POST)
|
||||
response = self.client.post(self.test_endpoint, process_response=False)
|
||||
@data(GET, POST)
|
||||
def test_no_response_processing(self, method):
|
||||
"""Check that get/post methods work with response processing disabled"""
|
||||
self._register_uri(method)
|
||||
response = GetOrPost(self.client, method).call(self.test_endpoint,
|
||||
process_response=False)
|
||||
self.assertEqual(response, json.dumps(self.test_data))
|
||||
|
||||
@httpretty.activate
|
||||
|
@ -127,23 +188,31 @@ class TestHttp(unittest.TestCase):
|
|||
self.assertIn(params["unicode_"], [["\xc3\xbcmlaut"], ["\xfcmlaut"]])
|
||||
|
||||
@httpretty.activate
|
||||
def test_get_with_api_version(self):
|
||||
"""Check that an API version can be specified for the get method"""
|
||||
self.client = trovebox.Trovebox(host=self.test_host, api_version=1)
|
||||
self._register_uri(httpretty.GET,
|
||||
@data(GET, POST)
|
||||
def test_api_version(self, method):
|
||||
"""Check that an API version can be specified"""
|
||||
self.client = trovebox.Trovebox(host=self.test_host, **self.test_oauth)
|
||||
self.client.configure(api_version=1)
|
||||
self._register_uri(method,
|
||||
uri="http://%s/v1/%s" % (self.test_host,
|
||||
self.test_endpoint))
|
||||
self.client.get(self.test_endpoint)
|
||||
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_with_api_version(self):
|
||||
"""Check that an API version can be specified for the post method"""
|
||||
self.client = trovebox.Trovebox(host=self.test_host, api_version=1,
|
||||
**self.test_oauth)
|
||||
self._register_uri(httpretty.POST,
|
||||
uri="http://%s/v1/%s" % (self.test_host,
|
||||
self.test_endpoint))
|
||||
self.client.post(self.test_endpoint)
|
||||
@mock.patch.object(trovebox.http.requests, 'Session')
|
||||
@data(GET, POST)
|
||||
def test_ssl_verify_disabled(self, method, mock_session):
|
||||
"""Check that SSL verification can be disabled for the get method"""
|
||||
session = mock_session.return_value.__enter__.return_value
|
||||
session.get.return_value.text = "response text"
|
||||
session.get.return_value.status_code = 200
|
||||
session.get.return_value.json.return_value = self.test_data
|
||||
# Handle either post or get
|
||||
session.post = session.get
|
||||
|
||||
self.client = trovebox.Trovebox(host=self.test_host, **self.test_oauth)
|
||||
self.client.configure(ssl_verify=False)
|
||||
GetOrPost(self.client, method).call(self.test_endpoint)
|
||||
self.assertEqual(session.verify, False)
|
||||
|
||||
@httpretty.activate
|
||||
def test_post_file(self):
|
||||
|
|
2
tox.ini
2
tox.ini
|
@ -6,11 +6,13 @@ commands = python -m unittest discover --catch tests/unit
|
|||
deps =
|
||||
mock >= 1.0.0
|
||||
httpretty >= 0.6.1
|
||||
ddt >= 0.3.0
|
||||
|
||||
[testenv:py26]
|
||||
commands = unit2 discover --catch tests/unit
|
||||
deps =
|
||||
mock >= 1.0.0
|
||||
httpretty >= 0.6.1
|
||||
ddt >= 0.3.0
|
||||
unittest2
|
||||
discover
|
||||
|
|
238
trovebox/.pylint-ignores.patch
Normal file
238
trovebox/.pylint-ignores.patch
Normal file
|
@ -0,0 +1,238 @@
|
|||
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"
|
|
@ -1,3 +1,6 @@
|
|||
"""
|
||||
__init__.py : Trovebox package top level
|
||||
"""
|
||||
from .http import Http
|
||||
from .errors import *
|
||||
from ._version import __version__
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
__version__ = "0.4"
|
||||
|
||||
__version__ = "0.5"
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
"""
|
||||
api_album.py : Trovebox Album API Classes
|
||||
"""
|
||||
from .objects import Album
|
||||
|
||||
class ApiAlbums:
|
||||
class ApiAlbums(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
|
@ -9,7 +12,7 @@ class ApiAlbums:
|
|||
results = self._client.get("/albums/list.json", **kwds)["result"]
|
||||
return [Album(self._client, album) for album in results]
|
||||
|
||||
class ApiAlbum:
|
||||
class ApiAlbum(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
|
@ -30,12 +33,15 @@ class ApiAlbum:
|
|||
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):
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
"""
|
||||
api_photo.py : Trovebox Photo API Classes
|
||||
"""
|
||||
import base64
|
||||
|
||||
from .errors import TroveboxError
|
||||
|
@ -17,7 +20,7 @@ def extract_ids(photos):
|
|||
ids.append(photo)
|
||||
return ids
|
||||
|
||||
class ApiPhotos:
|
||||
class ApiPhotos(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
|
@ -51,7 +54,7 @@ class ApiPhotos:
|
|||
raise TroveboxError("Delete response returned False")
|
||||
return True
|
||||
|
||||
class ApiPhoto:
|
||||
class ApiPhoto(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
|
@ -72,9 +75,11 @@ class ApiPhoto:
|
|||
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):
|
||||
|
@ -114,6 +119,7 @@ class ApiPhoto:
|
|||
return Photo(self._client, result)
|
||||
|
||||
def dynamic_url(self, photo, **kwds):
|
||||
""" Not yet implemented """
|
||||
raise NotImplementedError()
|
||||
|
||||
def next_previous(self, photo, **kwds):
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
"""
|
||||
api_tag.py : Trovebox Tag API Classes
|
||||
"""
|
||||
from .objects import Tag
|
||||
|
||||
class ApiTags:
|
||||
class ApiTags(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
|
@ -9,7 +12,7 @@ class ApiTags:
|
|||
results = self._client.get("/tags/list.json", **kwds)["result"]
|
||||
return [Tag(self._client, tag) for tag in results]
|
||||
|
||||
class ApiTag:
|
||||
class ApiTag(object):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
"""
|
||||
auth.py : OAuth Config File Parser
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
try:
|
||||
|
@ -9,7 +12,8 @@ try:
|
|||
except ImportError:
|
||||
import StringIO as io # Python2
|
||||
|
||||
class Config:
|
||||
class Auth(object):
|
||||
"""OAuth secrets"""
|
||||
def __init__(self, config_file, host,
|
||||
consumer_key, consumer_secret,
|
||||
token, token_secret):
|
||||
|
@ -46,7 +50,8 @@ def get_config_path(config_file):
|
|||
def read_config(config_path):
|
||||
"""
|
||||
Loads config data from the specified file path.
|
||||
If config_file doesn't exist, returns an empty authentication config for localhost.
|
||||
If config_file doesn't exist, returns an empty authentication config
|
||||
for localhost.
|
||||
"""
|
||||
section = "DUMMY"
|
||||
defaults = {'host': 'localhost',
|
|
@ -1,3 +1,6 @@
|
|||
"""
|
||||
errors.py : Trovebox Error Classes
|
||||
"""
|
||||
class TroveboxError(Exception):
|
||||
""" Indicates that an Trovebox operation failed """
|
||||
pass
|
||||
|
|
103
trovebox/http.py
103
trovebox/http.py
|
@ -1,16 +1,19 @@
|
|||
"""
|
||||
http.py : Trovebox HTTP Access
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
import requests
|
||||
import requests_oauthlib
|
||||
import logging
|
||||
try:
|
||||
from urllib.parse import urlunparse # Python3
|
||||
from urllib.parse import urlparse, urlunparse # Python3
|
||||
except ImportError:
|
||||
from urlparse import urlunparse # Python2
|
||||
from urlparse import urlparse, urlunparse # Python2
|
||||
|
||||
from .objects import TroveboxObject
|
||||
from .errors import *
|
||||
from .config import Config
|
||||
from .auth import Auth
|
||||
|
||||
if sys.version < '3':
|
||||
TEXT_TYPE = unicode
|
||||
|
@ -20,36 +23,58 @@ else:
|
|||
DUPLICATE_RESPONSE = {"code": 409,
|
||||
"message": "This photo already exists"}
|
||||
|
||||
class Http:
|
||||
class Http(object):
|
||||
"""
|
||||
Base class to handle HTTP requests to an Trovebox server.
|
||||
If no parameters are specified, config is loaded from the default
|
||||
location (~/.config/trovebox/default).
|
||||
If no parameters are specified, auth config is loaded from the
|
||||
default location (~/.config/trovebox/default).
|
||||
The config_file parameter is used to specify an alternate config file.
|
||||
If the host parameter is specified, no config file is loaded and
|
||||
OAuth tokens (consumer*, token*) can optionally be specified.
|
||||
All requests will include the api_version path, if specified.
|
||||
This should be used to ensure that your application will continue to work
|
||||
even if the Trovebox API is updated to a new revision.
|
||||
"""
|
||||
|
||||
_CONFIG_DEFAULTS = {"api_version" : None,
|
||||
"ssl_verify" : True,
|
||||
}
|
||||
|
||||
def __init__(self, config_file=None, host=None,
|
||||
consumer_key='', consumer_secret='',
|
||||
token='', token_secret='', api_version=None):
|
||||
self._api_version = api_version
|
||||
|
||||
self.config = dict(self._CONFIG_DEFAULTS)
|
||||
|
||||
if api_version is not None:
|
||||
print("Deprecation Warning: api_version should be set by "
|
||||
"calling the configure function")
|
||||
self.config["api_version"] = api_version
|
||||
|
||||
self._logger = logging.getLogger("trovebox")
|
||||
|
||||
self.config = Config(config_file, host,
|
||||
consumer_key, consumer_secret,
|
||||
token, token_secret)
|
||||
self.auth = Auth(config_file, host,
|
||||
consumer_key, consumer_secret,
|
||||
token, token_secret)
|
||||
|
||||
self.host = self.config.host
|
||||
self.host = self.auth.host
|
||||
|
||||
# Remember the most recent HTTP request and response
|
||||
self.last_url = None
|
||||
self.last_params = None
|
||||
self.last_response = None
|
||||
|
||||
def configure(self, **kwds):
|
||||
"""
|
||||
Update Trovebox HTTP client configuration.
|
||||
|
||||
:param api_version: Include a Trovebox API version in all requests.
|
||||
This can be used to ensure that your application will continue
|
||||
to work even if the Trovebox API is updated to a new revision.
|
||||
[default: None]
|
||||
:param ssl_verify: If true, HTTPS SSL certificates will always be
|
||||
verified [default: True]
|
||||
"""
|
||||
for item in kwds:
|
||||
self.config[item] = kwds[item]
|
||||
|
||||
def get(self, endpoint, process_response=True, **params):
|
||||
"""
|
||||
Performs an HTTP GET from the specified endpoint (API path),
|
||||
|
@ -62,21 +87,18 @@ class Http:
|
|||
Returns the raw response if process_response=False
|
||||
"""
|
||||
params = self._process_params(params)
|
||||
if not endpoint.startswith("/"):
|
||||
endpoint = "/" + endpoint
|
||||
if self._api_version is not None:
|
||||
endpoint = "/v%d%s" % (self._api_version, endpoint)
|
||||
url = urlunparse(('http', self.host, endpoint, '', '', ''))
|
||||
url = self._construct_url(endpoint)
|
||||
|
||||
if self.config.consumer_key:
|
||||
auth = requests_oauthlib.OAuth1(self.config.consumer_key,
|
||||
self.config.consumer_secret,
|
||||
self.config.token,
|
||||
self.config.token_secret)
|
||||
if self.auth.consumer_key:
|
||||
auth = requests_oauthlib.OAuth1(self.auth.consumer_key,
|
||||
self.auth.consumer_secret,
|
||||
self.auth.token,
|
||||
self.auth.token_secret)
|
||||
else:
|
||||
auth = None
|
||||
|
||||
with requests.Session() as session:
|
||||
session.verify = self.config["ssl_verify"]
|
||||
response = session.get(url, params=params, auth=auth)
|
||||
|
||||
self._logger.info("============================")
|
||||
|
@ -105,20 +127,17 @@ class Http:
|
|||
Returns the raw response if process_response=False
|
||||
"""
|
||||
params = self._process_params(params)
|
||||
if not endpoint.startswith("/"):
|
||||
endpoint = "/" + endpoint
|
||||
if self._api_version is not None:
|
||||
endpoint = "/v%d%s" % (self._api_version, endpoint)
|
||||
url = urlunparse(('http', self.host, endpoint, '', '', ''))
|
||||
url = self._construct_url(endpoint)
|
||||
|
||||
if not self.config.consumer_key:
|
||||
if not self.auth.consumer_key:
|
||||
raise TroveboxError("Cannot issue POST without OAuth tokens")
|
||||
|
||||
auth = requests_oauthlib.OAuth1(self.config.consumer_key,
|
||||
self.config.consumer_secret,
|
||||
self.config.token,
|
||||
self.config.token_secret)
|
||||
auth = requests_oauthlib.OAuth1(self.auth.consumer_key,
|
||||
self.auth.consumer_secret,
|
||||
self.auth.token,
|
||||
self.auth.token_secret)
|
||||
with requests.Session() as session:
|
||||
session.verify = self.config["ssl_verify"]
|
||||
if files:
|
||||
# Need to pass parameters as URL query, so they get OAuth signed
|
||||
response = session.post(url, params=params,
|
||||
|
@ -146,6 +165,22 @@ class Http:
|
|||
else:
|
||||
return response.text
|
||||
|
||||
def _construct_url(self, endpoint):
|
||||
"""Return the full URL to the specified endpoint"""
|
||||
parsed_url = urlparse(self.host)
|
||||
scheme = parsed_url[0]
|
||||
host = parsed_url[1]
|
||||
# Handle host without a scheme specified (eg. www.example.com)
|
||||
if scheme == "":
|
||||
scheme = "http"
|
||||
host = self.host
|
||||
|
||||
if not endpoint.startswith("/"):
|
||||
endpoint = "/" + endpoint
|
||||
if self.config["api_version"] is not None:
|
||||
endpoint = "/v%d%s" % (self.config["api_version"], endpoint)
|
||||
return urlunparse((scheme, host, endpoint, '', '', ''))
|
||||
|
||||
@staticmethod
|
||||
def _process_params(params):
|
||||
""" Converts Unicode/lists/booleans inside HTTP parameters """
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
main.py : Trovebox Console Script
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
@ -44,7 +47,7 @@ def main(args=sys.argv[1:]):
|
|||
action="store_true", dest="pretty", default=False)
|
||||
parser.add_option('-v', help="Verbose output",
|
||||
action="store_true", dest="verbose", default=False)
|
||||
parser.add_option('--version', help="Display the current version information",
|
||||
parser.add_option('--version', help="Display the current version",
|
||||
action="store_true")
|
||||
parser.add_option('--help', help='show this help message',
|
||||
action="store_true")
|
||||
|
@ -107,7 +110,8 @@ def main(args=sys.argv[1:]):
|
|||
|
||||
def extract_files(params):
|
||||
"""
|
||||
Extract filenames from the "photo" parameter, so they can be uploaded, returning (updated_params, files).
|
||||
Extract filenames from the "photo" parameter so they can be uploaded,
|
||||
returning (updated_params, files).
|
||||
Uses the same technique as the Trovebox PHP commandline tool:
|
||||
* Filename can only be in the "photo" parameter
|
||||
* Filename must be prefixed with "@"
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
"""
|
||||
objects.py : Basic Trovebox API Objects
|
||||
"""
|
||||
try:
|
||||
from urllib.parse import quote # Python3
|
||||
except ImportError:
|
||||
|
@ -5,7 +8,7 @@ except ImportError:
|
|||
|
||||
from .errors import TroveboxError
|
||||
|
||||
class TroveboxObject:
|
||||
class TroveboxObject(object):
|
||||
""" Base object supporting the storage of custom fields as attributes """
|
||||
def __init__(self, trovebox, json_dict):
|
||||
self.id = None
|
||||
|
@ -75,9 +78,11 @@ class Photo(TroveboxObject):
|
|||
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):
|
||||
|
@ -96,6 +101,7 @@ class Photo(TroveboxObject):
|
|||
self._replace_fields(new_dict)
|
||||
|
||||
def dynamic_url(self, **kwds):
|
||||
""" Not implemented yet """
|
||||
raise NotImplementedError()
|
||||
|
||||
def next_previous(self, **kwds):
|
||||
|
@ -194,12 +200,15 @@ class Album(TroveboxObject):
|
|||
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):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue