Skip to content

Conversation

@chrisflath
Copy link

@chrisflath chrisflath commented Jan 29, 2026

Summary

When display.dataframes is set to "plain", mo.sql() now uses plain() to format output instead of the rich interactive table viewer.

This makes SQL cell output consistent with regular DataFrame display behavior when the user has configured plain table output.

Changes

  • Added check for include_opinionated() in marimo/_sql/sql.py before creating table output
  • When include_opinionated() returns False (plain mode), uses plain(df) instead of table.table()

Testing

Added tests in tests/_sql/test_sql.py:

  • test_sql_plain_output_when_not_opinionated: Verifies that Plain object is used when include_opinionated() returns False
  • test_sql_rich_output_when_opinionated: Verifies that table is used when include_opinionated() returns True

Both tests mock include_opinionated() and verify the correct output type is passed to replace().

@vercel
Copy link

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jan 30, 2026 11:21pm

Request Review

@github-actions
Copy link

github-actions bot commented Jan 29, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

if SQLAlchemyEngine.is_cursor_result(df):
display_df = SQLAlchemyEngine.get_cursor_metadata(df)
elif DBAPIEngine.is_dbapi_cursor(df):
display_df = DBAPIEngine.get_cursor_metadata(df)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is some duplicate code from below:

            if SQLAlchemyEngine.is_cursor_result(df):
                display_df = SQLAlchemyEngine.get_cursor_metadata(df)
            elif DBAPIEngine.is_dbapi_cursor(df):
                display_df = DBAPIEngine.get_cursor_metadata(df)

you might be able to do to t = plain(display_df) (plain will run back through our formatters, but skip making them pretty dataframes)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion! A minimal elif instead of the long manual handling. The pull request has been updated with a simplified implementation.

@chrisflath
Copy link
Author

I have read the CLA Document and I hereby sign the CLA

Copy link
Contributor

@Light2Dark Light2Dark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be able to add some tests? Probably under test_sql.py, may need to mock different return values of include_opinionated()

Comment on lines 139 to 140
# Respect display.dataframes config - use plain formatting
from marimo._output.formatting import plain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better to move this import to below if output for consistency. In general I'd prefer moving all imports to top of the file, but for lazy loading and consistency, could move it below output imo

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

When display.dataframes is set to 'plain', mo.sql() now uses plain()
to format output instead of the rich interactive table viewer.

This makes SQL cell output consistent with regular DataFrame display
behavior when the user has configured plain table output.
- test_sql_plain_output_when_not_opinionated: verifies Plain object is used
- test_sql_rich_output_when_opinionated: verifies table is used
@chrisflath
Copy link
Author

Would you be able to add some tests? Probably under test_sql.py, may need to mock different return values of include_opinionated()

Forcepushed tests.

- Call replace() directly in each branch instead of assigning to shared
  variable 't' to avoid Plain vs Html type incompatibility
- Add missing _mock_include_opinionated parameter to test methods that
  use multiple @patch decorators
Copy link
Contributor

@Light2Dark Light2Dark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a few touch ups, i think it looks good!

import duckdb
import polars as pl

from marimo._output.formatting import Plain
Copy link
Contributor

@Light2Dark Light2Dark Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to move this import to the top of file. It's in a test file so it's fine.

from marimo._output.formatting import Plain

# Create a test table
duckdb.sql(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to just do sql("SELECT 1"). We don't need to create & drop a new table for this.

class TestDisplayConfigBehavior:
"""Test that sql() respects the display.dataframes config setting."""

@pytest.mark.usefixtures("_mock_include_opinionated")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this test seems to fail for me. The fixture cannot be found (not sure if/how CI passes)
You can patch it in the code itself for simplicity

    @patch("marimo._sql.sql.replace")
    @patch("marimo._output.formatters.df_formatters.include_opinionated")
    @pytest.mark.skipif(
        not HAS_POLARS or not HAS_DUCKDB, reason="polars and duckdb required"
    )
    def test_sql_plain_output_when_not_opinionated(
        self, mock_include_opinionated, mock_replace
    ):
        """Test that SQL uses plain() when include_opinionated returns False."""
        mock_include_opinionated.return_value = False
        import polars as pl

        from marimo._output.formatting import Plain

        # Test query
        result = sql("SELECT 1")
        assert isinstance(result, pl.DataFrame)

        # Should call replace with Plain object
        mock_replace.assert_called_once()
        call_args = mock_replace.call_args[0][0]

        # The call should be a Plain object (not a table)
        assert isinstance(call_args, Plain)
Each @patch decorator injects a mock argument into the test function.
The previous fix removed these parameters causing signature mismatch.
Restored _mock_opinionated parameter (underscore-prefixed for ruff)
and removed the usefixtures decorator referencing non-existent fixture.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

3 participants