This release is a pre-release and may not be stable for production use.
PageChaos
Python pagination testing, created and maintained by Dharani Eswaramurthi.
Test whether your Python API client silently loses records, returns duplicates, or stops before a cursor-paginated export is complete.
Alpha: 0.1.0a1. Synchronous HTTPX, flat JSON cursor responses, fixed snapshots. Python 3.11+. CI exercises Linux on Python 3.11–3.15, plus Windows and macOS on Python 3.12.
PageChaos runs your existing consumer against an in-process mock transport. It compares collected records with a known snapshot and explains pagination failures. It does not replace your paginator or retry policy.
Install the alpha
python -m pip install "pagechaos[httpx]==0.1.0a1"
For the optional pytest fixture:
python -m pip install "pagechaos[pytest]==0.1.0a1"
For development, clone this repository and install the local checkout:
git clone https://github.com/Dharani-Eswaramurthi/PageChaos.git
cd PageChaos
python -m pip install -e ".[httpx]"
See the defect
python -m pagechaos demo --client correct
python -m pagechaos demo --client short-page
The correct client collects 30 records in four requests. The broken client stops on the second, short page and returns only 13. Its command exits 1, intentionally:
FAIL: short-page-with-continuation
Expected records: 30
Observed records: 13 (13 unique)
Requests: 2; terminal reached: False
[MISSING_RECORDS] 17 snapshot record(s) missing.
[CONTINUATION_REMAINING] The last successful page had a continuation cursor.
[NO_TERMINAL_RESPONSE] A terminal response was not reached.
A runnable test
Copy this into a Python file after installing the HTTPX extra:
from pagechaos import CursorScenario, audit
def fetch_all_items(http):
cursor = None
while True:
response = http.get("/items", params={"cursor": cursor} if cursor else {})
response.raise_for_status()
page = response.json()
yield from page["items"]
cursor = page.get("next_cursor")
if not cursor:
return
scenario = CursorScenario(
records=[{"id": i} for i in range(1, 31)],
page_sizes=[10, 3, 10, 7],
)
report = audit(fetch_all_items, scenario=scenario, item_key="id", max_requests=12)
report.assert_complete()
print(report.explain())
Replace fetch_all_items with your consumer, using the supplied HTTPX client. Change the consumer to stop when a page contains fewer than 10 items and the audit fails with 17 missing records. A short page can still have continuation.
The default endpoint is GET /items, with flat JSON fields items and next_cursor. The injected client's base URL is https://pagechaos.test; requests through this client use the mock transport, including absolute URLs. Unexpected endpoints and origins receive errors, with no real-network fallback.
Return an iterator to preserve records yielded before a later-page exception. If your function buffers a list and raises before returning it, those internal records are unavailable to PageChaos.
See a complete runnable example.
Scenarios and faults
- Short and empty pages with valid continuation.
- Full final pages and null, empty, or absent terminal cursors.
- Custom opaque cursors, including large numeric-looking strings and Unicode.
- Repeated-cursor responses.
- One-shot HTTP errors and simulated read timeouts.
- Configurable flat field names and an optional has_more field.
- Fresh state for every audit.
from pagechaos import CursorScenario, Fault
scenario = CursorScenario(
[{"id": i} for i in range(12)],
page_size=4,
faults=[Fault.http_error(2, 429, retry_after="2")],
)
Faults target absolute request numbers, counting retries. PageChaos sends the header; the consumer owns retrying and waiting. An unexercised scheduled fault is a failed audit, so a test cannot claim coverage of a failure it never reached.
Assertions and reports
assert_complete() requires each snapshot record exactly once and unchanged, a terminal response, no consumer error, no protocol error, all scheduled faults exercised, and no budget exhaustion. Record ordering is not asserted.
Use report.explain(), report.to_dict(), or report.to_json(). JSON reports omit record bodies, request headers, and raw exception messages. IDs and cursors remain visible: use synthetic fixtures. The in-memory report.records and report.error remain available for local investigation.
A separate record limit stops endlessly yielding consumers: max_records defaults to max(100, 2 * snapshot size). max_requests defaults to 100. These limits do not interrupt blocking I/O, CPU loops without yields, or consumer code that catches BaseException. Use a process timeout for untrusted/hanging code.
pytest
def test_export(pagechaos_audit):
report = pagechaos_audit(fetch_all_items, scenario=scenario)
report.assert_complete()
The fixture performs no automatic auditing and does not patch unrelated clients.
Compatibility evidence
The suite exercises the existing notion-client 3.1.0 pagination helper against synthetic block-list responses, including short and empty pages. It uses a dummy token and forbids socket connections. This establishes offline compatibility for that helper, not live Notion API behavior or production adoption.
Boundaries
- Fixed response schedules: client limit parameters do not change page sizes.
- Flat JSON cursor APIs only; no nested envelopes, Link headers, offsets, or POST.
- No live dataset mutations or snapshot guarantees for a real upstream.
- Duplicate-delivery allowance, checkpointing, and sink writes need other contracts.
- Simulated timeouts exercise exception handling, not TCP timeout enforcement.
- Requests made through another client, subprocess, or library are outside interception.
- No elapsed-time verification, async runner, hosted service, or remote collection.
Read the API contract, validation evidence, and roadmap.
Feedback and support
Report a bug or suggest a scenario. Use synthetic fixtures and describe the API contract you need to test. Report security issues privately.
The library is freely usable under Apache 2.0. For a scoped integration or support inquiry, open an issue describing the technical need without confidential data. Community maintenance has no guaranteed response time.
Development
python -m venv .venv
python -m pip install -r requirements-dev.lock
python -m pip install --no-deps --no-build-isolation -e .
python -m pytest
python -m ruff check .
python -m ruff format --check .
python -m mypy
python -m build
Activate the virtual environment or use its Python executable for these commands. Contributing. Apache 2.0 license.
Metadata
Release files for pagechaos 0.1.0a1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pagechaos-0.1.0a1.tar.gz | 37.9 kB | With provenance | Details for pagechaos-0.1.0a1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pagechaos-0.1.0a1-py3-none-any.whl | Python 3 | none | any | Details for pagechaos-0.1.0a1-py3-none-any.whl |
Total release size: 58.9 kB
Release files / pagechaos-0.1.0a1.tar.gz
| Download URL | pagechaos-0.1.0a1.tar.gz |
|---|---|
| Size | 37.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6128ad25e3d4dbb71391a75bc5cbcffac4dc1c4a8cab73c38f111fda5bbf0328
|
|
BLAKE2b-256 checksum How to use checksums |
61a5136b8d47ee14fa049f306c8c7049c865ebff0af3a4dcad2113013edb58c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.
Transparency logRelease files / pagechaos-0.1.0a1-py3-none-any.whl
| Download URL | pagechaos-0.1.0a1-py3-none-any.whl |
|---|---|
| Size | 20.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4d9c11562d037508eaec9945714964b04b5a1cbbc71bc1dc85fd9ad44f4b08eb
|
|
BLAKE2b-256 checksum How to use checksums |
50d68de3a7836138f5dc986d0f2b4e4d93efaea2f698035720d69dfc7469bfe4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.
Transparency logSecurity for pagechaos 0.1.0a1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pagechaos-0.1.0a1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.