22
33from __future__ import annotations
44
5- from unittest .mock import patch
6-
75import pytest
86
97from marimo ._dependencies .dependencies import DependencyManager
@@ -64,7 +62,10 @@ def test_syntax_error_malformed_expression(self):
6462 sql ("SELECT ( FROM table" )
6563
6664 error = exc_info .value
67- assert "syntax error" in str (error ).lower () or "parser error" in str (error ).lower ()
65+ assert (
66+ "syntax error" in str (error ).lower ()
67+ or "parser error" in str (error ).lower ()
68+ )
6869
6970 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
7071 def test_data_type_error (self ):
@@ -82,7 +83,11 @@ def test_data_type_error(self):
8283 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
8384 def test_long_sql_statement_truncation (self ):
8485 """Test that long SQL statements are truncated in error messages."""
85- long_query = "SELECT " + ", " .join ([f"col_{ i } " for i in range (100 )]) + " FROM nonexistent_table"
86+ long_query = (
87+ "SELECT "
88+ + ", " .join ([f"col_{ i } " for i in range (100 )])
89+ + " FROM nonexistent_table"
90+ )
8691
8792 with pytest .raises (MarimoSQLException ) as exc_info :
8893 sql (long_query )
@@ -152,26 +157,23 @@ def test_is_sql_parse_error_duckdb(self):
152157 """Test detection of DuckDB parsing errors."""
153158 import duckdb
154159
155- try :
160+ with pytest . raises ( Exception ) as exc_info :
156161 duckdb .sql ("SELECT * FROM nonexistent_table" )
157- except Exception as e :
158- assert is_sql_parse_error (e ) is True
162+ assert is_sql_parse_error (exc_info .value ) is True
159163
160- try :
164+ with pytest . raises ( Exception ) as exc_info :
161165 duckdb .sql ("SELECT * FRM invalid_syntax" )
162- except Exception as e :
163- assert is_sql_parse_error (e ) is True
166+ assert is_sql_parse_error (exc_info .value ) is True
164167
165168 @pytest .mark .skipif (not HAS_SQLGLOT , reason = "SQLGlot not installed" )
166169 def test_is_sql_parse_error_sqlglot (self ):
167170 """Test detection of SQLGlot parsing errors."""
168171 from sqlglot import parse_one
169172 from sqlglot .errors import ParseError
170173
171- try :
174+ with pytest . raises ( ParseError ) as exc_info :
172175 parse_one ("SELECT CASE FROM table" )
173- except ParseError as e :
174- assert is_sql_parse_error (e ) is True
176+ assert is_sql_parse_error (exc_info .value ) is True
175177
176178 def test_is_sql_parse_error_non_sql_exception (self ):
177179 """Test that non-SQL exceptions are not detected as SQL errors."""
@@ -203,13 +205,17 @@ def __init__(self, sql_statement: str):
203205 assert len (error .msg ) > 0
204206 assert "nonexistent_table" in error .msg
205207 # Hint field should exist (may be None for this error)
206- assert hasattr (error , ' hint' )
208+ assert hasattr (error , " hint" )
207209
208210 def test_create_sql_error_long_statement (self ):
209211 """Test SQL statement truncation in error creation."""
210212 import duckdb
211213
212- long_statement = "SELECT " + ", " .join ([f"col_{ i } " for i in range (100 )]) + " FROM test"
214+ long_statement = (
215+ "SELECT "
216+ + ", " .join ([f"col_{ i } " for i in range (100 )])
217+ + " FROM test"
218+ )
213219
214220 class MockCell :
215221 def __init__ (self , sql_statement : str ):
@@ -244,7 +250,7 @@ def test_extract_sql_position_sqlglot_format(self):
244250 sqlglot_msg = "Parse error at line 2, col 10"
245251 line , col = extract_sql_position (sqlglot_msg )
246252 assert line == 1 # 0-based
247- assert col == 9 # 0-based
253+ assert col == 9 # 0-based
248254
249255 def test_extract_sql_position_no_position (self ):
250256 """Test position extraction when no position info available."""
@@ -266,10 +272,13 @@ class MockCell:
266272 except Exception as e :
267273 error = create_sql_error_from_exception (e , MockCell ())
268274 # Should have "SQL syntax error:" prefix for ParserException
269- assert error .msg .startswith ("SQL syntax error:" ) or error .msg .startswith ("SQL parse error:" )
275+ assert error .msg .startswith (
276+ "SQL syntax error:"
277+ ) or error .msg .startswith ("SQL parse error:" )
270278
271279 def test_error_message_cleaning (self ):
272280 """Test that error messages are cleaned of traces."""
281+
273282 class MockException (Exception ):
274283 def __str__ (self ):
275284 return "SQL error message\n Traceback (most recent call last):\n File..."
@@ -299,6 +308,7 @@ def test_sql_function_error_flow(self):
299308
300309 def test_empty_sql_statement_error_handling (self ):
301310 """Test error handling with empty SQL statements."""
311+
302312 class MockCell :
303313 sqls = []
304314
@@ -309,11 +319,14 @@ class MockCell:
309319
310320 def test_cell_without_sqls_attribute (self ):
311321 """Test error handling when cell doesn't have sqls attribute."""
322+
312323 class MockCellNoSqls :
313324 pass
314325
315326 mock_exception = Exception ("Test error" )
316- error = create_sql_error_from_exception (mock_exception , MockCellNoSqls ())
327+ error = create_sql_error_from_exception (
328+ mock_exception , MockCellNoSqls ()
329+ )
317330
318331 assert error .sql_statement == ""
319332
@@ -339,52 +352,70 @@ def test_duckdb_hints_preserved(self):
339352 import duckdb
340353
341354 # Create a table to generate "Did you mean?" suggestions
342- duckdb .sql ("CREATE OR REPLACE TABLE test_hints_table (id INT, name TEXT)" )
355+ duckdb .sql (
356+ "CREATE OR REPLACE TABLE test_hints_table (id INT, name TEXT)"
357+ )
343358
344359 with pytest .raises (MarimoSQLException ) as exc_info :
345360 sql ("SELECT * FROM test_hint" ) # Missing 's' in table name
346361
347- error_msg = str (exc_info .value )
348- # Check that both the error and hint are present
362+ error = exc_info .value
363+ error_msg = str (error )
364+ # Check that the main error message is present
349365 assert "does not exist" in error_msg
350- assert ("Did you mean" in error_msg or "candidate" in error_msg .lower ())
366+ # Check that the hint is properly extracted to the hint field
367+ assert error .hint is not None
368+ assert (
369+ "Did you mean" in error .hint or "candidate" in error .hint .lower ()
370+ )
351371
352372 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
353373 def test_column_candidates_preserved (self ):
354374 """Test that column candidate hints are preserved in error messages."""
355375 import duckdb
356376
357377 # Create a table to generate candidate binding suggestions
358- duckdb .sql ("CREATE OR REPLACE TABLE test_columns (id INT, user_name TEXT, email TEXT)" )
378+ duckdb .sql (
379+ "CREATE OR REPLACE TABLE test_columns (id INT, user_name TEXT, email TEXT)"
380+ )
359381
360382 with pytest .raises (MarimoSQLException ) as exc_info :
361383 sql ("SELECT fullname FROM test_columns" ) # Wrong column name
362384
363- error_msg = str (exc_info .value )
364- # Check that candidate bindings are included
385+ error = exc_info .value
386+ error_msg = str (error )
387+ # Check that the main error message is present
365388 assert "not found" in error_msg
366- assert ("Candidate" in error_msg or "candidate" in error_msg .lower ())
389+ # Check that the hint is properly extracted to the hint field
390+ assert error .hint is not None
391+ assert "Candidate" in error .hint or "candidate" in error .hint .lower ()
367392
368393 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
369394 def test_hint_field_in_sql_error_struct (self ):
370395 """Test that MarimoSQLError struct properly includes hint field."""
371396 import duckdb
372397
373398 # Create table for hint generation
374- duckdb .sql ("CREATE OR REPLACE TABLE hint_test_table (id INT, name TEXT)" )
399+ duckdb .sql (
400+ "CREATE OR REPLACE TABLE hint_test_table (id INT, name TEXT)"
401+ )
375402
376403 try :
377404 duckdb .sql ("SELECT * FROM hint_test" ) # Missing letters
378405 except Exception as e :
406+
379407 class MockCell :
380408 sqls = ["SELECT * FROM hint_test" ]
381409
382410 error_struct = create_sql_error_from_exception (e , MockCell ())
383411
384412 # Verify the struct has the hint field and it's populated
385- assert hasattr (error_struct , ' hint' )
413+ assert hasattr (error_struct , " hint" )
386414 assert error_struct .hint is not None
387- assert ("Did you mean" in error_struct .hint or "candidate" in error_struct .hint .lower ())
415+ assert (
416+ "Did you mean" in error_struct .hint
417+ or "candidate" in error_struct .hint .lower ()
418+ )
388419 # Main message should not contain the hint
389420 assert error_struct .hint not in error_struct .msg
390421
@@ -394,18 +425,23 @@ def test_multiline_hints_preserved(self):
394425 import duckdb
395426
396427 # Create table for multiline hint generation
397- duckdb .sql ("CREATE OR REPLACE TABLE hint_multiline_table (id INT, name TEXT)" )
428+ duckdb .sql (
429+ "CREATE OR REPLACE TABLE hint_multiline_table (id INT, name TEXT)"
430+ )
398431
399432 try :
400- duckdb .sql ("SELECT SUBSTRING(name) FROM hint_multiline_table" ) # Wrong args
433+ duckdb .sql (
434+ "SELECT SUBSTRING(name) FROM hint_multiline_table"
435+ ) # Wrong args
401436 except Exception as e :
437+
402438 class MockCell :
403439 sqls = ["SELECT SUBSTRING(name) FROM hint_multiline_table" ]
404440
405441 error_struct = create_sql_error_from_exception (e , MockCell ())
406442
407443 # Verify multiline hint is captured completely
408- assert hasattr (error_struct , ' hint' )
444+ assert hasattr (error_struct , " hint" )
409445 assert error_struct .hint is not None
410446 assert "Candidate functions:" in error_struct .hint
411447 assert "substring(VARCHAR, BIGINT, BIGINT)" in error_struct .hint
0 commit comments