Metadata-Version: 2.4
Name: abl-db
Version: 0.1.1
Summary: Un motor de base de datos tabular ligero, cifrado y ultrarrápido escrito en C con interfaz para Python.
Author: BluePanda
License-Expression: MIT
Project-URL: Homepage, https://github.com/aminbena010-ai/ABL
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: C
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

<div align="center">

# 🗃️ ABL — Array-Based Lightweight Database

**Motor de base de datos tabular embebido en C con API Python ultrarrápida.**

[![PyPI](https://img.shields.io/pypi/v/abl-db?style=flat-square&color=blue)](https://pypi.org/project/abl-db/)
[![Python](https://img.shields.io/pypi/pyversions/abl-db?style=flat-square&color=3776AB&logo=python&logoColor=white)](https://pypi.org/project/abl-db/)
[![License](https://img.shields.io/pypi/l/abl-db?style=flat-square&color=green)](LICENSE)
[![Downloads](https://img.shields.io/pypi/dm/abl-db?style=flat-square&color=orange)](https://pypi.org/project/abl-db/)
[![Build](https://img.shields.io/badge/build-passing-brightgreen?style=flat-square)](https://github.com/aminbena010-ai/abl-db)

> *Persistencia estructurada sin servidores. Un solo archivo `.abl`, una clave secreta, y listo.*

</div>

---

## 🚀 Instalación en 10 segundos

```bash
pip install abl-db
```

> **Requisito:** Python 3.7+ y un compilador C (GCC/Clang/MSVC). La extensión C se compila automáticamente durante la instalación.

---

## ✨ ¿Por qué ABL?

| Característica | ABL | SQLite | PostgreSQL | JSON |
|---|---|---|---|---|
| **Sin servidor** | ✅ | ✅ | ❌ | ✅ |
| **Esquemas tabulares** | ✅ | ✅ | ✅ | ❌ |
| **Ofuscación integrada** | ✅ | ❌ | ❌ | ❌ |
| **Motor en C nativo** | ✅ | ✅ | ✅ | ❌ |
| **Peso ligero** | ✅ | ✅ | ❌ | ✅ |
| **Zero-config** | ✅ | ✅ | ❌ | ✅ |

ABL llena el hueco entre **JSON** (sin estructura) y **SQLite** (overkill para proyectos pequeños). Ideal para scripts, herramientas CLI, microservicios y prototipos que necesitan persistencia tabular con un mínimo de fricción.

---

## 🎯 Uso en 30 segundos

```python
from abl import ABLDatabase

# 1. Abre o crea la base de datos (un solo archivo .abl)
with ABLDatabase("datos.abl", secret="mi_clave") as db:

    # 2. Define una tabla con columnas
    productos = db.table("productos")
    productos.cols(["id", "nombre", "precio", "stock"])

    # 3. Inserta filas como diccionarios
    productos.set("001", {
        "id": "001",
        "nombre": "Laptop Pro",
        "precio": "1299.99",
        "stock": "42"
    })

    # 4. Recupera filas como diccionarios
    fila = productos.get("001")
    print(fila)
    # {'id': '001', 'nombre': 'Laptop Pro', 'precio': '1299.99', 'stock': '42'}
```

---

## 📖 Documentación completa

### 📦 Instalación

#### Desde PyPI (recomendado)

```bash
pip install abl-db
```

#### Desde el código fuente

```bash
git clone https://github.com/tu-usuario/abl-db.git
cd abl-db
pip install -e .
```

#### Verifica la instalación

```python
from abl import ABLDatabase
print("✅ ABL instalado correctamente")
```

---

### 🏗️ Conceptos clave

ABL organiza los datos en **tablas** con **filas** identificadas por un `row_id` único. Cada fila es un diccionario donde las claves son las columnas definidas.

```
Base de datos (.abl)
├── Tabla: productos
│   ├── Esquema: ["id", "nombre", "precio", "stock"]
│   ├── Fila "001": {"id":"001", "nombre":"Laptop", ...}
│   └── Fila "002": {"id":"002", "nombre":"Monitor", ...}
│
└── Tabla: clientes
    ├── Esquema: ["id", "empresa", "email"]
    ├── Fila "C-01": {"id":"C-01", "empresa":"TechCorp", ...}
    └── Fila "C-02": {"id":"C-02", "empresa":"DevStudio", ...}
```

> 💡 **Nota:** El esquema de cada tabla se guarda automáticamente en la base de datos, así que no necesitas redefinirlo cada vez que abras el archivo.

---

### 🔧 API Reference

#### `ABLDatabase(filename, secret)`

Abre o crea una base de datos ABL.

| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
| `filename` | `str` | `"database.abl"` | Ruta del archivo `.abl` |
| `secret` | `str` | `"default_secret"` | Clave de ofuscación XOR (máx. 64 caracteres) |

```python
db = ABLDatabase("mi_db.abl", secret="supersecreto123")
```

**Métodos:**

| Método | Retorno | Descripción |
|---|---|---|
| `table(name)` | `ABLTable` | Obtiene o crea una tabla por nombre |
| `keys()` | `List[str]` | Lista todas las claves indexadas (filas + esquemas) |
| `close()` | `None` | Cierra la BD y persiste los índices en disco |

**Context manager:**

```python
with ABLDatabase("datos.abl", secret="clave") as db:
    # ... operaciones ...
# Se cierra automáticamente al salir del bloque
```

---

#### `ABLTable`

Representa una tabla dentro de la base de datos.

```python
tabla = db.table("nombre_tabla")
```

**Métodos:**

| Método | Parámetros | Descripción |
|---|---|---|
| `cols(columns)` | `List[str]` | Define las columnas del esquema. **Obligatorio antes de insertar.** |
| `set(row_id, data)` | `str`, `Dict[str, Any]` | Inserta o actualiza una fila |
| `get(row_id, buffer_size=4096)` | `str`, `int` | Recupera una fila como diccionario |

```python
# Definir esquema
tabla.cols(["id", "nombre", "email"])

# Insertar / actualizar
tabla.set("USR-001", {
    "id": "USR-001",
    "nombre": "Ana López",
    "email": "ana@ejemplo.com"
})

# Recuperar
fila = tabla.get("USR-001")
# {'id': 'USR-001', 'nombre': 'Ana López', 'email': 'ana@ejemplo.com'}
```

---

#### Excepciones

| Excepción | Herencia | Cuándo ocurre |
|---|---|---|
| `ABLError` | `Exception` | Error genérico del motor (fallo de E/S, sin memoria, etc.) |
| `KeyNotFoundError` | `ABLError`, `KeyError` | La fila o clave solicitada no existe |

```python
from abl import ABLDatabase, KeyNotFoundError

db = ABLDatabase("datos.abl", secret="clave")
tabla = db.table("usuarios")
tabla.cols(["id", "nombre"])

try:
    fila = tabla.get("ID_INEXISTENTE")
except KeyNotFoundError:
    print("❌ Esa fila no existe")
```

---

### 💡 Ejemplos prácticos

#### 📝 Ejemplo 1: Inventario de productos

```python
from abl import ABLDatabase

with ABLDatabase("inventario.abl", secret="inv2024") as db:
    productos = db.table("productos")
    productos.cols(["sku", "nombre", "categoria", "precio", "stock"])

    # Añadir productos
    productos.set("LAP-001", {
        "sku": "LAP-001",
        "nombre": "MacBook Air M3",
        "categoria": "Portátiles",
        "precio": "1199.00",
        "stock": "25"
    })

    productos.set("MON-002", {
        "sku": "MON-002",
        "nombre": "Dell UltraSharp 27\"",
        "categoria": "Monitores",
        "precio": "499.99",
        "stock": "12"
    })

    # Consultar
    laptop = productos.get("LAP-001")
    print(f"📦 {laptop['nombre']} — Stock: {laptop['stock']} unidades")

    # Listar todas las claves
    print("\n📋 Claves registradas:")
    for k in db.keys():
        print(f"   • {k}")
```

**Salida:**
```
📦 MacBook Air M3 — Stock: 25 unidades

📋 Claves registradas:
   • __schema__productos
   • productos:LAP-001
   • productos:MON-002
```

---

#### 👥 Ejemplo 2: Gestión de empleados y clientes (múltiples tablas)

```python
from abl import ABLDatabase

with ABLDatabase("empresa.abl", secret="empresaSecreta") as db:

    # Tabla de empleados
    empleados = db.table("empleados")
    empleados.cols(["dni", "nombre", "departamento", "salario", "activo"])

    empleados.set("72839102F", {
        "dni": "72839102F",
        "nombre": "María García",
        "departamento": "Ingeniería",
        "salario": "45000",
        "activo": "true"
    })

    empleados.set("83920184G", {
        "dni": "83920184G",
        "nombre": "Carlos Ruiz",
        "departamento": "Ventas",
        "salario": "38000",
        "activo": "true"
    })

    # Tabla de clientes
    clientes = db.table("clientes")
    clientes.cols(["id", "empresa", "contacto", "telefono", "pais"])

    clientes.set("C-9921", {
        "id": "C-9921",
        "empresa": "TechCorp SL",
        "contacto": "Juan Pérez",
        "telefono": "+34 600 123 456",
        "pais": "España"
    })

    # Consultas cruzadas
    emp = empleados.get("72839102F")
    cli = clientes.get("C-9921")

    print(f"👤 {emp['nombre']} ({emp['departamento']})")
    print(f"🏢 Cliente: {cli['empresa']} — {cli['pais']}")
```

---

#### 🔐 Ejemplo 3: Configuración segura con ofuscación

```python
from abl import ABLDatabase

# Los datos se ofuscan automáticamente con la clave secreta
with ABLDatabase("config.abl", secret="MiClaveSuperSecreta2024!") as db:
    config = db.table("settings")
    config.cols(["clave", "valor"])

    config.set("api_key", {"clave": "api_key", "valor": "sk-live-abc123xyz"})
    config.set("db_password", {"clave": "db_password", "valor": "p@ssw0rd!"})

    # Los datos están ofuscados en disco, pero legibles en memoria
    api = config.get("api_key")
    print(f"🔑 API Key: {api['valor']}")
```

> ⚠️ **Nota de seguridad:** La ofuscación XOR protege contra lectura casual del archivo, pero **no es cifrado criptográfico robusto**. Para datos altamente sensibles, considera cifrar el archivo a nivel de sistema operativo.

---

#### 🔄 Ejemplo 4: Actualización de filas existentes

```python
from abl import ABLDatabase

with ABLDatabase("stock.abl", secret="stock") as db:
    items = db.table("items")
    items.cols(["sku", "nombre", "cantidad"])

    # Insertar
    items.set("ITEM-01", {"sku": "ITEM-01", "nombre": "Teclado", "cantidad": "10"})

    # Actualizar (sobrescribe la fila con el mismo row_id)
    items.set("ITEM-01", {"sku": "ITEM-01", "nombre": "Teclado Mecánico", "cantidad": "5"})

    actualizado = items.get("ITEM-01")
    print(actualizado)
    # {'sku': 'ITEM-01', 'nombre': 'Teclado Mecánico', 'cantidad': '5'}
```

---

### 📋 Límites y especificaciones técnicas

| Límite | Valor | Notas |
|---|---|---|
| Índices iniciales | 1,000 | Auto-escalable ×2 cuando se alcanza el límite |
| Longitud máx. de clave (`row_id`) | 64 caracteres | Incluye prefijo `tabla:` |
| Columnas por tabla | 32 | Definidas con `cols()` |
| Longitud máx. de celda | 256 caracteres | Por columna individual |
| Tamaño máx. de fila | 4,096 bytes (default) | Auto-escalable al doble si es necesario |
| Longitud máx. de clave secreta | 64 caracteres | Usada para la ofuscación XOR |

---

### 🏗️ Arquitectura interna

```
┌─────────────────────────────────────────────┐
│              ABLDatabase (Python)           │
│         ┌─────────────────────┐             │
│         │   ABLTable (Python) │             │
│         │  ┌───────────────┐  │             │
│         │  │ abl.c (Motor C)│  │             │
│         │  │ ┌───────────┐ │  │             │
│         │  │ │ archivo   │ │  │             │
│         │  │ │  .abl     │ │  │             │
│         │  │ │ [Datos    │ │  │             │
│         │  │ │  XOR]     │ │  │             │
│         │  │ │ [Índices] │ │  │             │
│         │  │ └───────────┘ │  │             │
│         │  └───────────────┘  │             │
│         └─────────────────────┘             │
└─────────────────────────────────────────────┘
```

**Formato del archivo `.abl` (binario):**

```
┌──────────────────────────────────────────────────────────────┐
│  [Bloques de datos ofuscados]                                │
│  Cada bloque = fila CSV cifrada con XOR (clave secreta)      │
├──────────────────────────────────────────────────────────────┤
│  [Tabla de índices en memoria]                               │
│  Array de: { key[64 bytes], offset(uint64), length(uint32) } │
├──────────────────────────────────────────────────────────────┤
│  count   (uint32)  → Número total de registros               │
│  offset  (uint64)  → Posición del inicio de la tabla índices │
└──────────────────────────────────────────────────────────────┘
```

- **Persistencia:** Los índices se guardan automáticamente al cerrar la base de datos (`close()` o salir del `with`).
- **Recuperación:** Al abrir un archivo `.abl` existente, los índices se cargan desde el final del archivo.
- **Ofuscación:** Todos los datos se cifran/descifran con XOR usando la clave secreta. Sin la clave correcta, los datos son ilegibles.

---

### 🛠️ Troubleshooting

#### Error: `No se pudo cargar el motor C`

```
ABLSError: No se pudo cargar el motor C ('_abl_engine.so')
```

**Solución:** La extensión C no está compilada. Instala desde el código fuente:

```bash
git clone https://github.com/tu-usuario/abl-db.git
cd abl-db
python setup.py build_ext --inplace
pip install -e .
```

Asegúrate de tener **GCC** instalado:
- **Ubuntu/Debian:** `sudo apt-get install build-essential`
- **macOS:** `xcode-select --install`
- **Windows:** Instala [Build Tools para Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022)

---

#### Error: `Fila no encontrada` (KeyNotFoundError)

```python
KeyNotFoundError: Fila 'XYZ' no encontrada.
```

**Causas comunes:**
1. El `row_id` no existe en la tabla.
2. Intentas acceder a una fila de otra tabla (los IDs incluyen el prefijo `tabla:` internamente).
3. La base de datos se abrió con una clave secreta diferente a la usada para escribir los datos.

---

#### Los datos aparecen como basura / caracteres extraños

**Causa:** La base de datos se abrió con una clave secreta diferente a la que se usó para escribir.

**Solución:** Asegúrate de usar exactamente la misma clave:

```python
# Escritura
with ABLDatabase("datos.abl", secret="clave_correcta") as db:
    ...

# Lectura (debe ser la MISMA clave)
with ABLDatabase("datos.abl", secret="clave_correcta") as db:
    ...
```

---

### 📁 Estructura del paquete

```
abl/
├── __init__.py       # Exporta ABLDatabase, ABLError, KeyNotFoundError
├── core.py           # API Python + bindings ctypes al motor C
├── exceptions.py     # Jerarquía de excepciones
├── abl.c             # Motor principal en C (ofuscación, E/S, índices)
├── abl.h             # Headers y estructuras del motor
└── _abl_engine.so    # Extensión compilada (generada automáticamente)

setup.py              # Configuración de setuptools + Extension C
README.md             # Este archivo
LICENSE               # Licencia MIT
```

---

### 🤝 Contribuir

¡Las contribuciones son bienvenidas! Sigue estos pasos:

1. **Fork** el repositorio
2. Crea una rama: `git checkout -b feature/nueva-funcionalidad`
3. Commitea tus cambios: `git commit -am 'Añade nueva funcionalidad'`
4. Push: `git push origin feature/nueva-funcionalidad`
5. Abre un **Pull Request**

#### Reportar bugs

Abre un [issue en GitHub](https://github.com/tu-usuario/abl-db/issues) con:
- Versión de Python (`python --version`)
- Sistema operativo
- Comando o código que reproduce el error
- Traceback completo del error

---

<div align="center">

**ABL** — *Persistencia tabular sin fronteras.* 🚀

[⭐ Star en GitHub](https://github.com/aminbena010-ai/abl-db) · [📦 PyPI](https://pypi.org/project/abl-db/) · [🐛 Reportar bug](https://github.com/aminbena010-ai/abl-db/issues)

</div>
