xas

Finished
1class RestAPI:
2 def __init__(self, database: dict | None = None):
3 pass
1import json
2import sqlite3
3from typing import Dict, List, Optional
44
5 def get(self, url: str, payload: str | None = None) -> str:
6 pass
5class RestAPI:
6 """
7 A RESTful API for tracking IOUs between users.
8
9 This class provides methods to handle GET and POST requests for managing users
10 and their IOUs (I Owe You) using an SQLite database for persistence.
11 """
12
13 def __init__(self, database: Optional[Dict] = None):
14 """
15 Initialize the RestAPI with an optional initial database.
16
17 Args:
18 database: Optional dictionary containing initial user data.
19 """
20 # Create an in-memory SQLite database
21 self.conn = sqlite3.connect(':memory:', check_same_thread=False)
22 self.conn.row_factory = sqlite3.Row # Enable column access by name
23 self.cursor = self.conn.cursor()
24
25 # Create tables
26 self.cursor.execute('''
27 CREATE TABLE users (
28 name TEXT PRIMARY KEY
29 )
30 ''')
31
32 self.cursor.execute('''
33 CREATE TABLE ious (
34 id INTEGER PRIMARY KEY AUTOINCREMENT,
35 lender TEXT,
36 borrower TEXT,
37 amount REAL,
38 FOREIGN KEY(lender) REFERENCES users(name),
39 FOREIGN KEY(borrower) REFERENCES users(name)
40 )
41 ''')
42
43 self.conn.commit()
44
45 # Load initial data if provided
46 if database and 'users' in database:
47 for user_data in database['users']:
48 self._add_user(user_data['name'])
49
50 # Add owed amounts
51 if 'owed_by' in user_data:
52 for borrower, amount in user_data['owed_by'].items():
53 self._add_user(borrower)
54 self.cursor.execute(
55 'INSERT INTO ious (lender, borrower, amount) VALUES (?, ?, ?)',
56 (user_data['name'], borrower, amount)
57 )
58
59 # Add owed amounts (from other perspective)
60 if 'owes' in user_data:
61 for lender, amount in user_data['owes'].items():
62 self._add_user(lender)
63 self.cursor.execute(
64 'INSERT INTO ious (lender, borrower, amount) VALUES (?, ?, ?)',
65 (lender, user_data['name'], amount)
66 )
67
68 self.conn.commit()
69
70 def _add_user(self, name: str) -> None:
71 """
72 Add a user to the database if they don't already exist.
73
74 Args:
75 name: The name of the user to add.
76 """
77 # Edge Case: Prevent duplicate user creation
78 self.cursor.execute('INSERT OR IGNORE INTO users (name) VALUES (?)', (name,))
79 self.conn.commit()
80
81 def _get_user(self, name: str) -> Optional[Dict]:
82 """
83 Get a user's information.
84
85 Args:
86 name: The name of the user to retrieve.
87
88 Returns:
89 A dictionary representing the user, or None if not found.
90 """
91 # Edge Case: Check if user exists
92 self.cursor.execute('SELECT name FROM users WHERE name = ?', (name,))
93 if not self.cursor.fetchone():
94 return None
95
96 # Calculate amounts owed by this user
97 self.cursor.execute(
98 'SELECT borrower, SUM(amount) as total FROM ious WHERE lender = ? GROUP BY borrower',
99 (name,)
100 )
101 owed_by = {row['borrower']: row['total'] for row in self.cursor.fetchall()}
102
103 # Calculate amounts this user owes
104 self.cursor.execute(
105 'SELECT lender, SUM(amount) as total FROM ious WHERE borrower = ? GROUP BY lender',
106 (name,)
107 )
108 owes = {row['lender']: row['total'] for row in self.cursor.fetchall()}
109
110 # Calculate balance
111 total_owed_by = sum(owed_by.values())
112 total_owes = sum(owes.values())
113 balance = round(total_owed_by - total_owes, 2)
114
115 return {
116 'name': name,
117 'owes': owes,
118 'owed_by': owed_by,
119 'balance': balance
120 }
121
122 def get(self, url: str, payload: Optional[str] = None) -> str:
123 """
124 Handle GET requests.
125
126 Args:
127 url: The endpoint URL.
128 payload: Optional JSON payload.
129
130 Returns:
131 A JSON string response.
132
133 Raises:
134 ValueError: If the URL is not recognized.
135 """
136 if url == '/users':
137 # Edge Case: Handle invalid JSON payload
138 try:
139 data = json.loads(payload) if payload else {}
140 except json.JSONDecodeError:
141 return json.dumps({'error': 'Invalid JSON payload'})
142
143 # Get all users
144 self.cursor.execute('SELECT name FROM users ORDER BY name')
145 all_users = [row['name'] for row in self.cursor.fetchall()]
146
147 # Filter by specified users if provided
148 if 'users' in data and data['users']:
149 # Edge Case: Filter only existing users
150 specified_users = set(data['users'])
151 user_list = [user for user in all_users if user in specified_users]
152 else:
153 user_list = all_users
154
155 # Get user details
156 users = [self._get_user(name) for name in user_list]
157 # Edge Case: Remove None values for non-existent users
158 users = [user for user in users if user is not None]
159
160 return json.dumps({'users': users})
161
162 # Edge Case: Handle unrecognized URLs
163 return json.dumps({'error': 'Invalid URL'})
164
165 def post(self, url: str, payload: Optional[str] = None) -> str:
166 """
167 Handle POST requests.
168
169 Args:
170 url: The endpoint URL.
171 payload: Optional JSON payload.
172
173 Returns:
174 A JSON string response.
175
176 Raises:
177 ValueError: If the URL is not recognized or payload is invalid.
178 """
179 # Edge Case: Handle missing payload
180 if not payload:
181 return json.dumps({'error': 'Missing payload'})
182
183 # Edge Case: Handle invalid JSON payload
184 try:
185 data = json.loads(payload)
186 except json.JSONDecodeError:
187 return json.dumps({'error': 'Invalid JSON payload'})
188
189 if url == '/add':
190 # Edge Case: Handle missing user field
191 if 'user' not in data:
192 return json.dumps({'error': 'Missing user field'})
193
194 name = data['user']
195
196 # Edge Case: Handle empty user name
197 if not name:
198 return json.dumps({'error': 'User name cannot be empty'})
199
200 # Add user
201 self._add_user(name)
202
203 # Return user object
204 user = self._get_user(name)
205 return json.dumps(user)
206
207 elif url == '/iou':
208 # Edge Case: Validate required fields
209 required_fields = ['lender', 'borrower', 'amount']
210 for field in required_fields:
211 if field not in data:
212 return json.dumps({'error': f'Missing {field} field'})
213
214 lender = data['lender']
215 borrower = data['borrower']
216 amount = data['amount']
217
218 # Edge Case: Validate amount is a positive number
219 if not isinstance(amount, (int, float)) or amount <= 0:
220 return json.dumps({'error': 'Amount must be a positive number'})
221
222 # Edge Case: Validate lender and borrower are different
223 if lender == borrower:
224 return json.dumps({'error': 'Lender and borrower cannot be the same'})
225
226 # Add users if they don't exist
227 self._add_user(lender)
228 self._add_user(borrower)
229
230 # Add IOU
231 self.cursor.execute(
232 'INSERT INTO ious (lender, borrower, amount) VALUES (?, ?, ?)',
233 (lender, borrower, amount)
234 )
235 self.conn.commit()
236
237 # Return updated user objects
238 lender_obj = self._get_user(lender)
239 borrower_obj = self._get_user(borrower)
240
241 # Edge Case: Handle database inconsistency
242 if not lender_obj or not borrower_obj:
243 return json.dumps({'error': 'Failed to retrieve user data'})
244
245 return json.dumps({'users': sorted([lender_obj, borrower_obj], key=lambda x: x['name'])})
246
247 # Edge Case: Handle unrecognized URLs
248 return json.dumps({'error': 'Invalid URL'})
249
250 def __del__(self):
251 """
252 Close the database connection when the object is destroyed.
253 """
254 if hasattr(self, 'conn'):
255 self.conn.close()
7256
8 def post(self, url: str, payload: str | None = None) -> str:
9 pass
257# Handled Edge Cases:
258# - Prevent duplicate user creation
259# - Check if user exists
260# - Handle invalid JSON payload
261# - Filter only existing users
262# - Remove None values for non-existent users
263# - Handle unrecognized URLs
264# - Handle missing payload
265# - Handle missing user field
266# - Handle empty user name
267# - Validate required fields
268# - Validate amount is a positive number
269# - Validate lender and borrower are different
270# - Handle database inconsistency
Test NameStatus
test_borrower_has_negative_balance
Fail
test_lender_has_negative_balance
Fail
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_both_users_have_0_balance
Pass
test_get_single_user
Pass
test_no_users
Pass

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