diff --git a/openphoto/__init__.py b/openphoto/__init__.py index 7e497bd..8132570 100644 --- a/openphoto/__init__.py +++ b/openphoto/__init__.py @@ -4,15 +4,25 @@ import api_photo import api_tag import api_album +LATEST_API_VERSION = 2 + class OpenPhoto(OpenPhotoHttp): - """ Client library for OpenPhoto """ - def __init__(self, host, + """ + Python client library for the specified OpenPhoto host. + 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 OpenPhoto API is updated to a new revision. + """ + def __init__(self, host, consumer_key='', consumer_secret='', - token='', token_secret=''): - OpenPhotoHttp.__init__(self, host, + token='', token_secret='', + api_version=None): + OpenPhotoHttp.__init__(self, host, consumer_key, consumer_secret, - token, token_secret) - + token, token_secret, api_version) + self.photos = api_photo.ApiPhotos(self) self.photo = api_photo.ApiPhoto(self) self.tags = api_tag.ApiTags(self) diff --git a/openphoto/openphoto_http.py b/openphoto/openphoto_http.py index 59431f6..3422aba 100644 --- a/openphoto/openphoto_http.py +++ b/openphoto/openphoto_http.py @@ -19,12 +19,13 @@ DUPLICATE_RESPONSE = {"code": 409, class OpenPhotoHttp: """ Base class to handle HTTP requests to an OpenPhoto server """ def __init__(self, host, consumer_key='', consumer_secret='', - token='', token_secret=''): + token='', token_secret='', api_version=None): self._host = host self._consumer_key = consumer_key self._consumer_secret = consumer_secret self._token = token self._token_secret = token_secret + self._api_version = api_version self._logger = logging.getLogger("openphoto") @@ -37,11 +38,18 @@ class OpenPhotoHttp: """ Performs an HTTP GET from the specified endpoint (API path), passing parameters if given. - Returns the decoded JSON dictionary, and raises exceptions if an + The api_version is prepended to the endpoint, + if it was specified when the OpenPhoto object was created. + + Returns the decoded JSON dictionary, and raises exceptions if an error code is received. 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 = urlparse.urlunparse(('http', self._host, endpoint, '', urllib.urlencode(params), '')) if self._consumer_key: @@ -72,13 +80,20 @@ class OpenPhotoHttp: """ Performs an HTTP POST to the specified endpoint (API path), passing parameters if given. - Returns the decoded JSON dictionary, and raises exceptions if an + The api_version is prepended to the endpoint, + if it was specified when the OpenPhoto object was created. + + Returns the decoded JSON dictionary, and raises exceptions if an error code is received. 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 = urlparse.urlunparse(('http', self._host, endpoint, '', '', '')) - + if not self._consumer_key: raise OpenPhotoError("Cannot issue POST without OAuth tokens") diff --git a/tests/test_framework.py b/tests/test_framework.py new file mode 100644 index 0000000..2627e9e --- /dev/null +++ b/tests/test_framework.py @@ -0,0 +1,46 @@ +import unittest +import logging +import openphoto +import test_base + +class TestFramework(test_base.TestBase): + def setUp(self): + """Override the default setUp, since we don't need a populated database""" + logging.info("\nRunning %s..." % self.id()) + + def create_client_from_base(self, api_version): + return openphoto.OpenPhoto(self.client._host, + self.client._consumer_key, + self.client._consumer_secret, + self.client._token, + self.client._token_secret, + api_version=api_version) + + def test_api_version_zero(self): + # API v0 has a special hello world message + client = self.create_client_from_base(api_version=0) + result = client.get("hello.json") + self.assertEqual(result['message'], "Hello, world! This is version zero of the API!") + self.assertEqual(result['result']['__route__'], "/v0/hello.json") + + def test_specified_api_version(self): + # For all API versions >0, we get a generic hello world message + for api_version in range(1, openphoto.LATEST_API_VERSION + 1): + client = self.create_client_from_base(api_version=api_version) + result = client.get("hello.json") + self.assertEqual(result['message'], "Hello, world!") + self.assertEqual(result['result']['__route__'], "/v%d/hello.json" % api_version) + + def test_unspecified_api_version(self): + # If the API version is unspecified, we get a generic hello world message + client = self.create_client_from_base(api_version=None) + result = client.get("hello.json") + self.assertEqual(result['message'], "Hello, world!") + self.assertEqual(result['result']['__route__'], "/hello.json") + + def test_future_api_version(self): + # If the API version is unsupported, we should get an error + # (it's a ValueError, since the returned 404 HTML page is not valid JSON) + client = self.create_client_from_base(api_version=openphoto.LATEST_API_VERSION + 1) + with self.assertRaises(ValueError): + client.get("hello.json")