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'))}