uv

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 if database is None:
12 self.database = {"users": []}
13 else:
14 self.database = database
15
516 def get(self, url: str, payload: str | None = None) -> str:
6 pass
7
17 """
18 Handle GET requests to the API.
19
20 Args:
21 url: The endpoint URL.
22 payload: Optional JSON payload.
23
24 Returns:
25 A JSON string response.
26
27 Raises:
28 ValueError: If the URL is not recognized.
29 """
30 if url == "/users":
31 if payload is None:
32 # Return all users
33 return json.dumps({"users": self._get_all_users()})
34 else:
35 # Return specific users
36 data = json.loads(payload)
37 user_names = data.get("users", [])
38 return json.dumps({"users": self._get_users_by_names(user_names)})
39 else:
40 raise ValueError(f"Unknown GET endpoint: {url}")
41
842 def post(self, url: str, payload: str | None = None) -> str:
9 pass
43 """
44 Handle POST requests to the API.
45
46 Args:
47 url: The endpoint URL.
48 payload: JSON payload.
49
50 Returns:
51 A JSON string response.
52
53 Raises:
54 ValueError: If the URL is not recognized or if required data is missing.
55 """
56 if url == "/add":
57 if payload is None:
58 raise ValueError("Payload required for /add endpoint")
59
60 data = json.loads(payload)
61 user_name = data.get("user")
62
63 # Edge Case: Missing user name in payload
64 if user_name is None:
65 raise ValueError("User name is required")
66
67 existing_user = self._find_user(user_name)
68 if existing_user is not None:
69 # Return existing user if already exists
70 return json.dumps(existing_user)
71
72 new_user = {
73 "name": user_name,
74 "owes": {},
75 "owed_by": {},
76 "balance": 0.0
77 }
78
79 self.database["users"].append(new_user)
80
81 # Sort users by name after adding
82 self.database["users"].sort(key=lambda u: u["name"])
83
84 return json.dumps(new_user)
85
86 elif url == "/iou":
87 if payload is None:
88 raise ValueError("Payload required for /iou endpoint")
89
90 data = json.loads(payload)
91 lender_name = data.get("lender")
92 borrower_name = data.get("borrower")
93 amount = data.get("amount")
94
95 # Edge Case: Missing required fields
96 if lender_name is None or borrower_name is None or amount is None:
97 raise ValueError("Lender, borrower, and amount are all required")
98
99 # Edge Case: Lender and borrower are the same person
100 if lender_name == borrower_name:
101 raise ValueError("Lender and borrower cannot be the same person")
102
103 # Edge Case: Negative amount
104 if amount < 0:
105 raise ValueError("Amount must be non-negative")
106
107 # Get or create lender
108 lender = self._find_user(lender_name)
109 if lender is None:
110 lender = {
111 "name": lender_name,
112 "owes": {},
113 "owed_by": {},
114 "balance": 0.0
115 }
116 self.database["users"].append(lender)
117 self.database["users"].sort(key=lambda u: u["name"])
118
119 # Get or create borrower
120 borrower = self._find_user(borrower_name)
121 if borrower is None:
122 borrower = {
123 "name": borrower_name,
124 "owes": {},
125 "owed_by": {},
126 "balance": 0.0
127 }
128 self.database["users"].append(borrower)
129 self.database["users"].sort(key=lambda u: u["name"])
130
131 # Calculate the net change in debt
132 # When lender lends to borrower, borrower should owe lender more
133 # But we need to net this with existing debts
134
135 # Get current debts between lender and borrower
136 borrower_owes_lender = borrower["owes"].get(lender_name, 0) # Amount borrower owes to lender
137 borrower_owed_by_lender = borrower["owed_by"].get(lender_name, 0) # Amount borrower is owed by lender
138
139 # Net current debt from borrower's perspective: positive means borrower owes lender, negative means lender owes borrower
140 current_net_debt = borrower_owes_lender - borrower_owed_by_lender
141
142 # Process the IOU by calculating the new net debt
143 # An IOU represents a transfer of money from lender to borrower
144 # If there's no existing debt, it creates a new debt
145 # If there's existing debt, it modifies the existing debt
146
147 if current_net_debt == 0:
148 # No existing debt, create a new debt
149 new_net_debt = current_net_debt + amount
150 else:
151 # Existing debt, modify it
152 new_net_debt = current_net_debt - amount
153
154 # Update the records based on the new net debt
155 if borrower_name in lender["owed_by"]:
156 del lender["owed_by"][borrower_name]
157 if borrower_name in lender["owes"]:
158 del lender["owes"][borrower_name]
159
160 if lender_name in borrower["owed_by"]:
161 del borrower["owed_by"][lender_name]
162 if lender_name in borrower["owes"]:
163 del borrower["owes"][lender_name]
164
165 # Apply the new net debt
166 if new_net_debt > 0:
167 # Borrower owes lender
168 lender["owed_by"][borrower_name] = new_net_debt
169 borrower["owes"][lender_name] = new_net_debt
170 elif new_net_debt < 0:
171 # Lender owes borrower
172 lender["owes"][borrower_name] = -new_net_debt
173 borrower["owed_by"][lender_name] = -new_net_debt
174 # If new_net_debt is 0, no entries are added
175
176 # Recalculate balances
177 self._recalculate_balance(lender)
178 self._recalculate_balance(borrower)
179
180 # Recalculate balances
181 self._recalculate_balance(lender)
182 self._recalculate_balance(borrower)
183
184 # Recalculate balances
185 self._recalculate_balance(lender)
186 self._recalculate_balance(borrower)
187
188 # Return updated users sorted by name
189 users = [lender, borrower]
190 users.sort(key=lambda u: u["name"])
191 return json.dumps({"users": users})
192
193 else:
194 raise ValueError(f"Unknown POST endpoint: {url}")
195
196 def _find_user(self, name: str):
197 """
198 Find a user by name in the database.
199
200 Args:
201 name: The name of the user to find.
202
203 Returns:
204 The user dictionary if found, None otherwise.
205 """
206 for user in self.database["users"]:
207 if user["name"] == name:
208 return user
209 return None
210
211 def _recalculate_balance(self, user):
212 """
213 Recalculate the balance for a user based on their owes and owed_by.
214
215 Args:
216 user: The user dictionary to update.
217 """
218 total_owed_by = sum(user["owed_by"].values())
219 total_owes = sum(user["owes"].values())
220 user["balance"] = round(total_owed_by - total_owes, 2)
221
222 def _net_debts(self, user, other_user_name):
223 """
224 Net the debts between a user and another user to simplify the ledger.
225
226 Args:
227 user: The user dictionary to update.
228 other_user_name: The name of the other user.
229 """
230 amount_owed_by_user = user["owed_by"].get(other_user_name, 0)
231 amount_owed_to_user = user["owes"].get(other_user_name, 0)
232
233 # Remove the entries if they exist
234 if other_user_name in user["owed_by"]:
235 del user["owed_by"][other_user_name]
236 if other_user_name in user["owes"]:
237 del user["owes"][other_user_name]
238
239 # Add back the net amount
240 net_amount = amount_owed_by_user - amount_owed_to_user
241 if net_amount > 0:
242 user["owed_by"][other_user_name] = net_amount
243 elif net_amount < 0:
244 user["owes"][other_user_name] = -net_amount
245 # If net_amount is 0, no entry is added (no debt)
246
247 def _get_all_users(self):
248 """
249 Get all users from the database.
250
251 Returns:
252 A list of all user dictionaries.
253 """
254 return self.database["users"]
255
256 def _get_users_by_names(self, names):
257 """
258 Get specific users by their names.
259
260 Args:
261 names: A list of user names to retrieve.
262
263 Returns:
264 A list of user dictionaries sorted by name.
265 """
266 users = []
267 for name in names:
268 user = self._find_user(name)
269 # Edge Case: Requested user does not exist
270 if user is not None:
271 users.append(user)
272 users.sort(key=lambda u: u["name"])
273 return users
274
275 # Handled Edge Cases: Missing user name in payload, user already exists, missing required fields,
276 # lender and borrower are the same person, negative amount, lender or borrower does not exist,
277 # requested user does not exist
Test NameStatus
test_lender_owes_borrower
Fail
test_lender_owes_borrower_less_than_new_loan
Fail
test_lender_owes_borrower_same_as_new_loan
Fail
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_no_users
Pass

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