Metadata-Version: 2.4
Name: whytrail
Version: 0.2.0
Summary: Python tells you where. whytrail tells you why.
Project-URL: Homepage, https://github.com/bhouvana/Whytrail
Project-URL: Documentation, https://bhouvana.github.io/Whytrail/
Project-URL: Issues, https://github.com/bhouvana/Whytrail/issues
Author: whytrail contributors
License-Expression: MIT
License-File: LICENSE
Keywords: debugging,exceptions,explainability,provenance,tracing
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Debuggers
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: aiohttp
Requires-Dist: aiohttp>=3.9; extra == 'aiohttp'
Provides-Extra: all
Requires-Dist: aiohttp>=3.9; extra == 'all'
Requires-Dist: anthropic>=0.30; extra == 'all'
Requires-Dist: asyncpg>=0.29; extra == 'all'
Requires-Dist: boto3>=1.34; extra == 'all'
Requires-Dist: celery>=5.3; extra == 'all'
Requires-Dist: ddtrace>=2.0; extra == 'all'
Requires-Dist: django>=4.2; extra == 'all'
Requires-Dist: dramatiq>=1.14; extra == 'all'
Requires-Dist: flask>=2.3; extra == 'all'
Requires-Dist: google-api-core>=2.15; extra == 'all'
Requires-Dist: grpcio>=1.60; extra == 'all'
Requires-Dist: httpx>=0.27; extra == 'all'
Requires-Dist: huggingface-hub>=0.20; extra == 'all'
Requires-Dist: jsonschema>=4.0; extra == 'all'
Requires-Dist: langchain-core>=0.2; extra == 'all'
Requires-Dist: marshmallow>=3.0; extra == 'all'
Requires-Dist: openai>=1.0; extra == 'all'
Requires-Dist: pandas>=2.0; extra == 'all'
Requires-Dist: polars>=0.20; extra == 'all'
Requires-Dist: prefect>=2.14; extra == 'all'
Requires-Dist: pydantic>=2.0; extra == 'all'
Requires-Dist: pymongo>=4.0; extra == 'all'
Requires-Dist: pytest>=7.0; extra == 'all'
Requires-Dist: pyyaml>=6.0; extra == 'all'
Requires-Dist: requests>=2.20; extra == 'all'
Requires-Dist: rq>=1.15; extra == 'all'
Requires-Dist: scrapy>=2.8; extra == 'all'
Requires-Dist: sentry-sdk>=2.0; extra == 'all'
Requires-Dist: sqlalchemy>=2.0; extra == 'all'
Requires-Dist: starlette>=0.36; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.30; extra == 'anthropic'
Provides-Extra: asyncpg
Requires-Dist: asyncpg>=0.29; extra == 'asyncpg'
Provides-Extra: boto3
Requires-Dist: boto3>=1.34; extra == 'boto3'
Provides-Extra: celery
Requires-Dist: celery>=5.3; extra == 'celery'
Provides-Extra: cli
Requires-Dist: rich>=13.0; extra == 'cli'
Provides-Extra: ddtrace
Requires-Dist: ddtrace>=2.0; extra == 'ddtrace'
Provides-Extra: dev
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-benchmark>=4.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: rich>=13.0; extra == 'dev'
Provides-Extra: django
Requires-Dist: django>=4.2; extra == 'django'
Provides-Extra: docs
Requires-Dist: mkdocs-include-markdown-plugin>=6.0; extra == 'docs'
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.6; extra == 'docs'
Provides-Extra: dramatiq
Requires-Dist: dramatiq>=1.14; extra == 'dramatiq'
Provides-Extra: fastapi
Requires-Dist: starlette>=0.36; extra == 'fastapi'
Provides-Extra: flask
Requires-Dist: flask>=2.3; extra == 'flask'
Provides-Extra: google-cloud
Requires-Dist: google-api-core>=2.15; extra == 'google-cloud'
Provides-Extra: grpcio
Requires-Dist: grpcio>=1.60; extra == 'grpcio'
Provides-Extra: httpx
Requires-Dist: httpx>=0.27; extra == 'httpx'
Provides-Extra: huggingface-hub
Requires-Dist: huggingface-hub>=0.20; extra == 'huggingface-hub'
Provides-Extra: jsonschema
Requires-Dist: jsonschema>=4.0; extra == 'jsonschema'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2; extra == 'langchain'
Provides-Extra: marshmallow
Requires-Dist: marshmallow>=3.0; extra == 'marshmallow'
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == 'openai'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
Provides-Extra: pandas
Requires-Dist: pandas>=2.0; extra == 'pandas'
Provides-Extra: polars
Requires-Dist: polars>=0.20; extra == 'polars'
Provides-Extra: prefect
Requires-Dist: prefect>=2.14; extra == 'prefect'
Provides-Extra: pydantic
Requires-Dist: pydantic>=2.0; extra == 'pydantic'
Provides-Extra: pymongo
Requires-Dist: pymongo>=4.0; extra == 'pymongo'
Provides-Extra: pytest
Requires-Dist: pytest>=7.0; extra == 'pytest'
Provides-Extra: pyyaml
Requires-Dist: pyyaml>=6.0; extra == 'pyyaml'
Provides-Extra: requests
Requires-Dist: requests>=2.20; extra == 'requests'
Provides-Extra: rich
Requires-Dist: rich>=13.0; extra == 'rich'
Provides-Extra: rq
Requires-Dist: rq>=1.15; extra == 'rq'
Provides-Extra: scrapy
Requires-Dist: scrapy>=2.8; extra == 'scrapy'
Provides-Extra: sentry
Requires-Dist: sentry-sdk>=2.0; extra == 'sentry'
Provides-Extra: sqlalchemy
Requires-Dist: sqlalchemy>=2.0; extra == 'sqlalchemy'
Description-Content-Type: text/markdown

<p align="center">
  <a href="https://pypi.org/project/whytrail/">
    <img src="docs/assets/banner.svg" alt="whytrail — Python tells you where something happened. whytrail tells you why. pip install whytrail" width="100%">
  </a>
</p>

## 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:

```python
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`](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

```bash
pip install whytrail            # core, zero dependencies
pip install whytrail[rich]      # + Explanation.rich() tree rendering
pip install whytrail[cli]       # + the `whytrail` CLI
pip install whytrail[requests]  # + auto-explain requests.RequestException, etc.
pip install whytrail[all]       # every integration below, one install
```

One package, one version -- all 30 integrations below are extras of
`whytrail` itself, not separate PyPI packages (ADR 0006). `pip install
whytrail[X]` pulls in exactly the library `X` explains, nothing else;
`why()` picks it up automatically the moment it's installed, no further
setup.

## 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 extras of this same package, not new verbs -- see
`docs/plugin-guide.md`.

## Ecosystem

30 integrations 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]` |

All 30 in one install: `pip install whytrail[all]`. Want to publish your
own, outside this repo, for a library not on this list?
`python scripts/new_plugin.py <library> --kind explainer|integration`
scaffolds that (ADR 0006 -- the entry-point extensibility mechanism the
bundled 30 used to use is still there for exactly this). `.github/actions
/whytrail-run` packages the CLI as a GitHub Action for CI.

**On test coverage:** every integration above is verified against a real
object from the real library, not a mock, and every one'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`](docs/adr/0001-whytrail-architecture.md) -- feasibility and the
  original two-tier architecture.
- [`0002`](docs/adr/0002-category-strategy.md) -- category positioning
  and the pre-1.0 API fixes it drove.
- [`0003`](docs/adr/0003-ecosystem-scale-triage.md) -- how the plugin
  ecosystem scales (and how it doesn't).
- [`0004`](docs/adr/0004-rename-to-whytrail.md) -- why this project is
  called `whytrail` and not `butwhy`.
- [`0005`](docs/adr/0005-vscode-extension-scope.md) -- VS Code extension
  scope assessment (not started, and why).
- [`0006`](docs/adr/0006-unify-plugins-into-extras.md) -- why the 30
  integrations became extras of one package instead of 30 separate
  PyPI distributions.

Full documentation site (same content, easier to browse):
https://bhouvana.github.io/Whytrail/
