Spaces:
Running
Running
Upload 10 files
Browse files- main.py +245 -22
- 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
52 |
raise Exception(error_msg)
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
-
|
64 |
|
65 |
-
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
109 |
-
|
110 |
except Exception as e:
|
111 |
-
error_msg = f"Failed to
|
112 |
-
|
113 |
raise Exception(error_msg)
|
114 |
|
115 |
@app.on_event("shutdown")
|
116 |
async def shutdown_db_client():
|
|
|
117 |
try:
|
118 |
-
|
119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
except Exception as e:
|
121 |
-
|
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 |
-
|
|
|
|
|
|
|
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
|