Skip to content

Migrate from IfxPy

If you have working IfxPy code, migration is mostly mechanical. Both drivers are PEP 249 with similar shapes; the differences are in connection construction, a few cursor extensions, and async support.

# IfxPy
import IfxPy
conn_str = (
"DATABASE=mydb;HOSTNAME=db.example.com;PORT=9088;"
"PROTOCOL=onsoctcp;UID=informix;PWD=...;SERVICE=informix"
)
conn = IfxPy.connect(conn_str, "", "")
# informix-driver
import informix_db
conn = informix_db.connect(
host="db.example.com", port=9088,
user="informix", password="...",
database="mydb", server="informix",
)

The informix-driver keyword-argument form is closer to psycopg/asyncpg shapes. Connection strings aren’t supported, deliberately: they’re a security and parsing footgun.

cursor(), execute(), fetchone(), fetchmany(), fetchall(), executemany(), description, rowcount, and close() all behave per PEP 249.

The exception hierarchy is identical: Error, Warning, InterfaceError, DatabaseError, DataError, OperationalError, IntegrityError, InternalError, ProgrammingError, NotSupportedError.

  • Named-parameter callproc. We have fast_path_call for direct UDF/SPL invocation but the API shape differs.
  • IBM-specific scrollable cursor extensions. We support PEP 249 scrollable cursors (scroll(value, mode)) but not IfxPy’s last/prior/relative shortcuts.
  • cursor.set_chunk_size. We tune fetch behavior via the buffered reader; no per-cursor knob.
  • Async API (from informix_db import aio)
  • Connection pool (informix_db.create_pool / aio.create_pool)
  • Type-safe annotations: informix-driver ships with py.typed
  • Python 3.12+ support
  • Pipelined executemany: 1.6× faster than IfxPy’s per-row implementation
  • Rows addressable by name: row["col"] and row.col, not just row[0]

IfxPy gives you positional access and nothing else, which is why most IfxPy codebases grow a helper like this:

cols = [c[0].lower() for c in cur.description]
row_dict = dict(zip(cols, cur.fetchone()))

That gets you a dict and still no attribute access. Ask for Row instead and all three work at once:

conn = informix_db.connect(..., row_factory=informix_db.Row)
cur.execute("SELECT config_key, config_value FROM settings")
row = cur.fetchone()
row[0], row["config_key"], row.config_key

Set on the connection, so it applies to every cursor from it. The .lower() in the hand-written version is already a no-op, incidentally: Informix folds unquoted identifiers to lower case.

It is opt-in because it costs about 9% on bulk fetch. See the API reference for the numbers and the edge cases.

If your codebase has hundreds of IfxPy call sites, you can do a partial migration:

  1. Replace connection construction with informix-driver at the application boundary.
  2. Where you used IfxPy.fetch_assoc (returns dict), wrap our cursor with a small adapter:
    def fetch_assoc(cur):
    row = cur.fetchone()
    if row is None:
    return False
    return dict(zip([c[0] for c in cur.description], row))
  3. For IfxPy.exec_immediate, use cur.execute(sql) with no params.
  4. For IfxPy.bind_param, use the params arg of execute(): cur.execute(sql, (a, b, c)).

Most application code can be migrated with sed-level transformations.