🗃️ ABL — Array-Based Lightweight Database
Motor de base de datos tabular embebido en C con API Python ultrarrápida.
Persistencia estructurada sin servidores. Un solo archivo
.abl, una clave secreta, y listo.
🚀 Instalación en 10 segundos
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
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)
pip install abl-db
Desde el código fuente
git clone https://github.com/tu-usuario/abl-db.git
cd abl-db
pip install -e .
Verifica la instalación
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) |
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:
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.
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 |
# 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 |
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
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)
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
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
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 delwith). - Recuperación: Al abrir un archivo
.ablexistente, 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:
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
Error: Fila no encontrada (KeyNotFoundError)
KeyNotFoundError: Fila 'XYZ' no encontrada.
Causas comunes:
- El
row_idno existe en la tabla. - Intentas acceder a una fila de otra tabla (los IDs incluyen el prefijo
tabla:internamente). - 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:
# 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:
- Fork el repositorio
- Crea una rama:
git checkout -b feature/nueva-funcionalidad - Commitea tus cambios:
git commit -am 'Añade nueva funcionalidad' - Push:
git push origin feature/nueva-funcionalidad - Abre un Pull Request
Reportar bugs
Abre un issue en GitHub con:
- Versión de Python (
python --version) - Sistema operativo
- Comando o código que reproduce el error
- Traceback completo del error
ABL — Persistencia tabular sin fronteras. 🚀
Metadata
Release files for abl-db 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| abl_db-0.1.1.tar.gz | 14.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| abl_db-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.9 kB
Release files / abl_db-0.1.1.tar.gz
| Download URL | abl_db-0.1.1.tar.gz |
|---|---|
| Size | 14.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d6dd17250df16516981251d8731a291115df4b2a7ba79689d9caa8dc93f54d8
|
|
BLAKE2b-256 checksum How to use checksums |
0ccd83f5ee80026baf178c7ff934a6b54bbd5cb04ce9b001e70e67bc8c24c377
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / abl_db-0.1.1-py3-none-any.whl
| Download URL | abl_db-0.1.1-py3-none-any.whl |
|---|---|
| Size | 9.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
44296bd817635191635c73e0e4b59842ad5002f86ad7a8d3a187b689620a49ff
|
|
BLAKE2b-256 checksum How to use checksums |
6f7d6449b8bc3bb05ce77e09ca78433982e57ce34d2994d7eaf9153e0fb76d98
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|