addons-server/lib/pay_server/base.py

189 строки
6.4 KiB
Python

import datetime
import decimal
from functools import partial
import json
import logging
import urllib
from django.conf import settings
from curling.lib import API
from django_statsd.clients import statsd
import requests
from tower import ugettext_lazy as _
from .errors import lookup
log = logging.getLogger('s.client')
class SolitudeError(Exception):
def __init__(self, *args, **kwargs):
self.code = kwargs.pop('code', 0)
super(SolitudeError, self).__init__(*args, **kwargs)
class SolitudeOffline(SolitudeError):
pass
class SolitudeTimeout(SolitudeError):
pass
mapping = {
'buyer': ['generic', 'buyer', ['get', 'post']],
'buyer_paypal': ['paypal', 'buyer', ['get', 'post', 'patch', 'delete']],
'seller': ['generic', 'seller', ['get', 'post']],
'seller_paypal': ['paypal', 'seller', ['get', 'post', 'patch']],
'seller_bluevia': ['bluevia', 'seller', ['get', 'post', 'patch']],
'product': ['generic', 'product', ['get', 'post']],
# PayPal APIs
'account_check': ['paypal', 'account-check', ['post']],
'ipn': ['paypal', 'ipn', ['post']],
'preapproval': ['paypal', 'preapproval', ['post', 'put', 'delete']],
'pay': ['paypal', 'pay', ['post']],
'pay_check': ['paypal', 'pay-check', ['post']],
'permission_url': ['paypal', 'permission-url', ['post']],
'permission_token': ['paypal', 'permission-token', ['post']],
'personal_basic': ['paypal', 'personal-basic', ['post']],
'personal_advanced': ['paypal', 'personal-advanced', ['post']],
'refund': ['paypal', 'refund', ['post']],
# Bango APIs
'package': ['bango', 'package', ['get', 'post', 'patch']],
'bank_details': ['bango', 'bank', ['get', 'post']],
'product_bango': ['bango', 'product', ['get', 'post']],
'make_premium': ['bango', 'premium', ['post']],
'make_free': ['bango', 'free', ['post']],
'update_rating': ['bango', 'rating', ['post']],
}
date_format = '%Y-%m-%d'
time_format = '%H:%M:%S'
class Encoder(json.JSONEncoder):
ENCODINGS = {
datetime.datetime:
lambda v: v.strftime('%s %s' % (date_format, time_format)),
datetime.date: lambda v: v.strftime(date_format),
datetime.time: lambda v: v.strftime(time_format),
decimal.Decimal: str,
}
def default(self, v):
"""Encode some of our basic types in ways solitude understands."""
return self.ENCODINGS.get(type(v), super(Encoder, self).default)(v)
general_error = _('Oops, we had an error processing that.')
class Client(object):
def __init__(self, config=None):
self.config = self.parse(config)
self.api = API(config['server'])
self.api.activate_oauth(settings.SOLITUDE_OAUTH.get('key'),
settings.SOLITUDE_OAUTH.get('secret'))
self.encoder = None
self.filter_encoder = urllib.urlencode
def call_uri(self, uri, method='get', data=None):
"""If you were given a URI by Solitude, pass it here and get that
the value back. Since the URLs are relative, they couldn't simply be
passed to `call()`. This handles all the prefixing and whatnot.
"""
uri = uri.lstrip('/')
return self.call('%s/%s' % (self.config['server'], uri), method, data)
def _url(self, context, name, pk=None):
url = '%s/%s/%s/' % (self.config['server'], context, name)
if pk:
url = '%s%s/' % (url, pk)
return url
def parse(self, config=None):
config = {
'server': config.get('server')
# TODO: add in OAuth stuff.
}
return config
def call(self, url, method_name, data=None):
log.info('Deprecated, please use curling: %s, %s' % (url, method_name))
if data and method_name.lower() == 'get':
raise TypeError('You cannot use data in a GET request. '
'Maybe you meant to use filters=...')
data = (json.dumps(data, cls=self.encoder or Encoder)
if data else json.dumps({}))
method = getattr(requests, method_name)
try:
with statsd.timer('solitude.call.%s' % method_name):
result = method(url, data=data,
headers={'content-type': 'application/json'},
timeout=self.config.get('timeout', 10))
except requests.ConnectionError:
log.error('Solitude not accessible')
raise SolitudeOffline(general_error)
except requests.Timeout:
log.error('Solitude timed out, limit %s'
% self.config.get('timeout', 10))
raise SolitudeTimeout(general_error)
if result.status_code in (200, 201, 202, 204):
return json.loads(result.text) if result.text else {}
else:
log.error('Solitude error with %s: %r' % (url, result.text))
res = {}
try:
res = json.loads(result.text) if result.text else {}
except:
# Not a JSON error.
pass
code = res.get('error_code', 0)
raise SolitudeError(lookup(code, res.get('error_data', {})),
code=code)
def upsert(self, method, params, lookup_by):
"""Shortcut function for calling get_<method> and subsequently calling
post_<method> if there are no results. The values passed to the data
param of get_<method> are the keys defined in the sequence `lookup_by`.
"""
lookup_data = dict((k, v) for k, v in params.items() if k in lookup_by)
existing = self.__getattr__('get_%s' % method)(filters=lookup_data)
if existing['meta']['total_count']:
return existing['objects'][0]
return self.__getattr__('post_%s' % method)(data=params)
def __getattr__(self, attr):
try:
method, action = attr.split('_', 1)
except:
raise AttributeError(attr)
target = mapping.get(action)
if not target:
raise AttributeError(attr)
if method not in target[2]:
raise AttributeError(attr)
return partial(self.wrapped, **{'target': target, 'method': method})
def wrapped(self, target=None, method=None, data=None, pk=None,
filters=None):
url = self._url(*target[:2], pk=pk)
if filters:
url = '%s?%s' % (url, self.filter_encoder(filters))
return self.call(url, method, data=data)