47
47
APP_ROOT = Path (os .path .join (Path (__file__ ).parent )).absolute ()
48
48
49
49
import os
50
+ import tempfile
50
51
51
52
app = Flask (__name__ , static_url_path = '' , static_folder = os .path .join (APP_ROOT , "dist" ))
52
53
app .secret_key = secrets .token_hex (16 ) # Generate a random secret key for sessions
@@ -555,15 +556,27 @@ def request_code_expl():
555
556
expl = ""
556
557
return expl
557
558
558
- @app .route ('/api/get-session-id' , methods = ['GET' ])
559
+ @app .route ('/api/get-session-id' , methods = ['GET' , 'POST' ])
559
560
def get_session_id ():
560
561
"""Endpoint to get or confirm a session ID from the client"""
562
+ # if it is a POST request, we expect a session_id in the body
563
+ # if it is a GET request, we do not expect a session_id in the query params
561
564
565
+ current_session_id = None
566
+ if request .is_json :
567
+ content = request .get_json ()
568
+ current_session_id = content .get ("session_id" , None )
569
+
562
570
# Create session if it doesn't exist
563
- if 'session_id' not in session :
564
- session ['session_id' ] = secrets .token_hex (16 )
565
- session .permanent = True
566
- logger .info (f"Created new session: { session ['session_id' ]} " )
571
+ if current_session_id is None :
572
+ if 'session_id' not in session :
573
+ session ['session_id' ] = secrets .token_hex (16 )
574
+ session .permanent = True
575
+ logger .info (f"Created new session: { session ['session_id' ]} " )
576
+ else :
577
+ # override the session_id
578
+ session ['session_id' ] = current_session_id
579
+ session .permanent = True
567
580
568
581
return flask .jsonify ({
569
582
"status" : "ok" ,
@@ -575,15 +588,9 @@ def get_session_id():
575
588
def get_app_config ():
576
589
"""Provide frontend configuration settings from environment variables"""
577
590
578
- # Create session if it doesn't exist
579
- if 'session_id' not in session :
580
- session ['session_id' ] = secrets .token_hex (16 )
581
- session .permanent = True
582
- logger .info (f"Created new session: { session ['session_id' ]} " )
583
-
584
591
config = {
585
592
"SHOW_KEYS_ENABLED" : os .getenv ("SHOW_KEYS_ENABLED" , "true" ).lower () == "true" ,
586
- "SESSION_ID" : session [ 'session_id' ]
593
+ "SESSION_ID" : session . get ( 'session_id' , None )
587
594
}
588
595
return flask .jsonify (config )
589
596
@@ -603,19 +610,32 @@ def list_tables():
603
610
# Get row count
604
611
row_count = db .execute (f"SELECT COUNT(*) FROM { table_name } " ).fetchone ()[0 ]
605
612
sample_rows = db .execute (f"SELECT * FROM { table_name } LIMIT 1000" ).fetchall ()
613
+
614
+ # Check if this is a view or a table
615
+ is_view = False
616
+ try :
617
+ # In most SQL databases, views are listed in a system table
618
+ # For DuckDB, we can check if it's a view by querying the system tables
619
+ view_check = db .execute (f"SELECT * FROM duckdb_views() WHERE view_name = '{ table_name } '" ).fetchone ()
620
+ is_view = view_check is not None
621
+ except Exception :
622
+ # If the query fails, assume it's a regular table
623
+ pass
606
624
607
625
result .append ({
608
626
"name" : table_name ,
609
627
"columns" : [{"name" : col [0 ], "type" : col [1 ]} for col in columns ],
610
628
"row_count" : row_count ,
611
- "sample_rows" : [dict (zip ([col [0 ] for col in columns ], row )) for row in sample_rows ]
629
+ "sample_rows" : [dict (zip ([col [0 ] for col in columns ], row )) for row in sample_rows ],
630
+ "is_view" : is_view ,
612
631
})
613
632
614
633
return jsonify ({
615
634
"status" : "success" ,
616
635
"tables" : result
617
636
})
618
637
except Exception as e :
638
+ print (e )
619
639
return jsonify ({
620
640
"status" : "error" ,
621
641
"message" : str (e )
@@ -821,6 +841,166 @@ def drop_table():
821
841
}), 500
822
842
823
843
844
+ @app .route ('/api/tables/upload-db-file' , methods = ['POST' ])
845
+ def upload_db_file ():
846
+ """Upload a db file"""
847
+ try :
848
+ if 'file' not in request .files :
849
+ return jsonify ({"status" : "error" , "message" : "No file provided" }), 400
850
+
851
+ file = request .files ['file' ]
852
+ if not file .filename .endswith ('.db' ):
853
+ return jsonify ({"status" : "error" , "message" : "Invalid file format. Only .db files are supported" }), 400
854
+
855
+ # Get the session ID
856
+ if 'session_id' not in session :
857
+ return jsonify ({"status" : "error" , "message" : "No session ID found" }), 400
858
+
859
+ session_id = session ['session_id' ]
860
+
861
+ # Create temp directory if it doesn't exist
862
+ temp_dir = os .path .join (tempfile .gettempdir ())
863
+ os .makedirs (temp_dir , exist_ok = True )
864
+
865
+ # Save the file temporarily to verify it
866
+ temp_db_path = os .path .join (temp_dir , f"temp_{ session_id } .db" )
867
+ file .save (temp_db_path )
868
+
869
+ # Verify if it's a valid DuckDB file
870
+ try :
871
+ import duckdb
872
+ # Try to connect to the database
873
+ conn = duckdb .connect (temp_db_path , read_only = True )
874
+ # Try a simple query to verify it's a valid database
875
+ conn .execute ("SELECT 1" ).fetchall ()
876
+ conn .close ()
877
+
878
+ # If we get here, the file is valid - move it to final location
879
+ db_file_path = os .path .join (temp_dir , f"df_{ session_id } .db" )
880
+ os .replace (temp_db_path , db_file_path )
881
+
882
+ # Update the db_manager's file mapping
883
+ db_manager ._db_files [session_id ] = db_file_path
884
+
885
+ except Exception as db_error :
886
+ # Clean up temp file
887
+ if os .path .exists (temp_db_path ):
888
+ os .remove (temp_db_path )
889
+ return jsonify ({
890
+ "status" : "error" ,
891
+ "message" : f"Invalid DuckDB database file: { str (db_error )} "
892
+ }), 400
893
+
894
+ return jsonify ({
895
+ "status" : "success" ,
896
+ "message" : "Database file uploaded successfully" ,
897
+ "session_id" : session_id
898
+ })
899
+
900
+ except Exception as e :
901
+ logger .error (f"Error uploading database file: { str (e )} " )
902
+ return jsonify ({
903
+ "status" : "error" ,
904
+ "message" : f"Failed to upload database file: { str (e )} "
905
+ }), 500
906
+
907
+ @app .route ('/api/tables/download-db-file' , methods = ['GET' ])
908
+ def download_db_file ():
909
+ """Download the db file for a session"""
910
+ try :
911
+ # Check if session exists
912
+ if 'session_id' not in session :
913
+ return jsonify ({
914
+ "status" : "error" ,
915
+ "message" : "No session ID found"
916
+ }), 400
917
+
918
+ session_id = session ['session_id' ]
919
+
920
+ # Get the database file path from db_manager
921
+ if session_id not in db_manager ._db_files :
922
+ return jsonify ({
923
+ "status" : "error" ,
924
+ "message" : "No database file found for this session"
925
+ }), 404
926
+
927
+ db_file_path = db_manager ._db_files [session_id ]
928
+
929
+ # Check if file exists
930
+ if not os .path .exists (db_file_path ):
931
+ return jsonify ({
932
+ "status" : "error" ,
933
+ "message" : "Database file not found"
934
+ }), 404
935
+
936
+ # Generate a filename for download
937
+ download_name = f"data_formulator_{ session_id } .db"
938
+
939
+ # Return the file as an attachment
940
+ return send_from_directory (
941
+ os .path .dirname (db_file_path ),
942
+ os .path .basename (db_file_path ),
943
+ as_attachment = True ,
944
+ download_name = download_name ,
945
+ mimetype = 'application/x-sqlite3'
946
+ )
947
+
948
+ except Exception as e :
949
+ logger .error (f"Error downloading database file: { str (e )} " )
950
+ return jsonify ({
951
+ "status" : "error" ,
952
+ "message" : f"Failed to download database file: { str (e )} "
953
+ }), 500
954
+
955
+
956
+ @app .route ('/api/tables/reset-db-file' , methods = ['POST' ])
957
+ def reset_db_file ():
958
+ """Reset the db file for a session"""
959
+ try :
960
+ if 'session_id' not in session :
961
+ return jsonify ({
962
+ "status" : "error" ,
963
+ "message" : "No session ID found"
964
+ }), 400
965
+
966
+ session_id = session ['session_id' ]
967
+
968
+ print (f"session_id: { session_id } " )
969
+
970
+ # First check if there's a reference in db_manager
971
+ if session_id in db_manager ._db_files :
972
+ db_file_path = db_manager ._db_files [session_id ]
973
+
974
+ # Remove the file if it exists
975
+ if db_file_path and os .path .exists (db_file_path ):
976
+ os .remove (db_file_path )
977
+
978
+ # Clear the reference
979
+ db_manager ._db_files [session_id ] = None
980
+
981
+ # Also check for any temporary files
982
+ temp_db_path = os .path .join (tempfile .gettempdir (), f"temp_{ session_id } .db" )
983
+ if os .path .exists (temp_db_path ):
984
+ os .remove (temp_db_path )
985
+
986
+ # Check for the main db file
987
+ main_db_path = os .path .join (tempfile .gettempdir (), f"df_{ session_id } .db" )
988
+ if os .path .exists (main_db_path ):
989
+ os .remove (main_db_path )
990
+
991
+ return jsonify ({
992
+ "status" : "success" ,
993
+ "message" : "Database file reset successfully"
994
+ })
995
+
996
+ except Exception as e :
997
+ logger .error (f"Error resetting database file: { str (e )} " )
998
+ return jsonify ({
999
+ "status" : "error" ,
1000
+ "message" : f"Failed to reset database file: { str (e )} "
1001
+ }), 500
1002
+
1003
+
824
1004
@app .route ('/api/tables/query' , methods = ['POST' ])
825
1005
def query_table ():
826
1006
"""Execute a query on a table"""
0 commit comments