Repository: streamlit/streamlit
File: lib/streamlit/dataframe_util.py (Python)

Code before:
        return cast("pd.DataFrame", data)

    if is_dbapi_cursor(data):
        # Based on the specification, the first item in the description is the
        # column name (if available)
        columns = (
            [d[0] if d else "" for d in data.description] if data.description else None
        )
        data = pd.DataFrame(data.fetchmany(max_unevaluated_rows), columns=columns)
        if data.shape[0] == max_unevaluated_rows:
            _show_data_information(
                f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
                "rows. Call `fetchall()` on the Cursor to show more."
            )
        return cast("DataFrame", data)  # ty: ignore[redundant-cast]

    if is_snowpark_row_list(data):
        return pd.DataFrame([row.as_dict() for row in data])

    if is_sequence_of_pydantic_models(data):
        # Try to convert pydantic models to DataFrame. If some elements are not
        # pydantic models (mixed sequence), fall through to pandas' native handling.
        try:
            first_element = next(iter(data))
            if has_callable_attr(first_element, "model_dump"):
                return pd.DataFrame([item.model_dump() for item in data])
            return pd.DataFrame([item.dict() for item in data])
        except AttributeError:
            pass

    if has_callable_attr(data, "to_pandas"):
        return pd.DataFrame(data.to_pandas())

    # Check for dataframe interchange protocol
    # Only available in pandas >= 1.5.0
    # https://pandas.pydata.org/docs/whatsnew/v1.5.0.html#dataframe-interchange-protocol-implementation
    if (
        has_callable_attr(data, "__dataframe__")
        and is_pandas_version_less_than("1.5.0") is False
    ):
        data_df = pd.api.interchange.from_dataframe(data)
        return data_df.copy() if ensure_copy else data_df

    # Support for generator functions
    if inspect.isgeneratorfunction(data):
        data = _fix_column_naming(
            pd.DataFrame(_iterable_to_list(data(), max_iterations=max_unevaluated_rows))
        )

        if data.shape[0] == max_unevaluated_rows:
            _show_data_information(

Review comment (bug):
@lukasmasuch  would it be better to use model_dump(mode='json') here. There's a bug in st.dataframe where it incorrectly displays small Decimal values but it's solved when you do mode='json'. 

```
from decimal import Decimal

import streamlit as st
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name: str
    is_active: bool
    decimal_val: Decimal


users = [
    User(id=5, name="Eve", is_active=True, decimal_val=Decimal("-0.01")),
]
st.write("Model dump python mode")
st.dataframe([user.model_dump() for user in users])
st.write("Model dump json mode")
st.dataframe([user.model_dump(mode="json") for user in users])
st.write("Actual value: Decimal('-0.01'):", Decimal("-0.01"))
```
<img width="739" height="342" alt="Screenshot 2025-12-14 at 2 32 01 PM" src="https://github.com/user-attachments/assets/bf6b0994-4f49-43e4-8ab2-1be825dc2c2d" />



Code after:
            )
        return cast("pd.DataFrame", data)

    if is_dbapi_cursor(data):
        # Based on the specification, the first item in the description is the
        # column name (if available)
        columns = (
            [d[0] if d else "" for d in data.description] if data.description else None
        )
        data = pd.DataFrame(data.fetchmany(max_unevaluated_rows), columns=columns)
        if data.shape[0] == max_unevaluated_rows:
            _show_data_information(
                f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
                "rows. Call `fetchall()` on the Cursor to show more."
            )
        return cast("DataFrame", data)  # ty: ignore[redundant-cast]

    if is_snowpark_row_list(data):
        return pd.DataFrame([row.as_dict() for row in data])

    if is_sequence_of_pydantic_models(data):
        # Try to convert pydantic models to DataFrame. If some elements are not
        # pydantic models (mixed sequence), fall through to pandas' native handling.
        try:
            return pd.DataFrame(dump_pydantic_sequence(data))
        except AttributeError:
            pass

    if has_callable_attr(data, "to_pandas"):
        return pd.DataFrame(data.to_pandas())

    # Check for dataframe interchange protocol
    # Only available in pandas >= 1.5.0
    # https://pandas.pydata.org/docs/whatsnew/v1.5.0.html#dataframe-interchange-protocol-implementation
    if (
        has_callable_attr(data, "__dataframe__")
        and is_pandas_version_less_than("1.5.0") is False
    ):
        data_df = pd.api.interchange.from_dataframe(data)
        return data_df.copy() if ensure_copy else data_df

    # Support for generator functions
    if inspect.isgeneratorfunction(data):
        data = _fix_column_naming(
            pd.DataFrame(_iterable_to_list(data(), max_iterations=max_unevaluated_rows))
        )

        if data.shape[0] == max_unevaluated_rows:
            _show_data_information(
                f"⚠️ Showing only {string_util.simplify_number(max_unevaluated_rows)} "
                "rows. Convert the data to a list to show more."