Metadata-Version: 2.4
Name: unwrapr
Version: 1.0.0
Summary: Normalise any API response into a consistent envelope: ok, data, error, status, meta
Author: eritrouib
License: MIT
Project-URL: Homepage, https://github.com/eritrouib/unwrapr-py
Project-URL: Repository, https://github.com/eritrouib/unwrapr-py
Project-URL: Issues, https://github.com/eritrouib/unwrapr-py/issues
Keywords: api,response,normalise,normalize,envelope,http,rest
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
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 :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# unwrapr

Stop writing the same API response unwrapping code in every project.

```bash
pip install unwrapr
```

> **Requires Python 3.9+** · Zero dependencies

---

## The problem

Every API you call returns a different shape:

```python
{"data": {...}, "error": null}           # some APIs
{"success": True, "result": {...}}       # other APIs
{"status": 200, "payload": {...}}        # yet others
[{"id": 1}, {"id": 2}]                  # or just raw lists
```

You end up writing custom unwrapping logic for every single one.

**unwrapr fixes that.** One function. Any shape. Consistent output.

---

## Quick start

```python
from unwrapr import unwrap

# Works with any API response shape
env = unwrap({"success": True, "data": {"id": 1, "name": "Alice"}})

env.ok      # True
env.data    # {"id": 1, "name": "Alice"}
env.error   # None
env.status  # None
env.meta    # {}
```

---

## Supported shapes

unwrapr auto-detects the response shape and normalises it:

```python
# { data, error }
unwrap({"data": {"id": 1}, "error": None})

# { success, data }
unwrap({"success": True, "result": {"id": 1}})

# { status, payload }
unwrap({"status": 200, "payload": {"id": 1}})

# JSON:API
unwrap({"data": [...], "included": [], "meta": {"total": 5}})

# Plain dict or list
unwrap({"id": 1, "name": "Alice"})
unwrap([1, 2, 3])
```

---

## HTTP status override

Pass the HTTP status code to let it override the body:

```python
env = unwrap(response.json(), status=response.status_code)
env.ok      # True if 2xx, False otherwise
env.status  # the actual HTTP status code
```

---

## Safe data access

```python
env = unwrap({"success": False, "error": "Not found"})

# Raises UnwraprError if not ok
data = env.unwrap()

# Returns default if not ok — never raises
data = env.unwrap_or([])
data = env.unwrap_or(None)

# Use as a boolean
if env:
    print(env.data)
```

---

## Custom strategies

Add your own shape detection logic:

```python
from unwrapr import unwrap, DEFAULT_STRATEGIES

def my_api_strategy(raw):
    if isinstance(raw, dict) and "response" in raw:
        return {
            "ok": raw["response"]["success"],
            "data": raw["response"]["body"],
            "error": raw["response"].get("error"),
            "status": None,
            "meta": {},
        }
    return None  # return None to try the next strategy

env = unwrap(response, strategies=[my_api_strategy] + DEFAULT_STRATEGIES)
```

---

## Works great with petchr

```python
from petchr import petch
from unwrapr import unwrap

resp = petch("https://api.example.com/users/1")
env = unwrap(resp.data, status=resp.status_code)

if env:
    print(env.data)
else:
    print(f"Error: {env.error}")
```

---

## The Envelope

Every call returns an `Envelope`:

| Field    | Type       | Description                          |
|----------|------------|--------------------------------------|
| `ok`     | `bool`     | True if response is successful       |
| `data`   | `Any`      | The extracted payload                |
| `error`  | `str|None` | Error message if not ok              |
| `status` | `int|None` | HTTP status code                     |
| `meta`   | `dict`     | Extra fields (pagination, links etc) |
| `raw`    | `Any`      | Original response before normalising |

---

## License

MIT
