From 42a2cb8119da458c278bbf6fbaa1924a6f2f74aa Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 23:32:41 +0000 Subject: [PATCH] Fix live-processing API key bug and non-Windows startup crash - Live Methods (single/multi-label classification, keyword extraction) cached the OpenAI client at import time, so entering an API key in Settings never took effect for them (only Batch Methods refreshed correctly). They now fetch a fresh client via batch_method.get_client() at call time and show a clear error if no key is configured. - Main window crashed on startup on Linux/macOS because .ico icons only work with iconbitmap on Windows; now the failure is caught gracefully. - Removed dead duplicate _selected_header_label/_current_header_labels definitions in data_import.py and an unused tkinter import in batch_creation.py. - Replaced Pydantic's deprecated min_items with min_length to remove deprecation warnings ahead of Pydantic v3. --- batch_processing/batch_creation.py | 5 ++--- file_handling/data_import.py | 11 ----------- live_processing/keyword_extraction_live.py | 21 ++++++++++----------- live_processing/multi_label_live.py | 21 ++++++++++----------- live_processing/single_label_live.py | 19 +++++++++---------- ui/main_window.py | 7 ++++++- 6 files changed, 37 insertions(+), 47 deletions(-) diff --git a/batch_processing/batch_creation.py b/batch_processing/batch_creation.py index 1f10bb9..ccacef5 100644 --- a/batch_processing/batch_creation.py +++ b/batch_processing/batch_creation.py @@ -11,7 +11,6 @@ from enum import Enum from io import BytesIO from typing import Optional, List -import tkinter as tk from pydantic import BaseModel, ConfigDict, Field from file_handling.data_conversion import make_str_enum @@ -108,7 +107,7 @@ def generate_multi_label_batch(labels, quotes) -> BytesIO | None: labels_enum = make_str_enum("Label", labels) class LabeledQuoteMulti(BaseModel): - label: List[labels_enum] = Field(..., min_items=1) + label: List[labels_enum] = Field(..., min_length=1) model_config = ConfigDict(use_enum_values=True, extra='forbid') SCHEMA = LabeledQuoteMulti.model_json_schema() @@ -156,7 +155,7 @@ def generate_keyword_extraction_batch(texts) -> BytesIO | None: Returns a BytesIO whose .name is set to 'batchinput.jsonl'. """ class KeywordExtraction(BaseModel): - keywords: list[str] = Field(..., min_items=1) + keywords: list[str] = Field(..., min_length=1) model_config = ConfigDict(extra='forbid') SCHEMA = KeywordExtraction.model_json_schema() diff --git a/file_handling/data_import.py b/file_handling/data_import.py index a2b44b1..3651303 100644 --- a/file_handling/data_import.py +++ b/file_handling/data_import.py @@ -360,17 +360,6 @@ def _initial_blank_preview(): _fill_preview_rows([[""] * 5 for _ in range(5)]) _update_dataset_name_preview() - def _current_header_labels() -> list[str]: - # Return the labels currently shown atop the preview - return [tree.heading(cid)["text"] for cid in tree["columns"]] - - def _selected_header_label() -> str: - labels = _current_header_labels() - idx = selected_col.get() - if 0 <= idx < len(labels): - return str(labels[idx]) - return "" - def _update_dataset_name_controls_enabled(): # Enable/disable "selected column header" radio based on has_headers + data present has_data = bool(_loaded_rows) diff --git a/live_processing/keyword_extraction_live.py b/live_processing/keyword_extraction_live.py index 68ad06c..109681f 100644 --- a/live_processing/keyword_extraction_live.py +++ b/live_processing/keyword_extraction_live.py @@ -8,29 +8,28 @@ from typing import Optional import tkinter as tk +from tkinter import messagebox from pydantic import BaseModel, ValidationError, Field, ConfigDict -from openai import OpenAI from file_handling.data_import import import_data from file_handling.data_conversion import save_as_csv, to_long_df -from settings import secrets_store, config +from settings import config +from batch_processing.batch_method import get_client # Progress UI lives in a separate module from ui.progress_ui import ProgressController -# Initialize OpenAI client with stored API key -try: - OPENAI_API_KEY = secrets_store.load_api_key() - client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None -except Exception: - OPENAI_API_KEY = None - client = None - def keyword_extraction_pipeline(parent: Optional[tk.Misc] = None): """ Prompt for quotes CSVs, extract keywords from each quote, show progress, then save results to CSV. """ + try: + client = get_client() + except Exception as e: + messagebox.showerror("API Key Required", str(e)) + return + # Get quotes data from_import = import_data(parent, "Select the quotes data") if from_import is None: @@ -40,7 +39,7 @@ def keyword_extraction_pipeline(parent: Optional[tk.Misc] = None): class KeywordExtraction(BaseModel): id: int | None = None quote: str - keywords: list[str] = Field(..., min_items=1) + keywords: list[str] = Field(..., min_length=1) model_config = ConfigDict() total = len(quotes) diff --git a/live_processing/multi_label_live.py b/live_processing/multi_label_live.py index a16a335..e40b8f0 100644 --- a/live_processing/multi_label_live.py +++ b/live_processing/multi_label_live.py @@ -8,29 +8,28 @@ from typing import Optional, List import tkinter as tk +from tkinter import messagebox from pydantic import BaseModel, ValidationError, Field, ConfigDict -from openai import OpenAI from file_handling.data_import import import_data from file_handling.data_conversion import make_str_enum, save_as_csv, to_long_df -from settings import secrets_store, config +from settings import config +from batch_processing.batch_method import get_client # Progress UI lives in a separate module from ui.progress_ui import ProgressController -# Initialize OpenAI client with stored API key -try: - OPENAI_API_KEY = secrets_store.load_api_key() - client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None -except Exception: - OPENAI_API_KEY = None - client = None - def multi_label_pipeline(parent: Optional[tk.Misc] = None): """ Prompt for labels/quotes CSVs, classify each quote with 1+ labels, show progress, then save results to CSV. """ + try: + client = get_client() + except Exception as e: + messagebox.showerror("API Key Required", str(e)) + return + # Get labels data from_import = import_data(parent, "Select the labels data") if from_import is None: @@ -47,7 +46,7 @@ def multi_label_pipeline(parent: Optional[tk.Misc] = None): class LabeledQuoteMulti(BaseModel): id: int | None = None quote: str - label: List[labels] = Field(..., min_items=1) + label: List[labels] = Field(..., min_length=1) model_config = ConfigDict(use_enum_values=True, extra='forbid') total = len(quotes) diff --git a/live_processing/single_label_live.py b/live_processing/single_label_live.py index ee804c3..896232c 100644 --- a/live_processing/single_label_live.py +++ b/live_processing/single_label_live.py @@ -8,29 +8,28 @@ from typing import Optional import tkinter as tk +from tkinter import messagebox from pydantic import BaseModel, ValidationError, Field, ConfigDict -from openai import OpenAI from file_handling.data_import import import_data from file_handling.data_conversion import make_str_enum, save_as_csv, to_long_df -from settings import secrets_store, config +from settings import config +from batch_processing.batch_method import get_client # Progress UI lives in a separate module from ui.progress_ui import ProgressController -# Initialize OpenAI client with stored API key -try: - OPENAI_API_KEY = secrets_store.load_api_key() - client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None -except Exception: - OPENAI_API_KEY = None - client = None - def single_label_pipeline(parent: Optional[tk.Misc] = None): """ Prompt for labels/quotes CSVs, classify each quote with exactly one label, show progress, then save results to CSV. """ + try: + client = get_client() + except Exception as e: + messagebox.showerror("API Key Required", str(e)) + return + # Get labels data from_import = import_data(parent, "Select the labels data") if from_import is None: diff --git a/ui/main_window.py b/ui/main_window.py index 80efc85..9f898d4 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -92,7 +92,12 @@ def build_ui(root: tk.Tk) -> None: root: The main Tkinter window to build the UI in """ root.title(APP_TITLE) - root.iconbitmap(asset_path("app.ico")) + try: + # .ico files are only natively supported by iconbitmap on Windows; + # this raises TclError on Linux/macOS Tk builds. + root.iconbitmap(asset_path("app.ico")) + except tk.TclError: + pass # ===== Top-level grid: header, spacer, table area ===== root.columnconfigure(0, weight=1)