orange3-db-connections 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- orange3_db_connections-0.1.3.dist-info/METADATA +33 -0
- orange3_db_connections-0.1.3.dist-info/RECORD +26 -0
- orange3_db_connections-0.1.3.dist-info/WHEEL +5 -0
- orange3_db_connections-0.1.3.dist-info/entry_points.txt +5 -0
- orange3_db_connections-0.1.3.dist-info/licenses/LICENSE +21 -0
- orange3_db_connections-0.1.3.dist-info/top_level.txt +1 -0
- orangecontrib/__init__.py +2 -0
- orangecontrib/dbconnections/__init__.py +10 -0
- orangecontrib/dbconnections/utils/__init__.py +3 -0
- orangecontrib/dbconnections/utils/driver_installer.py +43 -0
- orangecontrib/dbconnections/widgets/__init__.py +16 -0
- orangecontrib/dbconnections/widgets/_base_connection.py +240 -0
- orangecontrib/dbconnections/widgets/icons/addon_icon.png +0 -0
- orangecontrib/dbconnections/widgets/icons/clickhouse.png +0 -0
- orangecontrib/dbconnections/widgets/icons/mariadb.png +0 -0
- orangecontrib/dbconnections/widgets/icons/msql.png +0 -0
- orangecontrib/dbconnections/widgets/icons/mysql.png +0 -0
- orangecontrib/dbconnections/widgets/icons/oracle.png +0 -0
- orangecontrib/dbconnections/widgets/icons/postgres.png +0 -0
- orangecontrib/dbconnections/widgets/icons/sqlite.png +0 -0
- orangecontrib/dbconnections/widgets/ow_clickhouse_connection.py +32 -0
- orangecontrib/dbconnections/widgets/ow_env_check.py +223 -0
- orangecontrib/dbconnections/widgets/ow_mssql_connection.py +76 -0
- orangecontrib/dbconnections/widgets/ow_mysql_connection.py +20 -0
- orangecontrib/dbconnections/widgets/ow_oracle_connection.py +41 -0
- orangecontrib/dbconnections/widgets/ow_pg_connection.py +20 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orange3-db-connections
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: DB Connections – Add-on Orange untuk membuat koneksi database (PostgreSQL, MySQL, SQL Server, SQLite, ClickHouse).
|
|
5
|
+
Author-email: Devi Ardiana <deviardn@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/deviardn/orange3-db-connections
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: orange3>=3.36.0
|
|
15
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
16
|
+
Requires-Dist: AnyQt>=0.0.13
|
|
17
|
+
Requires-Dist: PyQt5>=5.15
|
|
18
|
+
Requires-Dist: PyQtWebEngine>=5.15
|
|
19
|
+
Provides-Extra: postgresql
|
|
20
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "postgresql"
|
|
21
|
+
Provides-Extra: mysql
|
|
22
|
+
Requires-Dist: pymysql>=1.0; extra == "mysql"
|
|
23
|
+
Provides-Extra: mssql
|
|
24
|
+
Requires-Dist: pyodbc>=4.0; extra == "mssql"
|
|
25
|
+
Provides-Extra: clickhouse
|
|
26
|
+
Requires-Dist: clickhouse-connect>=0.7; extra == "clickhouse"
|
|
27
|
+
Provides-Extra: oracle
|
|
28
|
+
Requires-Dist: cx-Oracle>=8.3; extra == "oracle"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# orange3-db-connections
|
|
32
|
+
|
|
33
|
+
Add-on Orange untuk membuat koneksi ke berbagai database (PostgreSQL, MySQL, SQL Server, SQLite, ClickHouse)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
orange3_db_connections-0.1.3.dist-info/licenses/LICENSE,sha256=ogVw-HCawhI-QFk4Rt3-oXImtQPK_OaSNy4jGDwAmm0,1061
|
|
2
|
+
orangecontrib/__init__.py,sha256=mBY-cpLPvimaNLcMQPc9KX8HHH47-Xvd5H_5E2lUbms,91
|
|
3
|
+
orangecontrib/dbconnections/__init__.py,sha256=ZoZIvm7reEp3C-LQS8zxNb3aGls3ioUwR_dhl-OYIEQ,257
|
|
4
|
+
orangecontrib/dbconnections/utils/__init__.py,sha256=GPXY9Ny1mnwse5QiGblfpjNFWqD62y-9T0UxZKpV7co,73
|
|
5
|
+
orangecontrib/dbconnections/utils/driver_installer.py,sha256=ibYCYxJBYnDO9nSeoNrWneiCq52bWQjaY2_2Js7WnBo,1129
|
|
6
|
+
orangecontrib/dbconnections/widgets/__init__.py,sha256=Kp_AnLn2RSUqwS3joRchYovJdZ62NhoQ5iLQBj7N9wc,450
|
|
7
|
+
orangecontrib/dbconnections/widgets/_base_connection.py,sha256=wqR_j0MxfFi4LEjdeRikHCX7rVRGq8RakWqPPxPvnsE,7263
|
|
8
|
+
orangecontrib/dbconnections/widgets/ow_clickhouse_connection.py,sha256=yf7nlTvXfeSrZit54k4CLg0C6tvW9cf6yBOK3HBsrwA,1017
|
|
9
|
+
orangecontrib/dbconnections/widgets/ow_env_check.py,sha256=1msOfLGbm0FOj4ORFN0yLTKFhedyLhzaPC0RKOIQAcU,7042
|
|
10
|
+
orangecontrib/dbconnections/widgets/ow_mssql_connection.py,sha256=Fg4uukupZQFVZiCYWv_oJiAgaglohspNIj6xXOXgxDk,2793
|
|
11
|
+
orangecontrib/dbconnections/widgets/ow_mysql_connection.py,sha256=qQhh_cnXIzujKxFX8w7hMrvXh1CzvQIC6lC6mY-G1OU,659
|
|
12
|
+
orangecontrib/dbconnections/widgets/ow_oracle_connection.py,sha256=ojnb72rTA85A-vwjmA0pOdIBSeW67fsY4569SqRA4Bw,1242
|
|
13
|
+
orangecontrib/dbconnections/widgets/ow_pg_connection.py,sha256=jEQoTo4oDLdAHcu4-uB_g1_TI_RFGi5pfIHKLoJi8Co,698
|
|
14
|
+
orangecontrib/dbconnections/widgets/icons/addon_icon.png,sha256=XWlSBWTzzPSr-fov_S_g-IfzMmYt5f_T8rUEdwNP4k4,63087
|
|
15
|
+
orangecontrib/dbconnections/widgets/icons/clickhouse.png,sha256=qGHZ0e_PJPE_qt2ZWKszvPe1u0zHRZpuLtXoLyCn8D0,1888
|
|
16
|
+
orangecontrib/dbconnections/widgets/icons/mariadb.png,sha256=0O2IrF0oHlFBvdQVpanCNV1gaalgSqeu-wRKbrK9VAA,4194
|
|
17
|
+
orangecontrib/dbconnections/widgets/icons/msql.png,sha256=DrsnbNiorX7pr5BWV5zeqcBmb0fMLT5PGpUgdP7RjDo,10291
|
|
18
|
+
orangecontrib/dbconnections/widgets/icons/mysql.png,sha256=k0p_hQp1e5E6prHLIsp0L49rcfGPUIViQbRdsHzLPZU,4993
|
|
19
|
+
orangecontrib/dbconnections/widgets/icons/oracle.png,sha256=63Zy0KZGTXIGTMyI_2gmxfErUiRJULdsQeLaUru5_rQ,4325
|
|
20
|
+
orangecontrib/dbconnections/widgets/icons/postgres.png,sha256=NZdkF4Ajh7vwnvGtm2nemgKUGrq9Bkqx152sFPhqr4o,8616
|
|
21
|
+
orangecontrib/dbconnections/widgets/icons/sqlite.png,sha256=qKX9CdbSd6gljKcZtZXwq-VRIRmQCFJA0F6oLxCfgNY,3827
|
|
22
|
+
orange3_db_connections-0.1.3.dist-info/METADATA,sha256=V74RrAG0errYTvOqTEZwKCkwASfQIr_CmckH_rg_gpc,1251
|
|
23
|
+
orange3_db_connections-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
orange3_db_connections-0.1.3.dist-info/entry_points.txt,sha256=-Yr3AwKCv-lWfULrkGgfenGzCcywSKmvWUSwlgs_5FI,132
|
|
25
|
+
orange3_db_connections-0.1.3.dist-info/top_level.txt,sha256=Iut-JTfT11SZHHm77_ZeszD7pZDWXcTweCbvrJpqDyQ,14
|
|
26
|
+
orange3_db_connections-0.1.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Devi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
orangecontrib
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""orange3-db-connections add-on.
|
|
2
|
+
|
|
3
|
+
Kumpulan widget koneksi database (PostgreSQL, MySQL, SQL Server, SQLite, ClickHouse).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
NAME = "DB Connections"
|
|
7
|
+
DESCRIPTION = "Widget koneksi ke berbagai database."
|
|
8
|
+
BACKGROUND = "#fdbc73"
|
|
9
|
+
ICON = "icons/addon_icon.png"
|
|
10
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
from typing import Tuple, Dict, Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _check_module(modname: str) -> Tuple[bool, str]:
|
|
6
|
+
try:
|
|
7
|
+
import_module(modname)
|
|
8
|
+
return True, f"Module {modname} tersedia."
|
|
9
|
+
except Exception as e:
|
|
10
|
+
return False, f"Module {modname} belum terpasang: {e}"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def ensure_driver(kind: str) -> Tuple[bool, str, Dict[str, Any]]:
|
|
14
|
+
"""Pastikan driver Python untuk DB terkait sudah tersedia.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
kind:
|
|
19
|
+
Nama jenis DB, misal: "PostgreSQL", "MySQL", "SQLite",
|
|
20
|
+
"SQL Server", "ClickHouse", "Oracle".
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
ok, log, extra
|
|
25
|
+
"""
|
|
26
|
+
k = kind.lower()
|
|
27
|
+
|
|
28
|
+
if "postgres" in k:
|
|
29
|
+
return (*_check_module("psycopg2"), {})
|
|
30
|
+
|
|
31
|
+
if "mysql" in k:
|
|
32
|
+
return (*_check_module("pymysql"), {})
|
|
33
|
+
|
|
34
|
+
if "sql server" in k or "mssql" in k or "pyodbc" in k:
|
|
35
|
+
return (*_check_module("pyodbc"), {})
|
|
36
|
+
|
|
37
|
+
if "clickhouse" in k:
|
|
38
|
+
return (*_check_module("clickhouse_connect"), {})
|
|
39
|
+
|
|
40
|
+
if "oracle" in k:
|
|
41
|
+
return (*_check_module("cx_Oracle"), {})
|
|
42
|
+
|
|
43
|
+
return False, f"Jenis DB tidak dikenali: {kind}", {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .ow_pg_connection import OWPostgresConnection
|
|
2
|
+
from .ow_mysql_connection import OWMySQLConnection
|
|
3
|
+
from .ow_mssql_connection import OWMSSQLConnection
|
|
4
|
+
from .ow_clickhouse_connection import OWClickHouseConnection
|
|
5
|
+
from .ow_env_check import OWDbEnvCheck
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"OWPostgresConnection",
|
|
9
|
+
"OWMySQLConnection",
|
|
10
|
+
"OWMSSQLConnection",
|
|
11
|
+
"OWClickHouseConnection",
|
|
12
|
+
"OWDbEnvCheck",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
BACKGROUND = "#fdbc73"
|
|
16
|
+
ICON = "icons/addon_icon.png"
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
from AnyQt import QtWidgets, QtCore
|
|
2
|
+
from AnyQt.QtCore import QThread, pyqtSignal
|
|
3
|
+
from Orange.widgets.widget import OWWidget, Output, Msg
|
|
4
|
+
from orangewidget.settings import Setting
|
|
5
|
+
from orangewidget import gui
|
|
6
|
+
|
|
7
|
+
import sqlalchemy as sa
|
|
8
|
+
from typing import Dict, Any, Optional, Callable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConnectWorker(QThread):
|
|
12
|
+
finished_ok = pyqtSignal(object) # engine
|
|
13
|
+
failed = pyqtSignal(str)
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
driver_kind: str,
|
|
18
|
+
params: Dict[str, Any],
|
|
19
|
+
build_url: Callable[[Dict[str, Any]], str],
|
|
20
|
+
parent=None,
|
|
21
|
+
):
|
|
22
|
+
super().__init__(parent)
|
|
23
|
+
from orangecontrib.dbconnections.utils import ensure_driver
|
|
24
|
+
|
|
25
|
+
self.driver_kind = driver_kind
|
|
26
|
+
self.params = params
|
|
27
|
+
self.build_url = build_url
|
|
28
|
+
self._ensure_driver = ensure_driver
|
|
29
|
+
|
|
30
|
+
def run(self):
|
|
31
|
+
ok, log, _ = self._ensure_driver(self.driver_kind)
|
|
32
|
+
if not ok:
|
|
33
|
+
self.failed.emit(
|
|
34
|
+
f"Driver Python belum tersedia untuk {self.driver_kind}.\n{log}\n"
|
|
35
|
+
"Catatan: beberapa DB juga butuh ODBC/Client di OS."
|
|
36
|
+
)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
url = self.build_url(self.params)
|
|
41
|
+
engine = sa.create_engine(url, pool_pre_ping=True)
|
|
42
|
+
|
|
43
|
+
with engine.connect() as con:
|
|
44
|
+
# pilih test query sesuai dialect
|
|
45
|
+
try:
|
|
46
|
+
dname = engine.dialect.name.lower()
|
|
47
|
+
except Exception:
|
|
48
|
+
dname = ""
|
|
49
|
+
|
|
50
|
+
test_sql = "SELECT 1"
|
|
51
|
+
if "oracle" in dname:
|
|
52
|
+
test_sql = "SELECT 1 FROM DUAL"
|
|
53
|
+
|
|
54
|
+
con.exec_driver_sql(test_sql)
|
|
55
|
+
|
|
56
|
+
self.finished_ok.emit(engine)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
self.failed.emit(str(e))
|
|
59
|
+
|
|
60
|
+
class BaseDBConnectionWidget(OWWidget, openclass=True):
|
|
61
|
+
"""Base class untuk semua widget koneksi database.
|
|
62
|
+
|
|
63
|
+
Subclass wajib set:
|
|
64
|
+
- DB_KIND
|
|
65
|
+
- DEFAULT_PORT
|
|
66
|
+
- name, id, description, icon
|
|
67
|
+
- override :meth:`_build_url`
|
|
68
|
+
- optional override :meth:`_extra_controls`, :meth:`_on_connected_extra`
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
DB_KIND: str = "Generic DB"
|
|
72
|
+
DEFAULT_PORT: int = 0
|
|
73
|
+
|
|
74
|
+
icon = "icons/db_connection.svg"
|
|
75
|
+
priority = 10
|
|
76
|
+
want_main_area = False
|
|
77
|
+
|
|
78
|
+
class Outputs:
|
|
79
|
+
Connection = Output("Connection", object, auto_summary=False)
|
|
80
|
+
|
|
81
|
+
# settings umum
|
|
82
|
+
host: str = Setting("localhost")
|
|
83
|
+
port: int = Setting(0)
|
|
84
|
+
database: str = Setting("")
|
|
85
|
+
user: str = Setting("")
|
|
86
|
+
remember_password: bool = Setting(False)
|
|
87
|
+
|
|
88
|
+
_password_mem: str = "" # tidak disimpan sebagai Setting
|
|
89
|
+
|
|
90
|
+
class Error(OWWidget.Error):
|
|
91
|
+
connect_error = Msg("Gagal konek: {}")
|
|
92
|
+
|
|
93
|
+
class Info(OWWidget.Information):
|
|
94
|
+
connected = Msg("Terkoneksi ke database.")
|
|
95
|
+
# hint = Msg("Password tidak disimpan kecuali centang Remember.")
|
|
96
|
+
hint = Msg("")
|
|
97
|
+
|
|
98
|
+
class Warning(OWWidget.Warning):
|
|
99
|
+
generic_warn = Msg("{}")
|
|
100
|
+
|
|
101
|
+
def __init__(self):
|
|
102
|
+
super().__init__()
|
|
103
|
+
|
|
104
|
+
# --- form umum ---
|
|
105
|
+
box = gui.widgetBox(self.controlArea, "Koneksi")
|
|
106
|
+
|
|
107
|
+
gui.lineEdit(box, self, "host", label="Host:")
|
|
108
|
+
gui.spin(box, self, "port", 0, 65535, label="Port:", step=1)
|
|
109
|
+
gui.lineEdit(box, self, "database", label="Database/Schema/Path:")
|
|
110
|
+
gui.lineEdit(box, self, "user", label="Username:")
|
|
111
|
+
gui.lineEdit(
|
|
112
|
+
box, self, "_password_mem",
|
|
113
|
+
label="Password:",
|
|
114
|
+
echoMode=QtWidgets.QLineEdit.Password,
|
|
115
|
+
)
|
|
116
|
+
# gui.checkBox(box, self, "remember_password", "Remember password (plaintext)")
|
|
117
|
+
|
|
118
|
+
# area ekstra untuk subclass
|
|
119
|
+
self._extra_controls(box)
|
|
120
|
+
|
|
121
|
+
btns = gui.widgetBox(box, orientation=QtCore.Qt.Horizontal)
|
|
122
|
+
self.btn_connect = gui.button(btns, self, "Connect", callback=self._connect)
|
|
123
|
+
|
|
124
|
+
self.Info.hint()
|
|
125
|
+
self._worker: Optional[ConnectWorker] = None
|
|
126
|
+
|
|
127
|
+
if not self.port:
|
|
128
|
+
self._apply_default_port()
|
|
129
|
+
|
|
130
|
+
# ===== hooks untuk subclass =====
|
|
131
|
+
def _extra_controls(self, box: QtWidgets.QGroupBox) -> None:
|
|
132
|
+
"""Override di subclass kalau butuh field tambahan."""
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def _apply_default_port(self):
|
|
136
|
+
self.port = getattr(self, "DEFAULT_PORT", 0)
|
|
137
|
+
|
|
138
|
+
def _params(self) -> Dict[str, Any]:
|
|
139
|
+
return {
|
|
140
|
+
"host": self.host.strip(),
|
|
141
|
+
"port": int(self.port),
|
|
142
|
+
"database": self.database.strip(),
|
|
143
|
+
"user": self.user.strip(),
|
|
144
|
+
"password": self._password_mem or "",
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
def _build_url(self, params: Dict[str, Any]) -> str:
|
|
148
|
+
"""Override di subclass, return SQLAlchemy URL."""
|
|
149
|
+
raise NotImplementedError
|
|
150
|
+
|
|
151
|
+
def _driver_kind(self) -> str:
|
|
152
|
+
return getattr(self, "DB_KIND", "Generic DB")
|
|
153
|
+
|
|
154
|
+
# ===== lifecycle =====
|
|
155
|
+
def onDeleteWidget(self):
|
|
156
|
+
self._safe_kill_worker()
|
|
157
|
+
super().onDeleteWidget()
|
|
158
|
+
|
|
159
|
+
# ===== worker handling =====
|
|
160
|
+
def _toggle_busy(self, busy: bool):
|
|
161
|
+
self.btn_connect.setDisabled(busy)
|
|
162
|
+
|
|
163
|
+
def _safe_kill_worker(self):
|
|
164
|
+
w = getattr(self, "_worker", None)
|
|
165
|
+
if not w:
|
|
166
|
+
return
|
|
167
|
+
try:
|
|
168
|
+
try:
|
|
169
|
+
w.finished_ok.disconnect(self._on_connected)
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
try:
|
|
173
|
+
w.failed.disconnect(self._on_failed)
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
try:
|
|
177
|
+
w.finished.disconnect(self._on_worker_finished)
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
if hasattr(w, "isRunning"):
|
|
182
|
+
try:
|
|
183
|
+
if w.isRunning():
|
|
184
|
+
getattr(w, "requestInterruption", lambda: None)()
|
|
185
|
+
w.quit()
|
|
186
|
+
w.wait(2000)
|
|
187
|
+
except RuntimeError:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
w.setParent(None)
|
|
192
|
+
except Exception:
|
|
193
|
+
pass
|
|
194
|
+
try:
|
|
195
|
+
w.deleteLater()
|
|
196
|
+
except Exception:
|
|
197
|
+
pass
|
|
198
|
+
finally:
|
|
199
|
+
self._worker = None
|
|
200
|
+
|
|
201
|
+
# ===== actions =====
|
|
202
|
+
def _connect(self):
|
|
203
|
+
self._safe_kill_worker()
|
|
204
|
+
self.Error.clear()
|
|
205
|
+
self.Info.clear()
|
|
206
|
+
self.Warning.clear()
|
|
207
|
+
self._toggle_busy(True)
|
|
208
|
+
|
|
209
|
+
params = self._params()
|
|
210
|
+
self._worker = ConnectWorker(
|
|
211
|
+
driver_kind=self._driver_kind(),
|
|
212
|
+
params=params,
|
|
213
|
+
build_url=self._build_url,
|
|
214
|
+
parent=self,
|
|
215
|
+
)
|
|
216
|
+
self._worker.finished_ok.connect(self._on_connected)
|
|
217
|
+
self._worker.failed.connect(self._on_failed)
|
|
218
|
+
self._worker.finished.connect(self._on_worker_finished)
|
|
219
|
+
self._worker.start()
|
|
220
|
+
|
|
221
|
+
def _on_worker_finished(self):
|
|
222
|
+
self._toggle_busy(False)
|
|
223
|
+
self._safe_kill_worker()
|
|
224
|
+
|
|
225
|
+
def _on_connected_extra(self, engine) -> None:
|
|
226
|
+
"""Subclass boleh override untuk cek tambahan setelah connect."""
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
def _on_connected(self, engine):
|
|
230
|
+
self._on_connected_extra(engine)
|
|
231
|
+
|
|
232
|
+
if not self.remember_password:
|
|
233
|
+
self._password_mem = ""
|
|
234
|
+
|
|
235
|
+
self.Info.connected()
|
|
236
|
+
self.Outputs.Connection.send(engine)
|
|
237
|
+
|
|
238
|
+
def _on_failed(self, err: str):
|
|
239
|
+
self.Outputs.Connection.send(None)
|
|
240
|
+
self.Error.connect_error(err)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from ._base_connection import BaseDBConnectionWidget
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OWClickHouseConnection(BaseDBConnectionWidget):
|
|
6
|
+
name = "ClickHouse"
|
|
7
|
+
id = "dbconnections-clickhouse-connection"
|
|
8
|
+
description = "Koneksi ke ClickHouse (driver native)."
|
|
9
|
+
icon = "icons/clickhouse.png"
|
|
10
|
+
|
|
11
|
+
DB_KIND = "ClickHouse"
|
|
12
|
+
DEFAULT_PORT = 8123 # HTTP port (clickhouse-connect)
|
|
13
|
+
|
|
14
|
+
def _build_url(self, params: Dict[str, Any]) -> str:
|
|
15
|
+
user = params.get("user") or ""
|
|
16
|
+
pwd = params.get("password") or ""
|
|
17
|
+
host = params.get("host") or "localhost"
|
|
18
|
+
port = params.get("port") or 8123
|
|
19
|
+
db = params.get("database") or ""
|
|
20
|
+
|
|
21
|
+
auth = ""
|
|
22
|
+
if user:
|
|
23
|
+
if pwd:
|
|
24
|
+
auth = f"{user}:{pwd}@"
|
|
25
|
+
else:
|
|
26
|
+
auth = f"{user}@"
|
|
27
|
+
|
|
28
|
+
# Dialect milik clickhouse-connect
|
|
29
|
+
return f"clickhousedb://{auth}{host}:{port}/{db}"
|
|
30
|
+
# atau kalau mau eksplisit:
|
|
31
|
+
# return f"clickhousedb+connect://{auth}{host}:{port}/{db}"
|
|
32
|
+
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import platform
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
|
|
5
|
+
from AnyQt.QtCore import Qt
|
|
6
|
+
from AnyQt.QtWidgets import (
|
|
7
|
+
QWidget,
|
|
8
|
+
QVBoxLayout,
|
|
9
|
+
QPushButton,
|
|
10
|
+
QTextEdit,
|
|
11
|
+
QTableWidget,
|
|
12
|
+
QTableWidgetItem,
|
|
13
|
+
QHeaderView,
|
|
14
|
+
QLabel,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from Orange.widgets.widget import OWWidget
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Daftar “tipe koneksi” yang ada di add-on DB Connections
|
|
21
|
+
# Fokus: modul Python & catatan OS/driver
|
|
22
|
+
CONNECTION_SPECS = [
|
|
23
|
+
{
|
|
24
|
+
"id": "mssql",
|
|
25
|
+
"label": "SQL Server (pyodbc + ODBC)",
|
|
26
|
+
"module": "pyodbc",
|
|
27
|
+
"pip": "pyodbc",
|
|
28
|
+
"note": "Butuh ODBC Driver 17/18 for SQL Server di Windows.",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "postgresql",
|
|
32
|
+
"label": "PostgreSQL (psycopg2-binary)",
|
|
33
|
+
"module": "psycopg2",
|
|
34
|
+
"pip": "psycopg2-binary",
|
|
35
|
+
"note": "Driver pure Python, tidak perlu client tambahan.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "mysql",
|
|
39
|
+
"label": "MySQL (pymysql)",
|
|
40
|
+
"module": "pymysql",
|
|
41
|
+
"pip": "pymysql",
|
|
42
|
+
"note": "Driver pure Python, tidak perlu client tambahan.",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "sqlite",
|
|
46
|
+
"label": "SQLite (builtin sqlite3)",
|
|
47
|
+
"module": "sqlite3",
|
|
48
|
+
"pip": None,
|
|
49
|
+
"note": "Sudah bawaan Python, seharusnya selalu tersedia.",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "clickhouse",
|
|
53
|
+
"label": "ClickHouse (clickhouse-connect)",
|
|
54
|
+
"module": "clickhouse_connect",
|
|
55
|
+
"pip": "clickhouse-connect",
|
|
56
|
+
"note": "Tidak perlu client tambahan.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "oracle",
|
|
60
|
+
"label": "Oracle (cx-Oracle)",
|
|
61
|
+
"module": "cx_Oracle",
|
|
62
|
+
"pip": "cx-Oracle",
|
|
63
|
+
"note": "Butuh Oracle Instant Client + konfigurasi PATH/ORACLE_HOME.",
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _check_module(mod_name: str):
|
|
69
|
+
"""
|
|
70
|
+
Coba import modul. Return (status, detail).
|
|
71
|
+
status: 'OK' | 'Missing'
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
mod = import_module(mod_name)
|
|
75
|
+
version = getattr(mod, "__version__", "?")
|
|
76
|
+
return "OK", f"Terpasang (versi {version})"
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return "Missing", f"Tidak ditemukan: {e.__class__.__name__}: {e}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _check_odbc():
|
|
82
|
+
"""
|
|
83
|
+
Cek daftar ODBC driver (kalau pyodbc tersedia).
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
import pyodbc
|
|
87
|
+
except ImportError:
|
|
88
|
+
return "Missing", "pyodbc belum terpasang, tidak bisa cek ODBC driver.", []
|
|
89
|
+
|
|
90
|
+
drivers = list(pyodbc.drivers())
|
|
91
|
+
if not drivers:
|
|
92
|
+
return (
|
|
93
|
+
"Warning",
|
|
94
|
+
"pyodbc ada, tapi tidak ada ODBC driver terdeteksi. "
|
|
95
|
+
"Pastikan ODBC Driver 17 atau 18 for SQL Server sudah terinstal.",
|
|
96
|
+
[],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return "OK", f"Ditemukan {len(drivers)} driver ODBC.", drivers
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class OWDbEnvCheck(OWWidget):
|
|
103
|
+
"""
|
|
104
|
+
Widget khusus DB Connections:
|
|
105
|
+
- Menampilkan info Python/OS yang dipakai Orange
|
|
106
|
+
- Mengecek modul driver DB (pyodbc, psycopg2, dll.)
|
|
107
|
+
- Mengecek ODBC driver (kalau pyodbc tersedia)
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
name = "Env Check"
|
|
111
|
+
description = "Cek environment untuk koneksi database (driver Python & ODBC)."
|
|
112
|
+
icon = "icons/db_env_check.svg" # ganti sementara ke icon yang ada kalau perlu
|
|
113
|
+
priority = 100 # supaya muncul agak ke kanan di palette
|
|
114
|
+
|
|
115
|
+
want_main_area = True
|
|
116
|
+
want_control_area = False
|
|
117
|
+
|
|
118
|
+
def __init__(self):
|
|
119
|
+
super().__init__()
|
|
120
|
+
|
|
121
|
+
# ---- Widgets UI ----
|
|
122
|
+
self.btn_scan = QPushButton("🔍 Scan DB Environment")
|
|
123
|
+
self.btn_scan.clicked.connect(self.scan_environment)
|
|
124
|
+
|
|
125
|
+
self.table = QTableWidget(0, 5)
|
|
126
|
+
self.table.setHorizontalHeaderLabels(
|
|
127
|
+
["Connection", "Python Module", "Status", "Detail", "Cara Perbaikan"]
|
|
128
|
+
)
|
|
129
|
+
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
130
|
+
self.table.setMinimumHeight(240)
|
|
131
|
+
|
|
132
|
+
self.txt_summary = QTextEdit()
|
|
133
|
+
self.txt_summary.setReadOnly(True)
|
|
134
|
+
self.txt_summary.setMinimumHeight(160)
|
|
135
|
+
|
|
136
|
+
main = QVBoxLayout()
|
|
137
|
+
main.addWidget(
|
|
138
|
+
QLabel(
|
|
139
|
+
"<b>Bidics DB Connections – Environment Check</b><br/>"
|
|
140
|
+
"Scan modul Python & driver ODBC yang dibutuhkan oleh widget koneksi database."
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
main.addWidget(self.btn_scan)
|
|
144
|
+
main.addWidget(self.table)
|
|
145
|
+
main.addWidget(QLabel("<b>Ringkasan</b>"))
|
|
146
|
+
main.addWidget(self.txt_summary)
|
|
147
|
+
|
|
148
|
+
w = QWidget()
|
|
149
|
+
w.setLayout(main)
|
|
150
|
+
self.mainArea.layout().addWidget(w)
|
|
151
|
+
|
|
152
|
+
# --------------------------------------------------
|
|
153
|
+
# HELPER
|
|
154
|
+
# --------------------------------------------------
|
|
155
|
+
def _build_fix_command(self, pip_name: str | None) -> str:
|
|
156
|
+
"""
|
|
157
|
+
Perintah yang disarankan untuk memperbaiki.
|
|
158
|
+
pip_name bisa None (untuk modul builtin seperti sqlite3).
|
|
159
|
+
"""
|
|
160
|
+
python_exe = sys.executable
|
|
161
|
+
if pip_name:
|
|
162
|
+
return f'"{python_exe}" -m pip install {pip_name}'
|
|
163
|
+
return "(builtin; kalau error, cek instalasi Python/Orange)."
|
|
164
|
+
|
|
165
|
+
# --------------------------------------------------
|
|
166
|
+
# MAIN ACTION
|
|
167
|
+
# --------------------------------------------------
|
|
168
|
+
def scan_environment(self):
|
|
169
|
+
"""
|
|
170
|
+
Scan lengkap dan isi tabel + summary.
|
|
171
|
+
"""
|
|
172
|
+
self.table.setRowCount(0)
|
|
173
|
+
|
|
174
|
+
# Info Python & OS
|
|
175
|
+
py_info_lines = [
|
|
176
|
+
f"Python Executable : {sys.executable}",
|
|
177
|
+
f"Python Version : {sys.version.split()[0]}",
|
|
178
|
+
f"Platform : {platform.platform()}",
|
|
179
|
+
f"Arch : {platform.architecture()[0]}",
|
|
180
|
+
"",
|
|
181
|
+
"=== Status Driver Python ===",
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
# Cek modul untuk tiap jenis koneksi
|
|
185
|
+
rows = []
|
|
186
|
+
for spec in CONNECTION_SPECS:
|
|
187
|
+
status, detail = _check_module(spec["module"])
|
|
188
|
+
fix_cmd = self._build_fix_command(spec["pip"])
|
|
189
|
+
rows.append(
|
|
190
|
+
(
|
|
191
|
+
spec["label"],
|
|
192
|
+
spec["module"],
|
|
193
|
+
status,
|
|
194
|
+
detail,
|
|
195
|
+
fix_cmd,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
py_info_lines.append(f"- {spec['label']}: {status} ({detail})")
|
|
199
|
+
|
|
200
|
+
# Tambah info ODBC (khusus SQL Server)
|
|
201
|
+
py_info_lines.append("")
|
|
202
|
+
py_info_lines.append("=== Status ODBC (SQL Server) ===")
|
|
203
|
+
odbc_status, odbc_detail, odbc_drivers = _check_odbc()
|
|
204
|
+
py_info_lines.append(f"ODBC Drivers: {odbc_status} ({odbc_detail})")
|
|
205
|
+
if odbc_drivers:
|
|
206
|
+
py_info_lines.append("Daftar driver:")
|
|
207
|
+
for drv in odbc_drivers:
|
|
208
|
+
py_info_lines.append(f" - {drv}")
|
|
209
|
+
|
|
210
|
+
# Isi tabel
|
|
211
|
+
for row_data in rows:
|
|
212
|
+
r = self.table.rowCount()
|
|
213
|
+
self.table.insertRow(r)
|
|
214
|
+
for c, value in enumerate(row_data):
|
|
215
|
+
item = QTableWidgetItem(value)
|
|
216
|
+
if c == 2: # kolom Status
|
|
217
|
+
if value == "Missing":
|
|
218
|
+
item.setForeground(Qt.red)
|
|
219
|
+
elif value == "Warning":
|
|
220
|
+
item.setForeground(Qt.darkYellow)
|
|
221
|
+
self.table.setItem(r, c, item)
|
|
222
|
+
|
|
223
|
+
self.txt_summary.setText("\n".join(py_info_lines))
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
|
|
3
|
+
from AnyQt import QtWidgets
|
|
4
|
+
from Orange.widgets.widget import Msg
|
|
5
|
+
from orangewidget import gui
|
|
6
|
+
from orangewidget.settings import Setting
|
|
7
|
+
|
|
8
|
+
from ._base_connection import BaseDBConnectionWidget
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OWMSSQLConnection(BaseDBConnectionWidget):
|
|
12
|
+
name = "SQL Server"
|
|
13
|
+
id = "dbconnections-mssql-connection"
|
|
14
|
+
description = "Koneksi ke Microsoft SQL Server via pyodbc."
|
|
15
|
+
icon = "icons/msql.png"
|
|
16
|
+
|
|
17
|
+
DB_KIND = "SQL Server (pyodbc)"
|
|
18
|
+
DEFAULT_PORT = 1433
|
|
19
|
+
|
|
20
|
+
integrated_auth: bool = Setting(False)
|
|
21
|
+
odbc_driver: str = Setting("ODBC Driver 18 for SQL Server")
|
|
22
|
+
trust_server_cert: bool = Setting(True)
|
|
23
|
+
|
|
24
|
+
class Warning(BaseDBConnectionWidget.Warning):
|
|
25
|
+
mssql_odbc_missing = Msg(
|
|
26
|
+
"pyodbc terpasang, tetapi ODBC driver OS untuk SQL Server tidak ditemukan. "
|
|
27
|
+
"Pasang 'Microsoft ODBC Driver 18 for SQL Server' di OS Anda."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def _extra_controls(self, box: QtWidgets.QGroupBox) -> None:
|
|
31
|
+
# gui.checkBox(box, self, "integrated_auth", "Login dengan AD / Integrated")
|
|
32
|
+
gui.checkBox(box, self, "trust_server_cert", "Trust Server Certificate")
|
|
33
|
+
gui.lineEdit(box, self, "odbc_driver", label="ODBC Driver:")
|
|
34
|
+
|
|
35
|
+
def _params(self) -> Dict[str, Any]:
|
|
36
|
+
p = super()._params()
|
|
37
|
+
p.update({
|
|
38
|
+
"integrated_auth": bool(self.integrated_auth),
|
|
39
|
+
"odbc_driver": self.odbc_driver.strip(),
|
|
40
|
+
"trust_server_cert": bool(self.trust_server_cert),
|
|
41
|
+
})
|
|
42
|
+
return p
|
|
43
|
+
|
|
44
|
+
def _build_url(self, params: Dict[str, Any]) -> str:
|
|
45
|
+
host = params.get("host") or ""
|
|
46
|
+
port = params.get("port") or 1433
|
|
47
|
+
db = params.get("database") or ""
|
|
48
|
+
user = params.get("user") or ""
|
|
49
|
+
pwd = params.get("password") or ""
|
|
50
|
+
integrated = params.get("integrated_auth", False)
|
|
51
|
+
odbc_driver = params.get("odbc_driver", "ODBC Driver 18 for SQL Server")
|
|
52
|
+
extra = f"TrustServerCertificate={'yes' if params.get('trust_server_cert', True) else 'no'}"
|
|
53
|
+
|
|
54
|
+
if not integrated:
|
|
55
|
+
return (
|
|
56
|
+
"mssql+pyodbc://"
|
|
57
|
+
f"{user}:{pwd}@{host}:{port}/{db}"
|
|
58
|
+
f"?driver={odbc_driver.replace(' ', '+')}&{extra}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
"mssql+pyodbc://@"
|
|
63
|
+
f"{host}:{port}/{db}"
|
|
64
|
+
f"?driver={odbc_driver.replace(' ', '+')}&Trusted_Connection=yes&{extra}"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _on_connected_extra(self, engine) -> None:
|
|
68
|
+
# cek ODBC driver OS
|
|
69
|
+
try:
|
|
70
|
+
import pyodbc # type: ignore
|
|
71
|
+
drivers = {d.lower() for d in pyodbc.drivers()}
|
|
72
|
+
expected = self.odbc_driver.lower()
|
|
73
|
+
if not any(expected in d for d in drivers):
|
|
74
|
+
self.Warning.mssql_odbc_missing()
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from ._base_connection import BaseDBConnectionWidget
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OWMySQLConnection(BaseDBConnectionWidget):
|
|
6
|
+
name = "MySQL"
|
|
7
|
+
id = "dbconnections-mysql-connection"
|
|
8
|
+
description = "Koneksi ke MySQL/MariaDB via PyMySQL."
|
|
9
|
+
icon = "icons/mysql.png"
|
|
10
|
+
|
|
11
|
+
DB_KIND = "MySQL"
|
|
12
|
+
DEFAULT_PORT = 3306
|
|
13
|
+
|
|
14
|
+
def _build_url(self, params: Dict[str, Any]) -> str:
|
|
15
|
+
user = params.get("user") or ""
|
|
16
|
+
pwd = params.get("password") or ""
|
|
17
|
+
host = params.get("host") or ""
|
|
18
|
+
port = params.get("port") or 3306
|
|
19
|
+
db = params.get("database") or ""
|
|
20
|
+
return f"mysql+pymysql://{user}:{pwd}@{host}:{port}/{db}"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from ._base_connection import BaseDBConnectionWidget
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OWOracleConnection(BaseDBConnectionWidget):
|
|
6
|
+
name = "Oracle"
|
|
7
|
+
id = "dbconnections-oracle-connection"
|
|
8
|
+
description = "Koneksi ke Oracle Database. Output: SQLAlchemy Engine."
|
|
9
|
+
icon = "icons/oracle.png"
|
|
10
|
+
|
|
11
|
+
DB_KIND = "Oracle"
|
|
12
|
+
DEFAULT_PORT = 1521 # port default listener Oracle
|
|
13
|
+
|
|
14
|
+
def _build_url(self, params: Dict[str, Any]) -> str:
|
|
15
|
+
"""
|
|
16
|
+
Bangun SQLAlchemy URL untuk Oracle (cx_Oracle).
|
|
17
|
+
|
|
18
|
+
Field "Database/Schema/Path" di form diisi dengan SERVICE_NAME,
|
|
19
|
+
misalnya: ORCLPDB1
|
|
20
|
+
"""
|
|
21
|
+
user = params.get("user") or ""
|
|
22
|
+
pwd = params.get("password") or ""
|
|
23
|
+
host = params.get("host") or "localhost"
|
|
24
|
+
port = params.get("port") or 1521
|
|
25
|
+
service = params.get("database") or "" # SERVICE_NAME
|
|
26
|
+
|
|
27
|
+
# auth
|
|
28
|
+
auth = ""
|
|
29
|
+
if user:
|
|
30
|
+
if pwd:
|
|
31
|
+
auth = f"{user}:{pwd}@"
|
|
32
|
+
else:
|
|
33
|
+
auth = f"{user}@"
|
|
34
|
+
|
|
35
|
+
# easy connect dengan service_name
|
|
36
|
+
if service:
|
|
37
|
+
dsn = f"{host}:{port}/?service_name={service}"
|
|
38
|
+
else:
|
|
39
|
+
dsn = f"{host}:{port}"
|
|
40
|
+
|
|
41
|
+
return f"oracle+cx_oracle://{auth}{dsn}"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from ._base_connection import BaseDBConnectionWidget
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OWPostgresConnection(BaseDBConnectionWidget):
|
|
6
|
+
name = "PostgreSQL"
|
|
7
|
+
id = "dbconnections-postgresql-connection"
|
|
8
|
+
description = "Koneksi ke PostgreSQL. Output: SQLAlchemy Engine."
|
|
9
|
+
icon = "icons/postgres.png"
|
|
10
|
+
|
|
11
|
+
DB_KIND = "PostgreSQL"
|
|
12
|
+
DEFAULT_PORT = 5432
|
|
13
|
+
|
|
14
|
+
def _build_url(self, params: Dict[str, Any]) -> str:
|
|
15
|
+
user = params.get("user") or ""
|
|
16
|
+
pwd = params.get("password") or ""
|
|
17
|
+
host = params.get("host") or ""
|
|
18
|
+
port = params.get("port") or 5432
|
|
19
|
+
db = params.get("database") or ""
|
|
20
|
+
return f"postgresql+psycopg2://{user}:{pwd}@{host}:{port}/{db}"
|