Python tells you where. whytrail tells you why.
Project description
whytrail
Python tells you where something happened. whytrail tells you why.
import whytrail
try:
apply_discount(price, code)
except Exception as exc:
print(whytrail.why(exc))
why(KeyError: 'SUMMER'):
[explicit] ValueError: discount code table missing region 'EU' [pricing.py:12, in load_codes]
locals: region='EU', table={}
[explicit] which explicitly caused KeyError: 'SUMMER' [pricing.py:31, in apply_discount]
locals: price=12.5, code='SUMMER'
([explicit] / [inferred] / [heuristic] mark confidence in that step of
the chain -- spelled out rather than left as a symbol you'd need this README
open to decode.)
Two tiers, one function
Tier 1 -- zero configuration. why(some_exception) reassembles a causal
chain from data CPython already retains: __traceback__, __cause__,
__context__, and the locals of the frame where it actually originated.
No setup, no tracing engine, no overhead when unused.
Tier 2 -- opt-in, scoped. why(some_tracked_value) walks a small
provenance graph built only for values you deliberately watched:
import whytrail
with whytrail.trace():
raw = whytrail.track(fetch_row(), label="raw CSV row")
price = whytrail.track(float(raw["price"]), derived_from=raw)
print(whytrail.why(price))
Something that was never tracked gets an honest answer, not a guess:
why(3.14): unknown -- no provenance captured.
This value was never tracked. Wrap it with whytrail.track(),
@whytrail.tracked, or raise it as an exception to get an answer.
whytrail will never fabricate a causal chain it isn't sure about. See
docs/adr/0001-whytrail-architecture.md
for the full reasoning behind that design choice, and why a fully automatic
why(anything) isn't possible in the first place.
Install
pip install whytrail # core, zero dependencies
pip install whytrail[rich] # + Explanation.rich() tree rendering
pip install whytrail[cli] # + the `whytrail` CLI
Why not just use a debugger / logging / OpenTelemetry?
Each answers a different question:
| Tool | Answers |
|---|---|
pdb / IDE debugger |
What is the state right now, interactively |
logging |
Whatever you decided in advance to record |
traceback |
Where it crashed |
| OpenTelemetry | Cross-service request flow |
whytrail |
What produced this specific value, on demand, after the fact |
Public API
Five verbs (why, track, tracked, trace, register) plus two
persistence helpers (snapshot, restore). Domain-specific integrations
are separate plugin packages, not new verbs -- see docs/plugin-guide.md.
Ecosystem
30 plugins today, each earning its place by clearing one of three bars
(structured error data, a security-sensitive boundary, or a non-standard
capture mechanism) rather than existing just to exist -- see
docs/adr/0003-ecosystem-scale-triage.md for the full reasoning, the
next candidates, and the much longer list of libraries deliberately
not wrapped, because generic track()/@tracked already covers them
with zero extra code. Full table with what each one adds:
docs/plugin-guide.md.
whytrail-requests |
whytrail-httpx |
whytrail-aiohttp |
whytrail-huggingface-hub |
whytrail-openai |
whytrail-anthropic |
whytrail-boto3 |
whytrail-google-cloud |
whytrail-sqlalchemy |
whytrail-asyncpg |
whytrail-pymongo |
whytrail-grpcio |
whytrail-pydantic |
whytrail-marshmallow |
whytrail-jsonschema |
whytrail-pyyaml |
whytrail-pandas |
whytrail-polars |
whytrail-sentry |
whytrail-ddtrace |
whytrail-celery |
whytrail-rq |
whytrail-dramatiq |
whytrail-prefect |
whytrail-scrapy |
whytrail-pytest |
whytrail-fastapi |
whytrail-django |
whytrail-flask |
whytrail-langchain |
python scripts/new_plugin.py <library> --kind explainer|integration
scaffolds a new one. .github/actions/whytrail-run packages the CLI as a
GitHub Action for CI.
On test coverage: every plugin above is verified against a real
object from the real library, not a mock, and every plugin's stated
minimum dependency version is confirmed to actually install and work on
the newest supported Python -- both caught real bugs, twenty of them
version-compatibility gaps invisible from the version number alone,
eight of those found only once this project's CI actually ran on real
Linux for the first time rather than the Windows sandbox it was built in
(see CHANGELOG.md). It is not the same claim as "battle-tested in
every condition": see docs/testing-maturity.md for exactly what is and
isn't covered (the full Python 3.10-3.13 matrix, concurrency beyond the
three web frameworks, and full exception-surface breadth are the current
gaps).
Status
Pre-1.0. The public API may still change between minor versions. See
CHANGELOG.md for what's shipped at each version and docs/adr/ for the
architecture this was built from:
0001-- feasibility and the original two-tier architecture.0002-- category positioning and the pre-1.0 API fixes it drove.0003-- how the plugin ecosystem scales (and how it doesn't).0004-- why this project is calledwhytrailand notbutwhy.0005-- VS Code extension scope assessment (not started, and why).
Full documentation site (same content, easier to browse): https://bhouvana.github.io/Whytrail/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file whytrail-0.1.0.tar.gz.
File metadata
- Download URL: whytrail-0.1.0.tar.gz
- Upload date:
- Size: 130.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
285e4b5b6adcebc79c978220a1b53b79f54ac7e88c5258cdbaaa14196e4740a8
|
|
| MD5 |
a4e22067b818ddbf8e7a324c2db69d9a
|
|
| BLAKE2b-256 |
d9b53c1f75fd733ed8fdd5de0a233cbb8e038e3427ae87f59d87c1dab926ba0d
|
Provenance
The following attestation bundles were made for whytrail-0.1.0.tar.gz:
Publisher:
publish.yml on bhouvana/Whytrail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whytrail-0.1.0.tar.gz -
Subject digest:
285e4b5b6adcebc79c978220a1b53b79f54ac7e88c5258cdbaaa14196e4740a8 - Sigstore transparency entry: 2172877199
- Sigstore integration time:
-
Permalink:
bhouvana/Whytrail@1ce23e0c5b5862c9120e55c93575aeeaa8db4ac0 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bhouvana
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1ce23e0c5b5862c9120e55c93575aeeaa8db4ac0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file whytrail-0.1.0-py3-none-any.whl.
File metadata
- Download URL: whytrail-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
991e038dcba79aa217027d2bb5d66d77f510b969ed32eba2f32b328dc13fafef
|
|
| MD5 |
6284c9ffd1410a5179a3662f96b4aa64
|
|
| BLAKE2b-256 |
1409e6754962dee505d9c5bc9d13ff43ee2c873b1d9dfb0fb4ffb2399397d1ed
|
Provenance
The following attestation bundles were made for whytrail-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on bhouvana/Whytrail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whytrail-0.1.0-py3-none-any.whl -
Subject digest:
991e038dcba79aa217027d2bb5d66d77f510b969ed32eba2f32b328dc13fafef - Sigstore transparency entry: 2172877225
- Sigstore integration time:
-
Permalink:
bhouvana/Whytrail@1ce23e0c5b5862c9120e55c93575aeeaa8db4ac0 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bhouvana
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1ce23e0c5b5862c9120e55c93575aeeaa8db4ac0 -
Trigger Event:
release
-
Statement type: