2025, Oct 15 15:00

PyInstaller PostgreSQL client shows no output with psycopg2: diagnose and fix by switching to pg8000

Your .py works but the PyInstaller .exe prints nothing? Learn how to diagnose PostgreSQL/psycopg2 silence with simple checks and fix it by switching to pg8000.

Packaging a simple PostgreSQL client script with PyInstaller should be straightforward. Yet a frequent trap looks exactly like this: the .py runs fine, the PyInstaller build raises no errors, the executable starts … and produces no output at all. If you are querying a database and merely expect printed rows in the console, silent runs are especially confusing.

Problem setup

The script below connects to PostgreSQL, selects emails, and prints the results. As a Python file it works correctly; as a PyInstaller-built .exe it shows no result and no error.

import psycopg2

try:
    db_link = psycopg2.connect(
        dbname="xxxx",
        user="xxxx",
        password="xxxx",
        host="postgresql-xxxx-xxxx.xxxx.xxxx",
        port="xxxx"
    )

    cur = db_link.cursor()

    cur.execute("SELECT email FROM membre")

    result_rows = cur.fetchall()

    for rec in result_rows:
        print(rec)

except Exception as err:
    print(f"Une erreur est survenue : {err}")

finally:
    if cur:
        cur.close()

    if db_link:
        db_link.close()

The packaging command used attempted to pull in related modules explicitly. The build completed without errors, but the executable still produced no output.

pyinstaller --onefile --hidden-import=psycopg2 --hidden-import=psycopg2._psycopg --hidden-import=psycopg2.extensions --hidden-import=psycopg2.extras --hidden-import=sqlalchemy.dialects.postgresql --hidden-import=sqlalchemy.dialects.postgresql.psycopg2 creation_userform_sans_sql.py

What’s actually happening

There are no build-time errors and the executable starts, but it prints nothing. Running it from a terminal shows the same behavior: no traceback, no output. In situations like this, it is easy to conflate causes, but the only verified observation here is the absence of output at runtime when packaged with PyInstaller while the same script behaves correctly as plain Python.

When an executable appears silent, it helps to verify where the flow stops and what values are in play. One straightforward way is to add lightweight instrumentation with print() and to briefly delay exit to make sure any console window stays visible long enough to read the output. It is also reasonable to consider whether the query could be returning zero rows, and to check the row count as a first sanity step. Enabling PyInstaller’s debug options or changing optimization, as suggested by others, can also clarify whether the packager is altering runtime behavior. These checks do not change the program logic, but can reveal whether it executed as expected.

Minimal diagnostics to confirm execution flow

The following variant keeps the same logic and adds a few markers to observe execution in the packaged form. This does not fix the packaging issue; it only helps confirm what happens when the .exe runs.

import psycopg2
import time

try:
    print("start: connecting")
    db_link = psycopg2.connect(
        dbname="xxxx",
        user="xxxx",
        password="xxxx",
        host="postgresql-xxxx-xxxx.xxxx.xxxx",
        port="xxxx"
    )
    print("connected: opening cursor")
    cur = db_link.cursor()

    print("executing query")
    cur.execute("SELECT email FROM membre")

    print("fetching rows")
    result_rows = cur.fetchall()
    print(f"row count: {len(result_rows)}")

    for rec in result_rows:
        print(rec)

    print("done; sleeping briefly to keep console visible")
    time.sleep(10)

except Exception as err:
    print(f"Une erreur est survenue : {err}")
    time.sleep(10)

finally:
    if cur:
        cur.close()

    if db_link:
        db_link.close()

Resolution that worked

Replacing the PostgreSQL driver resolved the packaging problem: using pg8000 instead of psycopg2 produced an .exe that behaved the same as the .py, with the expected output. No additional changes to the application logic were required for this outcome to hold. The build completed without errors and the packaged executable printed the results as intended.

Why this matters

Silent runtime failures after packaging are highly time-consuming because you have little to no signal about what went wrong. When your application is I/O bound (database access, network calls), even a subtle driver or packaging nuance can be enough to suppress observable output. Knowing that swapping the PostgreSQL module fixed the behavior in this scenario gives you a concrete escape hatch when you face the same symptoms: .py works, PyInstaller build shows no errors, .exe runs but prints nothing.

Practical takeaways

When a PyInstaller-built executable does nothing while the plain script works, validate execution flow with print() and brief delays to rule out a disappearing console or an empty result set. If the build still runs silently, consider changing the PostgreSQL client module. In this case, moving from psycopg2 to pg8000 made the .exe behave identically to the .py. If needed, explore PyInstaller’s debug settings and try different optimization levels to observe whether the packager influences runtime, but keep the core script unchanged while you investigate.

The overarching advice is simple: isolate whether the problem is in your code or in the runtime environment created by the packager. If your logic is sound and the same program works unmodified with a different PostgreSQL module after packaging, you have a viable path forward without reworking the application.

The article is based on a question from StackOverflow by seblenor and an answer by seblenor.