Metadata-Version: 2.5
Name: shipping-carriers
Version: 0.1.0
Summary: Framework-agnostic multi-carrier shipment tracking (USPS/UPS/FedEx/DHL).
License-Expression: AGPL-3.0-only
License-File: license.txt
Requires-Python: >=3.10
Requires-Dist: requests-oauthlib<3,>=2
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Description-Content-Type: text/markdown

# shipping-carriers

Framework-agnostic multi-carrier shipment **tracking** for USPS, UPS, FedEx and
DHL, with a common normalized result model. Pricing and labels may follow.

It has no dependency on Frappe (or any web framework). Authentication is injected
via a small `TokenProvider` seam, so the same adapters work:

- **standalone** — `ClientCredentialsProvider` (OAuth2 client-credentials via
  `requests-oauthlib`) or `ApiKeyProvider` (DHL-style static key).
- **inside Frappe** — a provider backed by the `Connected App` doctype.

OAuth handling is delegated to `requests-oauthlib`, not reimplemented. Any object
with `auth_headers() -> dict` works, so you can supply your own.

```python
from shipping_carriers.registry import get_adapter
from shipping_carriers.providers import ClientCredentialsProvider

adapter = get_adapter(
    "fedex",
    token_provider=ClientCredentialsProvider(
        token_uri="https://apis.fedex.com/oauth/token",
        client_id="...", client_secret="...",
    ),
)
result = adapter.track("123456789012")
print(result.status, result.delivered_at)
for ev in result.events:
    print(ev.time, ev.status, ev.description, ev.location)
```

## Status

- `stub` adapter — returns deterministic mock data; used for development and tests.
- `usps` / `ups` / `fedex` / `dhl` — interface seams in place; live API calls
  raise `CarrierNotImplemented` until credentialed implementations land.

## Layout

- `models.py` — `TrackingStatus`, `TrackingEvent`, `TrackingResult`
- `base.py` — `CarrierAdapter` (ABC) and the `TokenProvider` protocol
- `providers.py` — standalone token providers (api-key, client-credentials)
- `registry.py` — `get_adapter(provider, token_provider=..., **opts)`
- `adapters/` — one module per carrier
