diff --git a/Orange/widgets/data/owsql.py b/Orange/widgets/data/owsql.py index f66bcae5351..28e0adbecfe 100644 --- a/Orange/widgets/data/owsql.py +++ b/Orange/widgets/data/owsql.py @@ -1,21 +1,17 @@ -from collections import OrderedDict - -from AnyQt.QtWidgets import ( - QLineEdit, QComboBox, QTextEdit, QMessageBox, QSizePolicy, QApplication) +from AnyQt.QtWidgets import QComboBox, QTextEdit, QMessageBox, QApplication from AnyQt.QtGui import QCursor from AnyQt.QtCore import Qt, QTimer -from Orange.widgets import report from Orange.data import Table from Orange.data.sql.backend import Backend from Orange.data.sql.backend.base import BackendError from Orange.data.sql.table import SqlTable, LARGE_TABLE, AUTO_DL_LIMIT from Orange.widgets import gui -from Orange.widgets.credentials import CredentialManager from Orange.widgets.settings import Setting from Orange.widgets.utils.itemmodels import PyListModel +from Orange.widgets.utils.owbasesql import OWBaseSql from Orange.widgets.utils.widgetpreview import WidgetPreview -from Orange.widgets.widget import OWWidget, Output, Msg +from Orange.widgets.widget import Output, Msg MAX_DL_LIMIT = 1000000 @@ -40,7 +36,7 @@ def data(self, index, role=Qt.DisplayRole): return super().data(index, role) -class OWSql(OWWidget): +class OWSql(OWBaseSql): name = "SQL Table" id = "orange.widgets.data.sql" description = "Load dataset from SQL." @@ -54,15 +50,6 @@ class Outputs: settings_version = 2 - want_main_area = False - resizing_enabled = False - - host = Setting(None) - port = Setting(None) - database = Setting(None) - schema = Setting(None) - username = "" - password = "" table = Setting(None) sql = Setting("") guess_values = Setting(True) @@ -71,83 +58,60 @@ class Outputs: materialize = Setting(False) materialize_table_name = Setting("") - class Information(OWWidget.Information): + class Information(OWBaseSql.Information): data_sampled = Msg("Data description was generated from a sample.") - class Error(OWWidget.Error): - connection = Msg("{}") - no_backends = Msg("Please install a backend to use this widget") - - class Warning(OWWidget.Warning): + class Warning(OWBaseSql.Warning): missing_extension = Msg("Database is missing extensions: {}") def __init__(self): super().__init__() - self.backend = None - self.data_desc_table = None - self.database_desc = None + QTimer.singleShot(0, self.select_table) - vbox = gui.vBox(self.controlArea, "Server", addSpace=True) - box = gui.vBox(vbox) + def setup_gui(self): + super().setup_gui() + gui.checkBox(self.serverbox, self, "guess_values", + "Auto-discover categorical variables", + callback=self.open_table) + self.downloadcb = gui.checkBox(self.serverbox, self, "download", + "Download data to local memory", + callback=self.open_table) + gui.rubber(self.buttonsArea) + if self.can_connect: + self.connect() + index = self.tablecombo.findText(str(self.table)) + if index != -1: + self.tablecombo.setCurrentIndex(index) + def add_main_layout(self): self.backends = BackendModel(Backend.available_backends()) - self.backendcombo = QComboBox(box) + self.backendcombo = QComboBox(self.serverbox) if self.backends: self.backendcombo.setModel(self.backends) else: self.Error.no_backends() - box.setEnabled(False) - box.layout().addWidget(self.backendcombo) - - self.servertext = QLineEdit(box) - self.servertext.setPlaceholderText('Server') - self.servertext.setToolTip('Server') - self.servertext.editingFinished.connect(self._load_credentials) - if self.host: - self.servertext.setText(self.host if not self.port else - '{}:{}'.format(self.host, self.port)) - box.layout().addWidget(self.servertext) - - self.databasetext = QLineEdit(box) - self.databasetext.setPlaceholderText('Database[/Schema]') - self.databasetext.setToolTip('Database or optionally Database/Schema') - if self.database: - self.databasetext.setText( - self.database if not self.schema else - '{}/{}'.format(self.database, self.schema)) - box.layout().addWidget(self.databasetext) - self.usernametext = QLineEdit(box) - self.usernametext.setPlaceholderText('Username') - self.usernametext.setToolTip('Username') - - box.layout().addWidget(self.usernametext) - self.passwordtext = QLineEdit(box) - self.passwordtext.setPlaceholderText('Password') - self.passwordtext.setToolTip('Password') - self.passwordtext.setEchoMode(QLineEdit.Password) - - box.layout().addWidget(self.passwordtext) - - self._load_credentials() - self.tables = TableModel() + self.serverbox.setEnabled(False) + self.serverbox.layout().addWidget(self.backendcombo) + + super().add_main_layout() - tables = gui.hBox(box) + self.tables = TableModel() + self.tablesbox = gui.hBox(self.serverbox) self.tablecombo = QComboBox( minimumContentsLength=35, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength ) self.tablecombo.setModel(self.tables) self.tablecombo.setToolTip('table') - tables.layout().addWidget(self.tablecombo) - - self.connectbutton = gui.button( - tables, self, '↻', callback=self.connect) - self.connectbutton.setSizePolicy( - QSizePolicy.Fixed, QSizePolicy.Fixed) - tables.layout().addWidget(self.connectbutton) + self.tablesbox.layout().addWidget(self.tablecombo) + # set up the callback to select_table in case of selection change + self.tablecombo.activated[int].connect(self.select_table) - self.custom_sql = gui.vBox(box) + def add_connect_button(self): + super().add_connect_button() + self.tablesbox.layout().addWidget(self.connectbutton) + self.custom_sql = gui.vBox(self.serverbox) self.custom_sql.setVisible(False) self.sqltext = QTextEdit(self.custom_sql) self.sqltext.setPlainText(self.sql) @@ -158,50 +122,11 @@ def __init__(self): cb.setToolTip('Save results of the query in a table') le = gui.lineEdit(mt, self, 'materialize_table_name') le.setToolTip('Save results of the query in a table') + self.serverbox.layout().addWidget(self.custom_sql) - self.executebtn = gui.button( - self.custom_sql, self, 'Execute', callback=self.open_table) - - box.layout().addWidget(self.custom_sql) - - gui.checkBox(box, self, "guess_values", - "Auto-discover categorical variables", - callback=self.open_table) - - self.downloadcb = gui.checkBox(box, self, "download", - "Download data to local memory", - callback=self.open_table) - - gui.rubber(self.buttonsArea) - - self.connect() - index = self.tablecombo.findText(str(self.table)) - if index != -1: - self.tablecombo.setCurrentIndex(index) - # set up the callback to select_table in case of selection change - self.tablecombo.activated[int].connect(self.select_table) - - QTimer.singleShot(0, self.select_table) - - def _load_credentials(self): - self._parse_host_port() - cm = self._credential_manager(self.host, self.port) - self.username = cm.username - self.password = cm.password - - if self.username: - self.usernametext.setText(self.username) - if self.password: - self.passwordtext.setText(self.password) - - def _save_credentials(self): - cm = self._credential_manager(self.host, self.port) - cm.username = self.username or '' - cm.password = self.password or '' - - @staticmethod - def _credential_manager(host, port): - return CredentialManager("SQL Table: {}:{}".format(host, port)) + def add_execute_button(self): + super().add_execute_button() + self.custom_sql.layout().addWidget(self.executebutton) def highlight_error(self, text=""): err = ['', 'QLineEdit {border: 2px solid red;}'] @@ -209,53 +134,33 @@ def highlight_error(self, text=""): self.usernametext.setStyleSheet(err['role' in text]) self.databasetext.setStyleSheet(err['database' in text]) - def _parse_host_port(self): - hostport = self.servertext.text().split(':') - self.host = hostport[0] - self.port = hostport[1] if len(hostport) == 2 else None - - def connect(self): - self._parse_host_port() - self.database, _, self.schema = self.databasetext.text().partition('/') - self.username = self.usernametext.text() or None - self.password = self.passwordtext.text() or None + def get_backend(self): + if self.backendcombo.currentIndex() < 0: + return None + return self.backends[self.backendcombo.currentIndex()] + + def on_connection_success(self): + if getattr(self.backend, 'missing_extension', False): + self.Warning.missing_extension( + ", ".join(self.backend.missing_extension)) + self.download = True + self.downloadcb.setEnabled(False) + if not is_postgres(self.backend): + self.download = True + self.downloadcb.setEnabled(False) + super().on_connection_success() + self.refresh_tables() + + def on_connection_error(self, err): + super().on_connection_error(err) + self.highlight_error(str(err).split("\n")[0]) + + def clear(self): + super().clear() self.Warning.missing_extension.clear() self.downloadcb.setEnabled(True) - try: - if self.backendcombo.currentIndex() < 0: - return - backend = self.backends[self.backendcombo.currentIndex()] - self.backend = backend(dict( - host=self.host, - port=self.port, - database=self.database, - user=self.username, - password=self.password - )) - self.Error.connection.clear() - self.highlight_error() - if getattr(self.backend, 'missing_extension', False): - self.Warning.missing_extension( - ", ".join(self.backend.missing_extension)) - self.download = True - self.downloadcb.setEnabled(False) - - if not is_postgres(self.backend): - self.download = True - self.downloadcb.setEnabled(False) - - self._save_credentials() - self.database_desc = OrderedDict(( - ("Host", self.host), ("Port", self.port), - ("Database", self.database), ("User name", self.username) - )) - self.refresh_tables() - except BackendError as err: - error = str(err).split('\n')[0] - self.Error.connection(error) - self.highlight_error(error) - self.database_desc = self.data_desc_table = None - self.tablecombo.clear() + self.highlight_error() + self.tablecombo.clear() def refresh_tables(self): self.tables.clear() @@ -282,16 +187,6 @@ def select_table(self): return self.open_table() return None - #self.Error.missing_extension( - # 's' if len(missing) > 1 else '', - # ', '.join(missing), - # shown=missing) - - def open_table(self): - table = self.get_table() - self.data_desc_table = table - self.Outputs.data.send(table) - def get_table(self): curIdx = self.tablecombo.currentIndex() if curIdx <= 0: @@ -414,15 +309,6 @@ def get_table(self): return table - def send_report(self): - if not self.database_desc: - self.report_paragraph("No database connection.") - return - self.report_items("Database", self.database_desc) - if self.data_desc_table: - self.report_items("Data", - report.describe_data(self.data_desc_table)) - @classmethod def migrate_settings(cls, settings, version): if version < 2: