weekends

Finished
1import json
2
13class RestAPI:
24 def __init__(self, database: dict | None = None):
3 pass
4
5 """
6 Initialize the REST API with an optional database.
7
8 Args:
9 database: A dictionary containing initial user data.
10 """
11 # Edge Case: Handle None database by initializing empty users dict
12 if database is None:
13 self.users = {}
14 else:
15 self.users = database.get('users', {})
16
17 # Convert list of users to dictionary for easier access
18 if isinstance(self.users, list):
19 self.users = {user['name']: user for user in self.users}
20
21 # Ensure all users have the required fields
22 for user in self.users.values():
23 if 'owes' not in user:
24 user['owes'] = {}
25 if 'owed_by' not in user:
26 user['owed_by'] = {}
27 if 'balance' not in user:
28 user['balance'] = 0.0
29
30 def _net_debts(self, user1, user2):
31 """
32 Net debts between two users by offsetting mutual debts.
33
34 Args:
35 user1: First user dictionary
36 user2: Second user dictionary
37 """
38 name1 = user1['name']
39 name2 = user2['name']
40
41 # Check if user1 owes user2 and user2 owes user1
42 user1_owes_user2 = user1['owes'].get(name2, 0)
43 user2_owes_user1 = user2['owes'].get(name1, 0)
44
45 if user1_owes_user2 > 0 and user2_owes_user1 > 0:
46 # Net the amounts
47 net_amount = abs(user1_owes_user2 - user2_owes_user1)
48
49 if user1_owes_user2 > user2_owes_user1:
50 # User1 owes more to user2
51 user1['owes'][name2] = net_amount
52 user2['owed_by'][name1] = net_amount
53 # Remove the opposite debt
54 del user2['owes'][name1]
55 del user1['owed_by'][name2]
56 elif user2_owes_user1 > user1_owes_user2:
57 # User2 owes more to user1
58 user2['owes'][name1] = net_amount
59 user1['owed_by'][name2] = net_amount
60 # Remove the opposite debt
61 del user1['owes'][name2]
62 del user2['owed_by'][name1]
63 else:
64 # Debts cancel out completely
65 del user1['owes'][name2]
66 del user2['owed_by'][name1]
67 del user2['owes'][name1]
68 del user1['owed_by'][name2]
69
70 def _calculate_balance(self, user):
71 """
72 Calculate the balance for a user based on what they owe and are owed.
73
74 Args:
75 user: The user dictionary to calculate balance for.
76
77 Returns:
78 float: The calculated balance.
79 """
80 total_owed_by_others = sum(user['owed_by'].values())
81 total_owed_to_others = sum(user['owes'].values())
82 return round(total_owed_by_others - total_owed_to_others, 2)
83
84 def _get_user(self, name):
85 """
86 Get a user by name, creating them if they don't exist.
87
88 Args:
89 name: The name of the user to retrieve.
90
91 Returns:
92 dict: The user object.
93 """
94 # Edge Case: Create user if they don't exist
95 if name not in self.users:
96 self.users[name] = {
97 'name': name,
98 'owes': {},
99 'owed_by': {},
100 'balance': 0.0
101 }
102 return self.users[name]
103
5104 def get(self, url: str, payload: str | None = None) -> str:
6 pass
7
105 """
106 Handle GET requests to the API.
107
108 Args:
109 url: The endpoint URL.
110 payload: Optional JSON payload.
111
112 Returns:
113 str: JSON response.
114 """
115 # Edge Case: Handle /users endpoint
116 if url == '/users':
117 # If payload exists, filter users
118 if payload:
119 data = json.loads(payload)
120 user_names = data.get('users', [])
121 # Edge Case: Handle empty users list in payload
122 if not user_names:
123 return json.dumps({'users': []})
124
125 # Edge Case: Filter only existing users
126 filtered_users = []
127 for name in user_names:
128 if name in self.users:
129 user = self.users[name].copy()
130 user['balance'] = self._calculate_balance(user)
131 filtered_users.append(user)
132
133 # Sort by name
134 filtered_users.sort(key=lambda x: x['name'])
135 return json.dumps({'users': filtered_users})
136 else:
137 # Return all users
138 all_users = []
139 for user in self.users.values():
140 user_copy = user.copy()
141 user_copy['balance'] = self._calculate_balance(user_copy)
142 all_users.append(user_copy)
143
144 # Sort by name
145 all_users.sort(key=lambda x: x['name'])
146 return json.dumps({'users': all_users})
147
148 # Edge Case: Handle unknown endpoints
149 return json.dumps({'error': 'Not found'})
150
8151 def post(self, url: str, payload: str | None = None) -> str:
9 pass
152 """
153 Handle POST requests to the API.
154
155 Args:
156 url: The endpoint URL.
157 payload: JSON payload.
158
159 Returns:
160 str: JSON response.
161 """
162 # Edge Case: Handle missing payload
163 if not payload:
164 return json.dumps({'error': 'Payload required'})
165
166 data = json.loads(payload)
167
168 # Edge Case: Handle /add endpoint
169 if url == '/add':
170 user_name = data.get('user')
171 # Edge Case: Handle missing user in payload
172 if not user_name:
173 return json.dumps({'error': 'User name required'})
174
175 # Edge Case: Handle duplicate user - return existing user
176 if user_name in self.users:
177 return json.dumps(self.users[user_name])
178
179 new_user = {
180 'name': user_name,
181 'owes': {},
182 'owed_by': {},
183 'balance': 0.0
184 }
185 self.users[user_name] = new_user
186 return json.dumps(new_user)
187
188 # Edge Case: Handle /iou endpoint
189 elif url == '/iou':
190 lender_name = data.get('lender')
191 borrower_name = data.get('borrower')
192 amount = data.get('amount')
193
194 # Edge Case: Handle missing required fields
195 if not lender_name or not borrower_name or amount is None:
196 return json.dumps({'error': 'Lender, borrower, and amount required'})
197
198 # Edge Case: Handle invalid amount type
199 if not isinstance(amount, (int, float)):
200 return json.dumps({'error': 'Amount must be a number'})
201
202 # Edge Case: Handle negative amount
203 if amount < 0:
204 return json.dumps({'error': 'Amount must be positive'})
205
206 # Edge Case: Handle same lender and borrower
207 if lender_name == borrower_name:
208 return json.dumps({'error': 'Lender and borrower cannot be the same'})
209
210 # Get or create users
211 lender = self._get_user(lender_name)
212 borrower = self._get_user(borrower_name)
213
214 # Update debts with proper netting
215 # Add to lender's owed_by and borrower's owes
216 if borrower_name in lender['owed_by']:
217 lender['owed_by'][borrower_name] += amount
218 else:
219 lender['owed_by'][borrower_name] = amount
220
221 if lender_name in borrower['owes']:
222 borrower['owes'][lender_name] += amount
223 else:
224 borrower['owes'][lender_name] = amount
225
226 # Net the debts if both users have debts to each other
227 self._net_debts(lender, borrower)
228
229 # Recalculate balances
230 lender['balance'] = self._calculate_balance(lender)
231 borrower['balance'] = self._calculate_balance(borrower)
232
233 # Return updated users, sorted by name
234 updated_users = [lender, borrower]
235 updated_users.sort(key=lambda x: x['name'])
236
237 # Create copies without modifying the originals
238 result_users = []
239 for user in updated_users:
240 user_copy = user.copy()
241 user_copy['balance'] = self._calculate_balance(user_copy)
242 result_users.append(user_copy)
243
244 return json.dumps({'users': result_users})
245
246 # Edge Case: Handle unknown endpoints
247 return json.dumps({'error': 'Not found'})
248
249 # Handled Edge Cases: None database, missing payload, unknown endpoints, /users filtering,
250 # empty users list, non-existing users in filter, /add missing user, duplicate user,
251 # /iou missing fields, invalid amount type, negative amount, same lender/borrower
Test NameStatus
test_add_user
Pass
test_borrower_has_negative_balance
Pass
test_both_users_have_0_balance
Pass
test_get_single_user
Pass
test_lender_has_negative_balance
Pass
test_lender_owes_borrower
Pass
test_lender_owes_borrower_less_than_new_loan
Pass
test_lender_owes_borrower_same_as_new_loan
Pass
test_no_users
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.