1+ # # pystackql/magic_ext/base.py
2+
3+ # """
4+ # Base Jupyter magic extension for PyStackQL.
5+
6+ # This module provides the base class for PyStackQL Jupyter magic extensions.
7+ # """
8+
9+ # from __future__ import print_function
10+ # from IPython.core.magic import Magics
11+ # from string import Template
12+
13+ # class BaseStackqlMagic(Magics):
14+ # """Base Jupyter magic extension enabling running StackQL queries.
15+
16+ # This extension allows users to conveniently run StackQL queries against cloud
17+ # or SaaS resources directly from Jupyter notebooks, and visualize the results in a tabular
18+ # format using Pandas DataFrames.
19+ # """
20+ # def __init__(self, shell, server_mode):
21+ # """Initialize the BaseStackqlMagic class.
22+
23+ # :param shell: The IPython shell instance.
24+ # :param server_mode: Whether to use server mode.
25+ # """
26+ # from ..core import StackQL
27+ # super(BaseStackqlMagic, self).__init__(shell)
28+ # self.stackql_instance = StackQL(server_mode=server_mode, output='pandas')
29+
30+ # def get_rendered_query(self, data):
31+ # """Substitute placeholders in a query template with variables from the current namespace.
32+
33+ # :param data: SQL query template containing placeholders.
34+ # :type data: str
35+ # :return: A SQL query with placeholders substituted.
36+ # :rtype: str
37+ # """
38+ # t = Template(data)
39+ # return t.substitute(self.shell.user_ns)
40+
41+ # def run_query(self, query):
42+ # """Execute a StackQL query
43+
44+ # :param query: StackQL query to be executed.
45+ # :type query: str
46+ # :return: Query results, returned as a Pandas DataFrame.
47+ # :rtype: pandas.DataFrame
48+ # """
49+ # # Check if the query starts with "registry pull" (case insensitive)
50+ # if query.strip().lower().startswith("registry pull"):
51+ # return self.stackql_instance.executeStmt(query)
52+
53+ # return self.stackql_instance.execute(query)
54+
55+ # def _display_with_csv_download(self, df):
56+ # """Display a CSV download link for the DataFrame without displaying the DataFrame again.
57+
58+ # :param df: The DataFrame to make downloadable.
59+ # """
60+ # import IPython.display
61+
62+ # try:
63+ # # Generate CSV data
64+ # import io
65+ # import base64
66+ # csv_buffer = io.StringIO()
67+ # df.to_csv(csv_buffer, index=False)
68+ # csv_data = csv_buffer.getvalue()
69+
70+ # # Encode to base64 for data URI
71+ # csv_base64 = base64.b64encode(csv_data.encode()).decode()
72+
73+ # # Create download link
74+ # download_link = f'data:text/csv;base64,{csv_base64}'
75+
76+ # # Only display the download button, not the DataFrame
77+ # download_html = f'''
78+ # <div style="margin-top: 15px; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;">
79+ # <a href="{download_link}" download="stackql_results.csv"
80+ # style="display: inline-flex; align-items: center; gap: 8px; padding: 9px 16px;
81+ # background-color: #2196F3; color: white; text-decoration: none;
82+ # border-radius: 4px; font-size: 14px; font-weight: 500;
83+ # box-shadow: 0 2px 4px rgba(0,0,0,0.08); transition: all 0.2s ease;">
84+ # <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
85+ # <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
86+ # <polyline points="7 10 12 15 17 10"></polyline>
87+ # <line x1="12" y1="15" x2="12" y2="3"></line>
88+ # </svg>
89+ # Download CSV
90+ # </a>
91+ # </div>
92+ # '''
93+ # IPython.display.display(IPython.display.HTML(download_html))
94+
95+ # except Exception as e:
96+ # # If CSV generation fails, just print an error message without displaying anything
97+ # print(f"Error generating CSV download: {e}")
98+
199# pystackql/magic_ext/base.py
2100
3101"""
7105"""
8106
9107from __future__ import print_function
10- from IPython .core .magic import Magics
108+ from IPython .core .magic import Magics , line_cell_magic
11109from string import Template
110+ import argparse
12111
13112class BaseStackqlMagic (Magics ):
14113 """Base Jupyter magic extension enabling running StackQL queries.
@@ -26,6 +125,7 @@ def __init__(self, shell, server_mode):
26125 from ..core import StackQL
27126 super (BaseStackqlMagic , self ).__init__ (shell )
28127 self .stackql_instance = StackQL (server_mode = server_mode , output = 'pandas' )
128+ self .server_mode = server_mode
29129
30130 def get_rendered_query (self , data ):
31131 """Substitute placeholders in a query template with variables from the current namespace.
@@ -52,55 +152,49 @@ def run_query(self, query):
52152
53153 return self .stackql_instance .execute (query )
54154
55- # def _display_with_csv_download(self, df):
56- # """Display DataFrame with CSV download link.
155+ @line_cell_magic
156+ def stackql (self , line , cell = None ):
157+ """A Jupyter magic command to run StackQL queries.
57158
58- # :param df: The DataFrame to display and make downloadable.
59- # """
60- # import IPython.display
159+ Can be used as both line and cell magic:
160+ - As a line magic: `%stackql QUERY`
161+ - As a cell magic: `%%stackql [OPTIONS]` followed by the QUERY in the next line.
61162
62- # try:
63- # # Generate CSV data
64- # import io
65- # import base64
66- # csv_buffer = io.StringIO()
67- # df.to_csv(csv_buffer, index=False)
68- # csv_data = csv_buffer.getvalue()
69-
70- # # Encode to base64 for data URI
71- # csv_base64 = base64.b64encode(csv_data.encode()).decode()
72-
73- # # Create download link
74- # download_link = f'data:text/csv;base64,{csv_base64}'
75-
76- # # # Display the DataFrame first
77- # # IPython.display.display(df)
78-
79- # # Create and display the download button
80- # download_html = f'''
81- # <div style="margin-top: 15px; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', sans-serif;">
82- # <a href="{download_link}" download="stackql_results.csv"
83- # style="display: inline-flex; align-items: center; gap: 8px; padding: 9px 16px;
84- # background-color: #2196F3; color: white; text-decoration: none;
85- # border-radius: 4px; font-size: 14px; font-weight: 500;
86- # box-shadow: 0 2px 4px rgba(0,0,0,0.08); transition: all 0.2s ease;">
87- # <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
88- # <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
89- # <polyline points="7 10 12 15 17 10"></polyline>
90- # <line x1="12" y1="15" x2="12" y2="3"></line>
91- # </svg>
92- # Download CSV
93- # </a>
94- # </div>
95- # '''
96-
97- # IPython.display.display(IPython.display.HTML(download_html))
98-
99- # except Exception as e:
100- # # If CSV generation fails, just display the DataFrame normally
101- # IPython.display.display(df)
102- # print(f"Error generating CSV download: {e}")
163+ :param line: The arguments and/or StackQL query when used as line magic.
164+ :param cell: The StackQL query when used as cell magic.
165+ :return: StackQL query results as a named Pandas DataFrame (`stackql_df`).
166+ """
167+ is_cell_magic = cell is not None
103168
169+ if is_cell_magic :
170+ parser = argparse .ArgumentParser ()
171+ parser .add_argument ("--no-display" , action = "store_true" , help = "Suppress result display." )
172+ parser .add_argument ("--csv-download" , action = "store_true" , help = "Add CSV download link to output." )
173+ args = parser .parse_args (line .split ())
174+ query_to_run = self .get_rendered_query (cell )
175+ else :
176+ args = None
177+ query_to_run = self .get_rendered_query (line )
178+
179+ results = self .run_query (query_to_run )
180+ self .shell .user_ns ['stackql_df' ] = results
181+
182+ if is_cell_magic and args and args .no_display :
183+ return None
184+ elif is_cell_magic and args and args .csv_download and not args .no_display :
185+ # First display the DataFrame
186+ import IPython .display
187+ IPython .display .display (results )
188+ # Then add the download button without displaying the DataFrame again
189+ self ._display_with_csv_download (results )
190+ return results
191+ elif is_cell_magic and args and not args .no_display :
192+ return results
193+ elif not is_cell_magic :
194+ return results
195+ else :
196+ return results
197+
104198 def _display_with_csv_download (self , df ):
105199 """Display a CSV download link for the DataFrame without displaying the DataFrame again.
106200
0 commit comments