-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Description
Checklist
- I have searched the existing issues for similar issues.
- I added a very descriptive title to this issue.
- I have provided sufficient information below to help reproduce this issue.
Summary
Issue Description
When using Streamlit with SQLModel, errors occur when reloading the application after making code changes to model files. The errors are related to model registration and mapper initialization.
Environment
- Python: 3.13
- Streamlit: 1.44.1
- SQLModel: 0.0.24
- OS: macOS
Steps to Reproduce
- Create a minimal project with the following structure:
- app/
- models/
- models.py # Contains all model definitions
- database.py # Database connection setup
- main.py # Streamlit application
- models/
- Define models with relationships:
# models.py
from datetime import datetime
from typing import List, Optional
from sqlmodel import SQLModel, Field, Relationship
class Restaurant(SQLModel, table=True):
id: int = Field(default=None, primary_key=True)
menus: List["Menu"] = Relationship(back_populates="restaurant")
class Menu(SQLModel, table=True):
id: int = Field(default=None, primary_key=True)
restaurant_id: int = Field(foreign_key="restaurant.id")
restaurant: Restaurant = Relationship(back_populates="menus")
orders: List["Order"] = Relationship(back_populates="menu_items", link_model="OrderMenuItem")
class Order(SQLModel, table=True):
id: int = Field(default=None, primary_key=True)
menu_items: List["Menu"] = Relationship(back_populates="orders", link_model="OrderMenuItem")-
Run the Streamlit application:
streamlit run app/main.py -
Make any code change to models.py (e.g., add a comment or modify a field description)
Note: Simply reloading the page without code changes does NOT trigger the error
The error occurs when reloading after making code modifications
- Make another code change and reload again
Expected Behavior
The application should properly handle reloading and maintain the relationships between models when code changes are made and the page is reloaded.
Actual Behavior
After First Code Change and Reload
sqlalchemy.exc.InvalidRequestError: Multiple classes found for path "[ClassName]" in the registry of this declarative base. Please use a fully module-qualified path.
After Second Code Change and Reload
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper[Restaurant(restaurant)]'. Original exception was: Multiple classes found for path "Menu" in the registry of this declarative base. Please use a fully module-qualified path.
Key observations:
- Errors only occur when reloading after code changes
- Simple page refreshes without code changes work normally
- First reload after code change produces a simpler error about multiple classes
- Second reload after code change produces a error involving mapper initialization
- Both errors indicate issues with multiple class registrations
Reproducible Code Example
## models.py
from datetime import datetime
from typing import List, Optional
from sqlmodel import SQLModel, Field, Relationship
class Restaurant(SQLModel, table=True):
__tablename__ = "restaurant"
__table_args__ = {"extend_existing": True}
id: int = Field(default=None, primary_key=True)
name: str = Field(max_length=100, nullable=False)
address: str = Field(max_length=200, nullable=False)
created_at: datetime = Field(default_factory=datetime.now, nullable=False)
updated_at: datetime = Field(default_factory=datetime.now, nullable=False)
menus: List["Menu"] = Relationship(back_populates="restaurant")
class OrderMenuItem(SQLModel, table=True):
__tablename__ = "order_menu_item"
__table_args__ = {"extend_existing": True}
order_id: int = Field(foreign_key="order.id", primary_key=True)
menu_id: int = Field(foreign_key="menu.id", primary_key=True)
quantity: int = Field(default=1, nullable=False)
created_at: datetime = Field(default_factory=datetime.now, nullable=False)
class Menu(SQLModel, table=True):
__tablename__ = "menu"
__table_args__ = {"extend_existing": True}
id: int = Field(default=None, primary_key=True)
restaurant_id: int = Field(foreign_key="restaurant.id", nullable=False)
name: str = Field(max_length=100, nullable=False)
price: int = Field(nullable=False)
description: Optional[str] = Field(max_length=500, nullable=True, description="menu's description")
created_at: datetime = Field(default_factory=datetime.now, nullable=False)
updated_at: datetime = Field(default_factory=datetime.now, nullable=False)
restaurant: Restaurant = Relationship(back_populates="menus")
orders: List["Order"] = Relationship(back_populates="menu_items", link_model=OrderMenuItem)
class Order(SQLModel, table=True):
__tablename__ = "order"
__table_args__ = {"extend_existing": True}
id: int = Field(default=None, primary_key=True)
table_number: int = Field(nullable=False)
status: str = Field(max_length=20, nullable=False, default="pending", description="order status")
created_at: datetime = Field(default_factory=datetime.now, nullable=False)
updated_at: datetime = Field(default_factory=datetime.now, nullable=False)
menu_items: List["Menu"] = Relationship(back_populates="orders", link_model=OrderMenuItem)
## database.py
import os
from sqlmodel import SQLModel, create_engine, Session, select
from sqlalchemy.orm import registry
from sqlalchemy import text
from dotenv import load_dotenv
import streamlit as st
from models.models import Restaurant, Menu
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///restaurant.db")
@st.cache_resource
def get_engine():
engine = create_engine(DATABASE_URL, echo=False)
SQLModel.metadata.create_all(engine)
print("registered classes:", list(registry().mappers))
return engine
def get_session():
engine = get_engine()
with Session(engine) as session:
yield session
def create_initial_data():
engine = get_engine()
with Session(engine) as session:
restaurants = session.exec(select(Restaurant)).all()
if not restaurants:
restaurant = Restaurant(
name="Sample Restaurant",
address="Shibuya, Tokyo..."
)
session.add(restaurant)
session.commit()
session.refresh(restaurant)
# Adding menu items
menus = [
Menu(
restaurant_id=restaurant.id,
name="Hamburger Set",
price=1200,
description="Juicy homemade hamburger"
),
Menu(
restaurant_id=restaurant.id,
name="Pasta Set",
price=1000,
description="Daily pasta with salad and drink"
)
]
session.add_all(menus)
session.commit()
## main.py
import streamlit as st
from sqlmodel import select
st.set_page_config(page_title="Restaurant Order System", layout="wide")
from database import get_session, create_initial_data
from models.models import Restaurant, Menu, Order, OrderMenuItem
st.title("Restaurant Order System")
# Create initial data
create_initial_data()
# Get session
session = next(get_session())
# Display restaurant list
restaurants = session.exec(select(Restaurant)).all()
# Select restaurant from sidebar
selected_restaurant = st.sidebar.selectbox(
"Select a restaurant",
restaurants,
format_func=lambda x: x.name
)
if selected_restaurant:
st.header(f"{selected_restaurant.name}")
st.write(f"Address: {selected_restaurant.address}")
# Display menu
st.subheader("Menu")
menus = session.exec(
select(Menu).where(Menu.restaurant_id == selected_restaurant.id)
).all()
# Display menu in two columns
col1, col2 = st.columns(2)
for i, menu in enumerate(menus):
with col1 if i % 2 == 0 else col2:
with st.expander(f"{menu.name} - ¥{menu.price:,}"):
st.write(menu.description or "No description")
quantity = st.number_input(
"Quantity",
min_value=0,
max_value=10,
value=0,
key=f"menu_{menu.id}"
)
# Order button
if st.button("Place Order"):
# Get menus with quantity >= 1
selected_menus = [
(menu, st.session_state[f"menu_{menu.id}"])
for menu in menus
if st.session_state[f"menu_{menu.id}"] > 0
]
if selected_menus:
session.commit()
st.success("Order completed!")
# Display order details
st.subheader("Order Details")
total = 0
for menu, quantity in selected_menus:
amount = menu.price * quantity
total += amount
st.write(f"- {menu.name} × {quantity} = ¥{amount:,}")
st.write(f"Total: ¥{total:,}")
else:
st.warning("Please select menu items.")Steps To Reproduce
- Run application
- Make changes to the code somewhere
- Reload browser
Expected Behavior
The application should properly handle reloading and maintain the relationships between models when code changes are made and the page is reloaded.
Current Behavior
Error message:
sqlalchemy.exc.InvalidRequestError: Multiple classes found for path "Menu" in the registry of this declarative base. Please use a fully module-qualified path.
Is this a regression?
- Yes, this used to work in a previous version.
Debug info
- Python: 3.13.2
- Streamlit: 1.44.1
- SQLModel: 0.0.24
- OS: macOS
- Browser: Chrome
Additional Information
It seems that the same error is occurring in the following post.
But since there was no answer, I created this issue again here.
https://discuss.streamlit.io/t/streamlit-with-sqlmodel/76021