Skip to content

Multiple classes found for path error when reload with model relationships #11232

@Raido-Tokuyama

Description

@Raido-Tokuyama

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

  1. 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
  1. 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")
  1. Run the Streamlit application:
    streamlit run app/main.py

  2. 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

  1. 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:

  1. Errors only occur when reloading after code changes
  2. Simple page refreshes without code changes work normally
  3. First reload after code change produces a simpler error about multiple classes
  4. Second reload after code change produces a error involving mapper initialization
  5. 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

  1. Run application
  2. Make changes to the code somewhere
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:bugSomething isn't working as expected

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions