BigW Consortium Gitlab

Commit 52d92eb6 by Prakash Pandey Committed by GitHub

Merge pull request #1 from joeirimpan/develop

Fix HTTP body none issue
parents a3eac450 a2567f60
{}
\ No newline at end of file
language: python
python:
- "2.7"
# command to install dependencies
install: "pip install -r requirements_dev.txt"
# before script run flake8
before_script: flake8 .
# command to run tests
script: python -m pytest test
......@@ -19,7 +19,7 @@ $ python setup.py build
$ sudo python setup.py install
</pre>
## Simple Example
## Example without authentication
To use, first [register for an Etsy developer key](http://developer.etsy.com/).
Below is an example session.
......@@ -37,6 +37,29 @@ Type "help", "copyright", "credits" or "license" for more information.
</pre>
## Example with authentication
To use, first [register for an Etsy developer key](http://developer.etsy.com/).
Below is an example session.
<pre>
$ python
python
Python 2.7.11+ (default, Apr 17 2016, 14:00:29)
[GCC 5.3.1 20160413] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from etsy import Etsy
>>> from etsy.oauth import EtsyOAuthClient
>>> from etsy import EtsyEnvProduction
>>> etsy_env = EtsyEnvProduction()
>>> etsy_oauth_client = EtsyOAuthClient('consumer_token','consumer_secret')
>>> signin_url = etsy_oauth_client.get_signin_url()
>>> etsy_oauth_client.set_oauth_verifier(verifier_received_from_signin_url)
>>> etsy_api = Etsy(etsy_oauth_client=etsy_oauth_client, etsy_env=etsy_env)
>>> etsy_api.getUser(user_id="__SELF__")
</pre>
See also [this blog post](http://codeascraft.etsy.com/2010/04/22/announcing-etsys-new-api/)
on Code as Craft.
......
from _v2 import EtsyV2 as Etsy
from etsy_env import EtsyEnvSandbox, EtsyEnvProduction
from _v2 import EtsyV2 as Etsy # noqa
from etsy_env import EtsyEnvProduction # noqa
__version__ = '0.3.1'
__author__ = 'Dan McKinley'
__author__ = 'Dan McKinley & Fulfil.IO Inc.'
__copyright__ = 'Copyright 2010, Etsy Inc.'
__license__ = 'GPL v3'
__email__ = 'dan@etsy.com'
......@@ -13,25 +13,24 @@ from _multipartformdataencode import encode_multipart_formdata
missing = object()
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 == 'includes': continue
if k == 'includes':
continue
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)
......@@ -40,7 +39,6 @@ class TypeChecker(object):
"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)
......@@ -49,37 +47,32 @@ class TypeChecker(object):
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):
if isinstance(value, long):
if isinstance(value, long): # noqa
return True, 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
return isinstance(value, basestring), value # noqa
class APIMethod(object):
def __init__(self, api, spec):
"""
Parameters:
......@@ -92,7 +85,7 @@ class APIMethod(object):
'string', 'materials': 'array(string)', 'shipping_template_id':
'int', 'quantity': 'int', 'shop_section_id': 'int'}, 'defaults':
{'materials': None, 'shop_section_id': None}, 'type': 'Listing',
'description': 'Creates a new Listing'}
'description': 'Creates a new Listing'}
"""
self.api = api
......@@ -100,14 +93,12 @@ class APIMethod(object):
self.type_checker = self.api.type_checker
self.__doc__ = self.spec['description']
self.compiled = False
def __call__(self, *args, **kwargs):
if not self.compiled:
self.compile()
return self.invoke(*args, **kwargs)
def compile(self):
uri = self.spec['uri']
self.positionals = re.findall('{(.*)}', uri)
......@@ -118,7 +109,6 @@ class APIMethod(object):
self.compiled = True
def invoke(self, *args, **kwargs):
if args and not self.positionals:
raise ValueError(
......@@ -142,13 +132,12 @@ class APIMethod(object):
del kwargs[p]
self.type_checker(self.spec, **kwargs)
return self.api._get(self.spec['http_method'], self.uri_format % ps, **kwargs)
return (self.api._get(self.spec['http_method'],
self.uri_format % ps, **kwargs))
class MethodTableCache(object):
max_age = 60*60*24
max_age = 60 * 60 * 24
def __init__(self, api, method_cache):
self.api = api
......@@ -156,30 +145,25 @@ class MethodTableCache(object):
self.used_cache = False
self.wrote_cache = False
def resolve_file(self, method_cache):
if method_cache is missing:
return self.default_file()
return method_cache
def etsy_home(self):
return self.api.etsy_home()
def default_file(self):
etsy_home = self.etsy_home()
d = etsy_home if os.path.isdir(etsy_home) else tempfile.gettempdir()
return os.path.join(d, 'methods.%s.json' % self.api.api_version)
def get(self):
ms = self.get_cached()
if not ms:
ms = self.api.get_method_table()
self.cache(ms)
return ms
def get_cached(self):
if self.filename is None or not os.path.isfile(self.filename):
......@@ -193,10 +177,10 @@ class MethodTableCache(object):
self.api.log('Reading method table cache: %s' % self.filename)
return json.loads(f.read())
def cache(self, methods):
if self.filename is None:
self.api.log('Method table caching disabled, not writing new cache.')
self.api.log(
'Method table caching disabled, not writing new cache.')
return
with open(self.filename, 'w') as f:
json.dump(methods, f)
......@@ -204,22 +188,21 @@ class MethodTableCache(object):
self.api.log('Wrote method table cache: %s' % self.filename)
class API(object):
def __init__(self, api_key='', key_file=None, method_cache=missing,
def __init__(self, api_key='', key_file=None, method_cache=missing,
log=None):
"""
Creates a new API instance. When called with no arguments,
reads the appropriate API key from the default ($HOME/.etsy/keys)
file.
Creates a new API instance. When called with no arguments,
reads the appropriate API key from the default ($HOME/.etsy/keys)
file.
Parameters:
api_key - An explicit API key to use.
key_file - A file to read the API keys from.
method_cache - A file to save the API method table in for
key_file - A file to read the API keys from.
method_cache - A file to save the API method table in for
24 hours. This speeds up the creation of API
objects.
objects.
log - An callable that accepts a string parameter.
Receives log messages. No logging is done if
this is None.
......@@ -227,9 +210,9 @@ class API(object):
Only one of api_key and key_file may be passed.
If method_cache is explicitly set to None, no method table
caching is performed. If the parameter is not passed, a file in
$HOME/.etsy is used if that directory exists. Otherwise, a
temp file is used.
caching is performed. If the parameter is not passed, a file in
$HOME/.etsy is used if that directory exists. Otherwise, a
temp file is used.
"""
if not getattr(self, 'api_url', None):
raise AssertionError('No api_url configured.')
......@@ -244,10 +227,11 @@ class API(object):
raise AssertionError('Keys can be read from a file or passed, '
'but not both.')
if api_key:
self.api_key = api_key
else:
self.api_key = self._read_key(key_file)
if self.etsy_oauth_client is None:
if api_key:
self.api_key = api_key
else:
self.api_key = self._read_key(key_file)
self.log = log or self._ignore
if not callable(self.log):
......@@ -258,15 +242,12 @@ class API(object):
self.decode = json.loads
self.log('Creating %s Etsy API, base url=%s.' % (
self.api_version, self.api_url))
self.api_version, self.api_url))
self._get_methods(method_cache)
def _ignore(self, _):
pass
def _get_methods(self, method_cache):
self.method_cache = MethodTableCache(self, method_cache)
ms = self.method_cache.get()
......@@ -277,15 +258,12 @@ class API(object):
# self.log('API._get_methods: self._methods = %r' % self._methods)
def etsy_home(self):
return os.path.expanduser('~/.etsy')
def get_method_table(self):
return self._get('GET', '/')
def _read_key(self, key_file):
key_file = key_file or os.path.join(self.etsy_home(), 'keys')
if not os.path.isfile(key_file):
......@@ -294,22 +272,21 @@ class API(object):
'pass an API key explicitly.' % key_file)
gs = {}
execfile(key_file, gs)
execfile(key_file, gs) # noqa
return gs[self.api_version]
def _get_url(self, url, http_method, content_type, body):
self.log("API._get_url: url = %r" % url)
with closing(urllib2.urlopen(url)) as f:
return f.read()
return f.read()
def _get(self, http_method, url, **kwargs):
kwargs.update(dict(api_key=self.api_key))
if self.etsy_oauth_client is None:
kwargs.update(dict(api_key=self.api_key))
if http_method == 'GET':
url = '%s%s?%s' % (self.api_url, url, urlencode(kwargs))
body = None
body = ''
content_type = None
elif http_method == 'POST':
url = '%s%s' % (self.api_url, url)
......@@ -327,13 +304,14 @@ class API(object):
self.last_url = url
data = self._get_url(url, http_method, content_type, body)
self.log('API._get: http_method = %r, url = %r, data = %r' % (http_method, url, data))
self.log('API._get: http_method = %r, url = %r, data = %r' %
(http_method, url, data))
try:
self.data = self.decode(data)
except json.JSONDecodeError:
raise ValueError('Could not decode response from Etsy as JSON: %r' % data)
raise ValueError(
'Could not decode response from Etsy as JSON: %r' % data)
self.count = self.data['count']
return self.data['results']
......@@ -6,10 +6,12 @@ Functions for encoding multipart/form-data
From http://code.activestate.com/recipes/146306/ (PSF License)
"""
def encode_multipart_formdata(fields, files):
"""
fields is a sequence of (name, value) elements for regular form fields.
files is a sequence of (name, filename, value) elements for data to be uploaded as files
files is a sequence of (name, filename, value) elements for data to be
uploaded as files
Return (content_type, body) ready for httplib.HTTP instance
"""
BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
......@@ -22,7 +24,9 @@ def encode_multipart_formdata(fields, files):
L.append(value)
for (key, filename, value) in files:
L.append('--' + BOUNDARY)
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
L.append(
'Content-Disposition: '
'form-data; name="%s"; filename="%s"' % (key, filename))
L.append('Content-Type: %s' % get_content_type(filename))
L.append('')
L.append(value)
......@@ -32,6 +36,6 @@ def encode_multipart_formdata(fields, files):
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
return content_type, body
def get_content_type(filename):
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
def get_content_type(filename):
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
import urllib
import urllib # noqa
from _core import API, missing
from etsy_env import EtsyEnvSandbox, EtsyEnvProduction
from etsy_env import EtsyEnvProduction
try:
from urlparse import parse_qsl
from urlparse import parse_qsl # noqa
except ImportError:
from cgi import parse_qsl
from cgi import parse_qsl # noqa
class EtsyV2(API):
api_version = 'v2'
def __init__(self, api_key='', key_file=None, method_cache=missing,
etsy_env=EtsyEnvSandbox(), log=None, etsy_oauth_client=None):
def __init__(self, api_key='', key_file=None, method_cache=missing,
etsy_env=EtsyEnvProduction(), log=None,
etsy_oauth_client=None):
self.api_url = etsy_env.api_url
self.etsy_oauth_client = None
......@@ -22,5 +24,8 @@ class EtsyV2(API):
def _get_url(self, url, http_method, content_type, body):
if self.etsy_oauth_client is not None:
return self.etsy_oauth_client.do_oauth_request(url, http_method, content_type, body)
return self.etsy_oauth_client.do_oauth_request(url,
http_method,
content_type,
body)
return API._get_url(self, url, http_method, content_type, body)
class EtsyEnvSandbox(object):
request_token_url = 'http://sandbox.openapi.etsy.com/v2/oauth/request_token'
access_token_url = 'http://sandbox.openapi.etsy.com/v2/oauth/access_token'
request_token_url = \
'https://sandbox.openapi.etsy.com/v2/oauth/request_token'
access_token_url = 'https://sandbox.openapi.etsy.com/v2/oauth/access_token'
signin_url = 'https://www.etsy.com/oauth/signin'
api_url = 'http://sandbox.openapi.etsy.com/v2'
api_url = 'https://sandbox.openapi.etsy.com/v2'
class EtsyEnvProduction(object):
request_token_url = 'http://openapi.etsy.com/v2/oauth/request_token'
access_token_url = 'http://openapi.etsy.com/v2/oauth/access_token'
request_token_url = 'https://openapi.etsy.com/v2/oauth/request_token'
access_token_url = 'https://openapi.etsy.com/v2/oauth/access_token'
signin_url = 'https://www.etsy.com/oauth/signin'
api_url = 'http://openapi.etsy.com/v2'
api_url = 'https://openapi.etsy.com/v2'
import oauth2 as oauth
import urllib
from cgi import parse_qsl
from etsy_env import EtsyEnvSandbox, EtsyEnvProduction
from etsy_env import EtsyEnvProduction
EtsyOAuthToken = oauth.Token
class EtsyOAuthClient(oauth.Client):
def __init__(self, oauth_consumer_key, oauth_consumer_secret, etsy_env=EtsyEnvSandbox(), token=None, logger=None):
def __init__(self, oauth_consumer_key,
oauth_consumer_secret, etsy_env=EtsyEnvProduction(),
token=None, logger=None):
consumer = oauth.Consumer(oauth_consumer_key, oauth_consumer_secret)
super(EtsyOAuthClient, self).__init__(consumer)
self.request_token_url = etsy_env.request_token_url
......@@ -16,17 +20,19 @@ class EtsyOAuthClient(oauth.Client):
self.logger = logger
def get_request_token(self, **kwargs):
request_token_url = '%s?%s' % (self.request_token_url, urllib.urlencode(kwargs))
request_token_url = '%s?%s' % (
self.request_token_url, urllib.urlencode(kwargs))
resp, content = self.request(request_token_url, 'GET')
return self._get_token(content)
def get_signin_url(self, **kwargs):
self.token = self.get_request_token(**kwargs)
if self.token is None: return None
if self.token is None:
return None
return self.signin_url + '?' + \
urllib.urlencode({'oauth_token': self.token.key})
urllib.urlencode({'oauth_token': self.token.key})
def get_access_token(self, oauth_verifier):
self.token.set_verifier(oauth_verifier)
......@@ -37,8 +43,10 @@ class EtsyOAuthClient(oauth.Client):
self.token = self.get_access_token(oauth_verifier)
def do_oauth_request(self, url, http_method, content_type, body):
if content_type and content_type != 'application/x-www-form-urlencoded':
resp, content = self.request(url, http_method, body=body, headers={'Content-Type': content_type})
if (content_type and content_type !=
'application/x-www-form-urlencoded'):
resp, content = self.request(url, http_method, body=body, headers={
'Content-Type': content_type})
else:
resp, content = self.request(url, http_method, body=body)
......@@ -52,5 +60,5 @@ class EtsyOAuthClient(oauth.Client):
try:
return oauth.Token(d['oauth_token'], d['oauth_token_secret'])
except KeyError, e:
except KeyError:
return None
#!/usr/bin/env python
import os, sys
import os
import sys
import oauth2 as oauth
import webbrowser
from etsy import Etsy, EtsyEnvSandbox, EtsyEnvProduction
from etsy import Etsy, EtsyEnvProduction
from etsy.oauth import EtsyOAuthClient
logging_enabled = True
etsy_env = EtsyEnvProduction()
def my_log(msg):
if logging_enabled: print(msg)
if logging_enabled:
print(msg)
def write_config_file(oauth_token):
os.umask(0077)
os.umask(0077) # noqa
config_file = file('config.py', 'w')
if config:
config_file.write("oauth_consumer_key = %r\n" % config.oauth_consumer_key)
config_file.write("oauth_consumer_secret = %r\n" % config.oauth_consumer_secret)
config_file.write("oauth_consumer_key = %r\n" %
config.oauth_consumer_key)
config_file.write("oauth_consumer_secret = %r\n" %
config.oauth_consumer_secret)
if oauth_token:
config_file.write("oauth_token_key = %r\n" % oauth_token.key)
......@@ -30,16 +36,20 @@ except ImportError:
config = None
write_config_file(oauth_token=None)
if hasattr(config, 'oauth_consumer_key') and hasattr(config, 'oauth_consumer_secret'):
if (hasattr(config, 'oauth_consumer_key') and
hasattr(config, 'oauth_consumer_secret')):
oauth_client = EtsyOAuthClient(
oauth_consumer_key=config.oauth_consumer_key,
oauth_consumer_secret=config.oauth_consumer_secret,
etsy_env=etsy_env)
else:
sys.stderr.write('ERROR: You must set oauth_consumer_key and oauth_consumer_secret in config.py\n')
sys.stderr.write(
'ERROR: You must set oauth_consumer_key and '
'oauth_consumer_secret in config.py\n')
sys.exit(1)
if hasattr(config, 'oauth_token_key') and hasattr(config, 'oauth_token_secret'):
if (hasattr(config, 'oauth_token_key') and
hasattr(config, 'oauth_token_secret')):
oauth_client.token = oauth.Token(
key=config.oauth_token_key,
secret=config.oauth_token_secret)
......@@ -50,17 +60,21 @@ else:
etsy_api = Etsy(etsy_oauth_client=oauth_client, etsy_env=etsy_env, log=my_log)
# print 'oauth access token: (key=%r; secret=%r)' % (oauth_client.token.key, oauth_client.token.secret)
# print 'oauth access token: (key=%r; secret=%r)' %
# (oauth_client.token.key, oauth_client.token.secret)
print('findAllShopListingsActive => %r' % etsy_api.findAllShopListingsActive(shop_id=config.user_id, sort_on='created', limit=1))
print('findAllShopListingsActive => %r' % etsy_api.findAllShopListingsActive(
shop_id=config.user_id, sort_on='created', limit=1))
# print('getListing => %r' % etsy_api.getListing(listing_id=63067548))
print('findAllUserShippingTemplates => %r' % etsy_api.findAllUserShippingTemplates(user_id=config.user_id))
print('findAllUserShippingTemplates => %r' %
etsy_api.findAllUserShippingTemplates(user_id=config.user_id))
def testCreateListing():
print "Creating listing..."
result = etsy_api.createListing(
description=config.description,
title=config.title,
......@@ -75,9 +89,9 @@ def testCreateListing():
print "Created listing with listing id %d" % listing_id
result = etsy_api.uploadListingImage(listing_id=listing_id, image=config.image_file)
result = etsy_api.uploadListingImage(listing_id=listing_id,
image=config.image_file)
print "Result of uploading image: %r" % result
testCreateListing()
pip==8.1.2
httplib2==0.9.2
oauth2==1.9.0.post1
simplejson==3.8.2
flake8==2.6.0
pytest==2.9.2
\ No newline at end of file
......@@ -5,19 +5,26 @@ from setuptools import setup
this_dir = os.path.realpath(os.path.dirname(__file__))
long_description = open(os.path.join(this_dir, 'README.md'), 'r').read()
requirements = [
]
test_requirements = [
'pytest',
]
setup(
name = 'etsy',
version = '0.3.1',
author = 'Dan McKinley',
author_email = 'dan@etsy.com',
description = 'Python access to the Etsy API',
license = 'GPL v3',
keywords = 'etsy api handmade',
packages = ['etsy'],
long_description = long_description,
test_suite = 'test',
name='etsy',
version='0.3.1',
author='Dan McKinley & Fulfil.IO Inc.',
author_email='dan@etsy.com',
description='Python access to the Etsy API',
license='GPL v3',
keywords='etsy api handmade',
packages=['etsy'],
long_description=long_description,
test_suite='test',
install_requires=['simplejson >= 2.0'],
extras_require = {
extras_require={
'OAuth': ["oauth2>=1.2.0"],
}
)
......@@ -10,28 +10,22 @@ class Test(TestCase):
scratch_dir = os.path.join(this_dir, 'scratch')
def setUp(self):
if not os.path.isdir(self.scratch_dir):
os.mkdir(self.scratch_dir)
def tearDown(self):
self.delete_scratch()
def delete_scratch(self):
if os.path.isdir(self.scratch_dir):
shutil.rmtree(self.scratch_dir)
def assertRaises(self, cls, f, *args, **kwargs):
try:
f(*args, **kwargs)
except cls, e:
except cls, e: # noqa
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