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:
parent
0b13acb13d
commit
eb8d15d204
62
README.md
62
README.md
@ -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) §10–11 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) §10–11 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
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user