Last of the untested surface: pipelined executemany, scrollable cursors, and smart LOBs. One bug, in the first. executemany builds all its BIND+EXECUTE PDUs after the PREPARE and before anything is drained — that batching is what makes the pipeline fast. If encoding a row raises there (a value the connection's codec cannot represent), the exception escaped without sending the RELEASE, leaking the prepared statement. The next PREPARE collided with it and every later call on the connection failed with an error pointing at the previous SQL. Same failure as 2026.08.31.2, in a sibling path. _execute_dml_with_params has guarded this exact case for the single-row path for a long time; the pipelined path was never given the same treatment. That is the recurring shape of these bugs: a hazard understood in one place and not carried to the code beside it. What the fuzzer found sound, which is the larger part of the result: executemany constraint failures — duplicate-key and NOT NULL at first/mid/last of batches from 2 to 1000 rows all recover, and COUNT(*) always agrees with a full fetch, so the drain-N-responses invariant holds under partial failure. Scrollable cursors — fetch_first/last/prior/relative/absolute correct at 0, 1, 2, 5, 50, 300 rows, including off both ends (None, not a wrap or a crash) and a full forward walk after arbitrary positioning. Twenty abandoned scroll cursors leak nothing. Smart LOBs — round-trip at 0, 1, 255, 256, 1023, 1024, 4095, 4096, 65535, 65536 bytes, straddling the 4096-byte SQ_FILE chunk and the 64K mark, plus recovery from failed reads. Still not fuzzed, stated plainly: TLS is handshake-tested against a self-signed local socket, not a real Informix TLS listener (that needs server-side keystore + onconfig SSL setup absent from the test containers). The SQLI layer above the socket is identical either way. 399/399 integration on 15, 14.10 and 12.10 (was 356).
112 lines
3.8 KiB
TOML
112 lines
3.8 KiB
TOML
[project]
|
|
name = "informix-driver"
|
|
version = "2026.08.31.3"
|
|
description = "Pure-Python driver for IBM Informix IDS — speaks the SQLI wire protocol over raw sockets. No CSDK, no JVM, no native libraries."
|
|
readme = "README.md"
|
|
license = { text = "MIT" }
|
|
authors = [{ name = "Ryan Malloy", email = "ryan@supported.systems" }]
|
|
requires-python = ">=3.10"
|
|
keywords = ["informix", "database", "sqli", "db-api", "pep-249", "asyncio", "async"]
|
|
classifiers = [
|
|
"Development Status :: 5 - Production/Stable",
|
|
"Framework :: AsyncIO",
|
|
"Intended Audience :: Developers",
|
|
"License :: OSI Approved :: MIT License",
|
|
"Operating System :: OS Independent",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3 :: Only",
|
|
"Programming Language :: Python :: 3.10",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Programming Language :: Python :: 3.13",
|
|
"Programming Language :: Python :: 3.14",
|
|
"Topic :: Database",
|
|
"Topic :: Database :: Front-Ends",
|
|
"Typing :: Typed",
|
|
]
|
|
dependencies = []
|
|
|
|
[project.urls]
|
|
Homepage = "https://informix-driver.warehack.ing"
|
|
Documentation = "https://informix-driver.warehack.ing"
|
|
Source = "https://git.supported.systems/warehack.ing/informix-db"
|
|
Issues = "https://git.supported.systems/warehack.ing/informix-db/issues"
|
|
Changelog = "https://git.supported.systems/warehack.ing/informix-db/src/branch/main/CHANGELOG.md"
|
|
|
|
[project.optional-dependencies]
|
|
dev = [
|
|
"pytest>=8.0",
|
|
"ruff>=0.6",
|
|
]
|
|
|
|
[build-system]
|
|
requires = ["hatchling"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[tool.hatch.build.targets.wheel]
|
|
packages = ["src/informix_db"]
|
|
|
|
[tool.hatch.build.targets.sdist]
|
|
# Defense in depth: exclude operator-private and dev-only artifacts from the sdist
|
|
# (the wheel doesn't ship these by default, but the sdist would).
|
|
# See ~/.claude/rules/python.md for the full pre-publish PII audit playbook.
|
|
exclude = [
|
|
"CLAUDE.md", # operator-private context
|
|
".env", ".env.local", ".env.*",
|
|
".mcp.json", # may contain local filesystem paths
|
|
"build/", # decompiled JDBC, downloaded JARs
|
|
"audits/",
|
|
"docs/**", # protocol notes / decision log / captures — go to GitHub for the depth
|
|
"docs-site/**", # Astro/Starlight site sources — published at informix-driver.warehack.ing
|
|
"tests/reference/**", # Java reference client — spike infra
|
|
"tests/benchmarks/.results/**", # pytest-benchmark cache (gitignored, but still ships unless excluded)
|
|
".pytest_cache/", ".ruff_cache/", ".mypy_cache/",
|
|
"dist/", "*.egg-info/",
|
|
]
|
|
|
|
[tool.ruff]
|
|
line-length = 100
|
|
target-version = "py310"
|
|
src = ["src", "tests"]
|
|
|
|
[tool.ruff.lint]
|
|
select = [
|
|
"E", # pycodestyle errors
|
|
"W", # pycodestyle warnings
|
|
"F", # pyflakes
|
|
"I", # isort (import sorting)
|
|
"B", # flake8-bugbear
|
|
"C4", # flake8-comprehensions
|
|
"UP", # pyupgrade
|
|
"SIM", # flake8-simplify
|
|
"PTH", # flake8-use-pathlib
|
|
"RUF", # ruff-specific
|
|
]
|
|
ignore = [
|
|
"E501", # line too long — handled by formatter
|
|
]
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"tests/**" = ["B011"] # allow assert False in tests
|
|
|
|
[tool.pytest.ini_options]
|
|
minversion = "8.0"
|
|
testpaths = ["tests"]
|
|
asyncio_mode = "auto" # pytest-asyncio: auto-detect ``async def`` tests
|
|
addopts = [
|
|
"-ra", # short summary for non-passing
|
|
"--strict-markers",
|
|
"--strict-config",
|
|
"-m", "not integration and not benchmark", # default: unit-only. Override with: pytest -m integration / -m benchmark
|
|
]
|
|
markers = [
|
|
"integration: requires a running Informix container (docker compose up); skipped by default",
|
|
"benchmark: pytest-benchmark performance test; skipped by default. Run with `make bench`.",
|
|
]
|
|
|
|
[dependency-groups]
|
|
dev = [
|
|
"pytest-asyncio>=1.3.0",
|
|
"pytest-benchmark>=5.2.3",
|
|
]
|