Merge branch 'sneakypete81-better_config' into development

This commit is contained in:
sneakypete81 2013-08-16 17:54:30 +01:00
commit cb0ea24515
5 changed files with 111 additions and 54 deletions

View file

@ -77,11 +77,16 @@ API Versioning
============== ==============
It may be useful to lock your application to a particular version of the Trovebox API. 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. 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 SSL Verification
client = Trovebox(api_version=2) ================
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 Commandline Tool
================ ================

View file

@ -10,7 +10,7 @@ from trovebox import Trovebox
CONFIG_HOME_PATH = os.path.join("tests", "config") CONFIG_HOME_PATH = os.path.join("tests", "config")
CONFIG_PATH = os.path.join(CONFIG_HOME_PATH, "trovebox") CONFIG_PATH = os.path.join(CONFIG_HOME_PATH, "trovebox")
class TestConfig(unittest.TestCase): class TestAuth(unittest.TestCase):
def setUp(self): def setUp(self):
""" Override XDG_CONFIG_HOME env var, to use test configs """ """ Override XDG_CONFIG_HOME env var, to use test configs """
try: try:
@ -42,47 +42,47 @@ class TestConfig(unittest.TestCase):
""" Ensure the default config is loaded """ """ Ensure the default config is loaded """
self.create_config("default", "Test Default Host") self.create_config("default", "Test Default Host")
client = Trovebox() client = Trovebox()
config = client.config auth = client.auth
self.assertEqual(client.host, "Test Default Host") self.assertEqual(client.host, "Test Default Host")
self.assertEqual(config.consumer_key, "default_consumer_key") self.assertEqual(auth.consumer_key, "default_consumer_key")
self.assertEqual(config.consumer_secret, "default_consumer_secret") self.assertEqual(auth.consumer_secret, "default_consumer_secret")
self.assertEqual(config.token, "default_token") self.assertEqual(auth.token, "default_token")
self.assertEqual(config.token_secret, "default_token_secret") self.assertEqual(auth.token_secret, "default_token_secret")
def test_custom_config(self): def test_custom_config(self):
""" Ensure a custom config can be loaded """ """ Ensure a custom config can be loaded """
self.create_config("default", "Test Default Host") self.create_config("default", "Test Default Host")
self.create_config("custom", "Test Custom Host") self.create_config("custom", "Test Custom Host")
client = Trovebox(config_file="custom") client = Trovebox(config_file="custom")
config = client.config auth = client.auth
self.assertEqual(client.host, "Test Custom Host") self.assertEqual(client.host, "Test Custom Host")
self.assertEqual(config.consumer_key, "custom_consumer_key") self.assertEqual(auth.consumer_key, "custom_consumer_key")
self.assertEqual(config.consumer_secret, "custom_consumer_secret") self.assertEqual(auth.consumer_secret, "custom_consumer_secret")
self.assertEqual(config.token, "custom_token") self.assertEqual(auth.token, "custom_token")
self.assertEqual(config.token_secret, "custom_token_secret") self.assertEqual(auth.token_secret, "custom_token_secret")
def test_full_config_path(self): def test_full_config_path(self):
""" Ensure a full custom config path can be loaded """ """ Ensure a full custom config path can be loaded """
self.create_config("path", "Test Path Host") self.create_config("path", "Test Path Host")
full_path = os.path.abspath(CONFIG_PATH) full_path = os.path.abspath(CONFIG_PATH)
client = Trovebox(config_file=os.path.join(full_path, "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(client.host, "Test Path Host")
self.assertEqual(config.consumer_key, "path_consumer_key") self.assertEqual(auth.consumer_key, "path_consumer_key")
self.assertEqual(config.consumer_secret, "path_consumer_secret") self.assertEqual(auth.consumer_secret, "path_consumer_secret")
self.assertEqual(config.token, "path_token") self.assertEqual(auth.token, "path_token")
self.assertEqual(config.token_secret, "path_token_secret") self.assertEqual(auth.token_secret, "path_token_secret")
def test_host_override(self): def test_host_override(self):
""" Ensure that specifying a host overrides the default config """ """ Ensure that specifying a host overrides the default config """
self.create_config("default", "Test Default Host") self.create_config("default", "Test Default Host")
client = Trovebox(host="host_override") client = Trovebox(host="host_override")
config = client.config auth = client.auth
self.assertEqual(config.host, "host_override") self.assertEqual(auth.host, "host_override")
self.assertEqual(config.consumer_key, "") self.assertEqual(auth.consumer_key, "")
self.assertEqual(config.consumer_secret, "") self.assertEqual(auth.consumer_secret, "")
self.assertEqual(config.token, "") self.assertEqual(auth.token, "")
self.assertEqual(config.token_secret, "") self.assertEqual(auth.token_secret, "")
def test_missing_config_files(self): def test_missing_config_files(self):
""" Ensure that missing config files raise exceptions """ """ Ensure that missing config files raise exceptions """

View file

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import json import json
import mock
import httpretty import httpretty
try: try:
import unittest2 as unittest # Python2.6 import unittest2 as unittest # Python2.6
@ -44,7 +45,7 @@ class TestHttp(unittest.TestCase):
def test_attributes(self): def test_attributes(self):
"""Check that the host attribute has been set correctly""" """Check that the host attribute has been set correctly"""
self.assertEqual(self.client.host, self.test_host) 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 @httpretty.activate
def test_get_with_http_scheme(self): def test_get_with_http_scheme(self):
@ -219,7 +220,8 @@ class TestHttp(unittest.TestCase):
@httpretty.activate @httpretty.activate
def test_get_with_api_version(self): def test_get_with_api_version(self):
"""Check that an API version can be specified for the get method""" """Check that an API version can be specified for the get method"""
self.client = trovebox.Trovebox(host=self.test_host, api_version=1) self.client = trovebox.Trovebox(host=self.test_host)
self.client.configure(api_version=1)
self._register_uri(httpretty.GET, self._register_uri(httpretty.GET,
uri="http://%s/v1/%s" % (self.test_host, uri="http://%s/v1/%s" % (self.test_host,
self.test_endpoint)) self.test_endpoint))
@ -228,13 +230,39 @@ class TestHttp(unittest.TestCase):
@httpretty.activate @httpretty.activate
def test_post_with_api_version(self): def test_post_with_api_version(self):
"""Check that an API version can be specified for the post method""" """Check that an API version can be specified for the post method"""
self.client = trovebox.Trovebox(host=self.test_host, api_version=1, self.client = trovebox.Trovebox(host=self.test_host, **self.test_oauth)
**self.test_oauth) self.client.configure(api_version=1)
self._register_uri(httpretty.POST, self._register_uri(httpretty.POST,
uri="http://%s/v1/%s" % (self.test_host, uri="http://%s/v1/%s" % (self.test_host,
self.test_endpoint)) self.test_endpoint))
self.client.post(self.test_endpoint) self.client.post(self.test_endpoint)
@mock.patch.object(trovebox.http.requests, 'Session')
def test_get_with_ssl_verify_disabled(self, 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
self.client = trovebox.Trovebox(host=self.test_host, **self.test_oauth)
self.client.configure(ssl_verify=False)
self.client.get(self.test_endpoint)
self.assertEqual(session.verify, False)
@mock.patch.object(trovebox.http.requests, 'Session')
def test_post_with_ssl_verify_disabled(self, mock_session):
"""Check that SSL verification can be disabled for the post method"""
session = mock_session.return_value.__enter__.return_value
session.post.return_value.text = "response text"
session.post.return_value.status_code = 200
session.post.return_value.json.return_value = self.test_data
self.client = trovebox.Trovebox(host=self.test_host, **self.test_oauth)
self.client.configure(ssl_verify=False)
self.client.post(self.test_endpoint)
self.assertEqual(session.verify, False)
@httpretty.activate @httpretty.activate
def test_post_file(self): def test_post_file(self):
"""Check that a file can be posted""" """Check that a file can be posted"""

View file

@ -1,5 +1,5 @@
""" """
config.py : OAuth Config File Parser auth.py : OAuth Config File Parser
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
@ -12,7 +12,7 @@ try:
except ImportError: except ImportError:
import StringIO as io # Python2 import StringIO as io # Python2
class Config(object): class Auth(object):
def __init__(self, config_file, host, def __init__(self, config_file, host,
consumer_key, consumer_secret, consumer_key, consumer_secret,
token, token_secret): token, token_secret):

View file

@ -13,7 +13,7 @@ except ImportError:
from .objects import TroveboxObject from .objects import TroveboxObject
from .errors import * from .errors import *
from .config import Config from .auth import Auth
if sys.version < '3': if sys.version < '3':
TEXT_TYPE = unicode TEXT_TYPE = unicode
@ -26,33 +26,55 @@ 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 an Trovebox server.
If no parameters are specified, config is loaded from the default If no parameters are specified, auth config is loaded from the
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.
If the host parameter is specified, no config file is loaded and If the host parameter is specified, no config file is loaded and
OAuth tokens (consumer*, token*) can optionally be specified. 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, def __init__(self, config_file=None, host=None,
consumer_key='', consumer_secret='', consumer_key='', consumer_secret='',
token='', token_secret='', api_version=None): 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._logger = logging.getLogger("trovebox")
self.config = Config(config_file, host, self.auth = Auth(config_file, host,
consumer_key, consumer_secret, consumer_key, consumer_secret,
token, token_secret) token, token_secret)
self.host = self.config.host self.host = self.auth.host
# Remember the most recent HTTP request and response # Remember the most recent HTTP request and response
self.last_url = None self.last_url = None
self.last_params = None self.last_params = None
self.last_response = 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): def get(self, endpoint, process_response=True, **params):
""" """
Performs an HTTP GET from the specified endpoint (API path), Performs an HTTP GET from the specified endpoint (API path),
@ -67,15 +89,16 @@ class Http(object):
params = self._process_params(params) params = self._process_params(params)
url = self._construct_url(endpoint) url = self._construct_url(endpoint)
if self.config.consumer_key: if self.auth.consumer_key:
auth = requests_oauthlib.OAuth1(self.config.consumer_key, auth = requests_oauthlib.OAuth1(self.auth.consumer_key,
self.config.consumer_secret, self.auth.consumer_secret,
self.config.token, self.auth.token,
self.config.token_secret) self.auth.token_secret)
else: else:
auth = None auth = None
with requests.Session() as session: with requests.Session() as session:
session.verify = self.config["ssl_verify"]
response = session.get(url, params=params, auth=auth) response = session.get(url, params=params, auth=auth)
self._logger.info("============================") self._logger.info("============================")
@ -106,14 +129,15 @@ class Http(object):
params = self._process_params(params) params = self._process_params(params)
url = self._construct_url(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") raise TroveboxError("Cannot issue POST without OAuth tokens")
auth = requests_oauthlib.OAuth1(self.config.consumer_key, auth = requests_oauthlib.OAuth1(self.auth.consumer_key,
self.config.consumer_secret, self.auth.consumer_secret,
self.config.token, self.auth.token,
self.config.token_secret) self.auth.token_secret)
with requests.Session() as session: with requests.Session() as session:
session.verify = self.config["ssl_verify"]
if files: if files:
# Need to pass parameters as URL query, so they get OAuth signed # Need to pass parameters as URL query, so they get OAuth signed
response = session.post(url, params=params, response = session.post(url, params=params,
@ -153,8 +177,8 @@ class Http(object):
if not endpoint.startswith("/"): if not endpoint.startswith("/"):
endpoint = "/" + endpoint endpoint = "/" + endpoint
if self._api_version is not None: if self.config["api_version"] is not None:
endpoint = "/v%d%s" % (self._api_version, endpoint) endpoint = "/v%d%s" % (self.config["api_version"], endpoint)
return urlunparse((scheme, host, endpoint, '', '', '')) return urlunparse((scheme, host, endpoint, '', '', ''))
@staticmethod @staticmethod