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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -52
app.py CHANGED
@@ -1,5 +1,8 @@
1
  import streamlit as st
2
  from pathlib import Path # Ensure Path is imported if used for settings.LOGO_PATH
 
 
 
3
 
4
  from config.settings import settings
5
  from models import create_db_and_tables, get_session_context, User, ChatMessage, ChatSession
@@ -28,10 +31,10 @@ init_db()
28
 
29
  # --- Session State Initialization ---
30
  if 'authenticated_user' not in st.session_state:
31
- st.session_state.authenticated_user = None # Stores User object (ideally one that is "live" or has loaded attributes)
32
  if 'current_chat_session_id' not in st.session_state:
33
  st.session_state.current_chat_session_id = None
34
- if 'chat_messages' not in st.session_state: # For the current active chat
35
  st.session_state.chat_messages = []
36
 
37
 
@@ -39,50 +42,41 @@ if 'chat_messages' not in st.session_state: # For the current active chat
39
  def display_login_form():
40
  with st.form("login_form"):
41
  st.subheader("Login")
42
- # Use unique keys for inputs if there's any chance of collision or for better state tracking
43
  username_input = st.text_input("Username", key="login_username_input")
44
  password_input = st.text_input("Password", type="password", key="login_password_input")
45
  submit_button = st.form_submit_button("Login")
46
 
47
  if submit_button:
48
- # IMPORTANT: 'authenticate_user' should return a User object where essential attributes
49
- # like 'id' and 'username' are already loaded (not expired).
50
- # If 'authenticate_user' involves a commit (e.g., updating last_login) and
51
- # session.expire_on_commit=True (default), it should call db.refresh(user_object)
52
- # before closing its session and returning the user object.
53
  user_from_auth = authenticate_user(username_input, password_input)
54
 
55
  if user_from_auth:
56
- # FIX 1: For the success message, use the 'username_input' from the form directly.
57
- # This avoids accessing 'user_from_auth.username' which might be on a detached/expired instance.
58
  st.success(f"Welcome back, {username_input}!")
59
  app_logger.info(f"User {username_input} logged in successfully.")
60
 
61
  try:
62
  with get_session_context() as db_session:
63
- # FIX 2: Re-fetch or merge the user into the current db_session to ensure it's "live"
64
- # This assumes 'user_from_auth.id' is accessible (i.e., was loaded by authenticate_user).
65
- # If 'user_from_auth.id' itself is expired, 'authenticate_user' MUST be fixed.
66
- try:
67
- user_id = user_from_auth.id
68
- except AttributeError:
69
- st.error("Authentication error: User data is incomplete. Please try again.")
70
- app_logger.error(f"User object from authenticate_user for '{username_input}' lacks 'id' attribute or it's inaccessible.")
71
- st.session_state.authenticated_user = None # Clear potentially bad state
72
- st.rerun()
73
- return
74
 
75
- live_user = db_session.get(User, user_id)
76
  if not live_user:
77
- st.error("Critical user session error. Please log out and log in again.")
78
- app_logger.error(f"Failed to re-fetch user with id {user_id} (username: {username_input}) in new session.")
79
- st.session_state.authenticated_user = None # Clear broken state
80
- st.rerun()
 
 
 
81
  return
82
 
83
- # Store the "live" user object (attached to db_session or with loaded attributes)
84
- # in session_state. This 'live_user' will be used by the sidebar.
85
  st.session_state.authenticated_user = live_user
 
86
 
87
  # Now use 'live_user' for creating the chat session
88
  new_chat_session = ChatSession(user_id=live_user.id, title=f"Session for {live_user.username}")
@@ -91,11 +85,27 @@ def display_login_form():
91
  db_session.refresh(new_chat_session) # Refresh to get DB-generated values for new_chat_session
92
  st.session_state.current_chat_session_id = new_chat_session.id
93
  st.session_state.chat_messages = [] # Clear previous messages
 
94
  st.rerun() # Rerun to reflect login state and navigate
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  except Exception as e:
96
  app_logger.error(f"Error creating chat session for user {username_input}: {e}", exc_info=True)
97
  st.error(f"Could not start a new session: {e}")
98
- # Optionally, clear authenticated_user if session creation is critical
99
  # st.session_state.authenticated_user = None
100
  # st.rerun()
101
  else:
@@ -123,19 +133,13 @@ def display_signup_form():
123
  email=new_email if new_email else None
124
  )
125
  # 'create_user_in_db' should return a User object with attributes loaded
126
- # (e.g., by calling session.refresh(user) before its session closes).
127
  user = create_user_in_db(user_data)
128
  if user:
129
- # Use 'new_username' (from form input) for the message to avoid DetachedInstanceError
130
  st.success(f"Account created for {new_username}. Please log in.")
131
  app_logger.info(f"Account created for {new_username}.")
132
- # Optionally log them in directly:
133
- # To do this safely, 'user' returned by create_user_in_db must be "live"
134
- # or its attributes fully loaded. You might need to re-fetch it in a new session here
135
- # if you intend to store it in st.session_state.authenticated_user.
136
- # For now, redirecting to login is simpler.
137
  else:
138
- st.error("Username might already be taken or an error occurred during signup.")
139
  app_logger.warning(f"Signup failed for username: {new_username}")
140
 
141
  # --- Main App Logic ---
@@ -149,19 +153,15 @@ if not st.session_state.authenticated_user:
149
  with signup_tab:
150
  display_signup_form()
151
  else:
152
- # If authenticated
153
  with st.sidebar:
154
- # Accessing st.session_state.authenticated_user.username.
155
- # This relies on the object stored in authenticated_user having its username loaded.
156
- # The `display_login_form` now stores the 'live_user' which should have this.
157
  try:
 
 
158
  st.markdown(f"### Welcome, {st.session_state.authenticated_user.username}!")
159
- except AttributeError as e: # Fallback if username somehow isn't accessible
160
  st.markdown(f"### Welcome!")
161
- app_logger.error(f"Could not access username for authenticated_user in sidebar: {e}. User object: {st.session_state.authenticated_user}")
162
 
163
-
164
- # Example for conditional logo display
165
  logo_path_str = getattr(settings, "LOGO_PATH", None)
166
  if logo_path_str:
167
  logo_path = Path(logo_path_str)
@@ -172,24 +172,26 @@ else:
172
  app_logger.warning(f"Could not load logo from {logo_path_str}: {e}")
173
  else:
174
  app_logger.warning(f"Logo path specified but does not exist: {logo_path_str}")
175
- elif settings.APP_TITLE: # Fallback to title if no logo
176
  st.markdown(f"#### {settings.APP_TITLE}")
177
 
178
  st.markdown("---")
179
 
180
  if st.button("Logout"):
181
- if hasattr(st.session_state.authenticated_user, 'username'):
182
- app_logger.info(f"User {st.session_state.authenticated_user.username} logging out.")
183
- else:
184
- app_logger.info("User logging out (username not accessible from session state object).")
 
 
 
185
  st.session_state.authenticated_user = None
186
  st.session_state.current_chat_session_id = None
187
  st.session_state.chat_messages = []
188
  st.success("You have been logged out.")
189
  st.rerun()
190
 
191
- # Main area content (could be your "Home" page if no `pages/1_Home.py`)
192
- st.sidebar.success("Select a page from the navigation.") # More generic message
193
  st.header(f"Dashboard - {settings.APP_TITLE}")
194
  st.markdown("Navigate using the sidebar to consult with the AI or view your reports.")
195
  st.markdown("---")
 
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
 
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
 
 
42
  def display_login_form():
43
  with st.form("login_form"):
44
  st.subheader("Login")
 
45
  username_input = st.text_input("Username", key="login_username_input")
46
  password_input = st.text_input("Password", type="password", key="login_password_input")
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}")
 
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:
 
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 ---
 
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:
167
  logo_path = Path(logo_path_str)
 
172
  app_logger.warning(f"Could not load logo from {logo_path_str}: {e}")
173
  else:
174
  app_logger.warning(f"Logo path specified but does not exist: {logo_path_str}")
175
+ elif settings.APP_TITLE:
176
  st.markdown(f"#### {settings.APP_TITLE}")
177
 
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.")
192
  st.rerun()
193
 
194
+ st.sidebar.success("Select a page from the navigation.")
 
195
  st.header(f"Dashboard - {settings.APP_TITLE}")
196
  st.markdown("Navigate using the sidebar to consult with the AI or view your reports.")
197
  st.markdown("---")