Skip to content

Install & first query

This page gets you from a clean Python environment to a working query against a real Informix server in five minutes. We’ll use the official IBM Informix Developer Edition Docker image so you don’t need an Informix license.

  • Python 3.10 or newer
  • Docker, for the dev server (skip if you already have an Informix instance)
  • 4 GB of free RAM for the dev container
Terminal window
uv add informix-driver

That’s the entire dependency. No system packages, no LD_LIBRARY_PATH, no libcrypt.so.1.

Terminal window
docker run -d --name informix-dev \
-e LICENSE=accept \
-p 9088:9088 \
-p 9089:9089 \
--privileged \
icr.io/informix/informix-developer-database:15.0.1.0.3DE

The image takes ~90 seconds to initialize. Watch the logs until you see oninit running:

Terminal window
docker logs -f informix-dev

Save this as hello.py:

import informix_db
with informix_db.connect(
host="127.0.0.1",
port=9088,
user="informix",
password="in4mix",
database="sysmaster",
server="informix",
) as conn:
cur = conn.cursor()
cur.execute(
"SELECT FIRST 5 dbsname, tabname "
"FROM systables WHERE tabid > 99"
)
for row in cur.fetchall():
print(row)

Then:

Terminal window
python hello.py

You should see five rows from Informix’s system catalog. If you do, congratulations — you’ve spoken SQLI to an IBM database from pure Python with zero native code in the call stack.

When informix_db.connect() returned, the driver had:

  1. Opened a TCP socket to 127.0.0.1:9088
  2. Sent an SQ_INFO PDU containing client capabilities (locale, byte order, app name)
  3. Received the server’s identification (IBM Informix Dynamic Server Version 15.0.1.0.3)
  4. Negotiated authentication via SQ_PASSWD
  5. Opened the sysmaster database via SQ_DBOPEN
  6. Returned a Connection object ready for queries

cur.execute() sent an SQ_PREPARE PDU with your SQL, parsed the response into a column descriptor, sent SQ_DESCRIBE, then SQ_OPEN to start the cursor. cur.fetchall() issued SQ_FETCH PDUs and decoded each SQ_TUPLE payload via per-column readers.

If you want to see this happen byte-by-byte, the architecture page walks through the wire protocol with annotated captures.

with informix_db.connect(host="127.0.0.1", port=9088, user="informix",
password="in4mix", database="sysmaster",
server="informix") as conn:
cur = conn.cursor()
cur.execute(
"SELECT tabname FROM systables WHERE tabid = ?",
(1,),
)
print(cur.fetchone())

? and :1 both work — Informix’s native paramstyle is numeric, but ? is supported as a synonym.

For real applications, prefer the pool:

import informix_db
pool = informix_db.create_pool(
host="127.0.0.1", port=9088,
user="informix", password="in4mix",
database="sysmaster", server="informix",
min_size=2, max_size=10,
acquire_timeout=5.0,
)
with pool.connection() as conn:
cur = conn.cursor()
cur.execute("SELECT 1 FROM systables WHERE tabid = 1")
print(cur.fetchone())
pool.close()

The pool is thread-safe and has a per-connection wire lock — accidental sharing across threads doesn’t corrupt the wire stream, though PEP 249 advice still holds (one connection per thread).

  1. Going async? Async with FastAPI → walks through a real FastAPI app with the async pool.

  2. Connecting to production? Connect with TLS → covers TLS-listener configuration and bring-your-own-context patterns.

  3. Bulk-loading? Bulk inserts (executemany) → — and the 53× transaction-vs-autocommit gotcha you’ll hit otherwise.

  4. Migrating from IfxPy? Migrate from IfxPy → covers the API differences and the things IfxPy does that we don’t (yet).