Metadata-Version: 2.4
Name: compickle
Version: 1.0.1
Summary: Fast binary serializer for Python with optional C acceleration.
Author: Luis Fernando Montaño Hernandez
License-Expression: MIT
Keywords: serializer,pickle,binary,performance,python,serialization
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: C
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Archiving
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# 🥒 compickle

> **Serialización binaria para Python con motor en C — rápido, compacto y sin dependencias.**

[![Python](https://img.shields.io/badge/Python-3.9%2B-blue?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
[![Lenguaje C](https://img.shields.io/badge/Motor-C%20nativo-orange?style=flat-square&logo=c&logoColor=white)]()
[![Licencia](https://img.shields.io/badge/Licencia-MIT-green?style=flat-square)]()
[![Compilación](https://img.shields.io/badge/Compilado-march%3Dnative-red?style=flat-square)]()

---

## ¿Qué es compickle?

`compickle` es un serializador binario escrito principalmente en **C** y expuesto como extensión nativa de Python. Está diseñado para ser **muy simple de usar** y **más rápido que el módulo estándar `pickle`** en la mayoría de cargas de trabajo, gracias a:

- Un **buffer de salida dinámico** que crece en potencias de 2.
- Una **tabla de deduplicación** con hashing FNV-1a O(1) amortizado (capacidad de hasta 8 192 entradas).
- **Compilación nativa** con `-O3 -march=native -mtune=native`.
- Un **fallback puro en Python** que se activa automáticamente si la extensión C no está disponible.

---

## ⚙️ Instalación

```bash
pip install .
```

O para desarrollo (compilación en el directorio actual):

```bash
python setup.py build_ext --inplace
```

> El compilador usa `-O3 -march=native -mtune=native` automáticamente, generando código optimizado para tu arquitectura específica.

---

## 🚀 Uso rápido

```python
import compickle

# Serializar cualquier objeto a archivo
datos = {
    "nombre": "Rex",
    "edad": 5,
    "activo": True,
    "coordenadas": (4.0, 2.0),
    "etiquetas": {"perro", "mascota"},
}

compickle.dump(datos, "datos.cpkl")

# Deserializar desde archivo
copia = compickle.load("datos.cpkl")

# Verificar qué motor está activo
print(compickle.backend())  # → 'c' o 'python'
```

---

## 📖 API completa

### `compickle.dump(obj, path)`

Serializa `obj` y escribe el resultado binario en `path`.

```python
compickle.dump(mi_objeto, "salida.cpkl")
```

- Usa el motor C si está disponible; Python puro en caso contrario.
- Resetea automáticamente la tabla de deduplicación antes de cada serialización.

---

### `compickle.load(path)`

Lee el archivo binario en `path` y reconstruye el objeto original.

```python
obj = compickle.load("salida.cpkl")
```

---

### `compickle.dedup_reset()`

Resetea manualmente la tabla interna de deduplicación (tanto la del motor C como la del fallback Python).

```python
compickle.dedup_reset()
```

> Útil si reutilizas el proceso para serializar muchos objetos distintos en sesiones largas.

---

### `compickle.backend()`

Devuelve el motor activo.

```python
compickle.backend()  # → 'c'  (extensión compilada)
                     # → 'python'  (fallback puro Python)
```

---

## 🧩 Tipos soportados

| Tipo Python         | Deduplicado | Notas                                           |
|---------------------|:-----------:|-------------------------------------------------|
| `None`              | —           | 1 byte: `0x00`                                 |
| `bool`              | —           | 1 byte: `0x80` / `0x81`                        |
| `int`               | —           | Enteros 0–59 en 1 byte; arbitrario big-endian  |
| `float`             | —           | IEEE 754 doble precisión (8 bytes)              |
| `complex`           | —           | Dos `float64` big-endian (16 bytes)             |
| `str`               | ✅           | UTF-8 + FNV-1a hash                             |
| `bytes`             | ✅           | Deduplicado por contenido                       |
| `bytearray`         | ✅           | Deduplicado por contenido                       |
| `list`              | —           | Elementos serializados recursivamente           |
| `tuple`             | —           | Elementos serializados recursivamente           |
| `set`               | —           | Ordenado por `repr()` para reproducibilidad     |
| `frozenset`         | —           | Ordenado por `repr()` para reproducibilidad     |
| `dict`              | —           | Claves y valores serializados recursivamente    |
| `function`          | ✅           | Nombre + código fuente (via `inspect.getsource`)|
| `closure`           | ✅           | Fuente + variables capturadas del closure       |
| `class` (tipo)      | ✅           | Nombre + módulo + código fuente                 |
| Instancia (`__dict__`) | ✅       | Clase + fuente + estado del `__dict__`          |
| Instancia (`__slots__`) | ✅      | Recorre MRO completo para capturar todos los slots |
| Objeto con `__reduce__` | ✅     | Prioridad máxima; soporta estado opcional       |

---

## 🔬 Cómo funciona internamente

### Motor C (`compickle.c`)

El corazón de compickle es una extensión CPython que implementa:

**Buffer dinámico (`Buf`)**
```
buf_grow → realloc en potencias de 2 (inicio: 8 192 bytes)
buf_u8  → escribe 1 byte
buf_raw → escribe N bytes con memcpy
```

**Tabla de deduplicación (FNV-1a)**
```
Capacidad: 8 192 entradas (DEDUP_CAP)
Buckets:   16 384 (HASH_BUCKETS)
Colisiones: lista enlazada (campo .next)
```

Cuando un bloque de bytes se ha visto antes, se emite una **referencia compacta** (2–5 bytes) en lugar de repetir los datos. Los strings largos, bloques de código fuente y datos binarios se benefician enormemente de esto.

**Codificación de longitudes (variable)**
```
n ≤ 0x3F      → 1 byte
n ≤ 0x3FFF    → 2 bytes (bit 0x40 marcador)
n > 0x3FFF    → 5 bytes (0xFF + uint32 big-endian)
```

**Prioridad de serialización de objetos**
```
1. __reduce__ explícito en la clase  →  tag 0x1F
2. __dict__ disponible               →  tag 0x1C
3. __slots__ sin __dict__            →  tag 0x1E (recorre MRO completo)
```

---

### Fallback Python (`compickle.py`)

Si la extensión C no compila, `compickle.py` implementa exactamente el mismo protocolo binario en Python puro, con las mismas tags y el mismo esquema de deduplicación. El API público es idéntico; solo cambia la velocidad.

---

### Carga diferida (`__init__.py`)

El módulo público usa **lazy loading**: la extensión C solo se importa en la primera llamada a `dump`, `load`, `dedup_reset` o `backend()`. Esto evita errores en import si la extensión no está compilada todavía.

```python
compickle.backend()  # ← importa la extensión C aquí, la primera vez
```

---

## 🏗️ Estructura del proyecto

```
compickle/
├── compickle.c        # Motor principal en C (extensión CPython)
├── compickle.py       # Implementación de referencia en Python puro
├── __init__.py        # API pública con lazy loading
└── setup.py           # Build con -O3 -march=native -mtune=native
```

---

## 🧪 Ejemplo avanzado: clases, slots y __reduce__

```python
import compickle

# ── Clase con __dict__ ──────────────────────────────────────────────
class Punto:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Punto(3.0, 7.5)
compickle.dump(p, "punto.cpkl")
p2 = compickle.load("punto.cpkl")
print(p2.x, p2.y)  # → 3.0  7.5

# ── Clase con __slots__ ─────────────────────────────────────────────
class Vector:
    __slots__ = ("x", "y", "z")
    def __init__(self, x, y, z):
        self.x, self.y, self.z = x, y, z

v = Vector(1, 2, 3)
compickle.dump(v, "vector.cpkl")
v2 = compickle.load("vector.cpkl")
print(v2.x, v2.y, v2.z)  # → 1  2  3

# ── Objeto con __reduce__ ───────────────────────────────────────────
class Color:
    def __init__(self, r, g, b):
        self.r, self.g, self.b = r, g, b

    def __reduce__(self):
        return (Color, (self.r, self.g, self.b))

c = Color(255, 128, 0)
compickle.dump(c, "color.cpkl")
c2 = compickle.load("color.cpkl")
print(c2.r, c2.g, c2.b)  # → 255  128  0
```

---

## 📦 Formato binario (resumen de tags)

```
0x00        None
0x02        int (arbitrario)
0x03        float (8 bytes)
0x04        complex (16 bytes)
0x05        str (UTF-8, deduplicado)
0x06        bytes (deduplicado)
0x07        bytearray (deduplicado)
0x08        list
0x09        tuple
0x0A        set
0x0B        frozenset
0x0C        dict
0x0D        function (nombre + fuente)
0x12        class (nombre + módulo + fuente)
0x1C        instancia con __dict__
0x1E        instancia con __slots__
0x1F        instancia vía __reduce__
0x80/0x81   False / True
0xC0–0xFB   int pequeño (0–59, 1 byte)
0xFB        bloque nuevo para dedup
0xFC–0xFE   referencia dedup (4 / 2 / 1 byte de índice)
```

---

## 📄 Licencia

MIT — úsalo como quieras.
