-1

whenever i tried to show the sql table FROM INFORMATION_SCHEMA.TABLES Name list in a qtableView by using pandasModel class in Pyqt5 Application, i got this error 'pyodbc.Cursor ' object has no attribute 'index'. i am using mssql server 2019. i tried both command sql_conn.execute and pd.read_sql_query. how to handle this with pandas model? thanks in advance!

main.py

from PyQt5 import QtCore, QtGui, QtWidgets
import pyodbc
import pandas as pd

class PandasModel(QtCore.QAbstractTableModel): 
    def __init__(self, df = pd.DataFrame(), parent=None): 
        QtCore.QAbstractTableModel.__init__(self, parent=parent)
        self._df = df

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if role != QtCore.Qt.DisplayRole:
            return QtCore.QVariant()

        if orientation == QtCore.Qt.Horizontal:
            try:
                return self._df.columns.tolist()[section]
            except (IndexError, ):
                return QtCore.QVariant()
        elif orientation == QtCore.Qt.Vertical:
            try:
                return self._df.index.tolist()[section]
            except (IndexError, ):
                return QtCore.QVariant()

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role != QtCore.Qt.DisplayRole:
            return QtCore.QVariant()

        if not index.isValid():
            return QtCore.QVariant()

        return QtCore.QVariant(str(self._df.iloc[index.row(), index.column()]))

    def setData(self, index, value, role):
        row = self._df.index[index.row()]
        col = self._df.columns[index.column()]
        if hasattr(value, 'toPyObject'):
            value = value.toPyObject()
        else:
            dtype = self._df[col].dtype
            if dtype != object:
                value = None if value == '' else dtype.type(value)
        self._df.set_value(row, col, value)
        return True

    def rowCount(self, parent=QtCore.QModelIndex()): 
        return len(self._df.index)

    def columnCount(self, parent=QtCore.QModelIndex()): 
        return len(self._df.columns)

    def sort(self, column, order):
        colname = self._df.columns.tolist()[column]
        self.layoutAboutToBeChanged.emit()
        self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True)
        self._df.reset_index(inplace=True, drop=True)
        self.layoutChanged.emit()

class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent=None)
        vLayout = QtWidgets.QVBoxLayout(self)
        hLayout = QtWidgets.QHBoxLayout()
        self.pathLE = QtWidgets.QLineEdit(self)
        hLayout.addWidget(self.pathLE)
        self.loadBtn = QtWidgets.QPushButton("load", self)
        hLayout.addWidget(self.loadBtn)
        vLayout.addLayout(hLayout)
        self.tableView = QtWidgets.QTableView(self)
        vLayout.addWidget(self.tableView)
        self.loadBtn.clicked.connect(self.loadData)
        self.tableView.setSortingEnabled(True)

    def loadData(self):
        dbName="AccountDatabase"
        server = '-----'
        database = ''
        username = 'admin'
        password = 'admin#'
        sql_conn = pyodbc.connect('DRIVER={ODBC Driver 17 for SQL Server};SERVER=' +
                          server+';DATABASE='+database+';UID='+username+';PWD=' + password)

        query_string= "use " + dbName +" "+ "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'"
    
        df = sql_conn.execute(query_string)
        # df = pd.read_sql_query(query_string, sql_conn)
        # sql_conn.commint()
        # records = sqlSend.fetchall()
        model = PandasModel(df)
        self.tableView.setModel(model)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Dhurjati Riyan
  • 116
  • 1
  • 3
  • 13
  • 1
    Why define a database in the connection string and then start with a `USE` to change the database? Just connect to the right one in the first place. – Thom A May 30 '21 at 11:19
  • 1
    Why do you think `sql_conn.execute(query_string)` returns a pandas? – eyllanesc May 30 '21 at 12:01
  • @eyllanesc i tried 'pd.read_sql_query(query_string, sql_conn)' and 'pd.read_sql(query_string, sql_conn)' both. it give me error: 'NoneType object is not iterable' – Dhurjati Riyan May 30 '21 at 12:50
  • @Larnu i want to pass USE dbname as a parameter from the user. user can choose the dbname from a list and get the all tablenames which is belongs to the database. – Dhurjati Riyan May 30 '21 at 13:13
  • 1
    You can't parametrise a `USE` statement; it must be followed by a literal. connect to the right database in the first place @DhurjatiRiyan . – Thom A May 30 '21 at 18:41
  • 1
    @DhurjatiRiyan The problem is not PyQt5 but pandas. Haven't you noticed that the class is called PandasModel because it accepts pandas objects? – eyllanesc May 30 '21 at 19:34
  • @eyllanesc yes right. i use 'pd.read_sql_query(query_string, sql_conn)' which is correct for other normal select query. problem is in 'USE' statement. so i modified my query to: """SELECT * FROM """ +dbName+""".INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'""" and it works fine now – Dhurjati Riyan May 30 '21 at 20:50
  • thanks @Larnu to clearifying this – Dhurjati Riyan May 30 '21 at 20:51
  • thanks @eyllanesc as always you solved my problems – Dhurjati Riyan May 30 '21 at 20:52

1 Answers1

-1

this is working for me now:

def loadData(self):
            dbName="AccountDatabase"
            server = '-----'
            database = ''
            username = 'admin'
            password = 'admin#'
            sql_conn = pyodbc.connect('DRIVER={ODBC Driver 17 for SQL Server};SERVER=' +
                              server+';DATABASE='+database+';UID='+username+';PWD=' + password)
    
            query_string="""SELECT * FROM """ +dbName+""".INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'"""
        
           
            df = pd.read_sql_query(query_string, sql_conn)
            model = PandasModel(df)
            self.tableView.setModel(model)
Dhurjati Riyan
  • 116
  • 1
  • 3
  • 13
  • And it's wide open to injection... Again, don't do this. Put the database you want to connect to in your connection string – Thom A May 30 '21 at 21:07
  • @Larnu there are 18 databases in the server. how can i switching them . did you mean that every time i need establish the connection ! – Dhurjati Riyan May 30 '21 at 21:11
  • @Larnu in my case user choose database name from a drop down list which i not posted . when user select a database, all tables will listed in a tableView. – Dhurjati Riyan May 30 '21 at 21:15
  • 1
    That does *not* change my point. Injecting into a statement with no attempt to ensure its sanitised is an injection issue. Fix your connection string, and connect to the right database; then you don't need to use injection at all, and don't have a security risk. – Thom A May 30 '21 at 21:17