BigW Consortium Gitlab

Commit 3ca4e347 by Dan McKinley

added basic client-side type checking. readme update about how to run the tests.

parent 8efaf857
......@@ -79,6 +79,18 @@ api = Etsy(key_file='/usr/share/etsy/keys')
</pre>
## Tests
This package comes with a reasonably complete unit test suite. In order to run
the tests, use:
<pre>
$ python setup.py test
</pre>
Some of the tests (those that actually call the Etsy API) require your API key
to be locally configured. See the Configuration section, above.
## Future Work
......@@ -92,6 +104,7 @@ There will be enhancements in the future to address this problem in a number of
### Version 0.2 - in progress
* Added support for the v2 API
* Added local configuration (~/.etsy) to eliminate cutting & pasting of api keys.
* Added client-side type checking for parameters.
* Added a test suite.
* Added module to PyPI.
......
......@@ -8,6 +8,67 @@ import os
class TypeChecker(object):
def __init__(self):
self.checkers = {
'int': self.check_int,
'float': self.check_float,
'string': self.check_string,
}
def __call__(self, method, **kwargs):
params = method['params']
for k, v in kwargs.items():
if k not in params:
raise ValueError('Unexpected argument: %s=%s' % (k, v))
t = params[k]
checker = self.checkers.get(t, None) or self.compile(t)
ok, converted = checker(v)
if not ok:
raise ValueError(
"Bad value for parameter %s of type '%s' - %s" % (k, t, v))
kwargs[k] = converted
def compile(self, t):
if t.startswith('enum'):
f = self.compile_enum(t)
else:
f = self.always_ok
self.checkers[t] = f
return f
def compile_enum(self, t):
terms = [x.strip() for x in t[5:-1].split(',')]
def check_enum(value):
return (value in terms), value
return check_enum
def always_ok(self, value):
return True, value
def check_int(self, value):
return isinstance(value, int), value
def check_float(self, value):
if isinstance(value, int):
return True, value
return isinstance(value, float), value
def check_string(self, value):
return isinstance(value, basestring), value
class API(object):
def __init__(self, api_key='', key_file=None):
if not getattr(self, 'api_url', None):
......@@ -24,11 +85,16 @@ class API(object):
else:
self.api_key = self._read_key(key_file)
self.type_checker = TypeChecker()
d = JSONDecoder()
self.decode = d.decode
for method in self._get_method_table():
self._create_method(**method)
ms = self._get_method_table()
self._methods = dict([(m['name'], m) for m in ms])
for method in ms:
self._create_method(method)
def _get_method_table(self):
......@@ -47,12 +113,17 @@ class API(object):
return gs[self.api_version]
def _create_method(self, name, description, uri, **kw):
def _create_method(self, m):
def method(**kwargs):
return self._get(uri, **kwargs)
method.__name__ = name
method.__doc__ = description
setattr(self, name, method)
return self._invoke(m, **kwargs)
method.__name__ = m['name']
method.__doc__ = m['description']
setattr(self, m['name'], method)
def _invoke(self, m, **kwargs):
self.type_checker(m, **kwargs)
return self._get(m['uri'], **kwargs)
def _get_url(self, url):
......
from __future__ import with_statement
from unittest import TestCase
from etsy._core import API
from cgi import parse_qs
from urlparse import urlparse
import os
from util import Test
class MockAPI(API):
......@@ -17,8 +16,12 @@ class MockAPI(API):
'http_method': 'GET',
'params': {
'limit': 'int',
'test_id': 'int',
'test_id': 'user_id_or_name',
'offset': 'int',
'fizz': 'enum(foo, bar, baz)',
'buzz': 'float',
'blah': 'unknown type',
'kind': 'string',
},
'type': 'int',
'description': 'test method.'}]
......@@ -29,7 +32,7 @@ class MockAPI(API):
class CoreTests(TestCase):
class CoreTests(Test):
def setUp(self):
self.api = MockAPI('apikey')
......@@ -71,49 +74,33 @@ class CoreTests(TestCase):
self.assertEquals(self.api.testMethod.__doc__,
'test method.')
def test_api_url_required(self):
try:
API('')
except AssertionError, e:
self.assertEqual('No api_url configured.', e.message)
else:
self.fail('should have failed')
msg = self.assertRaises(AssertionError, API, '')
self.assertEqual('No api_url configured.', msg)
def test_api_url_cannot_end_with_slash(self):
class Foo(API):
api_url = 'http://host/'
try:
Foo('')
except AssertionError, e:
self.assertEqual('api_url should not end with a slash.',
e.message)
else:
self.fail('should have failed')
msg = self.assertRaises(AssertionError, Foo, '')
self.assertEqual('api_url should not end with a slash.', msg)
def test_api_should_define_version(self):
class Foo(API):
api_url = 'http://host'
try:
Foo()
except AssertionError, e:
self.assertEqual(e.message, 'API object should define api_version')
else:
self.fail('should have failed')
msg = self.assertRaises(AssertionError, Foo)
self.assertEqual(msg, 'API object should define api_version')
def test_key_file_does_not_exist(self):
try:
MockAPI(key_file='this does not exist')
except AssertionError, e:
self.assertTrue("'this does not exist' does not exist"
in e.message)
else:
self.fail('should have failed')
msg = self.assertRaises(AssertionError, MockAPI,
key_file='this does not exist')
self.assertTrue("'this does not exist' does not exist" in msg)
def test_reading_api_key(self):
......@@ -125,6 +112,62 @@ class CoreTests(TestCase):
os.unlink('testkeys')
def test_unrecognized_kwarg(self):
msg = self.assertRaises(ValueError, self.api.testMethod, not_an_arg=1)
self.assertEqual(msg, 'Unexpected argument: not_an_arg=1')
def test_unknown_parameter_type_is_passed(self):
self.api.testMethod(blah=1)
self.assertEqual(self.last_query()['blah'], ['1'])
def test_parameter_type_int(self):
self.api.testMethod(limit=5)
self.assertEqual(self.last_query()['limit'], ['5'])
def bad_value_msg(self, name, t, v):
return "Bad value for parameter %s of type '%s' - %s" % (name, t, v)
def test_invalid_parameter_type_int(self):
msg = self.assertRaises(ValueError, self.api.testMethod, limit=5.6)
self.assertEqual(msg, self.bad_value_msg('limit', 'int', 5.6))
def test_parameter_type_float(self):
self.api.testMethod(buzz=42.1)
self.assertEqual(self.last_query()['buzz'], ['42.1'])
def test_invalid_parameter_type_float(self):
msg = self.assertRaises(ValueError, self.api.testMethod, buzz='x')
self.assertEqual(msg, self.bad_value_msg('buzz', 'float', 'x'))
def test_int_accepted_as_float(self):
self.api.testMethod(buzz=3)
self.assertEqual(self.last_query()['buzz'], ['3'])
def test_parameter_type_enum(self):
self.api.testMethod(fizz='bar')
self.assertEqual(self.last_query()['fizz'], ['bar'])
def test_invalid_parameter_type_enum(self):
msg = self.assertRaises(ValueError, self.api.testMethod, fizz='goo')
self.assertEqual(msg, self.bad_value_msg(
'fizz', 'enum(foo, bar, baz)', 'goo'))
def test_parameter_type_string(self):
self.api.testMethod(kind='blah')
self.assertEqual(self.last_query()['kind'], ['blah'])
def test_invalid_parameter_type_string(self):
msg = self.assertRaises(ValueError, self.api.testMethod, kind=Test)
self.assertEqual(msg, self.bad_value_msg('kind', 'string', Test))
from __future__ import with_statement
from unittest import TestCase
from etsy import EtsyV1 as Etsy
class V1Tests(TestCase):
def test_v1_api_works(self):
api = Etsy()
x = api.getUserDetails(user_id='mcfunley')
self.assertEqual(x[0]['user_name'], 'mcfunley')
from unittest import TestCase
class Test(TestCase):
def assertRaises(self, cls, f, *args, **kwargs):
try:
f(*args, **kwargs)
except cls, e:
return e.message
else:
name = cls.__name__ if hasattr(cls, '__name__') else str(cls)
raise self.failureException, "%s not raised" % name
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment