Metadata-Version: 2.4
Name: crosscheck-python-sdk
Version: 0.1.0
Summary: CrossCheck error tracking for Python — Django, Celery, and logging
Author: Spatula Labs LLC
License-Expression: MIT
Project-URL: Homepage, https://crosscheck.app
Project-URL: Source, https://github.com/Spatula-Labs/crosscheck-python
Project-URL: Changelog, https://github.com/Spatula-Labs/crosscheck-python/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/Spatula-Labs/crosscheck-python/issues
Keywords: error-tracking,monitoring,observability,django,celery,crosscheck
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
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 :: Libraries :: Python Modules
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# crosscheck-python

Error tracking for Python apps, reporting to [CrossCheck](https://crosscheck.app).
Django, Celery, and stdlib logging — three lines to full production error
visibility.

> **Status:** beta. Battle-tested in production across six Django apps
> (it caught a months-old silent Celery failure on its first day live).
> The `init()` surface is settled; the wire format may still change
> before 1.0.

## Quickstart

```bash
pip install crosscheck-python-sdk
```

```python
# settings.py (bottom) — one call covers web and worker processes alike,
# because Django settings load in both.
import crosscheck
crosscheck.init(app="myapp")
```

Set the DSN (from your CrossCheck project page) in the environment:

```
CROSSCHECK_DSN=https://cc_<key>@crosscheck.app/<project_id>
```

That's the whole integration. No DSN → the SDK is completely inert, so dev
machines and CI need no special-casing.

## What gets captured

| Plane | Hook | Example |
|---|---|---|
| **Unhandled request exceptions** | Django's `got_request_exception` signal | any production 500 |
| **Background task failures** | Celery's `task_failure` signal | the nightly job that dies silently |
| **Explicit error logs** | a `logging` handler (ERROR+, with `exc_info`) | `logger.exception("send failed")` in your own try/except |

Each event ships with the exception type and message, the full stack
(source lines included, app frames flagged vs framework frames), request
context (method, path, user id, org slug from URL kwargs) or task/logger
context, your environment name, and the release (git SHA).

Each hook is optional: Django and Celery are imported lazily, so a plain
worker or a Flask app installs the logging path alone and never sees an
`ImportError`. The package itself has no dependencies.

**Deliberately not captured:** `django.request`/`django.security` log
records (the signal already covers 500s — nothing double-reports), ERROR
logs without a traceback, and anything on the PII list below.

## Design guarantees

- **The tracker can never become a second outage.** Shipping is
  fire-and-forget from a daemon thread with a 3-second timeout, no
  in-request retries, and every capture path swallows its own errors.
- **Client-side throttle** (30 events/min) so an error storm can't melt
  your app or your CrossCheck project.
- **PII rules enforced at capture:** user is reported as a primary key,
  never an email; query strings, cookies, headers, and request bodies are
  never read, so they can't leak.
- **Wrong-project pastes fail loudly:** the DSN's key is bound to one app —
  a mismatched `app=` gets a 403, not silent pollution of another project.

## Configuration

| Setting | How | Default |
|---|---|---|
| DSN | `CROSSCHECK_DSN` env, or `init(dsn=...)` | unset → SDK inert |
| App name | `init(app="...")` (required) | — |
| Environment | `init(environment=...)` or `CROSSCHECK_ENVIRONMENT` | `production` |
| Release | `init(release=...)`, `CROSSCHECK_RELEASE`, or `RAILWAY_GIT_COMMIT_SHA` | empty |

## Celery note

Nothing extra to do: if Celery is importable, task failures are hooked
automatically. Since your Celery workers load Django settings, the same
`init()` call covers them.

## Development

```bash
pip install -e . django
python -m unittest discover -s tests -v
```

Tests cover DSN parsing, all three capture paths, the PII assertions
(a planted email and query-string secret must appear nowhere in the
payload), double-report prevention, throttling, and transport behavior —
no network, no CrossCheck account needed.

## Releasing (maintainers)

Published to PyPI as **`crosscheck-python-sdk`** — the distribution name
carries the language, leaving room for `crosscheck-js-sdk` and friends. The
*import* is still plain `import crosscheck`. Versions follow semver.

`src/crosscheck/__init__.py`'s `__version__` is the single source of truth —
`pyproject.toml` reads it, so there is nothing to keep in sync:

1. Bump `__version__`, add a `CHANGELOG.md` entry, commit.
2. `git tag v<version> && git push --tags`.
3. `.github/workflows/release.yml` builds, runs `twine check --strict`, and
   uploads via **PyPI trusted publishing** (OIDC — no API token in the repo).

One-time setup on PyPI → project → Publishing → add a GitHub publisher:
owner `Spatula-Labs`, repo `crosscheck-python`, workflow `release.yml`,
environment `pypi`. For the very first upload the project does not exist yet,
so register a *pending* publisher with the same values under your account's
Publishing settings.

A version number on PyPI can never be reused — a bad upload is yanked and
superseded by a new version, not replaced.

## Roadmap

- Flask / FastAPI integrations (the core is framework-agnostic;
  integrations are thin)
- `crosscheck-js` sibling (Node + browser) speaking the same wire contract
- `1.0` once the wire contract has soaked in production
