Metadata-Version: 2.5
Name: datacrop
Version: 0.1.0
Summary: Official Python client for the DataCrop US-ag data API (api.datacrop.dev)
Project-URL: Homepage, https://datacrop.dev
Project-URL: Documentation, https://datacrop.dev/docs
Author-email: Synchro Labs LLC <support@datacrop.dev>
License: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
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: Topic :: Office/Business :: Financial
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1.0,>=0.24
Provides-Extra: pandas
Requires-Dist: pandas>=1.5; extra == 'pandas'
Provides-Extra: test
Requires-Dist: pandas>=1.5; extra == 'test'
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# datacrop — official Python client

Typed, httpx-based client for the [DataCrop](https://datacrop.dev) US-ag data
API (`api.datacrop.dev`). Auth is Bearer-only — there is no keyless data route.

```bash
pip install datacrop            # httpx only
pip install "datacrop[pandas]"  # + pandas for series_to_pandas
```

## Quickstart (free tier)

The free tier covers **corn, wheat and soybean** at 500 requests/day
(keys created before 2026-08-20 keep their original 2,000/day — check
`grandfathered` on `/v1/me`).

```python
from datacrop import DataCrop

client = DataCrop(api_key="dc_...")

# Price series with provenance on every row
corn = client.series("corn", start_date="2026-06-01")
for row in corn.series[:3]:
    print(row.date, row.value, row.unit, "←", row.source, row.source_ref)
print(corn.license.attribution)     # who to credit for these rows

# Which identifiers exist, and which is the curated primary
ids = client.identifiers("wheat")

# Straight to pandas via the CSV endpoint
df = client.series_to_pandas("corn")
print(df.attrs["datacrop"])          # pagination/freshness/attribution headers

# Quota awareness (X-RateLimit-* headers; None on unlimited tiers)
print(client.quota)                  # Quota(limit=500, remaining=..., reset=...)
```

## The warning gate (implemented, currently dormant server-side)

`GET /v1/series` returns **422** when the catalog documents that pooling the
series you asked for is structurally misleading. The client raises
`WarningGateError` with each warning's stable `id`, and `acknowledged_retry()`
re-issues the request acknowledging **those specific ids** — never the blanket
`acknowledge_warnings=true`, which would also wave through any *future*
critical warning without a human reading it.

Stated plainly, because you can test it: **as of 2026-08-20 no production
series populates a critical relational caveat, so this gate will not fire on
any request you make today.** The contract exists client-side so that when a
caveat does land in the catalog, your pipeline reads it before affirming it —
rather than discovering a breaking 422 in production. Non-critical warnings
already ride along on every response as `warnings[]`, each with a stable id.

## Surface

| Method | Endpoint | Tier |
|---|---|---|
| `series` / `iter_series` / `series_to_pandas` | `GET /v1/series` | all |
| `identifiers` | `GET /v1/identifiers` | all |
| `freshness` | `GET /v1/freshness/{commodity}` | all |
| `lineage` | `GET /v1/lineage` | all |
| `license` | `GET /v1/license` | all |
| `catalog` | `GET /v1/catalog` | all |
| `forecasts` | `GET /v1/forecasts` | Pro+ |
| `basis` | `GET /v1/basis` | Pro+ |
| `revisions` | `GET /v1/revisions` | Pro+ |
| `fundamentals` | `GET /v1/fundamentals` | Pro+ |
| `positioning` | `GET /v1/positioning` | Pro+ |
| `energy` | `GET /v1/energy` | Pro+ |
| `exports` | `GET /v1/exports` | Pro+ |
| `weather_events` | `GET /v1/weather-events` | Pro+ |

Responses are dataclasses mirroring the API's field names exactly (see
`api/API.md`). Rows keep `source` / `source_ref` (or the endpoint's
source-native `series_ref`, aliased as `.source_ref`), and every top-level
response exposes `.license` — `None` where the endpoint's payload carries no
licence block, in which case `client.license()` returns the full manifest.

Other honesty surfaces worth reading before you model:
`ForecastsResponse.point_published` (False ⇒ P50 is the band median, not a
call), `RevisionsResponse.coverage.tracking_since` (absence before that date
means "not observed", not "not revised"), and `FundamentalsRow.derived`
(DataCrop-computed rows carry their formula).

## Rate limits

`client.quota` reflects the last response's `X-RateLimit-*` headers; unlimited
tiers send none, so it stays `None` for them. On 429 the client raises
`RateLimitError` (with `resets_at`) by default; construct with
`DataCrop(api_key=..., wait_on_limit=True)` to sleep until the UTC-midnight
reset and retry once instead.

## Data sources and licensing

DataCrop serves public agricultural data from USDA AMS Market News, USDA NASS
QuickStats, and FRED, normalized with per-row provenance. Per-source licence
and redistribution terms are served with the data itself — `GET /v1/license`
returns the manifest for exactly the sources present in your response, and
every response exposes it as `.license`.

This product uses the FRED® API but is not endorsed or certified by the
Federal Reserve Bank of St. Louis. This product uses the NASS API but is not
endorsed or certified by NASS.

## Running the tests

All tests run against a mocked `httpx` transport — no network, no API key.

```bash
# from clients/python/ (bash)
PYTHONPATH=. python -m pytest tests -q
# or, after `pip install -e clients/python[test]`, from anywhere:
python -m pytest clients/python/tests -q
```
