Why doesn't Tkinter window close on `destroy` in interactive session (IDLE / REPL) in macOS?

Why doesn't Tkinter window close on `destroy` in interactive session (IDLE / REPL) in macOS?

Problem Description:

I have a function to display a GUI window where the user can type the password. I use IDLE shell a lot during development, so using getpass is not an option.

This function is part of a utility script that has other functions I want to be able to use (after submitting the password using this function).

import tkinter


def password_from_user():
    password = None
    
    def get_password(*__):
        nonlocal password
        password = entry.get()
        window.destroy()
    
    window = tkinter.Tk()
    entry = tkinter.Entry(window, width=40, show='*')
    entry.bind('<Return>', get_password)
    entry.pack()
    entry.focus_set()
    
    window.mainloop()
    
    return password

The password gets stored and returned from the function. However, the Tkinter window stays open. I can press +Tab to switch back to IDLE shell, but it’s slightly inconvenient. I’d like this window to be completely closed.

If I run this as a script, of course, everything gets closed at the end:

if __name__ == '__main__':
    print(password_from_user())

But, I need this function only for IDLE session. If I were only using command line, I would use getpass which would be more than sufficient.

Edit 1: I have tried using both destroy and quit. Neither of them works for me.

Edit 2: Just tested on a Windows machine with Python 3.8.5. It works. So, I’m pretty sure it’s something to do with macOS.

Solution – 1

Figured out a solution that satisfies me.

Save the Tkinter code in a separate module (say, _password_box_ui.py):

import tkinter

password = None

def get_password(*__):
    global password
    password = entry.get()
    window.quit()

window = tkinter.Tk()
window.title('Password')

entry = tkinter.Entry(window, width=40, show='*')
entry.bind('<Return>', get_password)
entry.pack()
entry.focus_set()

window.mainloop()

print(password)

Then, in another module (which I’ll import in the IDLE shell), use a function like this:

import pathlib
import subprocess
import sys


def password_from_user():
    process = subprocess.run(
        [
            f'{sys.base_exec_prefix}/bin/python',
            pathlib.Path(__file__).parent / '_password_box_ui.py',
        ],
        capture_output=True,
    )
    return process.stdout.rstrip(b'n').decode()

May not be very elegant, but serves my purpose, which is to use IDLE to get password from the user using a GUI (so that, ultimately, no password needs to be stored in plaintext anywhere).

Rate this post
We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept
Reject