mgbam commited on
Commit
79d193c
·
verified ·
1 Parent(s): c21fd02

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -77
app.py CHANGED
@@ -1,16 +1,12 @@
1
  import streamlit as st
2
- from pathlib import Path # Ensure Path is imported if used for settings.LOGO_PATH
3
- # Make sure sqlalchemy.orm.exc is available if you want to catch DetachedInstanceError specifically
4
- from sqlalchemy.orm.exc import DetachedInstanceError as SQLAlchemyDetachedInstanceError
5
-
6
 
7
  from config.settings import settings
8
  from models import create_db_and_tables, get_session_context, User, ChatMessage, ChatSession
9
- from models.user import UserCreate # For type hinting
10
  from services.auth import create_user_in_db, authenticate_user
11
  from services.logger import app_logger
12
- # Agent will be initialized and used in specific pages like Consult.py
13
- # from agent import get_agent_executor # Import in pages where needed
14
 
15
  # --- Page Configuration ---
16
  st.set_page_config(
@@ -21,7 +17,7 @@ st.set_page_config(
21
  )
22
 
23
  # --- Database Initialization ---
24
- @st.cache_resource # Ensure this runs only once
25
  def init_db():
26
  app_logger.info("Initializing database and tables...")
27
  create_db_and_tables()
@@ -29,15 +25,16 @@ def init_db():
29
 
30
  init_db()
31
 
32
- # --- Session State Initialization ---
33
- if 'authenticated_user' not in st.session_state:
34
- st.session_state.authenticated_user = None
 
 
35
  if 'current_chat_session_id' not in st.session_state:
36
  st.session_state.current_chat_session_id = None
37
  if 'chat_messages' not in st.session_state:
38
  st.session_state.chat_messages = []
39
 
40
-
41
  # --- Authentication Logic ---
42
  def display_login_form():
43
  with st.form("login_form"):
@@ -47,67 +44,57 @@ def display_login_form():
47
  submit_button = st.form_submit_button("Login")
48
 
49
  if submit_button:
50
- user_from_auth = authenticate_user(username_input, password_input)
 
 
 
51
 
52
- if user_from_auth:
53
- # Success message uses the username from form input, which is safe.
54
- st.success(f"Welcome back, {username_input}!")
55
- app_logger.info(f"User {username_input} logged in successfully.")
56
 
57
  try:
58
  with get_session_context() as db_session:
59
- # WORKAROUND for DetachedInstanceError from user_from_auth.id:
60
- # Re-fetch the user by username within this new session.
61
- # This ensures 'live_user' is attached and its attributes are loaded.
62
- # The PREFERRED FIX is in 'services.auth.authenticate_user' to return
63
- # a User object with its 'id' (and other essential attributes) already loaded/refreshed.
64
- app_logger.info(f"Attempting to fetch live user details for '{username_input}' in new session.")
65
  live_user = db_session.query(User).filter(User.username == username_input).first()
66
 
67
  if not live_user:
68
- # This case means authentication (somehow) passed,
69
- # but the user is not findable by username immediately after.
70
- # This indicates a more serious data consistency issue or flaw in authenticate_user.
71
- st.error("Authentication inconsistency. User details not found after successful login. Please contact support.")
72
- app_logger.error(f"CRITICAL: User '{username_input}' authenticated but then not found by username in new session context.")
73
- st.session_state.authenticated_user = None # Clear any partial auth state
74
- st.rerun() # Rerun to show login page again
75
  return
76
 
77
- # Store the "live" user object (attached to db_session and attributes loaded)
78
- st.session_state.authenticated_user = live_user
79
- app_logger.info(f"Live user '{live_user.username}' (ID: {live_user.id}) fetched and stored in session_state.")
 
 
80
 
81
- # Now use 'live_user' for creating the chat session
82
  new_chat_session = ChatSession(user_id=live_user.id, title=f"Session for {live_user.username}")
83
  db_session.add(new_chat_session)
84
- db_session.commit()
85
- db_session.refresh(new_chat_session) # Refresh to get DB-generated values for new_chat_session
 
 
86
  st.session_state.current_chat_session_id = new_chat_session.id
87
- st.session_state.chat_messages = [] # Clear previous messages
88
  app_logger.info(f"New chat session (ID: {new_chat_session.id}) created for user {live_user.username}.")
 
89
  st.rerun() # Rerun to reflect login state and navigate
90
 
91
- except SQLAlchemyDetachedInstanceError as detached_error:
92
- # This specific catch might be less likely if the above workaround (re-fetching live_user) is effective.
93
- # However, it's good for diagnostics if the issue persists due to other detached instances.
94
- app_logger.error(
95
- f"DetachedInstanceError occurred during session setup for user '{username_input}'. "
96
- f"This typically means an object from 'authenticate_user' or 'create_user_in_db' "
97
- f"had expired attributes. Error: {detached_error}",
98
- exc_info=True
99
- )
100
- st.error(
101
- "Could not start a new session due to an object state issue. "
102
- "Please ensure user services return objects with loaded attributes."
103
- )
104
- st.session_state.authenticated_user = None
105
  except Exception as e:
106
- app_logger.error(f"Error creating chat session for user {username_input}: {e}", exc_info=True)
107
- st.error(f"Could not start a new session: {e}")
108
- # Optionally, clear authenticated_user if session creation is critical and failed
109
- # st.session_state.authenticated_user = None
110
- # st.rerun()
111
  else:
112
  st.error("Invalid username or password.")
113
  app_logger.warning(f"Failed login attempt for username: {username_input}")
@@ -132,18 +119,16 @@ def display_signup_form():
132
  password=new_password,
133
  email=new_email if new_email else None
134
  )
135
- # 'create_user_in_db' should return a User object with attributes loaded
136
- # (e.g., by calling session.refresh(user) before its session closes if a commit occurred).
137
- user = create_user_in_db(user_data)
138
  if user:
139
- st.success(f"Account created for {new_username}. Please log in.")
140
  app_logger.info(f"Account created for {new_username}.")
141
  else:
142
  st.error("Username might already be taken or another error occurred during signup.")
143
  app_logger.warning(f"Signup failed for username: {new_username}")
144
 
145
- # --- Main App Logic ---
146
- if not st.session_state.authenticated_user:
147
  st.title(f"Welcome to {settings.APP_TITLE}")
148
  st.markdown("Your AI-powered partner for advanced healthcare insights.")
149
 
@@ -153,14 +138,11 @@ if not st.session_state.authenticated_user:
153
  with signup_tab:
154
  display_signup_form()
155
  else:
 
156
  with st.sidebar:
157
- try:
158
- # This relies on st.session_state.authenticated_user being a "live" object
159
- # with 'username' attribute accessible. The login logic now aims to ensure this.
160
- st.markdown(f"### Welcome, {st.session_state.authenticated_user.username}!")
161
- except AttributeError as e:
162
- st.markdown(f"### Welcome!")
163
- app_logger.error(f"Could not access username for authenticated_user in sidebar: {e}. User object: {st.session_state.authenticated_user}", exc_info=True)
164
 
165
  logo_path_str = getattr(settings, "LOGO_PATH", None)
166
  if logo_path_str:
@@ -178,14 +160,11 @@ else:
178
  st.markdown("---")
179
 
180
  if st.button("Logout"):
181
- username_on_logout = "UnknownUser"
182
- try:
183
- username_on_logout = st.session_state.authenticated_user.username
184
- except Exception:
185
- app_logger.warning("Could not get username during logout from session state object.")
186
- app_logger.info(f"User {username_on_logout} logging out.")
187
-
188
- st.session_state.authenticated_user = None
189
  st.session_state.current_chat_session_id = None
190
  st.session_state.chat_messages = []
191
  st.success("You have been logged out.")
 
1
  import streamlit as st
2
+ from pathlib import Path
3
+ from sqlalchemy.orm.exc import DetachedInstanceError as SQLAlchemyDetachedInstanceError # For specific error catching if needed
 
 
4
 
5
  from config.settings import settings
6
  from models import create_db_and_tables, get_session_context, User, ChatMessage, ChatSession
7
+ from models.user import UserCreate
8
  from services.auth import create_user_in_db, authenticate_user
9
  from services.logger import app_logger
 
 
10
 
11
  # --- Page Configuration ---
12
  st.set_page_config(
 
17
  )
18
 
19
  # --- Database Initialization ---
20
+ @st.cache_resource
21
  def init_db():
22
  app_logger.info("Initializing database and tables...")
23
  create_db_and_tables()
 
25
 
26
  init_db()
27
 
28
+ # --- Session State Initialization (Using new keys) ---
29
+ if 'authenticated_user_id' not in st.session_state:
30
+ st.session_state.authenticated_user_id = None
31
+ if 'authenticated_username' not in st.session_state:
32
+ st.session_state.authenticated_username = None
33
  if 'current_chat_session_id' not in st.session_state:
34
  st.session_state.current_chat_session_id = None
35
  if 'chat_messages' not in st.session_state:
36
  st.session_state.chat_messages = []
37
 
 
38
  # --- Authentication Logic ---
39
  def display_login_form():
40
  with st.form("login_form"):
 
44
  submit_button = st.form_submit_button("Login")
45
 
46
  if submit_button:
47
+ # authenticate_user should ideally return a User object with at least ID loaded,
48
+ # or be robust enough that its session handling doesn't cause immediate detachment issues
49
+ # if we were to access its attributes directly (though we now avoid that for long-term storage).
50
+ user_object_from_auth = authenticate_user(username_input, password_input)
51
 
52
+ if user_object_from_auth:
53
+ st.success(f"Welcome back, {username_input}!") # Use form input for immediate message
54
+ app_logger.info(f"User {username_input} authenticated successfully.")
 
55
 
56
  try:
57
  with get_session_context() as db_session:
58
+ # Fetch the "live" user to get definite ID and current username,
59
+ # and to ensure it's attached to *this* session for creating the chat session.
60
+ # The primary fix for 'authenticate_user' is still for it to return a usable object,
61
+ # but re-fetching here ensures data for st.session_state is from a controlled point.
 
 
62
  live_user = db_session.query(User).filter(User.username == username_input).first()
63
 
64
  if not live_user:
65
+ st.error("Authentication inconsistency. User details not found after login. Please contact support.")
66
+ app_logger.error(f"CRITICAL: User '{username_input}' authenticated but then not found in DB by username.")
67
+ # Clear any potentially partially set auth state from previous attempts
68
+ st.session_state.authenticated_user_id = None
69
+ st.session_state.authenticated_username = None
70
+ st.rerun()
 
71
  return
72
 
73
+ # IMPORTANT: Store primitive data in st.session_state
74
+ # Do this *before* the commit for new_chat_session, as commit might expire 'live_user' attributes.
75
+ st.session_state.authenticated_user_id = live_user.id
76
+ st.session_state.authenticated_username = live_user.username # Username from DB
77
+ app_logger.info(f"Stored user ID {live_user.id} and username '{live_user.username}' in session state.")
78
 
79
+ # Now create the chat session using the live_user's ID
80
  new_chat_session = ChatSession(user_id=live_user.id, title=f"Session for {live_user.username}")
81
  db_session.add(new_chat_session)
82
+ db_session.commit() # This commit is for new_chat_session
83
+ # After commit, live_user attributes might be expired if expire_on_commit=True.
84
+ # But we've already stored the primitives we need.
85
+ db_session.refresh(new_chat_session) # Get ID for new_chat_session
86
  st.session_state.current_chat_session_id = new_chat_session.id
87
+ st.session_state.chat_messages = []
88
  app_logger.info(f"New chat session (ID: {new_chat_session.id}) created for user {live_user.username}.")
89
+
90
  st.rerun() # Rerun to reflect login state and navigate
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  except Exception as e:
93
+ app_logger.error(f"Error during post-login session setup for user {username_input}: {e}", exc_info=True)
94
+ st.error(f"Could not complete login process: {e}")
95
+ # Clear auth state on error
96
+ st.session_state.authenticated_user_id = None
97
+ st.session_state.authenticated_username = None
98
  else:
99
  st.error("Invalid username or password.")
100
  app_logger.warning(f"Failed login attempt for username: {username_input}")
 
119
  password=new_password,
120
  email=new_email if new_email else None
121
  )
122
+ user = create_user_in_db(user_data) # Should handle its own session and return User or None
 
 
123
  if user:
124
+ st.success(f"Account created for {new_username}. Please log in.") # Use form input
125
  app_logger.info(f"Account created for {new_username}.")
126
  else:
127
  st.error("Username might already be taken or another error occurred during signup.")
128
  app_logger.warning(f"Signup failed for username: {new_username}")
129
 
130
+ # --- Main App Logic (Checks for authenticated_user_id) ---
131
+ if not st.session_state.get("authenticated_user_id"): # Check if user_id is set
132
  st.title(f"Welcome to {settings.APP_TITLE}")
133
  st.markdown("Your AI-powered partner for advanced healthcare insights.")
134
 
 
138
  with signup_tab:
139
  display_signup_form()
140
  else:
141
+ # User is authenticated (authenticated_user_id is present)
142
  with st.sidebar:
143
+ # Use the stored primitive username
144
+ username_for_display = st.session_state.get("authenticated_username", "User")
145
+ st.markdown(f"### Welcome, {username_for_display}!")
 
 
 
 
146
 
147
  logo_path_str = getattr(settings, "LOGO_PATH", None)
148
  if logo_path_str:
 
160
  st.markdown("---")
161
 
162
  if st.button("Logout"):
163
+ logged_out_username = st.session_state.get("authenticated_username", "UnknownUser")
164
+ app_logger.info(f"User {logged_out_username} logging out.")
165
+ # Clear authentication state
166
+ st.session_state.authenticated_user_id = None
167
+ st.session_state.authenticated_username = None
 
 
 
168
  st.session_state.current_chat_session_id = None
169
  st.session_state.chat_messages = []
170
  st.success("You have been logged out.")