Coverage for mindsdb / integrations / handlers / sheets_handler / sheets_handler.py: 0%
74 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-21 00:36 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-21 00:36 +0000
1from typing import Optional
3import pandas as pd
4import duckdb
6from mindsdb_sql_parser import parse_sql
7from mindsdb_sql_parser.ast.base import ASTNode
8from mindsdb.utilities import log
9from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
10from mindsdb.integrations.libs.base import DatabaseHandler
11from mindsdb.integrations.libs.response import (
12 HandlerStatusResponse as StatusResponse,
13 HandlerResponse as Response,
14 RESPONSE_TYPE,
15)
18logger = log.getLogger(__name__)
21class SheetsHandler(DatabaseHandler):
22 """
23 This handler handles connection and execution of queries against the Excel Sheet.
24 TODO: add authentication for private sheets
25 """
27 name = "sheets"
29 def __init__(self, name: str, connection_data: Optional[dict], **kwargs):
30 """
31 Initialize the handler.
32 Args:
33 name (str): name of particular handler instance
34 connection_data (dict): parameters for connecting to the database
35 **kwargs: arbitrary keyword arguments.
36 """
37 super().__init__(name)
38 self.parser = parse_sql
39 self.renderer = SqlalchemyRender("postgresql")
40 self.connection_data = connection_data
41 self.kwargs = kwargs
43 self.connection = None
44 self.is_connected = False
46 def __del__(self):
47 if self.is_connected is True:
48 self.disconnect()
50 def connect(self) -> StatusResponse:
51 """
52 Set up the connection required by the handler.
53 Returns:
54 HandlerStatusResponse
55 """
56 url = f"https://docs.google.com/spreadsheets/d/{self.connection_data['spreadsheet_id']}/gviz/tq?tqx=out:csv&sheet={self.connection_data['sheet_name']}"
57 self.sheet = pd.read_csv(url, on_bad_lines="skip")
58 self.connection = duckdb.connect()
59 self.connection.register(self.connection_data["sheet_name"], self.sheet)
60 self.is_connected = True
62 return self.connection
64 def disconnect(self):
65 """
66 Close any existing connections.
67 """
68 if self.is_connected is False:
69 return
71 self.connection.close()
72 self.is_connected = False
73 return self.is_connected
75 def check_connection(self) -> StatusResponse:
76 """
77 Check connection to the handler.
78 Returns:
79 HandlerStatusResponse
80 """
81 response = StatusResponse(False)
82 need_to_close = self.is_connected is False
84 try:
85 self.connect()
86 response.success = True
87 except Exception as e:
88 logger.error(f"Error connecting to the Google Sheet with ID {self.connection_data['spreadsheet_id']}, {e}!")
89 response.error_message = str(e)
90 finally:
91 if response.success is True and need_to_close:
92 self.disconnect()
93 if response.success is False and self.is_connected is True:
94 self.is_connected = False
96 return response
98 def native_query(self, query: str) -> StatusResponse:
99 """
100 Receive raw query and act upon it somehow.
101 Args:
102 query (str): query in native format
103 Returns:
104 HandlerResponse
105 """
107 need_to_close = self.is_connected is False
108 connection = self.connect()
109 try:
110 result = connection.execute(query).fetchdf()
111 if not result.empty:
112 response = Response(RESPONSE_TYPE.TABLE, result)
113 else:
114 response = Response(RESPONSE_TYPE.OK)
115 connection.commit()
116 except Exception as e:
117 logger.error(
118 f"Error running query: {query} on the Google Sheet with ID {self.connection_data['spreadsheet_id']}!"
119 )
120 response = Response(RESPONSE_TYPE.ERROR, error_message=str(e))
122 if need_to_close is True:
123 self.disconnect()
125 return response
127 def query(self, query: ASTNode) -> StatusResponse:
128 """
129 Receive query as AST (abstract syntax tree) and act upon it somehow.
130 Args:
131 query (ASTNode): sql query represented as AST. May be any kind
132 of query: SELECT, INTSERT, DELETE, etc
133 Returns:
134 HandlerResponse
135 """
136 query_str = self.renderer.get_string(query, with_failback=True)
137 return self.native_query(query_str)
139 def get_tables(self) -> StatusResponse:
140 """
141 Return list of entities that will be accessible as tables.
142 Returns:
143 HandlerResponse
144 """
145 response = Response(
146 RESPONSE_TYPE.TABLE, data_frame=pd.DataFrame([self.connection_data["sheet_name"]], columns=["table_name"])
147 )
149 return response
151 def get_columns(self) -> StatusResponse:
152 """
153 Returns a list of entity columns.
154 Args:
155 table_name (str): name of one of tables returned by self.get_tables()
156 Returns:
157 HandlerResponse
158 """
159 response = Response(
160 RESPONSE_TYPE.TABLE,
161 data_frame=pd.DataFrame({"column_name": list(self.sheet.columns), "data_type": self.sheet.dtypes}),
162 )
164 return response