[Python] Python PyQt5 hanging and exiting console when run twice

Discussão em 'Python' iniciado por Stack, Abril 24, 2025.

  1. Stack

    Stack Membro Participativo

    I received some amazing PyQt5 code from @Taras Melnyk in this question, which runs great as a standalone script.

    What I'm trying to do is turn that standalone script into a function that I can run as many times as I need to.

    I've done that, and the function below runs fine when I run it one time. However, a subsequent call to the function hangs and then exits the Python console.

    I've done a lot of reading on this trying to find a fix. I've learned how PyQt5 (Python wrapper for the C++ library) implements its own errors inside its threads, so it is very hard to catch those errors in Python. I've tried several solutions from several posts, with no luck catching the error (you can see these tries in the code below, with relevant links commented above them).

    What am I missing here, so that I can run this function multiple times without it hanging and erroring out?

    NOTE: excuse the print() statements - I just wanted a quick debug. I know logging is better and I do use it frequently.

    import json
    import traceback

    import requests
    from PyQt5.QtWidgets import QApplication
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    from PyQt5.QtCore import QUrl, Qt
    import sys

    from PyQt5.uic.properties import QtCore

    # https://stackoverflow.com/questions/40227047/python-pyqt5-how-to-show-an-error-message-with-pyqt5/47275100#47275100
    def my_exception_hook(exctype, value, traceback):
    # Print the error and traceback
    print(exctype, value, traceback)
    # Call the normal Exception hook after
    sys.__excepthook__(exctype, value, traceback)

    # Back up the reference to the exceptionhook
    sys._excepthook = sys.excepthook

    # Set the exception hook to our wrapping function
    sys.excepthook = my_exception_hook


    class SAMLBrowser(QWebEngineView):
    def __init__(self, idp_url, pvwa_cookie, on_saml_response):
    super().__init__()
    self.idp_url = idp_url
    self.pvwa_cookie = pvwa_cookie
    self.on_saml_response = on_saml_response
    self.loadFinished.connect(self.check_for_saml_response)
    self.navigate_to_idp()
    self.saml_token = ""

    # Make sure the window shows up on top so we can easily see it
    # https://stackoverflow.com/questions/50631443/how-to-make-application-window-stay-on-top-in-pyqt5
    self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint)

    def navigate_to_idp(self):
    print(f"inside SAMLBrowser.navigate_to_idp - running setUrl with idp_url '{self.idp_url}'")
    try:
    self.setUrl(QUrl(self.idp_url))
    except Exception as e:
    print(f"inside SAMLBrowser.navigate_to_idp - Oh no something went wrong!")
    traceback.print_exc()
    print(f"inside SAMLBrowser.navigate_to_idp - setUrl complete")

    def check_for_saml_response(self):
    """After every load, check for presence of SAMLResponse."""
    # Evaluate JavaScript to inspect the contents of the webpage
    print(f"inside SAMLBrowser.check_for_saml_response - calling process_page")
    self.page().toHtml(self.process_page)
    print(f"inside SAMLBrowser.check_for_saml_response - call to process_page complete")

    def process_page(self, html):
    print(f"inside SAMLBrowser.process_page")
    if "SAMLResponse" in html:
    print(f"inside SAMLBrowser.process_page - FOUND SAMLRESPONSE!!!")
    # This assumes the SAMLResponse is part of an <input> field
    self.page().runJavaScript('document.getElementsByName("SAMLResponse")[0].value', self.handle_saml_response)
    print(f"inside SAMLBrowser.process_page - runJavaScript() complete")

    def handle_saml_response(self, saml_response):
    print(f"inside SAMLBrowser.handle_saml_response")
    if saml_response:

    print(f"inside SAMLBrowser.handle_saml_response - FOUND SAMLRESPONSE!!!")
    # Trim and pass response back to the parent
    self.saml_token = self.on_saml_response(saml_response)
    print(f"inside SAMLBrowser.handle_saml_response - set saml_token successfully.")

    print(f"inside SAMLBrowser.handle_saml_response - calling .close()")
    self.close()
    print(f"inside SAMLBrowser.handle_saml_response - .close() called successfully")


    def saml_authentication(pvwa_address):
    session = requests.Session()

    # 1. First POST request to get the IDP URL and cookies
    logon_url = f"{pvwa_address}/PasswordVault/api/auth/saml/Logon"

    print(f"Getting initial IDP URL from Password Vault")

    try:
    response = session.post(logon_url, json={}, verify=False)
    response.raise_for_status()
    idp_url = response.json()
    print(f"Initial IDP URL returned as: {idp_url}")
    except Exception as e:
    print(f"Failed to fetch IDP URL: {e}")
    return None

    # Extract 'CA88888' cookie
    ca88888_cookie = None
    print(f"Looking in session cookies for the 'CA88888' cookie")
    for cookie in session.cookies:
    if cookie.name == "CA88888":
    print(f"Found the 'CA88888' cookie")
    ca88888_cookie = cookie.value

    if not ca88888_cookie:
    print("Could not extract CA88888 cookie.")
    return None

    # 2. Launch the SAML browser to interact with the IDP login page
    def on_saml_response(saml_response):
    print(f"Inside saml_authentication.on_saml_response")

    # Submit the SAML response to complete authentication
    body = {
    "concurrentSession": "true",
    "apiUse": "true",
    "SAMLResponse": saml_response.strip()
    }
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}

    print(f"Adding 'CA88888' cookie to our session cookies, in preparation for our final call to PWV to get our token")

    # Use a new session to send back with cookies
    session.cookies.set("CA88888", ca88888_cookie, domain=pvwa_address.split("/")[2], path="/")

    try:
    final_response = session.post(logon_url, data=body, headers=headers, verify=False)
    final_response.raise_for_status()
    print(f"final call to PWV made")

    # print("Session Token:", final_response.text)
    return final_response.text
    except Exception as e:
    print(f"Failed to retrieve session token: {e}")

    try:
    print(f"inside saml_authentication - starting QApplication")
    app = QApplication(sys.argv)

    print(f"inside saml_authentication - starting SAMLBrowser")
    browser = SAMLBrowser(idp_url, ca88888_cookie, on_saml_response)

    print(f"inside saml_authentication - running browser.show()")
    browser.show()

    # Start the event loop
    print(f"inside saml_authentication - starting app event loop")
    app.exec_()

    # https://stackoverflow.com/questions/55819330/catching-exceptions-raised-in-qapplication
    app.quit()

    my_saml_token = browser.saml_token

    # https://stackoverflow.com/questions/40704725/qwebengineview-not-closing-properly-in-some-cases
    del app
    del browser

    print(f"inside saml_authentication - returning our saml_token")
    # return our saml_token (will be blank if we didn't get one)
    return my_saml_token
    except Exception: # just trying to catch anything at this point
    traceback.print_exec()
    return None

    # Driver Code
    PVWA_ADDRESS = "https://passwordvault.acme.net"

    # This first function call works great, and returns my SAML Token
    token = saml_authentication(PVWA_ADDRESS)

    # This second function call hangs and exits the console, no errors/traceback printed
    token = saml_authentication(PVWA_ADDRESS)


    Here are the print statements from a successful run, and a failed run:

    SUCCESSFUL RUN

    Getting initial IDP URL from Password Vault
    Initial IDP URL returned as: 'https://login.microsoftonline.com/abcdefg123456/saml2?SAMLRequest=WXYZ9876'
    Looking in session cookies for the 'CA88888' cookie
    Found the 'CA88888' cookie
    inside saml_authentication - starting QApplication
    inside saml_authentication - starting SAMLBrowser
    inside SAMLBrowser.navigate_to_idp - running setUrl with idp_url 'https://login.microsoftonline.com/abcdefg123456/saml2?SAMLRequest=WXYZ9876'
    inside SAMLBrowser.navigate_to_idp - setUrl complete
    inside saml_authentication - running browser.show()
    inside saml_authentication - starting app event loop
    inside SAMLBrowser.check_for_saml_response - calling process_page
    inside SAMLBrowser.check_for_saml_response - call to process_page complete
    inside SAMLBrowser.process_page
    inside SAMLBrowser.check_for_saml_response - calling process_page
    inside SAMLBrowser.check_for_saml_response - call to process_page complete
    inside SAMLBrowser.process_page
    inside SAMLBrowser.check_for_saml_response - calling process_page
    inside SAMLBrowser.check_for_saml_response - call to process_page complete
    inside SAMLBrowser.process_page
    inside SAMLBrowser.check_for_saml_response - calling process_page
    inside SAMLBrowser.check_for_saml_response - call to process_page complete
    inside SAMLBrowser.process_page
    inside SAMLBrowser.process_page - FOUND SAMLRESPONSE!!!
    inside SAMLBrowser.process_page - runJavaScript() complete
    inside SAMLBrowser.handle_saml_response
    inside SAMLBrowser.handle_saml_response - FOUND SAMLRESPONSE!!!
    Inside saml_authentication.on_saml_response
    Adding 'CA88888' cookie to our session cookies, in preparation for our final call to PWV to get our token
    final call to PWV made
    inside SAMLBrowser.handle_saml_response - set saml_token successfully.
    inside SAMLBrowser.handle_saml_response - calling .close()
    inside SAMLBrowser.handle_saml_response - .close() called successfully
    inside SAMLBrowser.check_for_saml_response - calling process_page
    inside SAMLBrowser.check_for_saml_response - call to process_page complete
    inside SAMLBrowser.process_page
    inside saml_authentication - returning our saml_token


    FAILED RUN (when I run the function the 2nd time and it hangs and exits the console)

    Getting initial IDP URL from Password Vault
    Initial IDP URL returned as: 'https://login.microsoftonline.com/abcdefg123456/saml2?SAMLRequest=WXYZ9876'
    Looking in session cookies for the 'CA88888' cookie
    Found the 'CA88888' cookie
    inside saml_authentication - starting QApplication
    inside saml_authentication - starting SAMLBrowser
    inside SAMLBrowser.navigate_to_idp - running setUrl with idp_url 'https://login.microsoftonline.com/abcdefg123456/saml2?SAMLRequest=WXYZ9876'

    Continue reading...

Compartilhe esta Página