kamau1 commited on
Commit
316a659
·
verified ·
1 Parent(s): b70050e

Upload 10 files

Browse files
Files changed (2) hide show
  1. main.py +245 -22
  2. requirements.txt +4 -1
main.py CHANGED
@@ -1,9 +1,40 @@
1
  import os
 
 
2
  from fastapi import FastAPI, HTTPException, Depends
3
  from fastapi.middleware.cors import CORSMiddleware
4
  import uvicorn
5
  from dotenv import load_dotenv
6
- import libsql_experimental as libsql
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  # Load environment variables
9
  load_dotenv()
@@ -48,25 +79,201 @@ async def startup_db_client():
48
 
49
  if not db_url or not auth_token:
50
  error_msg = "Missing Turso credentials. TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be set."
51
- print(error_msg)
52
  raise Exception(error_msg)
53
 
54
- try:
55
- # Use the URL with embedded auth token
56
- print(f"Connecting to Turso database at: {db_url}")
57
- # Format the URL to include the auth token
58
- if "?" in db_url:
59
- connection_url = f"{db_url}&authToken={auth_token.strip()}"
60
- else:
61
- connection_url = f"{db_url}?authToken={auth_token.strip()}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- app.db_conn = libsql.connect(connection_url)
64
 
65
- # Test connection
66
- result = app.db_conn.execute("SELECT 1").fetchone()
67
- print(f"Connection test successful: {result}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- # Create tables if they don't exist
70
  app.db_conn.execute("""
71
  CREATE TABLE IF NOT EXISTS users (
72
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -105,20 +312,36 @@ async def startup_db_client():
105
  """)
106
 
107
  app.db_conn.commit()
108
- print("Database tables created successfully")
109
-
110
  except Exception as e:
111
- error_msg = f"Failed to connect to Turso database: {str(e)}"
112
- print(error_msg)
113
  raise Exception(error_msg)
114
 
115
  @app.on_event("shutdown")
116
  async def shutdown_db_client():
 
117
  try:
118
- app.db_conn.close()
119
- print("Database connection closed successfully")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  except Exception as e:
121
- print(f"Error closing database connection: {str(e)}")
122
 
123
  # Root endpoint
124
  @app.get("/")
 
1
  import os
2
+ import sys
3
+ import logging
4
  from fastapi import FastAPI, HTTPException, Depends
5
  from fastapi.middleware.cors import CORSMiddleware
6
  import uvicorn
7
  from dotenv import load_dotenv
8
+
9
+ # Configure logging
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
13
+ handlers=[logging.StreamHandler(sys.stdout)]
14
+ )
15
+ logger = logging.getLogger("auth-server")
16
+
17
+ # Import database libraries
18
+ try:
19
+ # First try the recommended libsql-experimental package
20
+ import libsql_experimental as libsql
21
+ logger.info("Successfully imported libsql-experimental package")
22
+ HAS_LIBSQL = True
23
+ LIBSQL_TYPE = "experimental"
24
+ except ImportError:
25
+ try:
26
+ # Then try the libsql-client package as fallback
27
+ import libsql_client
28
+ logger.info("Successfully imported libsql-client package")
29
+ HAS_LIBSQL = True
30
+ LIBSQL_TYPE = "client"
31
+ except ImportError:
32
+ logger.error("Failed to import any libsql package. Please install libsql-experimental==0.0.49")
33
+ logger.error("Falling back to HTTP API method for database access")
34
+ # We'll use requests for HTTP API fallback
35
+ import requests
36
+ HAS_LIBSQL = False
37
+ LIBSQL_TYPE = "http"
38
 
39
  # Load environment variables
40
  load_dotenv()
 
79
 
80
  if not db_url or not auth_token:
81
  error_msg = "Missing Turso credentials. TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be set."
82
+ logger.error(error_msg)
83
  raise Exception(error_msg)
84
 
85
+ # Clean the auth token to remove any problematic characters
86
+ clean_auth_token = auth_token.strip()
87
+
88
+ logger.info(f"Connecting to database at URL: {db_url}")
89
+ logger.info(f"Using libsql type: {LIBSQL_TYPE}")
90
+
91
+ # Initialize database connection
92
+ connected = False
93
+
94
+ # Method 1: Try with libsql-experimental
95
+ if HAS_LIBSQL and LIBSQL_TYPE == "experimental":
96
+ try:
97
+ logger.info("Connecting with libsql-experimental")
98
+
99
+ # Try multiple connection methods
100
+
101
+ # Method 1a: Try with auth_token parameter (works with version 0.0.49)
102
+ try:
103
+ logger.info("Trying connection with auth_token parameter")
104
+ app.db_conn = libsql.connect(db_url, auth_token=clean_auth_token)
105
+
106
+ # Test connection
107
+ result = app.db_conn.execute("SELECT 1").fetchone()
108
+ logger.info(f"Connection successful with auth_token parameter: {result}")
109
+ connected = True
110
+ app.db_type = "libsql-experimental"
111
+ except Exception as e:
112
+ logger.warning(f"Connection with auth_token parameter failed: {str(e)}")
113
+
114
+ # Method 1b: Try with auth token in URL (works with other versions)
115
+ if not connected:
116
+ try:
117
+ logger.info("Trying connection with auth token in URL")
118
+
119
+ # Format the URL to include the auth token
120
+ if "?" in db_url:
121
+ connection_url = f"{db_url}&authToken={clean_auth_token}"
122
+ else:
123
+ connection_url = f"{db_url}?authToken={clean_auth_token}"
124
+
125
+ logger.info(f"Using connection URL: {connection_url}")
126
+
127
+ # Use the direct URL connection method with auth token in URL
128
+ app.db_conn = libsql.connect(connection_url)
129
+
130
+ # Test connection
131
+ result = app.db_conn.execute("SELECT 1").fetchone()
132
+ logger.info(f"Connection successful with auth token in URL: {result}")
133
+ connected = True
134
+ app.db_type = "libsql-experimental"
135
+ except Exception as e:
136
+ logger.error(f"Connection with auth token in URL failed: {str(e)}")
137
+ except Exception as e:
138
+ logger.error(f"All libsql-experimental connection methods failed: {str(e)}")
139
+
140
+ # Method 2: Try with libsql-client
141
+ if not connected and HAS_LIBSQL and LIBSQL_TYPE == "client":
142
+ try:
143
+ logger.info("Connecting with libsql-client")
144
+
145
+ # Convert URL from libsql:// to https://
146
+ if db_url.startswith("libsql://"):
147
+ http_url = db_url.replace("libsql://", "https://")
148
+ else:
149
+ http_url = db_url
150
+
151
+ logger.info(f"Using URL: {http_url}")
152
+
153
+ # Connect using the client
154
+ app.db_conn = libsql_client.create_client_sync(
155
+ url=http_url,
156
+ auth_token=clean_auth_token
157
+ )
158
+
159
+ # Test connection
160
+ result = app.db_conn.execute("SELECT 1").rows()
161
+ logger.info(f"Connection test successful: {result}")
162
+ connected = True
163
+ app.db_type = "libsql-client"
164
+ except Exception as e:
165
+ logger.error(f"libsql-client connection failed: {str(e)}")
166
+
167
+ # Method 3: Fallback to HTTP API
168
+ if not connected:
169
+ try:
170
+ logger.info("Falling back to HTTP API method")
171
+
172
+ # Convert URL from libsql:// to https://
173
+ if db_url.startswith("libsql://"):
174
+ http_url = db_url.replace("libsql://", "https://")
175
+ else:
176
+ http_url = db_url
177
 
178
+ logger.info(f"Using HTTP URL: {http_url}")
179
 
180
+ # Create a simple HTTP API client class
181
+ class TursoHttpClient:
182
+ def __init__(self, url, auth_token):
183
+ self.url = url
184
+ self.auth_token = auth_token
185
+ self.headers = {
186
+ "Authorization": f"Bearer {auth_token}",
187
+ "Content-Type": "application/json"
188
+ }
189
+
190
+ def execute(self, query, params=None):
191
+ # Format the request according to the v2/pipeline specification
192
+ requests_data = []
193
+
194
+ # Prepare the statement
195
+ stmt = {"sql": query}
196
+
197
+ # Add parameters if provided
198
+ if params:
199
+ # Convert parameters to the expected format
200
+ args = []
201
+ for param in params:
202
+ if param is None:
203
+ args.append({"type": "null", "value": None})
204
+ elif isinstance(param, int):
205
+ args.append({"type": "integer", "value": str(param)})
206
+ elif isinstance(param, float):
207
+ args.append({"type": "float", "value": str(param)})
208
+ else:
209
+ args.append({"type": "text", "value": str(param)})
210
+
211
+ stmt["args"] = args
212
+
213
+ requests_data.append({"type": "execute", "stmt": stmt})
214
+
215
+ # Always close the connection at the end
216
+ requests_data.append({"type": "close"})
217
+
218
+ # Prepare the final request payload
219
+ data = {"requests": requests_data}
220
+
221
+ # Use the v2/pipeline endpoint
222
+ pipeline_url = f"{self.url}/v2/pipeline"
223
+ response = requests.post(pipeline_url, headers=self.headers, json=data)
224
+ response.raise_for_status()
225
+ result = response.json()
226
+
227
+ # Process the response
228
+ if "results" in result and len(result["results"]) > 0:
229
+ # Return a cursor-like object
230
+ return TursoHttpCursor(result["results"][0])
231
+
232
+ return TursoHttpCursor(None)
233
+
234
+ def commit(self):
235
+ # HTTP API is stateless, no need to commit
236
+ pass
237
+
238
+ def close(self):
239
+ # HTTP API is stateless, no need to close
240
+ pass
241
+
242
+ # Create a cursor-like class for HTTP API
243
+ class TursoHttpCursor:
244
+ def __init__(self, result):
245
+ self.result = result
246
+
247
+ def fetchone(self):
248
+ if self.result and "rows" in self.result and len(self.result["rows"]) > 0:
249
+ return self.result["rows"][0]["values"]
250
+ return None
251
+
252
+ def fetchall(self):
253
+ if self.result and "rows" in self.result:
254
+ return [row["values"] for row in self.result["rows"]]
255
+ return []
256
+
257
+ # Create the HTTP API client
258
+ app.db_conn = TursoHttpClient(http_url, clean_auth_token)
259
+
260
+ # Test connection
261
+ result = app.db_conn.execute("SELECT 1").fetchone()
262
+ logger.info(f"HTTP API connection test successful: {result}")
263
+ connected = True
264
+ app.db_type = "http-api"
265
+ except Exception as e:
266
+ logger.error(f"HTTP API connection failed: {str(e)}")
267
+
268
+ if not connected:
269
+ error_msg = "All database connection methods failed. Please check your credentials and try again."
270
+ logger.error(error_msg)
271
+ raise Exception(error_msg)
272
+
273
+ # Create tables if they don't exist
274
+ try:
275
+ logger.info("Creating database tables")
276
 
 
277
  app.db_conn.execute("""
278
  CREATE TABLE IF NOT EXISTS users (
279
  id INTEGER PRIMARY KEY AUTOINCREMENT,
 
312
  """)
313
 
314
  app.db_conn.commit()
315
+ logger.info("Database tables created successfully")
 
316
  except Exception as e:
317
+ error_msg = f"Failed to create database tables: {str(e)}"
318
+ logger.error(error_msg)
319
  raise Exception(error_msg)
320
 
321
  @app.on_event("shutdown")
322
  async def shutdown_db_client():
323
+ logger.info("Shutting down database connection")
324
  try:
325
+ # Close connection based on the type
326
+ if hasattr(app, 'db_type'):
327
+ if app.db_type == "libsql-experimental":
328
+ try:
329
+ app.db_conn.close()
330
+ logger.info("libsql-experimental connection closed successfully")
331
+ except Exception as e:
332
+ logger.warning(f"Error closing libsql-experimental connection: {str(e)}")
333
+ elif app.db_type == "libsql-client":
334
+ # libsql-client doesn't have a close method
335
+ logger.info("No close method needed for libsql-client")
336
+ elif app.db_type == "http-api":
337
+ # HTTP API is stateless, no need to close
338
+ logger.info("No close method needed for HTTP API")
339
+ else:
340
+ logger.warning(f"Unknown database type: {app.db_type}")
341
+ else:
342
+ logger.warning("No database connection to close")
343
  except Exception as e:
344
+ logger.error(f"Error closing database connection: {str(e)}")
345
 
346
  # Root endpoint
347
  @app.get("/")
requirements.txt CHANGED
@@ -5,6 +5,9 @@ python-jose==3.3.0
5
  passlib==1.7.4
6
  pydantic==2.4.2
7
  httpx==0.25.0
8
- libsql-experimental==0.0.6
 
 
 
9
  python-multipart==0.0.6
10
  email-validator==2.0.0
 
5
  passlib==1.7.4
6
  pydantic==2.4.2
7
  httpx==0.25.0
8
+ # Use version 0.0.49 of libsql-experimental which supports the auth_token parameter
9
+ libsql-experimental==0.0.49
10
+ # For HTTP API fallback
11
+ requests==2.31.0
12
  python-multipart==0.0.6
13
  email-validator==2.0.0