Metadata-Version: 2.4
Name: nippy-decoder
Version: 0.1.0
Summary: Pure Python decoder for Clojure Nippy-encoded data
Project-URL: Homepage, https://github.com/HariprasathSankaraiyan/nippy-decoder
Project-URL: Repository, https://github.com/HariprasathSankaraiyan/nippy-decoder
Project-URL: Issues, https://github.com/HariprasathSankaraiyan/nippy-decoder/issues
Author-email: Hariprasath Sankaraiyan <hariprasathsankaraiyan@gmail.com>
License: MIT
License-File: LICENSE
Keywords: clojure,decoder,nippy,serialization
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.8
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# nippy-decoder

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)

pure python decoder for [clojure nippy](https://github.com/taoensso/nippy) serialized data.

**zero dependencies.** uses only python standard library.

---

- [what and why](#what-and-why)
- [installation](#installation)
- [how to use](#how-to-use)
  - [basic decoding](#basic-decoding)
  - [from database](#from-database)
  - [from api response](#from-api-response)
  - [error handling](#error-handling)
- [type mapping](#type-mapping)
- [what's supported](#whats-supported)
- [what's not](#whats-not)
- [examples](#examples)
- [development](#development)
- [license](#license)

## what and why

nippy is clojure's fast serialization format. if you have clojure/java services storing data as nippy-encoded bytes (in postgres, redis, kafka, etc.), you need a way to decode it in python.

this decoder:
- reads nippy bytes into python dicts, lists, strings, etc.
- has **zero dependencies** (just stdlib)
- is **simple**: 200 lines of code
- is **validated** against official clojure `taoensso/nippy`

## installation

```bash
pip install nippy-decoder
```

## how to use

### basic decoding

```python
from nippy_decoder import NippyDecoder

decoder = NippyDecoder()

# from anywhere: database, file, api, kafka...
nippy_bytes = b'NPY\x00...'

result = decoder.decode(nippy_bytes)
# => {'name': 'alice', 'age': 30, 'active': True}
```

### from database

**postgres with psycopg2:**

```python
import psycopg2
from nippy_decoder import NippyDecoder

decoder = NippyDecoder()

conn = psycopg2.connect("dbname=mydb")
cur = conn.cursor()

# query nippy-encoded column
cur.execute("""
    SELECT id, data
    FROM facts
    WHERE created_at > %s
""", (since_date,))

for fact_id, nippy_bytes in cur:
    decoded = decoder.decode(nippy_bytes)
    print(f"{fact_id}: {decoded}")
```

**sqlite:**

```python
import sqlite3
from nippy_decoder import NippyDecoder

decoder = NippyDecoder()

conn = sqlite3.connect('/path/to/db.sqlite')
cur = conn.cursor()

cur.execute("SELECT id, nippy_data FROM records")

for row_id, nippy_bytes in cur:
    decoded = decoder.decode(nippy_bytes)
    # work with python dict/list/etc
```

### from api response

```python
import requests
from nippy_decoder import NippyDecoder

decoder = NippyDecoder()

response = requests.get("https://api.example.com/facts/123")
decoded = decoder.decode(response.content)

print(decoded['status'])  # => 'active'
```

### error handling

```python
from nippy_decoder import NippyDecoder

decoder = NippyDecoder()

try:
    result = decoder.decode(data)
except ValueError as e:
    print(f"decode failed: {e}")
    # "invalid nippy header"
    # "unsupported nippy version: 5"
    # "unsupported type: 200"
```

## type mapping

| clojure | python | example |
|---------|--------|---------|
| `nil` | `None` | |
| `true` / `false` | `True` / `False` | |
| integer | `int` | `42` → `42` |
| float / double | `float` | `3.14` → `3.14` |
| string | `str` | `"hello"` → `"hello"` |
| keyword | `str` | `:status` → `"status"` |
| vector | `list` | `[1 2 3]` → `[1, 2, 3]` |
| map | `dict` | `{:a 1 :b 2}` → `{"a": 1, "b": 2}` |
| set | `set` | `#{1 2 3}` → `{1, 2, 3}` |
| uuid | `str` | uuid → `"550e8400-..."` |
| bytes | `bytes` or `dict` | auto-parses json if detected |

**note on keywords:** clojure keywords like `:status` become python strings `"status"` (without the `:`).

**note on byte arrays:** if a byte array starts with `{` or `[`, the decoder attempts to parse it as json and returns a dict/list. otherwise returns raw bytes.

## what's supported

- **primitives**: null, boolean, integers (8-64 bit), floats, doubles
- **strings**: utf-8 strings with 1-4 byte length prefixes (types 11-14, 105)
- **keywords**: clojure keywords (types 33-35, 106)
- **collections**: vectors (types 19-23), maps (types 24-26, 112), sets (types 27-29), lists (types 30-32)
- **binary**: byte arrays (types 15-18), uuids (type 36)
- **metadata**: type 37 (skipped, value returned)
- **extended**: legacy collection types (44-127)

validated against **nippy version 0** (standard format since 2014).

## what's not

- **encoding** - this is decode-only
- **nippy v1+** - only v0 supported (but v0 is the standard)
- **custom types** - custom nippy extensions not supported
- **types > 127** - reserved/future types not implemented

if you need encoding, use the official [taoensso/nippy](https://github.com/taoensso/nippy) in clojure.

## examples

**decode primitives:**

```python
decoder = NippyDecoder()

# integer
decoder.decode(b'NPY\x00\x07\x00\x00\x00\x2a')  # => 42

# string
decoder.decode(b'NPY\x00\x0c\x05hello')  # => "hello"

# keyword
decoder.decode(b'NPY\x00\x21\x06status')  # => "status"
```

**decode collections:**

```python
# vector
decoder.decode(b'NPY\x00\x15\x03...')  # => [1, 2, 3]

# map
result = decoder.decode(b'NPY\x00\x18\x02...')
# => {"name": "test", "age": 25}

# nested
result = decoder.decode(b'NPY\x00\x18\x01...')
# => {"user": {"name": "alice", "active": True}}
```

**decode uuid:**

```python
result = decoder.decode(b'NPY\x00\x24...')
# => "ba8feab4-9efb-4635-97d2-be648a141fb4"
```

**complex nested data:**

```python
# decode nested maps with multiple data types
fact_bytes = get_from_database(record_id)
record = decoder.decode(fact_bytes)

print(record)
# {
#   'record-id': 'ba8feab4-9efb-4635-97d2-be648a141fb4',
#   'status': 'PENDING',
#   'priority': 1,
#   'metadata': {
#     'contribution': 179.03,
#     'interest': 50.25
#   }
# }
```

## development

```bash
# clone
git clone https://github.com/HariprasathSankaraiyan/nippy-decoder
cd nippy-decoder

# install in dev mode
pip install -e .

# run examples
python examples/basic_usage.py

# run tests (requires pytest)
pip install pytest
pytest tests/ -v
```

**structure:**

```
nippy-decoder/
├── src/nippy_decoder/
│   ├── decoder.py      # core logic
│   └── __init__.py
├── tests/              # pytest suite
│   ├── test_primitives.py
│   ├── test_collections.py
│   └── test_uuid.py
├── examples/
│   └── basic_usage.py  # usage examples
├── pyproject.toml      # package metadata
├── README.md
└── LICENSE
```

## license

MIT License

Copyright © 2025 nippy-decoder contributors

---

issues and prs welcome!
