From fc8365a465e82cae688ffbe41549c93eda8f3711 Mon Sep 17 00:00:00 2001 From: Jeffrey Aven Date: Thu, 12 Jun 2025 16:55:34 +1000 Subject: [PATCH 1/5] output formatting --- pystackql/magic_ext/base.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/pystackql/magic_ext/base.py b/pystackql/magic_ext/base.py index 45e2f3c..55c03c3 100644 --- a/pystackql/magic_ext/base.py +++ b/pystackql/magic_ext/base.py @@ -73,21 +73,9 @@ def _display_with_csv_download(self, df): # Create download link download_link = f'data:text/csv;base64,{csv_base64}' - # Display the DataFrame first - IPython.display.display(df) + # # Display the DataFrame first + # IPython.display.display(df) - # # Create and display the download button - # download_html = f''' - #
- # - # 📥 Download CSV - # - #
- # ''' - # Create and display the download button download_html = f'''
@@ -105,7 +93,7 @@ def _display_with_csv_download(self, df):
''' - + IPython.display.display(IPython.display.HTML(download_html)) except Exception as e: From 06f9c17c30d5b07aee184bc106d25606407d1642 Mon Sep 17 00:00:00 2001 From: Jeffrey Aven Date: Thu, 12 Jun 2025 18:28:35 +1000 Subject: [PATCH 2/5] output formatting --- tests/test_magic.py | 433 ++++++++++++++++++++----------------- tests/test_magic_base.py | 221 +++++++++++++++++++ tests/test_server_magic.py | 431 ++++++++++++++++++++---------------- 3 files changed, 700 insertions(+), 385 deletions(-) create mode 100644 tests/test_magic_base.py diff --git a/tests/test_magic.py b/tests/test_magic.py index aaaac49..67aef93 100644 --- a/tests/test_magic.py +++ b/tests/test_magic.py @@ -1,221 +1,268 @@ -# tests/test_magic.py +# # tests/test_magic.py -""" -Non-server magic extension tests for PyStackQL. +# """ +# Non-server magic extension tests for PyStackQL. -This module tests the Jupyter magic extensions for StackQL in non-server mode. -""" +# This module tests the Jupyter magic extensions for StackQL in non-server mode. +# """ -import os -import sys -import re -import pytest -import pandas as pd -from unittest.mock import MagicMock +# import os +# import sys +# import re +# import pytest +# import pandas as pd +# from unittest.mock import MagicMock -# Add the parent directory to the path so we can import from pystackql -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +# # Add the parent directory to the path so we can import from pystackql +# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) -# Add the current directory to the path so we can import test_constants -sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) +# # Add the current directory to the path so we can import test_constants +# sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) -# Import directly from the original modules - this is what notebooks would do -from pystackql import magic -from pystackql import StackqlMagic +# # Import directly from the original modules - this is what notebooks would do +# from pystackql import magic +# from pystackql import StackqlMagic -from tests.test_constants import ( - LITERAL_INT_QUERY, - REGISTRY_PULL_HOMEBREW_QUERY, - registry_pull_resp_pattern, - print_test_result -) +# from tests.test_constants import ( +# LITERAL_INT_QUERY, +# REGISTRY_PULL_HOMEBREW_QUERY, +# registry_pull_resp_pattern, +# print_test_result +# ) -class TestStackQLMagic: - """Tests for the non-server mode magic extension.""" +# class TestStackQLMagic: +# """Tests for the non-server mode magic extension.""" - @pytest.fixture(autouse=True) - def setup_method(self, mock_interactive_shell): - """Set up the test environment.""" - self.shell = mock_interactive_shell +# @pytest.fixture(autouse=True) +# def setup_method(self, mock_interactive_shell): +# """Set up the test environment.""" +# self.shell = mock_interactive_shell - # Load the magic extension - magic.load_ipython_extension(self.shell) +# # Load the magic extension +# magic.load_ipython_extension(self.shell) - # Create the magic instance - self.stackql_magic = StackqlMagic(shell=self.shell) +# # Create the magic instance +# self.stackql_magic = StackqlMagic(shell=self.shell) - # Set up test data - self.query = LITERAL_INT_QUERY - self.expected_result = pd.DataFrame({"literal_int_value": [1]}) - self.statement = REGISTRY_PULL_HOMEBREW_QUERY +# # Set up test data +# self.query = LITERAL_INT_QUERY +# self.expected_result = pd.DataFrame({"literal_int_value": [1]}) +# self.statement = REGISTRY_PULL_HOMEBREW_QUERY - def test_line_magic_query(self): - """Test line magic with a query.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Execute the magic with our query - result = self.stackql_magic.stackql(line=self.query, cell=None) - - # Validate the outcome - assert result.equals(self.expected_result), "Result should match expected DataFrame" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - print_test_result("Line magic query test", - result.equals(self.expected_result) and - 'stackql_df' in self.shell.user_ns and - self.shell.user_ns['stackql_df'].equals(self.expected_result), - False, True) +# def test_line_magic_query(self): +# """Test line magic with a query.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Execute the magic with our query +# result = self.stackql_magic.stackql(line=self.query, cell=None) + +# # Validate the outcome +# assert result.equals(self.expected_result), "Result should match expected DataFrame" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# print_test_result("Line magic query test", +# result.equals(self.expected_result) and +# 'stackql_df' in self.shell.user_ns and +# self.shell.user_ns['stackql_df'].equals(self.expected_result), +# False, True) - def test_cell_magic_query(self): - """Test cell magic with a query.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Execute the magic with our query - result = self.stackql_magic.stackql(line="", cell=self.query) - - # Validate the outcome - assert result.equals(self.expected_result), "Result should match expected DataFrame" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - print_test_result("Cell magic query test", - result.equals(self.expected_result) and - 'stackql_df' in self.shell.user_ns and - self.shell.user_ns['stackql_df'].equals(self.expected_result), - False, True) +# def test_cell_magic_query(self): +# """Test cell magic with a query.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Execute the magic with our query +# result = self.stackql_magic.stackql(line="", cell=self.query) + +# # Validate the outcome +# assert result.equals(self.expected_result), "Result should match expected DataFrame" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# print_test_result("Cell magic query test", +# result.equals(self.expected_result) and +# 'stackql_df' in self.shell.user_ns and +# self.shell.user_ns['stackql_df'].equals(self.expected_result), +# False, True) - def test_cell_magic_query_no_display(self): - """Test cell magic with a query and --no-display option.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Execute the magic with our query and --no-display option - result = self.stackql_magic.stackql(line="--no-display", cell=self.query) - - # Validate the outcome - assert result is None, "Result should be None with --no-display option" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - print_test_result("Cell magic query test (with --no-display)", - result is None and - 'stackql_df' in self.shell.user_ns and - self.shell.user_ns['stackql_df'].equals(self.expected_result), - False, True) - - def test_cell_magic_query_csv_download(self): - """Test cell magic with CSV download functionality.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Mock the _display_with_csv_download method to verify it's called - self.stackql_magic._display_with_csv_download = MagicMock() - - # Execute the magic with --csv-download option - result = self.stackql_magic.stackql(line="--csv-download", cell=self.query) - - # Validate the outcome - assert result.equals(self.expected_result), "Result should match expected DataFrame" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - # Verify that _display_with_csv_download was called - self.stackql_magic._display_with_csv_download.assert_called_once_with(self.expected_result) - - print_test_result("Cell magic query test with CSV download", - result.equals(self.expected_result) and - 'stackql_df' in self.shell.user_ns and - self.stackql_magic._display_with_csv_download.called, - False, True) - - def test_cell_magic_query_csv_download_with_no_display(self): - """Test that --no-display takes precedence over --csv-download.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Mock the _display_with_csv_download method to verify it's not called - self.stackql_magic._display_with_csv_download = MagicMock() - - # Execute the magic with both --csv-download and --no-display options - result = self.stackql_magic.stackql(line="--csv-download --no-display", cell=self.query) - - # Validate the outcome - assert result is None, "Result should be None with --no-display option" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - # Verify that _display_with_csv_download was NOT called - self.stackql_magic._display_with_csv_download.assert_not_called() - - print_test_result("Cell magic query test with CSV download and no-display", - result is None and - 'stackql_df' in self.shell.user_ns and - not self.stackql_magic._display_with_csv_download.called, - False, True) - - def test_display_with_csv_download_method(self): - """Test the _display_with_csv_download method directly.""" - import base64 - from unittest.mock import patch - - # Create a test DataFrame - test_df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]}) - - # Mock IPython display functionality - with patch('IPython.display.display') as mock_display, \ - patch('IPython.display.HTML') as mock_html: +# def test_cell_magic_query_no_display(self): +# """Test cell magic with a query and --no-display option.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Execute the magic with our query and --no-display option +# result = self.stackql_magic.stackql(line="--no-display", cell=self.query) + +# # Validate the outcome +# assert result is None, "Result should be None with --no-display option" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# print_test_result("Cell magic query test (with --no-display)", +# result is None and +# 'stackql_df' in self.shell.user_ns and +# self.shell.user_ns['stackql_df'].equals(self.expected_result), +# False, True) + +# def test_cell_magic_query_csv_download(self): +# """Test cell magic with CSV download functionality.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Mock the _display_with_csv_download method to verify it's called +# self.stackql_magic._display_with_csv_download = MagicMock() + +# # Execute the magic with --csv-download option +# result = self.stackql_magic.stackql(line="--csv-download", cell=self.query) + +# # Validate the outcome +# assert result.equals(self.expected_result), "Result should match expected DataFrame" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# # Verify that _display_with_csv_download was called +# self.stackql_magic._display_with_csv_download.assert_called_once_with(self.expected_result) + +# print_test_result("Cell magic query test with CSV download", +# result.equals(self.expected_result) and +# 'stackql_df' in self.shell.user_ns and +# self.stackql_magic._display_with_csv_download.called, +# False, True) + +# def test_cell_magic_query_csv_download_with_no_display(self): +# """Test that --no-display takes precedence over --csv-download.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Mock the _display_with_csv_download method to verify it's not called +# self.stackql_magic._display_with_csv_download = MagicMock() + +# # Execute the magic with both --csv-download and --no-display options +# result = self.stackql_magic.stackql(line="--csv-download --no-display", cell=self.query) + +# # Validate the outcome +# assert result is None, "Result should be None with --no-display option" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# # Verify that _display_with_csv_download was NOT called +# self.stackql_magic._display_with_csv_download.assert_not_called() + +# print_test_result("Cell magic query test with CSV download and no-display", +# result is None and +# 'stackql_df' in self.shell.user_ns and +# not self.stackql_magic._display_with_csv_download.called, +# False, True) + +# def test_display_with_csv_download_method(self): +# """Test the _display_with_csv_download method directly.""" +# import base64 +# from unittest.mock import patch + +# # Create a test DataFrame +# test_df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]}) + +# # Mock IPython display functionality +# with patch('IPython.display.display') as mock_display, \ +# patch('IPython.display.HTML') as mock_html: - # Call the method - self.stackql_magic._display_with_csv_download(test_df) +# # Call the method +# self.stackql_magic._display_with_csv_download(test_df) - # Verify display was called twice (once for DataFrame, once for HTML) - assert mock_display.call_count == 2, "Display should be called twice" +# # Verify display was called twice (once for DataFrame, once for HTML) +# assert mock_display.call_count == 2, "Display should be called twice" - # Verify HTML was called once - mock_html.assert_called_once() +# # Verify HTML was called once +# mock_html.assert_called_once() - # Check that the HTML call contains download link - html_call_args = mock_html.call_args[0][0] - assert 'download="stackql_results.csv"' in html_call_args - assert 'data:text/csv;base64,' in html_call_args +# # Check that the HTML call contains download link +# html_call_args = mock_html.call_args[0][0] +# assert 'download="stackql_results.csv"' in html_call_args +# assert 'data:text/csv;base64,' in html_call_args - print_test_result("_display_with_csv_download method test", - mock_display.call_count == 2 and mock_html.called, - False, True) - - def test_display_with_csv_download_error_handling(self): - """Test error handling in _display_with_csv_download method.""" - from unittest.mock import patch - - # Create a mock DataFrame that will raise an exception during to_csv() - mock_df = MagicMock() - mock_df.to_csv.side_effect = Exception("Test CSV error") - - # Mock IPython display functionality - with patch('IPython.display.display') as mock_display, \ - patch('IPython.display.HTML') as mock_html, \ - patch('builtins.print') as mock_print: +# print_test_result("_display_with_csv_download method test", +# mock_display.call_count == 2 and mock_html.called, +# False, True) + +# def test_display_with_csv_download_error_handling(self): +# """Test error handling in _display_with_csv_download method.""" +# from unittest.mock import patch + +# # Create a mock DataFrame that will raise an exception during to_csv() +# mock_df = MagicMock() +# mock_df.to_csv.side_effect = Exception("Test CSV error") + +# # Mock IPython display functionality +# with patch('IPython.display.display') as mock_display, \ +# patch('IPython.display.HTML') as mock_html, \ +# patch('builtins.print') as mock_print: - # Call the method with the problematic DataFrame - self.stackql_magic._display_with_csv_download(mock_df) +# # Call the method with the problematic DataFrame +# self.stackql_magic._display_with_csv_download(mock_df) - # Verify display was called once (for DataFrame only, not for HTML) - mock_display.assert_called_once_with(mock_df) +# # Verify display was called once (for DataFrame only, not for HTML) +# mock_display.assert_called_once_with(mock_df) - # Verify HTML was not called due to error - mock_html.assert_not_called() +# # Verify HTML was not called due to error +# mock_html.assert_not_called() - # Verify error message was printed - mock_print.assert_called_once() - error_message = mock_print.call_args[0][0] - assert "Error generating CSV download:" in error_message +# # Verify error message was printed +# mock_print.assert_called_once() +# error_message = mock_print.call_args[0][0] +# assert "Error generating CSV download:" in error_message - print_test_result("_display_with_csv_download error handling test", - mock_display.called and not mock_html.called and mock_print.called, - False, True) +# print_test_result("_display_with_csv_download error handling test", +# mock_display.called and not mock_html.called and mock_print.called, +# False, True) + +# def test_magic_extension_loading(mock_interactive_shell): +# """Test that non-server magic extension can be loaded.""" +# # Test loading non-server magic +# magic.load_ipython_extension(mock_interactive_shell) +# assert hasattr(mock_interactive_shell, 'magics'), "Magic should be registered" +# assert isinstance(mock_interactive_shell.magics, StackqlMagic), "Registered magic should be StackqlMagic" + +# print_test_result("Magic extension loading test", +# hasattr(mock_interactive_shell, 'magics') and +# isinstance(mock_interactive_shell.magics, StackqlMagic), +# False, True) + +# if __name__ == "__main__": +# pytest.main(["-v", __file__]) + +# tests/test_magic.py + +""" +Non-server magic extension tests for PyStackQL. + +This module tests the Jupyter magic extensions for StackQL in non-server mode. +""" + +import os +import sys +import pytest + +# Add the parent directory to the path so we can import from pystackql +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +# Import the base test class +from tests.test_magic_base import BaseStackQLMagicTest + +# Import directly from the original modules - this is what notebooks would do +from pystackql import magic +from pystackql import StackqlMagic + +from tests.test_constants import print_test_result + +class TestStackQLMagic(BaseStackQLMagicTest): + """Tests for the non-server mode magic extension.""" + + # Set the class attributes for the base test class + magic_module = magic + magic_class = StackqlMagic + is_server_mode = False def test_magic_extension_loading(mock_interactive_shell): """Test that non-server magic extension can be loaded.""" diff --git a/tests/test_magic_base.py b/tests/test_magic_base.py new file mode 100644 index 0000000..5e7cb88 --- /dev/null +++ b/tests/test_magic_base.py @@ -0,0 +1,221 @@ +# tests/test_magic_base.py + +""" +Base test class for Jupyter magic extensions for PyStackQL. + +This module provides a base test class for testing both local and server mode +magic extensions. +""" + +import os +import sys +import re +import pytest +import pandas as pd +from unittest.mock import MagicMock, patch + +# Add the parent directory to the path so we can import from pystackql +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +# Add the current directory to the path so we can import test_constants +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) + +from tests.test_constants import ( + LITERAL_INT_QUERY, + REGISTRY_PULL_HOMEBREW_QUERY, + registry_pull_resp_pattern, + print_test_result +) + +class BaseStackQLMagicTest: + """Base class for testing StackQL magic extensions.""" + + # Each derived class should define: + # - magic_module: the module to import + # - magic_class: the class to use + # - is_server_mode: True for server mode tests, False for local mode tests + magic_module = None + magic_class = None + is_server_mode = None + + @pytest.fixture(autouse=True) + def setup_method(self, mock_interactive_shell): + """Set up the test environment.""" + self.shell = mock_interactive_shell + + # Load the magic extension + self.magic_module.load_ipython_extension(self.shell) + + # Create the magic instance + self.stackql_magic = self.magic_class(shell=self.shell) + + # Set up test data + self.query = LITERAL_INT_QUERY + self.expected_result = pd.DataFrame({"literal_int_value": [1]}) + self.statement = REGISTRY_PULL_HOMEBREW_QUERY + + def test_line_magic_query(self): + """Test line magic with a query.""" + # Mock the run_query method to return a known DataFrame + self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + + # Execute the magic with our query + result = self.stackql_magic.stackql(line=self.query, cell=None) + + # Validate the outcome + assert result.equals(self.expected_result), "Result should match expected DataFrame" + assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" + assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + + print_test_result(f"Line magic query test{' (server mode)' if self.is_server_mode else ''}", + result.equals(self.expected_result) and + 'stackql_df' in self.shell.user_ns and + self.shell.user_ns['stackql_df'].equals(self.expected_result), + self.is_server_mode, True) + + def test_cell_magic_query(self): + """Test cell magic with a query.""" + # Mock the run_query method to return a known DataFrame + self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + + # Execute the magic with our query + result = self.stackql_magic.stackql(line="", cell=self.query) + + # Validate the outcome + assert result.equals(self.expected_result), "Result should match expected DataFrame" + assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" + assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + + print_test_result(f"Cell magic query test{' (server mode)' if self.is_server_mode else ''}", + result.equals(self.expected_result) and + 'stackql_df' in self.shell.user_ns and + self.shell.user_ns['stackql_df'].equals(self.expected_result), + self.is_server_mode, True) + + def test_cell_magic_query_no_display(self): + """Test cell magic with a query and --no-display option.""" + # Mock the run_query method to return a known DataFrame + self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + + # Execute the magic with our query and --no-display option + result = self.stackql_magic.stackql(line="--no-display", cell=self.query) + + # Validate the outcome + assert result is None, "Result should be None with --no-display option" + assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" + assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + + print_test_result(f"Cell magic query test with --no-display{' (server mode)' if self.is_server_mode else ''}", + result is None and + 'stackql_df' in self.shell.user_ns and + self.shell.user_ns['stackql_df'].equals(self.expected_result), + self.is_server_mode, True) + + def test_cell_magic_query_csv_download(self): + """Test cell magic with CSV download functionality.""" + # Mock the run_query method to return a known DataFrame + self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + + # Mock the _display_with_csv_download method to verify it's called + self.stackql_magic._display_with_csv_download = MagicMock() + + # Execute the magic with --csv-download option + result = self.stackql_magic.stackql(line="--csv-download", cell=self.query) + + # Validate the outcome + assert result.equals(self.expected_result), "Result should match expected DataFrame" + assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" + assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + + # Verify that _display_with_csv_download was called + self.stackql_magic._display_with_csv_download.assert_called_once_with(self.expected_result) + + print_test_result(f"Cell magic query test with CSV download{' (server mode)' if self.is_server_mode else ''}", + result.equals(self.expected_result) and + 'stackql_df' in self.shell.user_ns and + self.stackql_magic._display_with_csv_download.called, + self.is_server_mode, True) + + def test_cell_magic_query_csv_download_with_no_display(self): + """Test that --no-display takes precedence over --csv-download.""" + # Mock the run_query method to return a known DataFrame + self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + + # Mock the _display_with_csv_download method to verify it's not called + self.stackql_magic._display_with_csv_download = MagicMock() + + # Execute the magic with both --csv-download and --no-display options + result = self.stackql_magic.stackql(line="--csv-download --no-display", cell=self.query) + + # Validate the outcome + assert result is None, "Result should be None with --no-display option" + assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" + assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + + # Verify that _display_with_csv_download was NOT called + self.stackql_magic._display_with_csv_download.assert_not_called() + + print_test_result(f"Cell magic query test with CSV download and no-display{' (server mode)' if self.is_server_mode else ''}", + result is None and + 'stackql_df' in self.shell.user_ns and + not self.stackql_magic._display_with_csv_download.called, + self.is_server_mode, True) + + def test_display_with_csv_download_method(self): + """Test the _display_with_csv_download method directly.""" + import base64 + + # Create a test DataFrame + test_df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]}) + + # Mock IPython display functionality + with patch('IPython.display.display') as mock_display, \ + patch('IPython.display.HTML') as mock_html: + + # Call the method + self.stackql_magic._display_with_csv_download(test_df) + + # Verify display was called once (only for HTML, not for DataFrame) + assert mock_display.call_count == 1, "Display should be called once" + + # Verify HTML was called once + mock_html.assert_called_once() + + # Check that the HTML call contains download link + html_call_args = mock_html.call_args[0][0] + assert 'download="stackql_results.csv"' in html_call_args + assert 'data:text/csv;base64,' in html_call_args + + print_test_result(f"_display_with_csv_download method test{' (server mode)' if self.is_server_mode else ''}", + mock_display.call_count == 1 and mock_html.called, + self.is_server_mode, True) + + def test_display_with_csv_download_error_handling(self): + """Test error handling in _display_with_csv_download method.""" + + # Create a mock DataFrame that will raise an exception during to_csv() + mock_df = MagicMock() + mock_df.to_csv.side_effect = Exception("Test CSV error") + + # Mock IPython display functionality + with patch('IPython.display.display') as mock_display, \ + patch('IPython.display.HTML') as mock_html, \ + patch('builtins.print') as mock_print: + + # Call the method with the problematic DataFrame + self.stackql_magic._display_with_csv_download(mock_df) + + # Verify display was not called (we now only print an error message) + mock_display.assert_not_called() + + # Verify HTML was not called due to error + mock_html.assert_not_called() + + # Verify error message was printed + mock_print.assert_called_once() + error_message = mock_print.call_args[0][0] + assert "Error generating CSV download:" in error_message + + print_test_result(f"_display_with_csv_download error handling test{' (server mode)' if self.is_server_mode else ''}", + not mock_display.called and not mock_html.called and mock_print.called, + self.is_server_mode, True) \ No newline at end of file diff --git a/tests/test_server_magic.py b/tests/test_server_magic.py index 0296f37..3f79da9 100644 --- a/tests/test_server_magic.py +++ b/tests/test_server_magic.py @@ -1,219 +1,266 @@ -""" -Server-mode magic extension tests for PyStackQL. +# """ +# Server-mode magic extension tests for PyStackQL. -This module tests the Jupyter magic extensions for StackQL in server mode. -""" +# This module tests the Jupyter magic extensions for StackQL in server mode. +# """ -import os -import sys -import re -import pytest -import pandas as pd -from unittest.mock import MagicMock +# import os +# import sys +# import re +# import pytest +# import pandas as pd +# from unittest.mock import MagicMock -# Add the parent directory to the path so we can import from pystackql -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +# # Add the parent directory to the path so we can import from pystackql +# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) -# Add the current directory to the path so we can import test_constants -sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) +# # Add the current directory to the path so we can import test_constants +# sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) -# Import directly from the original modules - this is what notebooks would do -from pystackql import magics -from pystackql import StackqlServerMagic +# # Import directly from the original modules - this is what notebooks would do +# from pystackql import magics +# from pystackql import StackqlServerMagic -from tests.test_constants import ( - LITERAL_INT_QUERY, - REGISTRY_PULL_HOMEBREW_QUERY, - registry_pull_resp_pattern, - print_test_result -) +# from tests.test_constants import ( +# LITERAL_INT_QUERY, +# REGISTRY_PULL_HOMEBREW_QUERY, +# registry_pull_resp_pattern, +# print_test_result +# ) -class TestStackQLServerMagic: - """Tests for the server mode magic extension.""" +# class TestStackQLServerMagic: +# """Tests for the server mode magic extension.""" - @pytest.fixture(autouse=True) - def setup_method(self, mock_interactive_shell): - """Set up the test environment.""" - self.shell = mock_interactive_shell +# @pytest.fixture(autouse=True) +# def setup_method(self, mock_interactive_shell): +# """Set up the test environment.""" +# self.shell = mock_interactive_shell - # Load the magic extension - magics.load_ipython_extension(self.shell) +# # Load the magic extension +# magics.load_ipython_extension(self.shell) - # Create the magic instance - self.stackql_magic = StackqlServerMagic(shell=self.shell) +# # Create the magic instance +# self.stackql_magic = StackqlServerMagic(shell=self.shell) - # Set up test data - self.query = LITERAL_INT_QUERY - self.expected_result = pd.DataFrame({"literal_int_value": [1]}) - self.statement = REGISTRY_PULL_HOMEBREW_QUERY +# # Set up test data +# self.query = LITERAL_INT_QUERY +# self.expected_result = pd.DataFrame({"literal_int_value": [1]}) +# self.statement = REGISTRY_PULL_HOMEBREW_QUERY - def test_line_magic_query(self): - """Test line magic with a query in server mode.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Execute the magic with our query - result = self.stackql_magic.stackql(line=self.query, cell=None) - - # Validate the outcome - assert result.equals(self.expected_result), "Result should match expected DataFrame" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - print_test_result("Line magic query test (server mode)", - result.equals(self.expected_result) and - 'stackql_df' in self.shell.user_ns and - self.shell.user_ns['stackql_df'].equals(self.expected_result), - True, True) +# def test_line_magic_query(self): +# """Test line magic with a query in server mode.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Execute the magic with our query +# result = self.stackql_magic.stackql(line=self.query, cell=None) + +# # Validate the outcome +# assert result.equals(self.expected_result), "Result should match expected DataFrame" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# print_test_result("Line magic query test (server mode)", +# result.equals(self.expected_result) and +# 'stackql_df' in self.shell.user_ns and +# self.shell.user_ns['stackql_df'].equals(self.expected_result), +# True, True) - def test_cell_magic_query(self): - """Test cell magic with a query in server mode.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Execute the magic with our query - result = self.stackql_magic.stackql(line="", cell=self.query) - - # Validate the outcome - assert result.equals(self.expected_result), "Result should match expected DataFrame" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - print_test_result("Cell magic query test (server mode)", - result.equals(self.expected_result) and - 'stackql_df' in self.shell.user_ns and - self.shell.user_ns['stackql_df'].equals(self.expected_result), - True, True) +# def test_cell_magic_query(self): +# """Test cell magic with a query in server mode.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Execute the magic with our query +# result = self.stackql_magic.stackql(line="", cell=self.query) + +# # Validate the outcome +# assert result.equals(self.expected_result), "Result should match expected DataFrame" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# print_test_result("Cell magic query test (server mode)", +# result.equals(self.expected_result) and +# 'stackql_df' in self.shell.user_ns and +# self.shell.user_ns['stackql_df'].equals(self.expected_result), +# True, True) - def test_cell_magic_query_no_display(self): - """Test cell magic with a query and --no-display option in server mode.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Execute the magic with our query and --no-display option - result = self.stackql_magic.stackql(line="--no-display", cell=self.query) - - # Validate the outcome - assert result is None, "Result should be None with --no-display option" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - print_test_result("Cell magic query test with --no-display (server mode)", - result is None and - 'stackql_df' in self.shell.user_ns and - self.shell.user_ns['stackql_df'].equals(self.expected_result), - True, True) - - def test_cell_magic_query_csv_download(self): - """Test cell magic with CSV download functionality in server mode.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Mock the _display_with_csv_download method to verify it's called - self.stackql_magic._display_with_csv_download = MagicMock() - - # Execute the magic with --csv-download option - result = self.stackql_magic.stackql(line="--csv-download", cell=self.query) - - # Validate the outcome - assert result.equals(self.expected_result), "Result should match expected DataFrame" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - # Verify that _display_with_csv_download was called - self.stackql_magic._display_with_csv_download.assert_called_once_with(self.expected_result) - - print_test_result("Cell magic query test with CSV download (server mode)", - result.equals(self.expected_result) and - 'stackql_df' in self.shell.user_ns and - self.stackql_magic._display_with_csv_download.called, - True, True) - - def test_cell_magic_query_csv_download_with_no_display(self): - """Test that --no-display takes precedence over --csv-download in server mode.""" - # Mock the run_query method to return a known DataFrame - self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - - # Mock the _display_with_csv_download method to verify it's not called - self.stackql_magic._display_with_csv_download = MagicMock() - - # Execute the magic with both --csv-download and --no-display options - result = self.stackql_magic.stackql(line="--csv-download --no-display", cell=self.query) - - # Validate the outcome - assert result is None, "Result should be None with --no-display option" - assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" - assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - - # Verify that _display_with_csv_download was NOT called - self.stackql_magic._display_with_csv_download.assert_not_called() - - print_test_result("Cell magic query test with CSV download and no-display (server mode)", - result is None and - 'stackql_df' in self.shell.user_ns and - not self.stackql_magic._display_with_csv_download.called, - True, True) - - def test_display_with_csv_download_method(self): - """Test the _display_with_csv_download method directly in server mode.""" - import base64 - from unittest.mock import patch - - # Create a test DataFrame - test_df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]}) - - # Mock IPython display functionality - with patch('IPython.display.display') as mock_display, \ - patch('IPython.display.HTML') as mock_html: +# def test_cell_magic_query_no_display(self): +# """Test cell magic with a query and --no-display option in server mode.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Execute the magic with our query and --no-display option +# result = self.stackql_magic.stackql(line="--no-display", cell=self.query) + +# # Validate the outcome +# assert result is None, "Result should be None with --no-display option" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# print_test_result("Cell magic query test with --no-display (server mode)", +# result is None and +# 'stackql_df' in self.shell.user_ns and +# self.shell.user_ns['stackql_df'].equals(self.expected_result), +# True, True) + +# def test_cell_magic_query_csv_download(self): +# """Test cell magic with CSV download functionality in server mode.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Mock the _display_with_csv_download method to verify it's called +# self.stackql_magic._display_with_csv_download = MagicMock() + +# # Execute the magic with --csv-download option +# result = self.stackql_magic.stackql(line="--csv-download", cell=self.query) + +# # Validate the outcome +# assert result.equals(self.expected_result), "Result should match expected DataFrame" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# # Verify that _display_with_csv_download was called +# self.stackql_magic._display_with_csv_download.assert_called_once_with(self.expected_result) + +# print_test_result("Cell magic query test with CSV download (server mode)", +# result.equals(self.expected_result) and +# 'stackql_df' in self.shell.user_ns and +# self.stackql_magic._display_with_csv_download.called, +# True, True) + +# def test_cell_magic_query_csv_download_with_no_display(self): +# """Test that --no-display takes precedence over --csv-download in server mode.""" +# # Mock the run_query method to return a known DataFrame +# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) + +# # Mock the _display_with_csv_download method to verify it's not called +# self.stackql_magic._display_with_csv_download = MagicMock() + +# # Execute the magic with both --csv-download and --no-display options +# result = self.stackql_magic.stackql(line="--csv-download --no-display", cell=self.query) + +# # Validate the outcome +# assert result is None, "Result should be None with --no-display option" +# assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" +# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" + +# # Verify that _display_with_csv_download was NOT called +# self.stackql_magic._display_with_csv_download.assert_not_called() + +# print_test_result("Cell magic query test with CSV download and no-display (server mode)", +# result is None and +# 'stackql_df' in self.shell.user_ns and +# not self.stackql_magic._display_with_csv_download.called, +# True, True) + +# def test_display_with_csv_download_method(self): +# """Test the _display_with_csv_download method directly in server mode.""" +# import base64 +# from unittest.mock import patch + +# # Create a test DataFrame +# test_df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]}) + +# # Mock IPython display functionality +# with patch('IPython.display.display') as mock_display, \ +# patch('IPython.display.HTML') as mock_html: - # Call the method - self.stackql_magic._display_with_csv_download(test_df) +# # Call the method +# self.stackql_magic._display_with_csv_download(test_df) - # Verify display was called twice (once for DataFrame, once for HTML) - assert mock_display.call_count == 2, "Display should be called twice" +# # Verify display was called twice (once for DataFrame, once for HTML) +# assert mock_display.call_count == 2, "Display should be called twice" - # Verify HTML was called once - mock_html.assert_called_once() +# # Verify HTML was called once +# mock_html.assert_called_once() - # Check that the HTML call contains download link - html_call_args = mock_html.call_args[0][0] - assert 'download="stackql_results.csv"' in html_call_args - assert 'data:text/csv;base64,' in html_call_args +# # Check that the HTML call contains download link +# html_call_args = mock_html.call_args[0][0] +# assert 'download="stackql_results.csv"' in html_call_args +# assert 'data:text/csv;base64,' in html_call_args - print_test_result("_display_with_csv_download method test (server mode)", - mock_display.call_count == 2 and mock_html.called, - True, True) - - def test_display_with_csv_download_error_handling(self): - """Test error handling in _display_with_csv_download method in server mode.""" - from unittest.mock import patch - - # Create a mock DataFrame that will raise an exception during to_csv() - mock_df = MagicMock() - mock_df.to_csv.side_effect = Exception("Test CSV error") - - # Mock IPython display functionality - with patch('IPython.display.display') as mock_display, \ - patch('IPython.display.HTML') as mock_html, \ - patch('builtins.print') as mock_print: +# print_test_result("_display_with_csv_download method test (server mode)", +# mock_display.call_count == 2 and mock_html.called, +# True, True) + +# def test_display_with_csv_download_error_handling(self): +# """Test error handling in _display_with_csv_download method in server mode.""" +# from unittest.mock import patch + +# # Create a mock DataFrame that will raise an exception during to_csv() +# mock_df = MagicMock() +# mock_df.to_csv.side_effect = Exception("Test CSV error") + +# # Mock IPython display functionality +# with patch('IPython.display.display') as mock_display, \ +# patch('IPython.display.HTML') as mock_html, \ +# patch('builtins.print') as mock_print: - # Call the method with the problematic DataFrame - self.stackql_magic._display_with_csv_download(mock_df) +# # Call the method with the problematic DataFrame +# self.stackql_magic._display_with_csv_download(mock_df) - # Verify display was called once (for DataFrame only, not for HTML) - mock_display.assert_called_once_with(mock_df) +# # Verify display was called once (for DataFrame only, not for HTML) +# mock_display.assert_called_once_with(mock_df) - # Verify HTML was not called due to error - mock_html.assert_not_called() +# # Verify HTML was not called due to error +# mock_html.assert_not_called() - # Verify error message was printed - mock_print.assert_called_once() - error_message = mock_print.call_args[0][0] - assert "Error generating CSV download:" in error_message +# # Verify error message was printed +# mock_print.assert_called_once() +# error_message = mock_print.call_args[0][0] +# assert "Error generating CSV download:" in error_message - print_test_result("_display_with_csv_download error handling test (server mode)", - mock_display.called and not mock_html.called and mock_print.called, - True, True) +# print_test_result("_display_with_csv_download error handling test (server mode)", +# mock_display.called and not mock_html.called and mock_print.called, +# True, True) + +# def test_server_magic_extension_loading(mock_interactive_shell): +# """Test that server magic extension can be loaded.""" +# # Test loading server magic +# magics.load_ipython_extension(mock_interactive_shell) +# assert hasattr(mock_interactive_shell, 'magics'), "Magic should be registered" +# assert isinstance(mock_interactive_shell.magics, StackqlServerMagic), "Registered magic should be StackqlServerMagic" + +# print_test_result("Server magic extension loading test", +# hasattr(mock_interactive_shell, 'magics') and +# isinstance(mock_interactive_shell.magics, StackqlServerMagic), +# True, True) + +# if __name__ == "__main__": +# pytest.main(["-v", __file__]) + +# tests/test_server_magic.py + +""" +Server-mode magic extension tests for PyStackQL. + +This module tests the Jupyter magic extensions for StackQL in server mode. +""" + +import os +import sys +import pytest + +# Add the parent directory to the path so we can import from pystackql +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +# Import the base test class +from tests.test_magic_base import BaseStackQLMagicTest + +# Import directly from the original modules - this is what notebooks would do +from pystackql import magics +from pystackql import StackqlServerMagic + +from tests.test_constants import print_test_result + +class TestStackQLServerMagic(BaseStackQLMagicTest): + """Tests for the server mode magic extension.""" + + # Set the class attributes for the base test class + magic_module = magics + magic_class = StackqlServerMagic + is_server_mode = True def test_server_magic_extension_loading(mock_interactive_shell): """Test that server magic extension can be loaded.""" From 5d1155df069ade48a6157dda20fb898d239f42de Mon Sep 17 00:00:00 2001 From: Jeffrey Aven Date: Thu, 12 Jun 2025 18:34:58 +1000 Subject: [PATCH 3/5] output formatting --- pystackql/magic_ext/base.py | 64 +++++++++++++++++++++++++++++------ pystackql/magic_ext/local.py | 43 +++++++++++++++++++++++ pystackql/magic_ext/server.py | 43 +++++++++++++++++++++++ tests/test_magic_base.py | 42 +++++++++++++++++++---- 4 files changed, 176 insertions(+), 16 deletions(-) diff --git a/pystackql/magic_ext/base.py b/pystackql/magic_ext/base.py index 55c03c3..9e26e42 100644 --- a/pystackql/magic_ext/base.py +++ b/pystackql/magic_ext/base.py @@ -52,10 +52,59 @@ def run_query(self, query): return self.stackql_instance.execute(query) + # def _display_with_csv_download(self, df): + # """Display DataFrame with CSV download link. + + # :param df: The DataFrame to display and make downloadable. + # """ + # import IPython.display + + # try: + # # Generate CSV data + # import io + # import base64 + # csv_buffer = io.StringIO() + # df.to_csv(csv_buffer, index=False) + # csv_data = csv_buffer.getvalue() + + # # Encode to base64 for data URI + # csv_base64 = base64.b64encode(csv_data.encode()).decode() + + # # Create download link + # download_link = f'data:text/csv;base64,{csv_base64}' + + # # # Display the DataFrame first + # # IPython.display.display(df) + + # # Create and display the download button + # download_html = f''' + #
+ # + # + # + # + # + # + # Download CSV + # + #
+ # ''' + + # IPython.display.display(IPython.display.HTML(download_html)) + + # except Exception as e: + # # If CSV generation fails, just display the DataFrame normally + # IPython.display.display(df) + # print(f"Error generating CSV download: {e}") + def _display_with_csv_download(self, df): - """Display DataFrame with CSV download link. + """Display a CSV download link for the DataFrame without displaying the DataFrame again. - :param df: The DataFrame to display and make downloadable. + :param df: The DataFrame to make downloadable. """ import IPython.display @@ -73,10 +122,7 @@ def _display_with_csv_download(self, df): # Create download link download_link = f'data:text/csv;base64,{csv_base64}' - # # Display the DataFrame first - # IPython.display.display(df) - - # Create and display the download button + # Only display the download button, not the DataFrame download_html = f'''
- ''' - + ''' IPython.display.display(IPython.display.HTML(download_html)) except Exception as e: - # If CSV generation fails, just display the DataFrame normally - IPython.display.display(df) + # If CSV generation fails, just print an error message without displaying anything print(f"Error generating CSV download: {e}") \ No newline at end of file diff --git a/pystackql/magic_ext/local.py b/pystackql/magic_ext/local.py index e88479b..6b686d0 100644 --- a/pystackql/magic_ext/local.py +++ b/pystackql/magic_ext/local.py @@ -22,6 +22,43 @@ def __init__(self, shell): """ super().__init__(shell, server_mode=False) + # @line_cell_magic + # def stackql(self, line, cell=None): + # """A Jupyter magic command to run StackQL queries. + + # Can be used as both line and cell magic: + # - As a line magic: `%stackql QUERY` + # - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. + + # :param line: The arguments and/or StackQL query when used as line magic. + # :param cell: The StackQL query when used as cell magic. + # :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). + # """ + # is_cell_magic = cell is not None + + # if is_cell_magic: + # parser = argparse.ArgumentParser() + # parser.add_argument("--no-display", action="store_true", help="Suppress result display.") + # parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") + # args = parser.parse_args(line.split()) + # query_to_run = self.get_rendered_query(cell) + # else: + # args = None + # query_to_run = self.get_rendered_query(line) + + # results = self.run_query(query_to_run) + # self.shell.user_ns['stackql_df'] = results + + # if is_cell_magic and args and args.no_display: + # return None + # elif is_cell_magic and args and args.csv_download and not args.no_display: + # self._display_with_csv_download(results) + # return results + # elif is_cell_magic and args and not args.no_display: + # return results + # elif not is_cell_magic: + # return results + @line_cell_magic def stackql(self, line, cell=None): """A Jupyter magic command to run StackQL queries. @@ -52,12 +89,18 @@ def stackql(self, line, cell=None): if is_cell_magic and args and args.no_display: return None elif is_cell_magic and args and args.csv_download and not args.no_display: + # First display the DataFrame + import IPython.display + IPython.display.display(results) + # Then add the download button without displaying the DataFrame again self._display_with_csv_download(results) return results elif is_cell_magic and args and not args.no_display: return results elif not is_cell_magic: return results + else: + return results def load_ipython_extension(ipython): """Load the non-server magic in IPython. diff --git a/pystackql/magic_ext/server.py b/pystackql/magic_ext/server.py index 70d561c..a0f9a90 100644 --- a/pystackql/magic_ext/server.py +++ b/pystackql/magic_ext/server.py @@ -22,6 +22,45 @@ def __init__(self, shell): """ super().__init__(shell, server_mode=True) + # @line_cell_magic + # def stackql(self, line, cell=None): + # """A Jupyter magic command to run StackQL queries. + + # Can be used as both line and cell magic: + # - As a line magic: `%stackql QUERY` + # - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. + + # :param line: The arguments and/or StackQL query when used as line magic. + # :param cell: The StackQL query when used as cell magic. + # :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). + # """ + # is_cell_magic = cell is not None + + # if is_cell_magic: + # parser = argparse.ArgumentParser() + # parser.add_argument("--no-display", action="store_true", help="Suppress result display.") + # parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") + # args = parser.parse_args(line.split()) + # query_to_run = self.get_rendered_query(cell) + # else: + # args = None + # query_to_run = self.get_rendered_query(line) + + # results = self.run_query(query_to_run) + # self.shell.user_ns['stackql_df'] = results + + # if is_cell_magic and args and args.no_display: + # return None + # elif is_cell_magic and args and args.csv_download and not args.no_display: + # self._display_with_csv_download(results) + # return results + # elif is_cell_magic and args and not args.no_display: + # return results + # elif not is_cell_magic: + # return results + # else: + # return results + @line_cell_magic def stackql(self, line, cell=None): """A Jupyter magic command to run StackQL queries. @@ -52,6 +91,10 @@ def stackql(self, line, cell=None): if is_cell_magic and args and args.no_display: return None elif is_cell_magic and args and args.csv_download and not args.no_display: + # First display the DataFrame + import IPython.display + IPython.display.display(results) + # Then add the download button without displaying the DataFrame again self._display_with_csv_download(results) return results elif is_cell_magic and args and not args.no_display: diff --git a/tests/test_magic_base.py b/tests/test_magic_base.py index 5e7cb88..d7de886 100644 --- a/tests/test_magic_base.py +++ b/tests/test_magic_base.py @@ -190,6 +190,36 @@ def test_display_with_csv_download_method(self): mock_display.call_count == 1 and mock_html.called, self.is_server_mode, True) + # def test_display_with_csv_download_error_handling(self): + # """Test error handling in _display_with_csv_download method.""" + + # # Create a mock DataFrame that will raise an exception during to_csv() + # mock_df = MagicMock() + # mock_df.to_csv.side_effect = Exception("Test CSV error") + + # # Mock IPython display functionality + # with patch('IPython.display.display') as mock_display, \ + # patch('IPython.display.HTML') as mock_html, \ + # patch('builtins.print') as mock_print: + + # # Call the method with the problematic DataFrame + # self.stackql_magic._display_with_csv_download(mock_df) + + # # Verify display was not called (we now only print an error message) + # mock_display.assert_not_called() + + # # Verify HTML was not called due to error + # mock_html.assert_not_called() + + # # Verify error message was printed + # mock_print.assert_called_once() + # error_message = mock_print.call_args[0][0] + # assert "Error generating CSV download:" in error_message + + # print_test_result(f"_display_with_csv_download error handling test{' (server mode)' if self.is_server_mode else ''}", + # not mock_display.called and not mock_html.called and mock_print.called, + # self.is_server_mode, True) + def test_display_with_csv_download_error_handling(self): """Test error handling in _display_with_csv_download method.""" @@ -199,16 +229,16 @@ def test_display_with_csv_download_error_handling(self): # Mock IPython display functionality with patch('IPython.display.display') as mock_display, \ - patch('IPython.display.HTML') as mock_html, \ - patch('builtins.print') as mock_print: + patch('IPython.display.HTML') as mock_html, \ + patch('builtins.print') as mock_print: # Call the method with the problematic DataFrame self.stackql_magic._display_with_csv_download(mock_df) - # Verify display was not called (we now only print an error message) + # Verify display was not called in the error case mock_display.assert_not_called() - # Verify HTML was not called due to error + # Verify HTML was not called in the error case mock_html.assert_not_called() # Verify error message was printed @@ -217,5 +247,5 @@ def test_display_with_csv_download_error_handling(self): assert "Error generating CSV download:" in error_message print_test_result(f"_display_with_csv_download error handling test{' (server mode)' if self.is_server_mode else ''}", - not mock_display.called and not mock_html.called and mock_print.called, - self.is_server_mode, True) \ No newline at end of file + not mock_display.called and not mock_html.called and mock_print.called, + self.is_server_mode, True) \ No newline at end of file From b3a4b350fc336ac7eef4cb217d5c7f59c876b511 Mon Sep 17 00:00:00 2001 From: Jeffrey Aven Date: Thu, 12 Jun 2025 18:41:20 +1000 Subject: [PATCH 4/5] output formatting --- pystackql/magic_ext/base.py | 188 ++++++++++++++++++++------- pystackql/magic_ext/local.py | 162 ++++++++++++----------- pystackql/magic_ext/server.py | 158 +++++++++++------------ tests/test_magic.py | 234 ---------------------------------- tests/test_magic_base.py | 30 ----- tests/test_server_magic.py | 232 --------------------------------- 6 files changed, 295 insertions(+), 709 deletions(-) diff --git a/pystackql/magic_ext/base.py b/pystackql/magic_ext/base.py index 9e26e42..8467425 100644 --- a/pystackql/magic_ext/base.py +++ b/pystackql/magic_ext/base.py @@ -1,3 +1,101 @@ +# # pystackql/magic_ext/base.py + +# """ +# Base Jupyter magic extension for PyStackQL. + +# This module provides the base class for PyStackQL Jupyter magic extensions. +# """ + +# from __future__ import print_function +# from IPython.core.magic import Magics +# from string import Template + +# class BaseStackqlMagic(Magics): +# """Base Jupyter magic extension enabling running StackQL queries. + +# This extension allows users to conveniently run StackQL queries against cloud +# or SaaS resources directly from Jupyter notebooks, and visualize the results in a tabular +# format using Pandas DataFrames. +# """ +# def __init__(self, shell, server_mode): +# """Initialize the BaseStackqlMagic class. + +# :param shell: The IPython shell instance. +# :param server_mode: Whether to use server mode. +# """ +# from ..core import StackQL +# super(BaseStackqlMagic, self).__init__(shell) +# self.stackql_instance = StackQL(server_mode=server_mode, output='pandas') + +# def get_rendered_query(self, data): +# """Substitute placeholders in a query template with variables from the current namespace. + +# :param data: SQL query template containing placeholders. +# :type data: str +# :return: A SQL query with placeholders substituted. +# :rtype: str +# """ +# t = Template(data) +# return t.substitute(self.shell.user_ns) + +# def run_query(self, query): +# """Execute a StackQL query + +# :param query: StackQL query to be executed. +# :type query: str +# :return: Query results, returned as a Pandas DataFrame. +# :rtype: pandas.DataFrame +# """ +# # Check if the query starts with "registry pull" (case insensitive) +# if query.strip().lower().startswith("registry pull"): +# return self.stackql_instance.executeStmt(query) + +# return self.stackql_instance.execute(query) + +# def _display_with_csv_download(self, df): +# """Display a CSV download link for the DataFrame without displaying the DataFrame again. + +# :param df: The DataFrame to make downloadable. +# """ +# import IPython.display + +# try: +# # Generate CSV data +# import io +# import base64 +# csv_buffer = io.StringIO() +# df.to_csv(csv_buffer, index=False) +# csv_data = csv_buffer.getvalue() + +# # Encode to base64 for data URI +# csv_base64 = base64.b64encode(csv_data.encode()).decode() + +# # Create download link +# download_link = f'data:text/csv;base64,{csv_base64}' + +# # Only display the download button, not the DataFrame +# download_html = f''' +#
+# +# +# +# +# +# +# Download CSV +# +#
+# ''' +# IPython.display.display(IPython.display.HTML(download_html)) + +# except Exception as e: +# # If CSV generation fails, just print an error message without displaying anything +# print(f"Error generating CSV download: {e}") + # pystackql/magic_ext/base.py """ @@ -7,8 +105,9 @@ """ from __future__ import print_function -from IPython.core.magic import Magics +from IPython.core.magic import Magics, line_cell_magic from string import Template +import argparse class BaseStackqlMagic(Magics): """Base Jupyter magic extension enabling running StackQL queries. @@ -26,6 +125,7 @@ def __init__(self, shell, server_mode): from ..core import StackQL super(BaseStackqlMagic, self).__init__(shell) self.stackql_instance = StackQL(server_mode=server_mode, output='pandas') + self.server_mode = server_mode def get_rendered_query(self, data): """Substitute placeholders in a query template with variables from the current namespace. @@ -52,55 +152,49 @@ def run_query(self, query): return self.stackql_instance.execute(query) - # def _display_with_csv_download(self, df): - # """Display DataFrame with CSV download link. + @line_cell_magic + def stackql(self, line, cell=None): + """A Jupyter magic command to run StackQL queries. - # :param df: The DataFrame to display and make downloadable. - # """ - # import IPython.display + Can be used as both line and cell magic: + - As a line magic: `%stackql QUERY` + - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. - # try: - # # Generate CSV data - # import io - # import base64 - # csv_buffer = io.StringIO() - # df.to_csv(csv_buffer, index=False) - # csv_data = csv_buffer.getvalue() - - # # Encode to base64 for data URI - # csv_base64 = base64.b64encode(csv_data.encode()).decode() - - # # Create download link - # download_link = f'data:text/csv;base64,{csv_base64}' - - # # # Display the DataFrame first - # # IPython.display.display(df) - - # # Create and display the download button - # download_html = f''' - #
- # - # - # - # - # - # - # Download CSV - # - #
- # ''' - - # IPython.display.display(IPython.display.HTML(download_html)) - - # except Exception as e: - # # If CSV generation fails, just display the DataFrame normally - # IPython.display.display(df) - # print(f"Error generating CSV download: {e}") + :param line: The arguments and/or StackQL query when used as line magic. + :param cell: The StackQL query when used as cell magic. + :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). + """ + is_cell_magic = cell is not None + if is_cell_magic: + parser = argparse.ArgumentParser() + parser.add_argument("--no-display", action="store_true", help="Suppress result display.") + parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") + args = parser.parse_args(line.split()) + query_to_run = self.get_rendered_query(cell) + else: + args = None + query_to_run = self.get_rendered_query(line) + + results = self.run_query(query_to_run) + self.shell.user_ns['stackql_df'] = results + + if is_cell_magic and args and args.no_display: + return None + elif is_cell_magic and args and args.csv_download and not args.no_display: + # First display the DataFrame + import IPython.display + IPython.display.display(results) + # Then add the download button without displaying the DataFrame again + self._display_with_csv_download(results) + return results + elif is_cell_magic and args and not args.no_display: + return results + elif not is_cell_magic: + return results + else: + return results + def _display_with_csv_download(self, df): """Display a CSV download link for the DataFrame without displaying the DataFrame again. diff --git a/pystackql/magic_ext/local.py b/pystackql/magic_ext/local.py index 6b686d0..d9a057c 100644 --- a/pystackql/magic_ext/local.py +++ b/pystackql/magic_ext/local.py @@ -1,3 +1,82 @@ +# # pystackql/magic_ext/local.py + +# """ +# Local Jupyter magic extension for PyStackQL. + +# This module provides a Jupyter magic command for running StackQL queries +# using a local StackQL binary. +# """ + +# from IPython.core.magic import (magics_class, line_cell_magic) +# from .base import BaseStackqlMagic +# import argparse + +# @magics_class +# class StackqlMagic(BaseStackqlMagic): +# """Jupyter magic command for running StackQL queries in local mode.""" + +# def __init__(self, shell): +# """Initialize the StackqlMagic class. + +# :param shell: The IPython shell instance. +# """ +# super().__init__(shell, server_mode=False) + +# @line_cell_magic +# def stackql(self, line, cell=None): +# """A Jupyter magic command to run StackQL queries. + +# Can be used as both line and cell magic: +# - As a line magic: `%stackql QUERY` +# - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. + +# :param line: The arguments and/or StackQL query when used as line magic. +# :param cell: The StackQL query when used as cell magic. +# :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). +# """ +# is_cell_magic = cell is not None + +# if is_cell_magic: +# parser = argparse.ArgumentParser() +# parser.add_argument("--no-display", action="store_true", help="Suppress result display.") +# parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") +# args = parser.parse_args(line.split()) +# query_to_run = self.get_rendered_query(cell) +# else: +# args = None +# query_to_run = self.get_rendered_query(line) + +# results = self.run_query(query_to_run) +# self.shell.user_ns['stackql_df'] = results + +# if is_cell_magic and args and args.no_display: +# return None +# elif is_cell_magic and args and args.csv_download and not args.no_display: +# # First display the DataFrame +# import IPython.display +# IPython.display.display(results) +# # Then add the download button without displaying the DataFrame again +# self._display_with_csv_download(results) +# return results +# elif is_cell_magic and args and not args.no_display: +# return results +# elif not is_cell_magic: +# return results +# else: +# return results + +# def load_ipython_extension(ipython): +# """Load the non-server magic in IPython. + +# This is called when running %load_ext pystackql.magic in a notebook. +# It registers the %stackql and %%stackql magic commands. + +# :param ipython: The IPython shell instance +# """ +# # Create an instance of the magic class and register it +# magic_instance = StackqlMagic(ipython) +# ipython.register_magics(magic_instance) + # pystackql/magic_ext/local.py """ @@ -7,9 +86,8 @@ using a local StackQL binary. """ -from IPython.core.magic import (magics_class, line_cell_magic) +from IPython.core.magic import magics_class from .base import BaseStackqlMagic -import argparse @magics_class class StackqlMagic(BaseStackqlMagic): @@ -22,86 +100,6 @@ def __init__(self, shell): """ super().__init__(shell, server_mode=False) - # @line_cell_magic - # def stackql(self, line, cell=None): - # """A Jupyter magic command to run StackQL queries. - - # Can be used as both line and cell magic: - # - As a line magic: `%stackql QUERY` - # - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. - - # :param line: The arguments and/or StackQL query when used as line magic. - # :param cell: The StackQL query when used as cell magic. - # :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). - # """ - # is_cell_magic = cell is not None - - # if is_cell_magic: - # parser = argparse.ArgumentParser() - # parser.add_argument("--no-display", action="store_true", help="Suppress result display.") - # parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") - # args = parser.parse_args(line.split()) - # query_to_run = self.get_rendered_query(cell) - # else: - # args = None - # query_to_run = self.get_rendered_query(line) - - # results = self.run_query(query_to_run) - # self.shell.user_ns['stackql_df'] = results - - # if is_cell_magic and args and args.no_display: - # return None - # elif is_cell_magic and args and args.csv_download and not args.no_display: - # self._display_with_csv_download(results) - # return results - # elif is_cell_magic and args and not args.no_display: - # return results - # elif not is_cell_magic: - # return results - - @line_cell_magic - def stackql(self, line, cell=None): - """A Jupyter magic command to run StackQL queries. - - Can be used as both line and cell magic: - - As a line magic: `%stackql QUERY` - - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. - - :param line: The arguments and/or StackQL query when used as line magic. - :param cell: The StackQL query when used as cell magic. - :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). - """ - is_cell_magic = cell is not None - - if is_cell_magic: - parser = argparse.ArgumentParser() - parser.add_argument("--no-display", action="store_true", help="Suppress result display.") - parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") - args = parser.parse_args(line.split()) - query_to_run = self.get_rendered_query(cell) - else: - args = None - query_to_run = self.get_rendered_query(line) - - results = self.run_query(query_to_run) - self.shell.user_ns['stackql_df'] = results - - if is_cell_magic and args and args.no_display: - return None - elif is_cell_magic and args and args.csv_download and not args.no_display: - # First display the DataFrame - import IPython.display - IPython.display.display(results) - # Then add the download button without displaying the DataFrame again - self._display_with_csv_download(results) - return results - elif is_cell_magic and args and not args.no_display: - return results - elif not is_cell_magic: - return results - else: - return results - def load_ipython_extension(ipython): """Load the non-server magic in IPython. diff --git a/pystackql/magic_ext/server.py b/pystackql/magic_ext/server.py index a0f9a90..9853fd0 100644 --- a/pystackql/magic_ext/server.py +++ b/pystackql/magic_ext/server.py @@ -1,3 +1,76 @@ +# # pystackql/magic_ext/server.py + +# """ +# Server Jupyter magic extension for PyStackQL. + +# This module provides a Jupyter magic command for running StackQL queries +# using a StackQL server connection. +# """ + +# from IPython.core.magic import (magics_class, line_cell_magic) +# from .base import BaseStackqlMagic +# import argparse + +# @magics_class +# class StackqlServerMagic(BaseStackqlMagic): +# """Jupyter magic command for running StackQL queries in server mode.""" + +# def __init__(self, shell): +# """Initialize the StackqlServerMagic class. + +# :param shell: The IPython shell instance. +# """ +# super().__init__(shell, server_mode=True) + +# @line_cell_magic +# def stackql(self, line, cell=None): +# """A Jupyter magic command to run StackQL queries. + +# Can be used as both line and cell magic: +# - As a line magic: `%stackql QUERY` +# - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. + +# :param line: The arguments and/or StackQL query when used as line magic. +# :param cell: The StackQL query when used as cell magic. +# :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). +# """ +# is_cell_magic = cell is not None + +# if is_cell_magic: +# parser = argparse.ArgumentParser() +# parser.add_argument("--no-display", action="store_true", help="Suppress result display.") +# parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") +# args = parser.parse_args(line.split()) +# query_to_run = self.get_rendered_query(cell) +# else: +# args = None +# query_to_run = self.get_rendered_query(line) + +# results = self.run_query(query_to_run) +# self.shell.user_ns['stackql_df'] = results + +# if is_cell_magic and args and args.no_display: +# return None +# elif is_cell_magic and args and args.csv_download and not args.no_display: +# # First display the DataFrame +# import IPython.display +# IPython.display.display(results) +# # Then add the download button without displaying the DataFrame again +# self._display_with_csv_download(results) +# return results +# elif is_cell_magic and args and not args.no_display: +# return results +# elif not is_cell_magic: +# return results +# else: +# return results + +# def load_ipython_extension(ipython): +# """Load the server magic in IPython.""" +# # Create an instance of the magic class and register it +# magic_instance = StackqlServerMagic(ipython) +# ipython.register_magics(magic_instance) + # pystackql/magic_ext/server.py """ @@ -7,9 +80,8 @@ using a StackQL server connection. """ -from IPython.core.magic import (magics_class, line_cell_magic) +from IPython.core.magic import magics_class from .base import BaseStackqlMagic -import argparse @magics_class class StackqlServerMagic(BaseStackqlMagic): @@ -22,88 +94,6 @@ def __init__(self, shell): """ super().__init__(shell, server_mode=True) - # @line_cell_magic - # def stackql(self, line, cell=None): - # """A Jupyter magic command to run StackQL queries. - - # Can be used as both line and cell magic: - # - As a line magic: `%stackql QUERY` - # - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. - - # :param line: The arguments and/or StackQL query when used as line magic. - # :param cell: The StackQL query when used as cell magic. - # :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). - # """ - # is_cell_magic = cell is not None - - # if is_cell_magic: - # parser = argparse.ArgumentParser() - # parser.add_argument("--no-display", action="store_true", help="Suppress result display.") - # parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") - # args = parser.parse_args(line.split()) - # query_to_run = self.get_rendered_query(cell) - # else: - # args = None - # query_to_run = self.get_rendered_query(line) - - # results = self.run_query(query_to_run) - # self.shell.user_ns['stackql_df'] = results - - # if is_cell_magic and args and args.no_display: - # return None - # elif is_cell_magic and args and args.csv_download and not args.no_display: - # self._display_with_csv_download(results) - # return results - # elif is_cell_magic and args and not args.no_display: - # return results - # elif not is_cell_magic: - # return results - # else: - # return results - - @line_cell_magic - def stackql(self, line, cell=None): - """A Jupyter magic command to run StackQL queries. - - Can be used as both line and cell magic: - - As a line magic: `%stackql QUERY` - - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. - - :param line: The arguments and/or StackQL query when used as line magic. - :param cell: The StackQL query when used as cell magic. - :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). - """ - is_cell_magic = cell is not None - - if is_cell_magic: - parser = argparse.ArgumentParser() - parser.add_argument("--no-display", action="store_true", help="Suppress result display.") - parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") - args = parser.parse_args(line.split()) - query_to_run = self.get_rendered_query(cell) - else: - args = None - query_to_run = self.get_rendered_query(line) - - results = self.run_query(query_to_run) - self.shell.user_ns['stackql_df'] = results - - if is_cell_magic and args and args.no_display: - return None - elif is_cell_magic and args and args.csv_download and not args.no_display: - # First display the DataFrame - import IPython.display - IPython.display.display(results) - # Then add the download button without displaying the DataFrame again - self._display_with_csv_download(results) - return results - elif is_cell_magic and args and not args.no_display: - return results - elif not is_cell_magic: - return results - else: - return results - def load_ipython_extension(ipython): """Load the server magic in IPython.""" # Create an instance of the magic class and register it diff --git a/tests/test_magic.py b/tests/test_magic.py index 67aef93..edf1bdf 100644 --- a/tests/test_magic.py +++ b/tests/test_magic.py @@ -1,237 +1,3 @@ -# # tests/test_magic.py - -# """ -# Non-server magic extension tests for PyStackQL. - -# This module tests the Jupyter magic extensions for StackQL in non-server mode. -# """ - -# import os -# import sys -# import re -# import pytest -# import pandas as pd -# from unittest.mock import MagicMock - -# # Add the parent directory to the path so we can import from pystackql -# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -# # Add the current directory to the path so we can import test_constants -# sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) - -# # Import directly from the original modules - this is what notebooks would do -# from pystackql import magic -# from pystackql import StackqlMagic - -# from tests.test_constants import ( -# LITERAL_INT_QUERY, -# REGISTRY_PULL_HOMEBREW_QUERY, -# registry_pull_resp_pattern, -# print_test_result -# ) - -# class TestStackQLMagic: -# """Tests for the non-server mode magic extension.""" - -# @pytest.fixture(autouse=True) -# def setup_method(self, mock_interactive_shell): -# """Set up the test environment.""" -# self.shell = mock_interactive_shell - -# # Load the magic extension -# magic.load_ipython_extension(self.shell) - -# # Create the magic instance -# self.stackql_magic = StackqlMagic(shell=self.shell) - -# # Set up test data -# self.query = LITERAL_INT_QUERY -# self.expected_result = pd.DataFrame({"literal_int_value": [1]}) -# self.statement = REGISTRY_PULL_HOMEBREW_QUERY - -# def test_line_magic_query(self): -# """Test line magic with a query.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Execute the magic with our query -# result = self.stackql_magic.stackql(line=self.query, cell=None) - -# # Validate the outcome -# assert result.equals(self.expected_result), "Result should match expected DataFrame" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# print_test_result("Line magic query test", -# result.equals(self.expected_result) and -# 'stackql_df' in self.shell.user_ns and -# self.shell.user_ns['stackql_df'].equals(self.expected_result), -# False, True) - -# def test_cell_magic_query(self): -# """Test cell magic with a query.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Execute the magic with our query -# result = self.stackql_magic.stackql(line="", cell=self.query) - -# # Validate the outcome -# assert result.equals(self.expected_result), "Result should match expected DataFrame" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# print_test_result("Cell magic query test", -# result.equals(self.expected_result) and -# 'stackql_df' in self.shell.user_ns and -# self.shell.user_ns['stackql_df'].equals(self.expected_result), -# False, True) - -# def test_cell_magic_query_no_display(self): -# """Test cell magic with a query and --no-display option.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Execute the magic with our query and --no-display option -# result = self.stackql_magic.stackql(line="--no-display", cell=self.query) - -# # Validate the outcome -# assert result is None, "Result should be None with --no-display option" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# print_test_result("Cell magic query test (with --no-display)", -# result is None and -# 'stackql_df' in self.shell.user_ns and -# self.shell.user_ns['stackql_df'].equals(self.expected_result), -# False, True) - -# def test_cell_magic_query_csv_download(self): -# """Test cell magic with CSV download functionality.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Mock the _display_with_csv_download method to verify it's called -# self.stackql_magic._display_with_csv_download = MagicMock() - -# # Execute the magic with --csv-download option -# result = self.stackql_magic.stackql(line="--csv-download", cell=self.query) - -# # Validate the outcome -# assert result.equals(self.expected_result), "Result should match expected DataFrame" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# # Verify that _display_with_csv_download was called -# self.stackql_magic._display_with_csv_download.assert_called_once_with(self.expected_result) - -# print_test_result("Cell magic query test with CSV download", -# result.equals(self.expected_result) and -# 'stackql_df' in self.shell.user_ns and -# self.stackql_magic._display_with_csv_download.called, -# False, True) - -# def test_cell_magic_query_csv_download_with_no_display(self): -# """Test that --no-display takes precedence over --csv-download.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Mock the _display_with_csv_download method to verify it's not called -# self.stackql_magic._display_with_csv_download = MagicMock() - -# # Execute the magic with both --csv-download and --no-display options -# result = self.stackql_magic.stackql(line="--csv-download --no-display", cell=self.query) - -# # Validate the outcome -# assert result is None, "Result should be None with --no-display option" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# # Verify that _display_with_csv_download was NOT called -# self.stackql_magic._display_with_csv_download.assert_not_called() - -# print_test_result("Cell magic query test with CSV download and no-display", -# result is None and -# 'stackql_df' in self.shell.user_ns and -# not self.stackql_magic._display_with_csv_download.called, -# False, True) - -# def test_display_with_csv_download_method(self): -# """Test the _display_with_csv_download method directly.""" -# import base64 -# from unittest.mock import patch - -# # Create a test DataFrame -# test_df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]}) - -# # Mock IPython display functionality -# with patch('IPython.display.display') as mock_display, \ -# patch('IPython.display.HTML') as mock_html: - -# # Call the method -# self.stackql_magic._display_with_csv_download(test_df) - -# # Verify display was called twice (once for DataFrame, once for HTML) -# assert mock_display.call_count == 2, "Display should be called twice" - -# # Verify HTML was called once -# mock_html.assert_called_once() - -# # Check that the HTML call contains download link -# html_call_args = mock_html.call_args[0][0] -# assert 'download="stackql_results.csv"' in html_call_args -# assert 'data:text/csv;base64,' in html_call_args - -# print_test_result("_display_with_csv_download method test", -# mock_display.call_count == 2 and mock_html.called, -# False, True) - -# def test_display_with_csv_download_error_handling(self): -# """Test error handling in _display_with_csv_download method.""" -# from unittest.mock import patch - -# # Create a mock DataFrame that will raise an exception during to_csv() -# mock_df = MagicMock() -# mock_df.to_csv.side_effect = Exception("Test CSV error") - -# # Mock IPython display functionality -# with patch('IPython.display.display') as mock_display, \ -# patch('IPython.display.HTML') as mock_html, \ -# patch('builtins.print') as mock_print: - -# # Call the method with the problematic DataFrame -# self.stackql_magic._display_with_csv_download(mock_df) - -# # Verify display was called once (for DataFrame only, not for HTML) -# mock_display.assert_called_once_with(mock_df) - -# # Verify HTML was not called due to error -# mock_html.assert_not_called() - -# # Verify error message was printed -# mock_print.assert_called_once() -# error_message = mock_print.call_args[0][0] -# assert "Error generating CSV download:" in error_message - -# print_test_result("_display_with_csv_download error handling test", -# mock_display.called and not mock_html.called and mock_print.called, -# False, True) - -# def test_magic_extension_loading(mock_interactive_shell): -# """Test that non-server magic extension can be loaded.""" -# # Test loading non-server magic -# magic.load_ipython_extension(mock_interactive_shell) -# assert hasattr(mock_interactive_shell, 'magics'), "Magic should be registered" -# assert isinstance(mock_interactive_shell.magics, StackqlMagic), "Registered magic should be StackqlMagic" - -# print_test_result("Magic extension loading test", -# hasattr(mock_interactive_shell, 'magics') and -# isinstance(mock_interactive_shell.magics, StackqlMagic), -# False, True) - -# if __name__ == "__main__": -# pytest.main(["-v", __file__]) - # tests/test_magic.py """ diff --git a/tests/test_magic_base.py b/tests/test_magic_base.py index d7de886..e5e7b71 100644 --- a/tests/test_magic_base.py +++ b/tests/test_magic_base.py @@ -190,36 +190,6 @@ def test_display_with_csv_download_method(self): mock_display.call_count == 1 and mock_html.called, self.is_server_mode, True) - # def test_display_with_csv_download_error_handling(self): - # """Test error handling in _display_with_csv_download method.""" - - # # Create a mock DataFrame that will raise an exception during to_csv() - # mock_df = MagicMock() - # mock_df.to_csv.side_effect = Exception("Test CSV error") - - # # Mock IPython display functionality - # with patch('IPython.display.display') as mock_display, \ - # patch('IPython.display.HTML') as mock_html, \ - # patch('builtins.print') as mock_print: - - # # Call the method with the problematic DataFrame - # self.stackql_magic._display_with_csv_download(mock_df) - - # # Verify display was not called (we now only print an error message) - # mock_display.assert_not_called() - - # # Verify HTML was not called due to error - # mock_html.assert_not_called() - - # # Verify error message was printed - # mock_print.assert_called_once() - # error_message = mock_print.call_args[0][0] - # assert "Error generating CSV download:" in error_message - - # print_test_result(f"_display_with_csv_download error handling test{' (server mode)' if self.is_server_mode else ''}", - # not mock_display.called and not mock_html.called and mock_print.called, - # self.is_server_mode, True) - def test_display_with_csv_download_error_handling(self): """Test error handling in _display_with_csv_download method.""" diff --git a/tests/test_server_magic.py b/tests/test_server_magic.py index 3f79da9..d39084d 100644 --- a/tests/test_server_magic.py +++ b/tests/test_server_magic.py @@ -1,235 +1,3 @@ -# """ -# Server-mode magic extension tests for PyStackQL. - -# This module tests the Jupyter magic extensions for StackQL in server mode. -# """ - -# import os -# import sys -# import re -# import pytest -# import pandas as pd -# from unittest.mock import MagicMock - -# # Add the parent directory to the path so we can import from pystackql -# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -# # Add the current directory to the path so we can import test_constants -# sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) - -# # Import directly from the original modules - this is what notebooks would do -# from pystackql import magics -# from pystackql import StackqlServerMagic - -# from tests.test_constants import ( -# LITERAL_INT_QUERY, -# REGISTRY_PULL_HOMEBREW_QUERY, -# registry_pull_resp_pattern, -# print_test_result -# ) - -# class TestStackQLServerMagic: -# """Tests for the server mode magic extension.""" - -# @pytest.fixture(autouse=True) -# def setup_method(self, mock_interactive_shell): -# """Set up the test environment.""" -# self.shell = mock_interactive_shell - -# # Load the magic extension -# magics.load_ipython_extension(self.shell) - -# # Create the magic instance -# self.stackql_magic = StackqlServerMagic(shell=self.shell) - -# # Set up test data -# self.query = LITERAL_INT_QUERY -# self.expected_result = pd.DataFrame({"literal_int_value": [1]}) -# self.statement = REGISTRY_PULL_HOMEBREW_QUERY - -# def test_line_magic_query(self): -# """Test line magic with a query in server mode.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Execute the magic with our query -# result = self.stackql_magic.stackql(line=self.query, cell=None) - -# # Validate the outcome -# assert result.equals(self.expected_result), "Result should match expected DataFrame" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# print_test_result("Line magic query test (server mode)", -# result.equals(self.expected_result) and -# 'stackql_df' in self.shell.user_ns and -# self.shell.user_ns['stackql_df'].equals(self.expected_result), -# True, True) - -# def test_cell_magic_query(self): -# """Test cell magic with a query in server mode.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Execute the magic with our query -# result = self.stackql_magic.stackql(line="", cell=self.query) - -# # Validate the outcome -# assert result.equals(self.expected_result), "Result should match expected DataFrame" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# print_test_result("Cell magic query test (server mode)", -# result.equals(self.expected_result) and -# 'stackql_df' in self.shell.user_ns and -# self.shell.user_ns['stackql_df'].equals(self.expected_result), -# True, True) - -# def test_cell_magic_query_no_display(self): -# """Test cell magic with a query and --no-display option in server mode.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Execute the magic with our query and --no-display option -# result = self.stackql_magic.stackql(line="--no-display", cell=self.query) - -# # Validate the outcome -# assert result is None, "Result should be None with --no-display option" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# print_test_result("Cell magic query test with --no-display (server mode)", -# result is None and -# 'stackql_df' in self.shell.user_ns and -# self.shell.user_ns['stackql_df'].equals(self.expected_result), -# True, True) - -# def test_cell_magic_query_csv_download(self): -# """Test cell magic with CSV download functionality in server mode.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Mock the _display_with_csv_download method to verify it's called -# self.stackql_magic._display_with_csv_download = MagicMock() - -# # Execute the magic with --csv-download option -# result = self.stackql_magic.stackql(line="--csv-download", cell=self.query) - -# # Validate the outcome -# assert result.equals(self.expected_result), "Result should match expected DataFrame" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# # Verify that _display_with_csv_download was called -# self.stackql_magic._display_with_csv_download.assert_called_once_with(self.expected_result) - -# print_test_result("Cell magic query test with CSV download (server mode)", -# result.equals(self.expected_result) and -# 'stackql_df' in self.shell.user_ns and -# self.stackql_magic._display_with_csv_download.called, -# True, True) - -# def test_cell_magic_query_csv_download_with_no_display(self): -# """Test that --no-display takes precedence over --csv-download in server mode.""" -# # Mock the run_query method to return a known DataFrame -# self.stackql_magic.run_query = MagicMock(return_value=self.expected_result) - -# # Mock the _display_with_csv_download method to verify it's not called -# self.stackql_magic._display_with_csv_download = MagicMock() - -# # Execute the magic with both --csv-download and --no-display options -# result = self.stackql_magic.stackql(line="--csv-download --no-display", cell=self.query) - -# # Validate the outcome -# assert result is None, "Result should be None with --no-display option" -# assert 'stackql_df' in self.shell.user_ns, "stackql_df should still be in user namespace" -# assert self.shell.user_ns['stackql_df'].equals(self.expected_result), "stackql_df should match expected DataFrame" - -# # Verify that _display_with_csv_download was NOT called -# self.stackql_magic._display_with_csv_download.assert_not_called() - -# print_test_result("Cell magic query test with CSV download and no-display (server mode)", -# result is None and -# 'stackql_df' in self.shell.user_ns and -# not self.stackql_magic._display_with_csv_download.called, -# True, True) - -# def test_display_with_csv_download_method(self): -# """Test the _display_with_csv_download method directly in server mode.""" -# import base64 -# from unittest.mock import patch - -# # Create a test DataFrame -# test_df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]}) - -# # Mock IPython display functionality -# with patch('IPython.display.display') as mock_display, \ -# patch('IPython.display.HTML') as mock_html: - -# # Call the method -# self.stackql_magic._display_with_csv_download(test_df) - -# # Verify display was called twice (once for DataFrame, once for HTML) -# assert mock_display.call_count == 2, "Display should be called twice" - -# # Verify HTML was called once -# mock_html.assert_called_once() - -# # Check that the HTML call contains download link -# html_call_args = mock_html.call_args[0][0] -# assert 'download="stackql_results.csv"' in html_call_args -# assert 'data:text/csv;base64,' in html_call_args - -# print_test_result("_display_with_csv_download method test (server mode)", -# mock_display.call_count == 2 and mock_html.called, -# True, True) - -# def test_display_with_csv_download_error_handling(self): -# """Test error handling in _display_with_csv_download method in server mode.""" -# from unittest.mock import patch - -# # Create a mock DataFrame that will raise an exception during to_csv() -# mock_df = MagicMock() -# mock_df.to_csv.side_effect = Exception("Test CSV error") - -# # Mock IPython display functionality -# with patch('IPython.display.display') as mock_display, \ -# patch('IPython.display.HTML') as mock_html, \ -# patch('builtins.print') as mock_print: - -# # Call the method with the problematic DataFrame -# self.stackql_magic._display_with_csv_download(mock_df) - -# # Verify display was called once (for DataFrame only, not for HTML) -# mock_display.assert_called_once_with(mock_df) - -# # Verify HTML was not called due to error -# mock_html.assert_not_called() - -# # Verify error message was printed -# mock_print.assert_called_once() -# error_message = mock_print.call_args[0][0] -# assert "Error generating CSV download:" in error_message - -# print_test_result("_display_with_csv_download error handling test (server mode)", -# mock_display.called and not mock_html.called and mock_print.called, -# True, True) - -# def test_server_magic_extension_loading(mock_interactive_shell): -# """Test that server magic extension can be loaded.""" -# # Test loading server magic -# magics.load_ipython_extension(mock_interactive_shell) -# assert hasattr(mock_interactive_shell, 'magics'), "Magic should be registered" -# assert isinstance(mock_interactive_shell.magics, StackqlServerMagic), "Registered magic should be StackqlServerMagic" - -# print_test_result("Server magic extension loading test", -# hasattr(mock_interactive_shell, 'magics') and -# isinstance(mock_interactive_shell.magics, StackqlServerMagic), -# True, True) - -# if __name__ == "__main__": -# pytest.main(["-v", __file__]) - # tests/test_server_magic.py """ From 77658191a0a338489acef11a1bdff7b1a80638d4 Mon Sep 17 00:00:00 2001 From: Jeffrey Aven Date: Thu, 12 Jun 2025 18:44:08 +1000 Subject: [PATCH 5/5] output formatting --- pystackql/magic_ext/base.py | 98 ----------------------------------- pystackql/magic_ext/local.py | 79 ---------------------------- pystackql/magic_ext/server.py | 73 -------------------------- 3 files changed, 250 deletions(-) diff --git a/pystackql/magic_ext/base.py b/pystackql/magic_ext/base.py index 8467425..b48d770 100644 --- a/pystackql/magic_ext/base.py +++ b/pystackql/magic_ext/base.py @@ -1,101 +1,3 @@ -# # pystackql/magic_ext/base.py - -# """ -# Base Jupyter magic extension for PyStackQL. - -# This module provides the base class for PyStackQL Jupyter magic extensions. -# """ - -# from __future__ import print_function -# from IPython.core.magic import Magics -# from string import Template - -# class BaseStackqlMagic(Magics): -# """Base Jupyter magic extension enabling running StackQL queries. - -# This extension allows users to conveniently run StackQL queries against cloud -# or SaaS resources directly from Jupyter notebooks, and visualize the results in a tabular -# format using Pandas DataFrames. -# """ -# def __init__(self, shell, server_mode): -# """Initialize the BaseStackqlMagic class. - -# :param shell: The IPython shell instance. -# :param server_mode: Whether to use server mode. -# """ -# from ..core import StackQL -# super(BaseStackqlMagic, self).__init__(shell) -# self.stackql_instance = StackQL(server_mode=server_mode, output='pandas') - -# def get_rendered_query(self, data): -# """Substitute placeholders in a query template with variables from the current namespace. - -# :param data: SQL query template containing placeholders. -# :type data: str -# :return: A SQL query with placeholders substituted. -# :rtype: str -# """ -# t = Template(data) -# return t.substitute(self.shell.user_ns) - -# def run_query(self, query): -# """Execute a StackQL query - -# :param query: StackQL query to be executed. -# :type query: str -# :return: Query results, returned as a Pandas DataFrame. -# :rtype: pandas.DataFrame -# """ -# # Check if the query starts with "registry pull" (case insensitive) -# if query.strip().lower().startswith("registry pull"): -# return self.stackql_instance.executeStmt(query) - -# return self.stackql_instance.execute(query) - -# def _display_with_csv_download(self, df): -# """Display a CSV download link for the DataFrame without displaying the DataFrame again. - -# :param df: The DataFrame to make downloadable. -# """ -# import IPython.display - -# try: -# # Generate CSV data -# import io -# import base64 -# csv_buffer = io.StringIO() -# df.to_csv(csv_buffer, index=False) -# csv_data = csv_buffer.getvalue() - -# # Encode to base64 for data URI -# csv_base64 = base64.b64encode(csv_data.encode()).decode() - -# # Create download link -# download_link = f'data:text/csv;base64,{csv_base64}' - -# # Only display the download button, not the DataFrame -# download_html = f''' -#
-# -# -# -# -# -# -# Download CSV -# -#
-# ''' -# IPython.display.display(IPython.display.HTML(download_html)) - -# except Exception as e: -# # If CSV generation fails, just print an error message without displaying anything -# print(f"Error generating CSV download: {e}") - # pystackql/magic_ext/base.py """ diff --git a/pystackql/magic_ext/local.py b/pystackql/magic_ext/local.py index d9a057c..1830249 100644 --- a/pystackql/magic_ext/local.py +++ b/pystackql/magic_ext/local.py @@ -1,82 +1,3 @@ -# # pystackql/magic_ext/local.py - -# """ -# Local Jupyter magic extension for PyStackQL. - -# This module provides a Jupyter magic command for running StackQL queries -# using a local StackQL binary. -# """ - -# from IPython.core.magic import (magics_class, line_cell_magic) -# from .base import BaseStackqlMagic -# import argparse - -# @magics_class -# class StackqlMagic(BaseStackqlMagic): -# """Jupyter magic command for running StackQL queries in local mode.""" - -# def __init__(self, shell): -# """Initialize the StackqlMagic class. - -# :param shell: The IPython shell instance. -# """ -# super().__init__(shell, server_mode=False) - -# @line_cell_magic -# def stackql(self, line, cell=None): -# """A Jupyter magic command to run StackQL queries. - -# Can be used as both line and cell magic: -# - As a line magic: `%stackql QUERY` -# - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. - -# :param line: The arguments and/or StackQL query when used as line magic. -# :param cell: The StackQL query when used as cell magic. -# :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). -# """ -# is_cell_magic = cell is not None - -# if is_cell_magic: -# parser = argparse.ArgumentParser() -# parser.add_argument("--no-display", action="store_true", help="Suppress result display.") -# parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") -# args = parser.parse_args(line.split()) -# query_to_run = self.get_rendered_query(cell) -# else: -# args = None -# query_to_run = self.get_rendered_query(line) - -# results = self.run_query(query_to_run) -# self.shell.user_ns['stackql_df'] = results - -# if is_cell_magic and args and args.no_display: -# return None -# elif is_cell_magic and args and args.csv_download and not args.no_display: -# # First display the DataFrame -# import IPython.display -# IPython.display.display(results) -# # Then add the download button without displaying the DataFrame again -# self._display_with_csv_download(results) -# return results -# elif is_cell_magic and args and not args.no_display: -# return results -# elif not is_cell_magic: -# return results -# else: -# return results - -# def load_ipython_extension(ipython): -# """Load the non-server magic in IPython. - -# This is called when running %load_ext pystackql.magic in a notebook. -# It registers the %stackql and %%stackql magic commands. - -# :param ipython: The IPython shell instance -# """ -# # Create an instance of the magic class and register it -# magic_instance = StackqlMagic(ipython) -# ipython.register_magics(magic_instance) - # pystackql/magic_ext/local.py """ diff --git a/pystackql/magic_ext/server.py b/pystackql/magic_ext/server.py index 9853fd0..2c6d8f1 100644 --- a/pystackql/magic_ext/server.py +++ b/pystackql/magic_ext/server.py @@ -1,76 +1,3 @@ -# # pystackql/magic_ext/server.py - -# """ -# Server Jupyter magic extension for PyStackQL. - -# This module provides a Jupyter magic command for running StackQL queries -# using a StackQL server connection. -# """ - -# from IPython.core.magic import (magics_class, line_cell_magic) -# from .base import BaseStackqlMagic -# import argparse - -# @magics_class -# class StackqlServerMagic(BaseStackqlMagic): -# """Jupyter magic command for running StackQL queries in server mode.""" - -# def __init__(self, shell): -# """Initialize the StackqlServerMagic class. - -# :param shell: The IPython shell instance. -# """ -# super().__init__(shell, server_mode=True) - -# @line_cell_magic -# def stackql(self, line, cell=None): -# """A Jupyter magic command to run StackQL queries. - -# Can be used as both line and cell magic: -# - As a line magic: `%stackql QUERY` -# - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line. - -# :param line: The arguments and/or StackQL query when used as line magic. -# :param cell: The StackQL query when used as cell magic. -# :return: StackQL query results as a named Pandas DataFrame (`stackql_df`). -# """ -# is_cell_magic = cell is not None - -# if is_cell_magic: -# parser = argparse.ArgumentParser() -# parser.add_argument("--no-display", action="store_true", help="Suppress result display.") -# parser.add_argument("--csv-download", action="store_true", help="Add CSV download link to output.") -# args = parser.parse_args(line.split()) -# query_to_run = self.get_rendered_query(cell) -# else: -# args = None -# query_to_run = self.get_rendered_query(line) - -# results = self.run_query(query_to_run) -# self.shell.user_ns['stackql_df'] = results - -# if is_cell_magic and args and args.no_display: -# return None -# elif is_cell_magic and args and args.csv_download and not args.no_display: -# # First display the DataFrame -# import IPython.display -# IPython.display.display(results) -# # Then add the download button without displaying the DataFrame again -# self._display_with_csv_download(results) -# return results -# elif is_cell_magic and args and not args.no_display: -# return results -# elif not is_cell_magic: -# return results -# else: -# return results - -# def load_ipython_extension(ipython): -# """Load the server magic in IPython.""" -# # Create an instance of the magic class and register it -# magic_instance = StackqlServerMagic(ipython) -# ipython.register_magics(magic_instance) - # pystackql/magic_ext/server.py """