Skip to content

Commit

Permalink
Merge pull request #57 from makyo/wallets
Browse files Browse the repository at this point in the history
Add wallets and budgets
  • Loading branch information
jujugui authored Mar 20, 2018
2 parents 667b2fb + b6ae3c6 commit 92d9efa
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 17 deletions.
205 changes: 192 additions & 13 deletions theblues/plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@
DEFAULT_TIMEOUT,
)

Plan = namedtuple('Plan',
['url', 'plan', 'created_on', 'description', 'price'])
PLAN_VERSION = 'v2'
Plan = namedtuple(
'Plan',
['url', 'plan', 'created_on', 'description', 'price'])
Wallet = namedtuple(
'Wallet',
['owner', 'wallet', 'limit', 'budgeted', 'unallocated', 'available',
'consumed', 'default'])
WalletTotal = namedtuple(
'WalletTotal',
['limit', 'budgeted', 'unallocated', 'available', 'consumed', 'usage'])
PLAN_VERSION = 'v3'


class Plans(object):
Expand All @@ -42,7 +50,7 @@ def get_plans(self, reference):
@return a tuple of plans or an empty tuple if no plans.
@raise ServerError
"""
json = make_request(
response = make_request(
'{}charm?charm-url={}'.format(self.url,
'cs:' + reference.path()),
timeout=self.timeout, client=self._client)
Expand All @@ -54,18 +62,189 @@ def get_plans(self, reference):
"%Y-%m-%dT%H:%M:%SZ"
),
description=plan.get('description'),
price=plan.get('price')), json))
except (KeyError, TypeError, ValueError) as err:
log.info(
price=plan.get('price')), response))
except Exception as err:
log.error(
'cannot process plans: invalid JSON response: {!r}'.format(
json))
response))
raise ServerError(
'unable to get list of plans for {}: {}'.format(
reference.path(), err))

def list_wallets(self):
"""Get the list of wallets.
@return an dict containing a list of wallets, a total, and available
credit.
@raise ServerError
"""
response = make_request(
'{}wallet'.format(self.url),
timeout=self.timeout,
client=self._client)
try:
total = response['total']
return {
'credit': response['credit'],
'total': WalletTotal(
limit=total['limit'],
budgeted=total['budgeted'],
available=total['available'],
unallocated=total['unallocated'],
usage=total['usage'],
consumed=total['consumed']),
'wallets': tuple(Wallet(
owner=wallet['owner'],
wallet=wallet['wallet'],
limit=wallet['limit'],
budgeted=wallet['budgeted'],
unallocated=wallet['unallocated'],
available=wallet['available'],
consumed=wallet['consumed'],
default='default' in wallet)
for wallet in response['wallets']),
}
except Exception as err:
log.error(
'cannot process wallets: invalid JSON response: {!r}'.format(
response))
raise ServerError(
'unable to get list of wallets: {!r}'.format(err))

def get_wallet(self, wallet_name):
"""Get a single wallet.
@param the name of the wallet.
@return the wallet's total.
@raise ServerError
"""
response = make_request(
'{}wallet/{}'.format(self.url, wallet_name),
timeout=self.timeout,
client=self._client)
try:
total = response['total']
return {
'credit': response['credit'],
'limit': response['limit'],
'total': WalletTotal(
limit=total['limit'],
budgeted=total['budgeted'],
available=total['available'],
unallocated=total['unallocated'],
usage=total['usage'],
consumed=total['consumed'])
}
except Exception as exc:
log.info(
'cannot process plans: invalid JSON response: {!r}'.format(
json))
log.error(
'cannot get wallet from server: {!r}'.format(exc))
raise ServerError(
'unable to get list of plans for {}: {}'.format(
reference.path(), exc))
'unable to get list of wallets: {!r}'.format(exc))

def update_wallet(self, wallet_name, limit):
"""Update a wallet with a new limit.
@param the name of the wallet.
@param the new value of the limit.
@return a success string from the plans server.
@raise ServerError via make_request.
"""
request = {
'update': {
'limit': str(limit),
}
}
return make_request(
'{}wallet/{}'.format(self.url, wallet_name),
method='PATCH',
body=request,
timeout=self.timeout,
client=self._client)

def create_wallet(self, wallet_name, limit):
"""Create a new wallet.
@param the name of the wallet.
@param the value of the limit.
@return a success string from the plans server.
@raise ServerError via make_request.
"""
request = {
'wallet': wallet_name,
'limit': str(limit),
}
return make_request(
'{}wallet'.format(self.url),
method='POST',
body=request,
timeout=self.timeout,
client=self._client)

def delete_wallet(self, wallet_name):
"""Delete a wallet.
@param the name of the wallet.
@return a success string from the plans server.
@raise ServerError via make_request.
"""
return make_request(
'{}wallet/{}'.format(self.url, wallet_name),
method='DELETE',
timeout=self.timeout,
client=self._client)

def create_budget(self, wallet_name, model_uuid, limit):
"""Create a new budget for a model and wallet.
@param the name of the wallet.
@param the model UUID.
@param the new value of the limit.
@return a success string from the plans server.
@raise ServerError via make_request.
"""
request = {
'model': model_uuid,
'limit': limit,
}
return make_request(
'{}wallet/{}/budget'.format(self.url, wallet_name),
method='POST',
body=request,
timeout=self.timeout,
client=self._client)

def update_budget(self, wallet_name, model_uuid, limit):
"""Update a budget limit.
@param the name of the wallet.
@param the model UUID.
@param the new value of the limit.
@return a success string from the plans server.
@raise ServerError via make_request.
"""
request = {
'update': {
'wallet': wallet_name,
'limit': limit,
}
}
return make_request(
'{}model/{}/budget'.format(self.url, model_uuid),
method='PATCH',
body=request,
timeout=self.timeout,
client=self._client)

def delete_budget(self, model_uuid):
"""Delete a budget.
@param the name of the wallet.
@param the model UUID.
@return a success string from the plans server.
@raise ServerError via make_request.
"""
return make_request(
'{}model/{}/budget'.format(self.url, model_uuid),
method='DELETE',
timeout=self.timeout,
client=self._client)
133 changes: 131 additions & 2 deletions theblues/tests/test_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from theblues.plans import (
Plan,
Plans,
Wallet,
WalletTotal,
)
from theblues.errors import ServerError
from theblues.utils import DEFAULT_TIMEOUT
Expand All @@ -24,7 +26,7 @@ def setUp(self):
'cs:trusty/landscape-mock-0')

def test_init(self):
self.assertEqual(self.plans.url, 'http://example.com/v2/')
self.assertEqual(self.plans.url, 'http://example.com/v3/')

@patch('theblues.plans.make_request')
def test_get_plans(self, mocked):
Expand Down Expand Up @@ -67,7 +69,7 @@ def test_get_plans(self, mocked):
price='Free'),
), resp)
mocked.assert_called_once_with(
'http://example.com/v2/charm?charm-url=cs:trusty/landscape-mock-0',
'http://example.com/v3/charm?charm-url=cs:trusty/landscape-mock-0',
timeout=DEFAULT_TIMEOUT,
client=self.client
)
Expand Down Expand Up @@ -103,3 +105,130 @@ def test_get_plans_invalid_date_format(self, mocked):
'}]')
with self.assertRaises(ServerError):
self.plans.get_plans(self.ref)

@patch('theblues.plans.make_request')
def test_list_wallets(self, mocked):
mocked.return_value = {
'wallets': [
{
'owner': 'rose',
'wallet': 'default',
'limit': '100',
'budgeted': '0',
'unallocated': '100',
'available': '100.00',
'consumed': '0.00',
'default': True,
},
{
'owner': 'rose',
'wallet': 'qa',
'limit': '10',
'budgeted': '0',
'unallocated': '10',
'available': '10.00',
'consumed': '0.00',
}
],
'total': {
'limit': '110',
'budgeted': '0',
'available': '110.00',
'unallocated': '110',
'usage': '0%',
'consumed': '0.00',
},
'credit': '10000',
}
result = self.plans.list_wallets()
self.assertEqual(result, {
'wallets': (
Wallet(
owner='rose',
wallet='default',
limit='100',
budgeted='0',
unallocated='100',
available='100.00',
consumed='0.00',
default=True),
Wallet(
owner='rose',
wallet='qa',
limit='10',
budgeted='0',
unallocated='10',
available='10.00',
consumed='0.00',
default=False),
),
'total': WalletTotal(
limit='110',
budgeted='0',
available='110.00',
unallocated='110',
usage='0%',
consumed='0.00'),
'credit': '10000',
})

@patch('theblues.plans.make_request')
def test_list_wallets_exception(self, mocked):
mocked.return_value = {
'wallets': [
{
'bad': 'wolf',
},
],
'total': {
'dalek': True,
},
'credit': '10000',
}
with self.assertRaises(ServerError) as err:
self.plans.list_wallets()
self.assertEqual(
str(err.exception),
'unable to get list of wallets: KeyError(\'limit\',)')

@patch('theblues.plans.make_request')
def test_get_wallet(self, mocked):
mocked.return_value = {
'limit': '100',
'total': {
'limit': '100',
'budgeted': '0',
'available': '100.00',
'unallocated': '100',
'usage': '0%',
'consumed': '0.00',
},
'credit': '10000',
}
result = self.plans.get_wallet('default')
self.assertEqual(result, {
'limit': '100',
'total': WalletTotal(
limit='100',
budgeted='0',
available='100.00',
unallocated='100',
usage='0%',
consumed='0.00'),
'credit': '10000',
})

@patch('theblues.plans.make_request')
def test_get_wallet_exception(self, mocked):
mocked.return_value = {
'limit': '100',
'total': {
'bad': 'wolf',
},
'credit': '10000',
}
with self.assertRaises(ServerError) as err:
self.plans.get_wallet('default')
self.assertEqual(
str(err.exception),
'unable to get list of wallets: KeyError(\'limit\',)')
2 changes: 1 addition & 1 deletion theblues/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def make_request(
if method in ('GET', 'HEAD'):
if query:
url = '{}?{}'.format(url, urlencode(query, True))
elif method in ('POST', 'PUT'):
elif method in ('DELETE', 'PATCH', 'POST', 'PUT'):
kwargs['headers'] = {'Content-Type': 'application/json'}
else:
raise ValueError('invalid method {}'.format(method))
Expand Down
Loading

0 comments on commit 92d9efa

Please sign in to comment.