README + classifier polish for PyPI launch

PyPI users landing on the README need to know quickly:
- What this is (already strong)
- Whether it's safe to use in production (was missing)
- Performance expectations (was missing)
- Python version requirement (was only in pyproject.toml metadata)

Updates:
* Added "Status" section with the Hamilton audit findings table -
  every critical/high/medium addressed, 0 remaining. Names the
  Hamilton-style review process explicitly as the credibility signal.
* Added Python ≥ 3.10 requirement under the install command.
* Added "Performance" section with single-connection benchmarks and
  the 53x autocommit-cliff gotcha (most important perf pitfall).
* Updated "Standards & guarantees" to mention Phase 27's wire lock
  alongside the PEP 249 Threadsafety=1 declaration - accurate context
  for sophisticated readers.
* Tightened "Development" to PyPI-appropriate brevity (short Makefile
  target list instead of full uv invocations).
* Updated stale phase count (22+ → 30) and test counts (69 → 77 unit,
  163 → 231 integration). Added "300+ tests" rough number in the
  Status section to reduce future staleness churn.
* Fixed typo: "no thread of native machinery" → "no native machinery
  anywhere in the thread of execution".
* Bumped pyproject.toml classifier from "Development Status :: 4 -
  Beta" to "5 - Production/Stable" - earned by the audit work.

No code changes.
This commit is contained in:
Ryan Malloy 2026-05-05 11:06:49 -06:00
parent 0b13acb13d
commit eb8d15d204
2 changed files with 45 additions and 19 deletions

View File

@ -8,6 +8,25 @@ To our knowledge this is the **first pure-socket Informix driver in any language
pip install informix-db pip install informix-db
``` ```
Requires Python ≥ 3.10.
## Status
**Production ready.** Every finding from a system-wide failure-mode audit (data correctness, wire safety, resource leaks, concurrency, async cancellation) has been addressed:
| Severity | Finding | Status |
|---|---|---|
| Critical | Pool returns connections with open transactions | Fixed (Phase 26) |
| Critical | Unsynchronized wire path → PDU interleaving | Fixed (Phase 27) — per-connection wire lock |
| High | Async cancellation leaks running workers onto recycled connections | Fixed (Phase 27) |
| High | `_raise_sq_err` bare-except masks wire desync | Fixed (Phase 28) |
| High | Cursor finalizers — server-side resources leak on mid-fetch raise | Fixed (Phase 28+29) |
| Medium | 5 hardening items | Fixed (Phase 28+30) |
**0 critical, 0 high, 0 medium audit findings remain.** Every architectural change went through a Margaret Hamilton-style review focused on silent-failure modes, recovery paths, and documented invariants. Each documented invariant is paired with either a runtime guard or a CI tripwire test.
**Test coverage:** 300+ tests across unit / integration / benchmark suites. Integration tests run against the official IBM Informix Developer Edition Docker image (15.0.1.0.3DE).
## Quick start ## Quick start
```python ```python
@ -112,7 +131,7 @@ cur.write_blob_column(
) )
``` ```
Both work end-to-end in pure Python via the `lotofile` / `filetoblob` server functions intercepted at the `SQ_FILE` (98) wire-protocol level — no thread of native machinery. See [`docs/DECISION_LOG.md`](docs/DECISION_LOG.md) §1011 for the architecture pivot that made this possible. Both work end-to-end in pure Python via the `lotofile` / `filetoblob` server functions intercepted at the `SQ_FILE` (98) wire-protocol level — no native machinery anywhere in the thread of execution. See [`docs/DECISION_LOG.md`](docs/DECISION_LOG.md) §1011 for the architecture pivot that made this possible.
## Direct stored-procedure invocation (fast-path) ## Direct stored-procedure invocation (fast-path)
@ -135,34 +154,41 @@ For features that need server-side configuration (smart-LOBs, logged transaction
- Phase 8 — BYTE/TEXT (needs blobspace) - Phase 8 — BYTE/TEXT (needs blobspace)
- Phase 10/11 — BLOB/CLOB (needs sbspace + `SBSPACENAME` config + level-0 archive) - Phase 10/11 — BLOB/CLOB (needs sbspace + `SBSPACENAME` config + level-0 archive)
## Performance
Single-connection benchmarks against the dev container on loopback:
| Operation | Mean | Throughput |
|---|---:|---:|
| `decode(int)` per cell | 139 ns | 7.2M ops/sec |
| `parse_tuple_payload` per row (5 cols) | 1.4 µs | 715K rows/sec |
| `SELECT 1` round-trip | ~140 µs | ~7K queries/sec |
| 1000-row SELECT | ~1.0 ms | ~990K rows/sec sustained |
| `executemany(1000)` in transaction | 32 ms | **~31,000 rows/sec** |
| Pool acquire + query + release | 295 µs | ~3.4K queries/sec |
| Cold connect (login handshake) | 11 ms | ~90 connections/sec |
**Performance gotcha**: `executemany(...)` under `autocommit=True` is **53× slower** than the same call inside a single transaction (server flushes the transaction log per row). For bulk loads, `autocommit=False` (default) + `conn.commit()` at the end. See [`docs/USAGE.md`](docs/USAGE.md) for the full performance tips section.
## Standards & guarantees ## Standards & guarantees
* **PEP 249** (DB-API 2.0): `connect()`, `Connection`, `Cursor`, `description`, `rowcount`, exception hierarchy * **PEP 249** (DB-API 2.0): `connect()`, `Connection`, `Cursor`, `description`, `rowcount`, exception hierarchy
* **`paramstyle = "numeric"`** (Informix's native ESQL/C convention; `?` and `:1` both work) * **`paramstyle = "numeric"`** (Informix's native ESQL/C convention; `?` and `:1` both work)
* **Threadsafety = 1**: threads may share the module but not connections; the pool gives per-thread connection access * **Threadsafety = 1**: threads may share the module but not connections; the pool gives per-thread connection access. Phase 27 added a per-connection wire lock that makes accidental sharing safe (interleaved PDUs serialize correctly), but PEP 249 advice still holds — give each thread its own connection.
* **CalVer versioning**: `YYYY.MM.DD` releases. PEP 440 post-releases (`.1`, `.2`) for same-day fixes. * **CalVer versioning**: `YYYY.MM.DD` releases. PEP 440 post-releases (`.1`, `.2`) for same-day fixes.
## Development ## Development
```bash The full test + lint workflow is in the [Makefile](Makefile). Quick summary:
# Set up the dev environment
uv sync --dev
# Run the test suite (unit-only by default; no Docker needed)
uv run pytest # 69 unit tests
uv run pytest -m integration # 163 integration tests (needs Docker)
# Lint
uv run ruff check src/ tests/
```
The integration suite expects an Informix Developer Edition container on `localhost:9088`:
```bash ```bash
docker compose -f tests/docker-compose.yml up -d make test # 77 unit tests (no Docker)
make ifx-up && make test-integration # 231 integration tests
make bench # benchmark suite
make lint # ruff
``` ```
For the smart-LOB tests specifically, the dev container needs additional one-time setup (blobspace + sbspace + level-0 archive). See [`docs/DECISION_LOG.md`](docs/DECISION_LOG.md) §10 for the exact `onspaces` / `onmode` / `ontape` commands. For the smart-LOB tests specifically, the dev container needs additional one-time setup (blobspace + sbspace + level-0 archive). See [`docs/DECISION_LOG.md`](docs/DECISION_LOG.md) §10 for the `onspaces` / `onmode` / `ontape` commands.
## Documentation ## Documentation
@ -172,7 +198,7 @@ For the smart-LOB tests specifically, the dev container needs additional one-tim
## Project history & design rationale ## Project history & design rationale
This driver was built incrementally across 22+ phases, each with a focused scope and decision log. The reasoning trail lives in: This driver was built incrementally across 30 phases, each with a focused scope and decision log. The reasoning trail lives in:
- [`docs/PROTOCOL_NOTES.md`](docs/PROTOCOL_NOTES.md) — byte-level SQLI wire-format reference - [`docs/PROTOCOL_NOTES.md`](docs/PROTOCOL_NOTES.md) — byte-level SQLI wire-format reference
- [`docs/JDBC_NOTES.md`](docs/JDBC_NOTES.md) — index into the decompiled IBM JDBC driver, used as a clean-room reference - [`docs/JDBC_NOTES.md`](docs/JDBC_NOTES.md) — index into the decompiled IBM JDBC driver, used as a clean-room reference

View File

@ -8,7 +8,7 @@ authors = [{ name = "Ryan Malloy", email = "ryan@supported.systems" }]
requires-python = ">=3.10" requires-python = ">=3.10"
keywords = ["informix", "database", "sqli", "db-api", "pep-249", "asyncio", "async"] keywords = ["informix", "database", "sqli", "db-api", "pep-249", "asyncio", "async"]
classifiers = [ classifiers = [
"Development Status :: 4 - Beta", "Development Status :: 5 - Production/Stable",
"Framework :: AsyncIO", "Framework :: AsyncIO",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",