Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Main application class for ClickHouse Client."""

import logging

from dearpygui.dearpygui import *

from async_worker import AsyncWorker
Expand All @@ -17,6 +19,8 @@
from theme_manager import ThemeManager
from utils import UIHelpers

logger = logging.getLogger(__name__)


class ClickHouseClientApp:
"""Main application class that orchestrates all components."""
Expand Down Expand Up @@ -85,6 +89,9 @@ def __init__(self):

# Set up callbacks for connection manager
self.connection_manager.on_connect_success = self._handle_connect_success
self.connection_manager.on_connect_failure = (
self.table_browser_ui.handle_connect_failure
)

# Initialize status manager with theme
StatusManager.set_theme_manager(self.theme_manager)
Expand Down Expand Up @@ -114,6 +121,8 @@ def setup_ui(self):
def _handle_connect_success(self):
"""Handle additional tasks after successful connection."""
try:
self.table_browser_ui.clear_connection_failure_notice()

# Configure the connection pool with the current credentials
params = self.connection_manager.get_connection_parameters()
if params:
Expand All @@ -128,15 +137,15 @@ def _handle_connect_success(self):
self.table_browser_ui.filter_tables_callback(None, current_search)

except Exception as e:
print(f"[DEBUG] Error in connect success handler: {str(e)}")
logger.debug("Error in connect success handler: %s", e, exc_info=True)

def _handle_credentials_saved(self):
"""Handle tasks after credentials are saved."""
try:
# Update the connections list in the table browser UI
self.table_browser_ui.show_saved_connections()
except Exception as e:
print(f"[DEBUG] Error in credentials saved handler: {str(e)}")
logger.debug("Error in credentials saved handler: %s", e, exc_info=True)

def run(self):
"""Run the application."""
Expand All @@ -157,6 +166,7 @@ def run(self):
# Keep expensive result-grid callbacks within a small per-frame budget.
self.async_worker.process_pending()
self.table_browser_ui.process_pending_filter()
self.table_browser_ui.process_pending_connection_notice()
self.ui_layout.splitter.update()
self.tabbed_query_interface.update_resize()
self.tabbed_query_interface.poll_closed_tabs()
Expand Down
68 changes: 44 additions & 24 deletions components/connection_manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Connection management functionality for ClickHouse Client."""

import logging
import time
import traceback

Expand All @@ -21,6 +22,8 @@
from database import DatabaseManager
from utils import UIHelpers, validate_connection_params

logger = logging.getLogger(__name__)


class ConnectionManager:
"""Manages database connections and related operations."""
Expand All @@ -40,6 +43,12 @@ def __init__(

# Optional callbacks for additional functionality
self.on_connect_success = None # Called after successful connection
self.on_connect_failure = None # Called after a failed connection attempt

def _notify_connect_failure(self):
"""Notify UI components that the current connection attempt has ended."""
if self.on_connect_failure:
self.on_connect_failure()

def _show_modal_status(self, message, error=False):
"""Show a status message in the connection modal's status area."""
Expand All @@ -55,7 +64,7 @@ def _show_modal_status(self, message, error=False):

def get_connection_parameters(self):
"""Get connection parameters, prioritizing form values over stored credentials."""
print("[DEBUG] Attempting to get connection parameters")
logger.debug("Attempting to get connection parameters")

# Try to get values from form first
host = UIHelpers.safe_get_value("host_input", None)
Expand All @@ -73,7 +82,7 @@ def get_connection_parameters(self):
)

if form_has_values:
print("[DEBUG] Using form values")
logger.debug("Using form values")
# Use form values, with defaults for empty optional fields
return {
"host": host or DEFAULT_HOST,
Expand All @@ -83,7 +92,7 @@ def get_connection_parameters(self):
"database": database or DEFAULT_DATABASE,
}
elif self.stored_credentials:
print("[DEBUG] Form values not available, using stored credentials")
logger.debug("Form values not available, using stored credentials")
return {
"host": self.stored_credentials.get("host", DEFAULT_HOST),
"port": self.stored_credentials.get("port", DEFAULT_PORT),
Expand All @@ -92,8 +101,8 @@ def get_connection_parameters(self):
"database": self.stored_credentials.get("database", DEFAULT_DATABASE),
}
else:
print(
"[DEBUG] No form values or stored credentials available, using defaults"
logger.debug(
"No form values or stored credentials available, using defaults"
)
return {
"host": DEFAULT_HOST,
Expand All @@ -116,8 +125,12 @@ def connect_callback(self, sender, data):
password = params["password"]
database = params["database"]

print(
f"[DEBUG] Connection parameters: host={host}, port={port}, username={username}, database={database}"
logger.debug(
"Connection parameters: host=%s, port=%s, username=%s, database=%s",
host,
port,
username,
database,
)

is_valid, error_msg = validate_connection_params(
Expand All @@ -126,7 +139,7 @@ def connect_callback(self, sender, data):
if not is_valid:
raise ValueError(error_msg)

print("[DEBUG] Connection parameters validated successfully")
logger.debug("Connection parameters validated successfully")

except Exception as e:
# Validation failed on main thread — report immediately
Expand All @@ -139,6 +152,7 @@ def connect_callback(self, sender, data):
self.theme_manager.create_connection_indicator_theme(False),
)
UIHelpers.safe_configure_item("connect_button", enabled=True)
self._notify_connect_failure()
return

port = int(port)
Expand Down Expand Up @@ -177,8 +191,8 @@ def _on_connect_done(self, result):
success, message = result
UIHelpers.safe_configure_item("connect_button", enabled=True)

print(
f"[DEBUG] Connection attempt result: success={success}, message={message}"
logger.debug(
"Connection attempt result: success=%s, message=%s", success, message
)

if success:
Expand All @@ -200,6 +214,7 @@ def _on_connect_done(self, result):
"connection_indicator",
self.theme_manager.create_connection_indicator_theme(False),
)
self._notify_connect_failure()

def _on_connect_error(self, e: Exception):
"""Called on main thread when connection raises an unexpected exception."""
Expand All @@ -212,6 +227,7 @@ def _on_connect_error(self, e: Exception):
"connection_indicator",
self.theme_manager.create_connection_indicator_theme(False),
)
self._notify_connect_failure()

def test_credentials_callback(self, sender, data):
"""Test database credentials without establishing a persistent connection (non-blocking)."""
Expand All @@ -226,8 +242,12 @@ def test_credentials_callback(self, sender, data):
password = params["password"]
database = params["database"]

print(
f"[DEBUG] Testing credentials: host={host}, port={port}, username={username}, database={database}"
logger.debug(
"Testing credentials: host=%s, port=%s, username=%s, database=%s",
host,
port,
username,
database,
)

is_valid, error_msg = validate_connection_params(
Expand All @@ -236,7 +256,7 @@ def test_credentials_callback(self, sender, data):
if not is_valid:
raise ValueError(error_msg)

print("[DEBUG] Connection parameters validated successfully")
logger.debug("Connection parameters validated successfully")

except Exception as e:
error_msg = f"Credential test failed:\n{str(e)}"
Expand Down Expand Up @@ -285,7 +305,7 @@ def _on_test_done(self, result):
success, message = result
UIHelpers.safe_configure_item("connect_button", enabled=True)

print(f"[DEBUG] Credential test result: success={success}, message={message}")
logger.debug("Credential test result: success=%s, message=%s", success, message)

if success:
self._show_modal_status(f"✓ {message}", error=False)
Expand Down Expand Up @@ -322,18 +342,18 @@ def _on_test_error(self, e: Exception):
def auto_load_and_connect(self):
"""Auto-load credentials without attempting connection on startup."""
try:
print("[DEBUG] Starting auto_load_credentials")
logger.debug("Starting auto_load_credentials")

# Try to load the first available credentials
success, credentials, message = (
self.credentials_manager.load_credentials_legacy()
)
print(
f"[DEBUG] Load credentials result: success={success}, message={message}"
logger.debug(
"Load credentials result: success=%s, message=%s", success, message
)

if credentials:
print(f"[DEBUG] Credentials found: {credentials}")
logger.debug("Saved credentials found")

if success and credentials:
StatusManager.show_status(
Expand All @@ -346,23 +366,23 @@ def auto_load_and_connect(self):
# Only populate the form if the form elements exist (modal is open)
if does_item_exist("host_input"):
self.set_form_values(credentials)
print("[DEBUG] Form populated with auto-loaded credentials")
logger.debug("Form populated with auto-loaded credentials")
else:
print(
"[DEBUG] Form elements don't exist yet, credentials stored for later use"
logger.debug(
"Form elements do not exist yet; credentials stored for later use"
)

# No auto-connecting anymore
print("[DEBUG] Credentials loaded but not auto-connecting")
logger.debug("Credentials loaded but not auto-connecting")
else:
print("[DEBUG] No credentials found or load failed")
logger.debug("No credentials found or load failed")
StatusManager.show_status(
"No saved credentials found. Please enter connection details.",
error=False,
)

except Exception as e:
print(f"[DEBUG] Auto-load exception: {str(e)}")
logger.debug("Auto-load exception: %s", e, exc_info=True)
StatusManager.show_status(f"Auto-load failed: {str(e)}", error=True)

def set_form_values(self, credentials: dict):
Expand Down
9 changes: 6 additions & 3 deletions components/query_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import datetime
import json
import logging
import os
import re
import time
Expand All @@ -24,6 +25,8 @@
from icon_manager import icon_manager
from utils import get_result_column_types

logger = logging.getLogger(__name__)

MIN_QUERY_INPUT_HEIGHT = 80
MAX_QUERY_INPUT_HEIGHT = 600
RESIZER_HEIGHT = 6
Expand Down Expand Up @@ -826,7 +829,7 @@ def _show_loading(self, state: QueryTabState):
if self.status_callback:
self.status_callback("Executing query...", False)
except Exception as e:
print(f"Error showing loading indicator: {e}")
logger.debug("Error showing loading indicator: %s", e, exc_info=True)
state.loading_indicator = None
state.loading_animation_running = False

Expand All @@ -839,7 +842,7 @@ def _update_loading_message(self, state: QueryTabState, message: str):
):
set_value(f"{state.loading_indicator}_text", f"⏳ {message}")
except Exception as e:
print(f"Error updating loading message: {e}")
logger.debug("Error updating loading message: %s", e, exc_info=True)

def _hide_loading(self, state: QueryTabState):
state.loading_animation_running = False
Expand All @@ -848,7 +851,7 @@ def _hide_loading(self, state: QueryTabState):
if does_item_exist(state.loading_indicator):
delete_item(state.loading_indicator)
except Exception as e:
print(f"Error hiding loading indicator: {e}")
logger.debug("Error hiding loading indicator: %s", e, exc_info=True)
state.loading_indicator = None

# ------------------------------------------------------------------
Expand Down
Loading