exercism-solutions/python/rest-api/rest_api.py

116 lines
3.2 KiB
Python

import json
from collections import defaultdict
from functools import wraps
from operator import attrgetter
VALID_HTTP_METHODS = ['GET', 'POST']
class User:
def __init__(self, name):
self.name = name
self.owes = defaultdict(float)
self.owed_by = defaultdict(float)
self.balance = 0.0
def lend(self, borrower, amount):
total_owed = self.owed_by[borrower] - self.owes[borrower] + amount
if total_owed > 0:
self.owed_by[borrower] = total_owed
self.owes.pop(borrower, None)
elif total_owed < 0:
self.owes[borrower] = -total_owed
self.owed_by.pop(borrower, None)
else:
self.owed_by.pop(borrower, None)
self.owes.pop(borrower, None)
self.balance += amount
def borrow(self, lender, amount):
self.lend(lender, -amount)
@classmethod
def from_dict(cls, d):
user = cls(d['name'])
user.owed_by.update(d['owed_by'])
user.owes.update(d['owes'])
user.balance = d['balance']
return user
class MethodRegistry:
def __init__(self):
self.methods = {k: {} for k in VALID_HTTP_METHODS }
def register_url(self, url, http_method):
def actual_decorator(f):
@wraps(f)
def _wrapper(f_self, payload):
if payload:
payload = json.loads(payload)
#It would be essential to validate JSON schema here
f_result = f(f_self, payload)
return json.dumps(f_result, default=lambda obj: obj.__dict__, sort_keys=True)
self.methods[http_method][url] = _wrapper
return actual_decorator
class RestAPI:
registry = MethodRegistry()
def __init__(self, database=None):
self._users = {user['name'] : User.from_dict(user) for user in database['users']} if database else {}
def _process_request(self, method, url, payload):
methods = self.registry.methods[method]
#It would be natural to handle exceptions here
if url in methods.keys():
return methods[url](self, payload)
def get(self, url, payload=None):
return self._process_request('GET', url, payload)
def post(self, url, payload=None):
return self._process_request('POST', url, payload)
@registry.register_url('/users', 'GET')
def users(self, payload=None):
if payload:
requested_users = payload['users']
response = [user for user in self._users.values() if user.name in requested_users]
else:
response = [user for user in self._users.values()]
return {'users': response}
@registry.register_url('/add', 'POST')
def add(self, payload=None):
name = payload['user']
user = User(name)
self._users[user.name] = user
return user
@registry.register_url('/iou', 'POST')
def iou(self, payload=None):
lender = payload['lender']
borrower = payload['borrower']
amount = payload['amount']
self._users[lender].lend(borrower, amount)
self._users[borrower].borrow(lender, amount)
return {'users': sorted([self._users[lender], self._users[borrower]], key=attrgetter('name'))}