multi-interface input seeking
Date: 2025 10-October 01
Let's say my library for my CLI program can also serve a library for a GUI program. Currently, I seek inputs with Python's input() for the console. Is there a way to instead have the prompt cause a pop-up window, or an error in GUI to direct the user to some configuration tab in GUI?
That's the right direction for a robust library! The key is to check the execution environment and branch your logic accordingly.
You want your library to have three modes of operation for getting credentials:
-
CLI Mode (Interactive): Use
typer.promptandgetpass.getpass(which you already have). -
GUI Mode (Non-interactive): Use a simple GUI module to display a temporary popup window to get the input.
-
Library Mode (Non-interactive): If no terminal or GUI is available, raise a specific error that the caller (like a main GUI application) can catch and handle (e.g., by directing the user to a permanent configuration screen).
Here is a common pattern to achieve this, using a lightweight, standard-library-friendly GUI module like tkinter for the popup, or a popular dedicated popup library like PySimpleGUI or easygui (though those require an extra install).
Recommended Pattern using tkinter (Standard Library)
You can use the built-in tkinter library for simple popups without adding external dependencies.1
1. Create a Helper to Check Interactivity
A simple check to see if a terminal is available is to use sys.stdin.isatty().2
Python
import sys
import os
import getpass
from typing import Optional
def is_interactive_cli() -> bool:
"""Check if the script is running in an interactive terminal."""
# Check if a tty is attached to stdin
return sys.stdin.isatty() and os.isatty(sys.stdin.fileno())
# You might want another check if a display server is running, but isatty is usually sufficient.
2. Create a Helper for GUI Popup Input
This uses tkinter to create a simple popup window for input.
Python
# new_file/gui_input_fallback.py (or a new function in security.py)
import tkinter as tk
from tkinter import simpledialog
def gui_get_input(prompt_message: str, hide_input: bool = False) -> Optional[str]:
"""
Displays a modal GUI popup to get input when no terminal is available.
"""
try:
root = tk.Tk()
root.withdraw() # Hide the main window
# Determine the display character for password input
show_char = '*' if hide_input else None
# Use the simpledialog module for a standard input box
value = simpledialog.askstring(
title="Configuration Required",
prompt=prompt_message,
show=show_char # '*' for hidden/password input
)
root.destroy()
return value
except Exception:
# Fails if tkinter is not installed or the display is not running
return None
3. Update Your Core Functions
Now you modify your helper functions (_get_config_with_prompt and _get_credential_with_prompt) to fall back to the GUI prompt.
Python
# In pipeline/security.py (updated version)
# ... imports (including your new gui_get_input function)
# ... CredentialsNotFoundError definition
def _prompt_for_value(prompt_message: str, hide_input: bool) -> str:
"""Handles prompting with a fallback from CLI to GUI."""
if is_interactive_cli():
# 1. CLI Mode (Interactive)
typer.echo(f"\n --- One-time configuration required --- ")
if hide_input:
new_value = getpass.getpass(f"{prompt_message}: ")
else:
new_value = input(f"{prompt_message}: ")
return new_value
else:
# 2. GUI Mode (Non-interactive fallback)
typer.echo(f"\n --- Non-interactive process detected. Opening GUI prompt. --- ")
value = gui_get_input(prompt_message, hide_input)
if value is not None:
return value
else:
# 3. Library Mode (No Terminal or GUI Display)
raise CredentialsNotFoundError(
f"Configuration for '{prompt_message}' is missing and "
f"neither an interactive terminal nor a GUI display is available. "
f"Please use a configuration utility or provide the value programmatically."
)
def _get_credential_with_prompt(service_name: str, item_name: str, prompt_message: str, hide_password: bool = True, overwrite: bool = False) -> str:
# ... (same credential check logic as before) ...
if credential is None or overwrite:
# Use the new centralized prompting function
new_credential = _prompt_for_value(
prompt_message=prompt_message,
hide_input=hide_password
)
# ... (rest of the saving logic) ...
return new_credential
return credential
# You would update _get_config_with_prompt in a similar way.
Alternative: Error and Configuration Tab
If the application that is importing your library is a full GUI application (like an Electron app or a large desktop app), you don't want popups popping up randomly.
In this case, the best approach is to stick to the third method above:
-
Remove all
input,getpass, and GUI popup logic from your library. -
In the functions like
get_eds_api_credentials, if the value is missing, always raise a specific exception (CredentialsNotFoundError).
The main GUI application that imported your library would then do this:
Python
try:
creds = get_eds_api_credentials("Maxson")
# Use credentials
except CredentialsNotFoundError as e:
# Catch the error, display an unmissable error popup in the GUI,
# and direct the user to the application's permanent "Settings" tab.
gui_app.display_error("Missing Credentials", str(e))
gui_app.switch_to_settings_tab()
This makes your library cleaner and forces the caller to handle the missing configuration in a way that is consistent with their user interface.