tai-sql


Nametai-sql JSON
Version 0.3.10 PyPI version JSON
download
home_pageNone
SummaryNone
upload_time2025-07-12 17:57:25
maintainerNone
docs_urlNone
authorMateoSaezMata
requires_python<4.0,>=3.10
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 🚀 TAI-SQL Framework

**TAI-SQL** es un framework declarativo para Python que simplifica el trabajo con bases de datos relacionales usando SQLAlchemy. Permite definir esquemas de forma intuitiva y generar automáticamente modelos, CRUDs y diagramas ER.

## 📦 Instalación

### Usando Poetry (Recomendado)
```bash
poetry add tai-sql
```

### Usando pip
```bash
pip install tai-sql
```

### Dependencias del sistema
Para generar diagramas ER, necesitas instalar Graphviz:

```bash
# Ubuntu/Debian
sudo apt install graphviz

# macOS
brew install graphviz

# Windows
# Descargar desde: https://graphviz.org/download/
```

## 🗂️ Schema

Un **schema** es un archivo Python que define la estructura completa de tu base de datos. Es el punto central donde configuras la conexión, defines tus modelos y especificas qué recursos se generarán automáticamente.

### 📁 Estructura típica de un schema

```python
# schemas/mi_proyecto.py
from __future__ import annotations
from tai_sql import *
from tai_sql.generators import *

# 1️⃣ Configurar conexión a la base de datos
datasource(provider=env('DATABASE_URL'))

# 2️⃣ Configurar generadores
generate(
    ModelsGenerator(output_dir='mi_proyecto'),
    CRUDGenerator(output_dir='mi_proyecto'),
    ERDiagramGenerator(output_dir='mi_proyecto/diagrams')
)

# 3️⃣ Definir modelos (Tablas y Vistas)
class Usuario(Table):
    '''Tabla que almacena información de los usuarios del sistema'''
    __tablename__ = "usuario"
    
    id: int = column(primary_key=True, autoincrement=True)
    nombre: str
    pwd: str = column(encrypt=True)
    email: str = column(unique=True)
    
    posts: List[Post]  # Relación implícita

class Post(Table):
    '''Tabla que almacena los posts de los usuarios'''
    __tablename__ = "post"
    
    id: int = column(primary_key=True, autoincrement=True)
    titulo: str = 'Post title'
    contenido: str
    timestamp: datetime = column(default=datetime.now)
    usuario_id: int
    
    # Relación explícita
    usuario: Usuario = relation(
        fields=['usuario_id'],
        references=['id'], 
        backref='posts'
    )

class UserStats(View):
    '''Vista que muestra estadísticas de los usuarios'''
    __tablename__ = "user_stats"
    __query__ = query('user_stats.sql')

    usuario_id: int
    nombre_usuario: str
    post_count: int
```

### 🎯 Concepto clave

El schema actúa como el **"blueprint"** de tu aplicación:
- **Define** la estructura de base de datos (tablas, vistas, tipos, etc...)
- **Configura** la conexión y parámetros
- **Especifica** qué código se generará automáticamente
- **Centraliza** toda la configuración en un solo lugar

Una vez definido, el CLI de TAI-SQL usa este schema para:
- Sincronizar la base de datos (`tai-sql push`)
- Generar modelos SQLAlchemy, CRUDs y diagramas (`tai-sql generate`)

## 🏗️ Elementos del Schema

El esquema es el corazón de TAI-SQL. Define la estructura de tu base de datos y los recursos que se generarán automáticamente.

### 📊 `datasource()` - Configuración de la Base de Datos

La función `datasource()` configura la conexión a tu base de datos:

```python
from tai_sql import datasource, env, connection_string, params

# ✅ Opción 1: Variables de entorno (Recomendado para producción)
datasource(
    provider=env('DATABASE_URL')  # postgres://user:pass@host:port/dbname
)

# ✅ Opción 2: String de conexión directo (Para desarrollo/testing)
datasource(
    provider=connection_string('postgresql://user:password@localhost/mydb')
)

# ✅ Opción 3: Parámetros individuales (Para desarrollo/testing)
datasource(
    provider=params(
        drivername='postgresql',
        username='user',
        password='password',
        host='localhost',
        port=5432,
        database='mydb'
    )
)
```

**Opciones avanzadas:**
```python
datasource(
    provider=env('DATABASE_URL'),
    secret_key_name='SECRET_KEY',  # Variable de entorno para encriptación
    pool_size=20,           # Tamaño del pool de conexiones
    max_overflow=30,        # Conexiones adicionales permitidas
    pool_timeout=30,        # Timeout para obtener conexión
    pool_recycle=3600,      # Reciclar conexiones cada hora
    echo=True              # Mostrar consultas SQL en desarrollo
)
```

### 🔧 `generate()` - Configuración de Generadores

La función `generate()` define qué recursos se generarán automáticamente:

```python
from tai_sql import generate
from tai_sql.generators import ModelsGenerator, CRUDGenerator, ERDiagramGenerator

generate(
    # Generar modelos SQLAlchemy
    ModelsGenerator(
        output_dir='database/database'
    ),
    # Generar CRUDs sincronos
    CRUDGenerator(
        output_dir='database/database',
        mode='sync'  # 'sync', 'async', o 'both'
    ),
    # Generar diagramas ER
    ERDiagramGenerator(
        output_dir='database/diagrams'
    )
)
```

### 📋 `Table` - Definición de Tablas

Las tablas son la base de tu modelo de datos:

```python
from __future__ import annotations
from tai_sql import Table, column, relation
from typing import List, Optional
from datetime import date

class Usuario(Table):
    '''Tabla que almacena información de los usuarios'''
    __tablename__ = "usuario"
    
    # Columnas básicas
    id: int = column(primary_key=True, autoincrement=True)
    name: str
    email: str = column(unique=True)
    fecha_alta: date
    
    # Relaciones
    posts: List[Post] # Implícita

class Post(Table):
    '''Tabla que almacena la información de los posts de los usuarios'''
    __tablename__ = "post"
    
    id: int = column(primary_key=True, autoincrement=True)
    title: str = 'Post title'
    content: str
    author_id: int
    published: Optional[bool]
    
    # Relación explícita
    author: Usuario = relation(
        fields=['author_id'], 
        references=['id'], 
        backref='posts'
    )
```

#### 📝 Documentación de Tablas

TAI-SQL permite documentar las tablas de dos formas equivalentes para proporcionar contexto y descripción de cada modelo:

```python
# Opción 1: Usando docstring de la clase
class Usuario(Table):
    '''Tabla que almacena información de los usuarios del sistema'''
    __tablename__ = "usuario"
    
    id: int = column(primary_key=True, autoincrement=True)
    name: str
    email: str

# Opción 2: Usando el metaparámetro __description__
class Post(Table):
    __tablename__ = "post"
    __description__ = "Tabla que almacena los posts de los usuarios"
    
    id: int = column(primary_key=True, autoincrement=True)
    title: str
    content: str
```

**Prioridad**
- El uso del metaparámetro __description__ tiene preferencia sobre el docstring de la clase.
De esta forma si concurren ambos en una tabla, __description__ tendrá prioridad.

**Usos de la documentación:**
- 📊 **Diagramas ER**: Aparece en los diagramas generados por `ERDiagramGenerator`

Ambas formas son equivalentes y permiten que los generadores accedan a la descripción de la tabla para crear documentación automática, comentarios en los modelos generados y descripciones en los diagramas ER.

#### 🛠️ Función `column()` - Configuración de Columnas

La función `column()` permite configurar las propiedades específicas de las columnas:

```python
def column(
    primary_key=False,      # Si es clave primaria
    unique=False,           # Si debe ser único
    default=None,           # Valor por defecto
    server_now=False,       # Para usar NOW() del servidor
    index=False,            # Si debe tener índice
    autoincrement=False,    # Si es autoincremental
    encrypt=False           # Si queremos que se encripte
):
```

**Ejemplos de uso:**

```python
class Producto(Table):
    __tablename__ = "producto"
    
    # Clave primaria autoincremental
    id: int = column(primary_key=True, autoincrement=True)
    
    # Campo único
    sku: str = column(unique=True)
    
    # Campo con valor por defecto
    estado: str = "activo"
    
    # Equivalente a
    estado: str = column(default="activo")
    
    # Campo con índice para búsquedas rápidas
    categoria: str = column(index=True)
    
    # Campo opcional (nullable automático por tipo Optional)
    descripcion: Optional[str]
    
    # Campo obligatorio (nullable=False automático)
    nombre: str

    # Campo encriptado (necesita una SECRET_KEY)
    password: str = column(encrypt=True)
```

**Parámetros detallados:**

| Parámetro | Tipo | Descripción | Ejemplo |
|-----------|------|-------------|---------|
| `primary_key` | `bool` | Define si la columna es clave primaria | `column(primary_key=True)` |
| `unique` | `bool` | Garantiza valores únicos en la columna | `column(unique=True)` |
| `default` | `Any` | Valor por defecto para nuevos registros | `column(default="activo")` |
| `server_now` | `bool` | Usa la función NOW() del servidor de BD | `column(server_now=True)` |
| `index` | `bool` | Crea un índice en la columna para búsquedas rápidas | `column(index=True)` |
| `autoincrement` | `bool` | Incrementa automáticamente el valor (solo integers) | `column(autoincrement=True)` |
| `encrypt` | `bool` | Encripta automáticamente el contenido de la columna | `column(encrypt=True)` |

#### 🔗 Función `relation()` - Definición de Relaciones

La función `relation()` define relaciones explícitas entre tablas:

```python
def relation(
    fields: List[str],          # Campos en la tabla actual (foreign keys)
    references: List[str],      # Campos referenciados en la tabla destino
    backref: str,              # Nombre de la relación inversa
    onDelete='cascade',        # Comportamiento al eliminar
    onUpdate='cascade'         # Comportamiento al actualizar
):
```

**Conceptos importantes:**

1. **Relaciones Explícitas vs Implícitas:**
   - **Explícita:** Se define usando `relation()` en la tabla que CONTIENE la foreign key
   - **Implícita:** Se declara solo con el tipo en la tabla que NO contiene la foreign key

2. **Dónde usar `relation()`:**
   - SOLO en la tabla que tiene la columna foreign key
   - La tabla "origen" muestra la relación como `List[...]` (implícita)

**Ejemplo completo:**

```python
class Usuario(Table):
    __tablename__ = "usuario"
    
    id: int = column(primary_key=True, autoincrement=True)
    nombre: str
    email: str = column(unique=True)
    
    # Relación IMPLÍCITA - Usuario NO tiene foreign key hacia Post
    # Se muestra automáticamente como List por la relación inversa
    posts: List[Post]  # ← No necesita relation()

class Post(Table):
    __tablename__ = "post"
    
    id: int = column(primary_key=True, autoincrement=True)
    titulo: str
    contenido: str
    autor_id: int  # ← Esta ES la foreign key
    
    # Relación EXPLÍCITA - Post SÍ tiene foreign key hacia Usuario
    autor: Usuario = relation(
        fields=['autor_id'],     # Campo FK en esta tabla
        references=['id'],       # Campo PK en tabla destino
        backref='posts'         # Nombre de relación inversa en Usuario
    )
```

**Parámetros de `relation()`:**

| Parámetro | Descripción | Ejemplo |
|-----------|-------------|---------|
| `fields` | Lista de columnas FK en la tabla actual | `['autor_id']` |
| `references` | Lista de columnas PK en la tabla destino | `['id']` |
| `backref` | Nombre de la relación inversa | `'posts'` |
| `onDelete` | Acción al eliminar: `'cascade'`, `'restrict'`, `'set null'` | `'cascade'` |
| `onUpdate` | Acción al actualizar: `'cascade'`, `'restrict'`, `'set null'` | `'cascade'` |

**Regla fundamental:**
- ✅ Usa `relation()` SOLO en la tabla que tiene la foreign key
- ✅ La tabla "origen" automáticamente muestra `List[...]` por la relación inversa
- ❌ NO uses `relation()` en ambos lados de la relación

#### 🔐 Encriptación de Columnas

TAI-SQL soporta encriptación automática de columnas para proteger datos sensibles:

```python
from tai_sql import Table, column, datasource

# Configurar datasource con clave de encriptación
datasource(
    provider=env('DATABASE_URL'),
    secret_key_name='SECRET_KEY'  # Variable de entorno con la clave secreta
)

class Usuario(Table):
    __tablename__ = "usuarios"
    
    id: int = column(primary_key=True, autoincrement=True)
    email: str = column(unique=True)
    nombre: str
    
    # Columnas encriptadas - Los datos se encriptan automáticamente
    password: str = column(encrypt=True)
    telefono: Optional[str] = column(encrypt=True)
    datos_bancarios: Optional[str] = column(encrypt=True)

```

**Configuración requerida:**

1. **Variable de entorno**: Define una clave secreta segura
   ```bash
   export SECRET_KEY="tu_clave_secreta_de_al_menos_32_caracteres"
   ```

2. **Configuración en datasource**: Especifica el nombre de la variable
   ```python
   datasource(
       provider=env('DATABASE_URL'),
       secret_key_name='SECRET_KEY'  # Por defecto es 'SECRET_KEY'
   )
   ```

**Características de la encriptación:**

- ✅ **Automática**: Los datos se encriptan al escribir y desencriptan al leer
- ✅ **Transparente**: El código funciona igual que columnas normales
- ✅ **Segura**: Usa `cryptography.fernet` con claves de 256 bits
- ✅ **Validación**: Verifica la existencia de la clave secreta antes de generar

**Ejemplo de uso:**

```python
# El ModelGenerator crea propiedades híbridas automáticamente
user = Usuario(
    email="juan@example.com",
    nombre="Juan",
    password="mi_password_secreto",  # Se encripta automáticamente
    telefono="123-456-7890"          # Se encripta automáticamente
)

# Al leer, se desencripta automáticamente
print(user.password)  # "mi_password_secreto" (desencriptado)
print(user.telefono)  # "123-456-7890" (desencriptado)

# En la BD se almacena encriptado
print(user._password)  # "gAAAAABh..." (encriptado)
```

**Validaciones de seguridad:**

- ❗ **Clave requerida**: Si hay columnas con `encrypt=True`, la clave secreta debe existir
- ❗ **Longitud mínima**: La clave debe tener al menos 32 caracteres
- ❗ **Solo strings**: Solo columnas de tipo string pueden encriptarse


### 👁️ `View` - Definición de Vistas

Las vistas permiten crear consultas complejas reutilizables:

```python
from tai_sql import View, query

class UserStats(View):
    '''Estadísticas de usuarios y sus posts'''
    __tablename__ = "user_stats"
    __query__ = query('user_stats.sql')  # Archivo SQL en .../views/
    
    # Definir las columnas que retorna la vista
    user_id: int
    user_name: str
    post_count: int
    last_post_date: datetime
```

**Archivo SQL correspondiente** (`.../views/user_stats.sql`):
```sql
SELECT
    u.id AS user_id,
    u.name AS user_name,
    COUNT(p.id) AS post_count,
    MAX(p.created_at) AS last_post_date
FROM usuarios u
LEFT JOIN posts p ON u.id = p.author_id
WHERE u.active = true
GROUP BY u.id, u.name
```

## 🎯 Generadores Incluidos

### 📝 ModelsGenerator

Genera modelos SQLAlchemy estándar desde tus definiciones de `Table` y `View`.

```python
ModelsGenerator(
    output_dir='...'  # Directorio donde se generarán los modelos
)
```

### 🔄 CRUDGenerator

Genera clases CRUD completas con operaciones Create, Read, Update, Delete optimizadas.

```python
CRUDGenerator(
    output_dir='...',
    models_import_path='...',
    mode='sync'  # 'sync', 'async', o 'both'
)
```

**Estructura generada:**
```
.../<schema_name>/crud/
├── syn/                    # Si mode='sync' o 'both'
│   ├── __init__.py
│   ├── session_manager.py
│   └── endpoints.py
└── asyn/                   # Si mode='async' o 'both'
    ├── __init__.py
    ├── session_manager.py
    └── endpoints.py
```

**Ejemplo de uso del CRUD generado:**

El CRUD generado crea una API unificada que expone automáticamente métodos para cada tabla definida en tu schema:

```python
from database.main.crud.syn import db_api

# db_api contiene automáticamente un atributo por cada tabla:
# - db_api.usuario (para la tabla Usuario)
# - db_api.post (para la tabla Post)  
# Cada atributo implementa todos los métodos CRUD

# ✅ Operaciones básicas
# Crear usuario
user = db_api.usuario.create(name="Juan", email="juan@email.com", age=25)

# Buscar por ID (si la tabla tiene columna autoincrement)
user = db_api.usuario.find_by_id(1)

# Buscar los 10 primeros con filtros
users = db_api.usuario.find_many(limit=10, age=25)

# Buscar un registro específico
user = db_api.usuario.find(email="juan@email.com")

# Actualizar por ID
db_api.usuario.update_by_id(1, name="Juan Carlos", age=26)

# Eliminar por ID
db_api.usuario.delete_by_id(1)


# ✅ Operaciones avanzadas
# Crear múltiples usuarios
users_data = [
    {"name": "Ana", "email": "ana@email.com", "age": 28},
    {"name": "Pedro", "email": "pedro@email.com", "age": 32}
]
users = db_api.usuario.create_many(users_data)

# Upsert (crear o actualizar)
user = db_api.usuario.upsert(email="maria@email.com", name="María", age=30)

# Operaciones masivas
db_api.usuario.update_many(
    filters={"age": 26}, 
    **{"last_seen": datetime.now()}
)

# Contar registros
total_users = db_api.usuario.count()

# Verificar existencia
exists = db_api.usuario.exists(email="juan@email.com")

# ✅ Integración con Pandas

# Obtener como DataFrame
users_df = db_api.usuario.as_dataframe()

# Insertar desde DataFrame
import pandas as pd

new_users_df = pd.DataFrame({
    'name': ['Luis', 'Carmen'],
    'email': ['luis@email.com', 'carmen@email.com'],
    'age': [25, 30]
})
users = db_api.usuario.from_df(new_users_df, mode='create')
```

**Ventajas del patrón `db_api`:**
- ✅ **Una sola importación**: Todo el CRUD en un objeto
- ✅ **Autocompletado**: Tu IDE sugiere automáticamente todas las tablas disponibles
- ✅ **Consistencia**: Todos los métodos funcionan igual en todas las tablas
- ✅ **Simplicidad**: No necesitas gestionar instancias ni session managers manualmente

**Métodos disponibles en cada CRUD:**

| Método | Descripción | Ejemplo |
|--------|-------------|---------|
| `find(**filters)` | Busca un registro | `find(email="test@example.com")` |
| `find_many(limit, offset, **filters)` | Busca múltiples registros | `find_many(10, 0, active=True)` |
| `find_by_id(id)` | Busca por ID | `find_by_id(1)` |
| `create(**data)` | Crea un registro | `create(name="Juan", email="juan@example.com")` |
| `create_many(records)` | Crea múltiples registros | `create_many([{...}, {...}])` |
| `update(filters, **data)` | Actualiza un registro | `update({'name': 'Pedro'}, age=25)` |
| `update_by_id(id, **data)` | Actualiza por ID | `update_by_id(1, name="Nuevo nombre")` |
| `update_many(filters, **data)` | Actualización masiva | `update_many({"active": False}, last_seen=datetime.now())` |
| `upsert(**data)` | Crear o actualizar | `upsert(email="test@example.com", name="Juan")` |
| `upsert_many(records)` | Upsert múltiple | `upsert_many([{...}, {...}])` |
| `delete_by_id(id)` | Elimina por ID | `delete_by_id(1)` |
| `delete(**filters)` | Elimina con filtros | `delete(active=False)` |
| `count(**filters)` | Cuenta registros | `count(age__gte=18)` |
| `exists(**filters)` | Verifica existencia | `exists(email="test@example.com")` |
| `as_dataframe(**filters)` | Exporta a DataFrame | `as_dataframe(limit=1000)` |
| `from_dataframe(df, mode)` | Importa desde DataFrame | `from_df(df, mode='upsert')` |

### 📊 ERDiagramGenerator

Genera diagramas Entity-Relationship profesionales usando Graphviz.

```python
ERDiagramGenerator(
    output_dir='docs/diagrams',
    format='png',           # 'png', 'svg', 'pdf', 'dot'
    include_views=True,     # Incluir vistas en el diagrama
    include_columns=True,   # Mostrar detalles de columnas
    include_relationships=True,  # Mostrar relaciones
    dpi=300                # Resolución para formatos bitmap
)
```

**Características del diagrama:**
- 🔑 **Primary Keys**: Marcadas con icono de llave
- 🔗 **Foreign Keys**: Marcadas con icono de enlace
- ⭐ **Unique**: Columnas únicas marcadas
- ❗ **Not Null**: Columnas obligatorias marcadas
- ⬆️ **Auto Increment**: Columnas auto-incrementales marcadas
- 👁️ **Views**: Diferenciadas visualmente de las tablas

## 🖥️ Comandos CLI

### `tai-sql init` - Inicializar Proyecto

Crea un nuevo proyecto TAI-SQL con la estructura completa:

```bash
# Crear proyecto básico
tai-sql init

# Crear proyecto con nombre personalizado
tai-sql init --name mi-proyecto --schema-name mi-esquema

# Estructura generada:
mi-proyecto/
├── pyproject.toml
├── README.md
├── mi_proyecto/             # CRUD/Models Folder
├── schemas/
│   └── mi_esquema.py        # Schema principal
├── views/
│   └── mi_esquema/
│       └── user_stats.sql   # Vista de ejemplo
└── diagrams/                
    └── mi_esquema.png       # ERD Diagram
```
**Opciones:**
- `--name, -n`: Nombre del proyecto (default: `database`)
- `--schema, -s`: Nombre del primer schema (default: `public`)

### `tai-sql new-schema` - Crear Nuevo Schema

Agrega un nuevo schema a un proyecto existente:

```bash
# Crear nuevo schema en proyecto existente
tai-sql new-schema productos

# Con proyecto personalizado
tai-sql new-schema --project mi-empresa productos
```

**Características:**
- ✅ Detecta automáticamente el proyecto TAI-SQL actual
- ✅ Crea archivo de schema con plantilla completa
- ✅ Crea directorio de vistas correspondiente
- ✅ Actualiza configuración del proyecto si es necesario

### `tai-sql set-default-schema` - Establecer Schema por Defecto

Configura qué schema se usará por defecto en los comandos:

```bash
# Establecer schema por defecto
tai-sql set-default-schema productos

# Si el schema no existe, muestra opciones disponibles:
# ❌ El schema 'nonexistent' no existe en el proyecto
# 
# 📄 Schemas disponibles:
#    ✅ public (actual por defecto)
#       productos  
#       ventas
```

### `tai-sql info` - Información del Proyecto

Muestra información completa del proyecto actual:

```bash
tai-sql info
```

**Información mostrada:**
```bash
📁 Información del proyecto:
   Nombre: mi-proyecto
   Directorio: /path/to/mi-proyecto
   Schema por defecto: productos

📄 Schemas disponibles:
   • public
   • productos (✅ default, 📌 current)
   • ventas
     └─ Estado: Cargado

🔧 Comandos disponibles:
   tai-sql generate              # Usa schema por defecto
   tai-sql push                  # Usa schema por defecto
   tai-sql set-default-schema <nombre>  # Cambiar default

### `tai-sql generate` - Generar Recursos

Ejecuta todos los generadores configurados en el schema:

```bash
# Generar usando schema por defecto
tai-sql generate

# Generar usando schema específico
tai-sql generate --schema database/schemas/productos.py
```

**Proceso de generación:**
1. ✅ Carga y valida el schema
2. 🔍 Descubre modelos (tablas y vistas)
3. 🏗️ Ejecuta generadores configurados
4. 📊 Muestra resumen de archivos generados


### `tai-sql generate` - Generar Recursos

Ejecuta todos los generadores configurados en el schema:

```bash
# Generar usando schema por defecto
tai-sql generate

# Generar usando schema específico
tai-sql generate --schema productos

# Generar para todos los schemas del proyecto
tai-sql generate --all
```

**Opciones:**
- `--schema, -s`: Schema específico a procesar
- `--all`: Procesar todos los schemas del proyecto

**Proceso de generación:**
1. ✅ Carga y valida el schema
2. 🔍 Descubre modelos (tablas y vistas)
3. 🏗️ Ejecuta generadores configurados
4. 📊 Muestra resumen de archivos generados


### `tai-sql push` - Sincronizar con Base de Datos

Aplica los cambios del schema a la base de datos:

```bash
# Push básico
tai-sql push

# Con opciones avanzadas
tai-sql push --schema public --createdb --force --verbose

# Dry run (mostrar cambios sin aplicar)
tai-sql push --dry-run
```

**Opciones disponibles:**
- `--createdb, -c`: Crear base de datos si no existe
- `--force, -f`: Aplicar cambios sin confirmación
- `--dry-run, -d`: Mostrar DDL sin ejecutar
- `--verbose, -v`: Mostrar información detallada

**Proceso de push:**
1. 🔍 Analiza diferencias entre schema y BD
2. 📋 Genera sentencias DDL necesarias
3. ⚠️ Muestra advertencias de operaciones peligrosas
4. ✅ Aplica cambios tras confirmación
5. 🚀 Ejecuta generadores automáticamente

**Ejemplo de salida:**
```bash
🚀 Push schema: database/schemas/main.py

📋 Resumen de cambios:
   🆕 2 tabla(s) nueva(s): usuarios, posts
   ➕ 3 columna(s) a añadir en 1 tabla(s)
   🆕 1 vista(s) nueva(s): user_stats

¿Deseas ejecutar estas sentencias en la base de datos? [y/N]: y

✅ Esquema sincronizado exitosamente
🚀 Ejecutando generadores...
   ✅ ModelsGenerator completado
   ✅ CRUDGenerator completado  
   ✅ ERDiagramGenerator completado
```


### `tai-sql ping` - Verificar Conectividad

Verifica la conectividad con el servidor de base de datos:

```bash
# Verificación básica (ping al host)
tai-sql ping

# Verificación con schema específico
tai-sql ping --schema productos

# Verificación completa (incluye ping ICMP, TCP y BD)
tai-sql ping --full

# Verificar también existencia de la base de datos
tai-sql ping --check-db

# Modo silencioso (solo resultado final)
tai-sql ping --quiet
```

**Opciones:**
- `--schema, -s`: Schema específico para conectividad
- `--timeout, -t`: Timeout en segundos (default: 5)
- `--check-db, -d`: Verificar si la base de datos específica existe
- `--full, -f`: Verificación completa (ICMP + TCP + BD)
- `--quiet, -q`: Modo silencioso, solo resultado final

**Tipos de verificación:**

1. **Básica** (default): Solo ping al host
2. **Full** (`--full`): Ping ICMP + conectividad TCP + conexión BD
3. **Con BD** (`--check-db`): Incluye verificación de existencia de BD

**Ejemplo de salida:**
```bash
🔧 Información de conexión:
   Motor: postgresql
   Host: localhost
   Puerto: 5432
   Base de datos: mi_proyecto
   Usuario: postgres

🏓 Verificación BASIC

✅ Host accesible

🗄️  Verificando existencia de la base de datos...

✅ La base de datos existe

🎉 Verificación de conectividad completada exitosamente
```

### Gestión Automática de Schemas

**Resolución automática del schema:**
- Si no especificas `--schema`, los comandos usan automáticamente el schema por defecto
- Si no hay schema por defecto configurado, el comando te guía para establecer uno
- Todos los comandos muestran qué schema están usando

**Mensajes de ayuda inteligentes:**
```bash
# Si no hay schema por defecto:
❌ No existe ningún esquema por defecto
   Puedes definir uno con: tai-sql set-default-schema <nombre>
   O usar la opción: --schema <nombre_esquema>

# Si especificas un schema que no existe:
❌ El schema 'inexistente' no existe en el proyecto

📄 Schemas disponibles:
   ✅ public
      productos
      ventas
```

### Workflow Típico

```bash
# 1. Crear nuevo proyecto
tai-sql init --name mi-empresa --schema productos

# 2. Entrar al proyecto
cd mi-empresa

# 3. Configurar base de datos
export DATABASE_URL="postgresql://user:pass@localhost/mi_empresa"

# 4. Editar el schema
# Editar schemas/productos.py

# 5. Sincronizar con BD (crear BD si no existe)
tai-sql push --createdb

# 6. Verificar conectividad
tai-sql ping --full

# 7. Crear schema adicional
tai-sql new-schema ventas

# 8. Cambiar schema por defecto
tai-sql set-default-schema ventas

# 9. Ver información del proyecto
tai-sql info

# 10. Generar recursos para todos los schemas
tai-sql generate --all
```

### Gestión de Proyectos Multi-Schema

TAI-SQL soporta múltiples schemas en un mismo proyecto:

```bash
# Crear schemas adicionales
tai-sql new-schema productos
tai-sql new-schema ventas  
tai-sql new-schema usuarios

# Trabajar con schemas específicos
tai-sql push --schema productos
tai-sql generate --schema ventas

# O procesar todos a la vez
tai-sql generate --all

# Cambiar entre schemas por defecto
tai-sql set-default-schema productos
tai-sql push  # Usa 'productos' automáticamente

tai-sql set-default-schema ventas  
tai-sql generate  # Usa 'ventas' automáticamente
```

**Ventajas del multi-schema:**
- ✅ **Modularidad**: Separar lógicamente diferentes dominios
- ✅ **Escalabilidad**: Cada schema puede tener su propia configuración
- ✅ **Flexibilidad**: Procesar schemas individualmente o en conjunto
- ✅ **Organización**: Mejor estructura para proyectos complejos


## 🛠️ Crear tu Propio Generador

Puedes crear generadores personalizados heredando de `BaseGenerator`:

```python
from tai_sql.generators.base import BaseGenerator
from tai_sql import db
import os

class APIDocsGenerator(BaseGenerator):
    """Generador de documentación API desde los modelos"""
    
    def __init__(self, output_dir=None, format='markdown'):
        super().__init__(output_dir or 'docs/api')
        self.format = format
    
    def generate(self) -> str:
        """Genera la documentación API"""
        
        docs_content = self._create_header()
        
        # Procesar cada modelo
        for model in self.models:
            if hasattr(model, '__tablename__'):  # Es una tabla
                docs_content += self._generate_table_docs(model)
            else:  # Es una vista
                docs_content += self._generate_view_docs(model)
        
        # Guardar archivo
        output_path = os.path.join(self.config.output_dir, f'api.{self.format}')
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(docs_content)
        
        return output_path
    
    def _create_header(self) -> str:
        """Crea el header de la documentación"""
        return f"""# API Documentation
                    
            Database: {db.provider.database}
            Schema: {db.schema_name}
            Generated: {datetime.now().isoformat()}

            ## Models

        """
    
    def _generate_table_docs(self, model) -> str:
        """Genera documentación para una tabla"""
        docs = f"### {model.__name__} (Table)\n\n"
        docs += f"**Table name:** `{model.__tablename__}`\n\n"
        
        if hasattr(model, '__description__'):
            docs += f"**Description:** {model.__description__}\n\n"
        
        docs += "**Columns:**\n\n"
        docs += "| Column | Type | Constraints |\n"
        docs += "|--------|------|-------------|\n"
        
        for name, column in model.columns.items():
            constraints = []
            if column.primary_key:
                constraints.append("PRIMARY KEY")
            if not column.nullable:
                constraints.append("NOT NULL")
            if column.unique:
                constraints.append("UNIQUE")
            if column.autoincrement:
                constraints.append("AUTO INCREMENT")
                
            docs += f"| {name} | {column.type} | {', '.join(constraints)} |\n"
        
        docs += "\n"
        return docs
    
    def _generate_view_docs(self, model) -> str:
        """Genera documentación para una vista"""
        docs = f"### {model.__name__} (View)\n\n"
        docs += f"**View name:** `{model.__tablename__}`\n\n"
        
        if hasattr(model, '__description__'):
            docs += f"**Description:** {model.__description__}\n\n"
        
        # Agregar información de la vista...
        return docs

# Uso del generador personalizado

generate(
    ...,
    APIDocsGenerator(output_dir='docs/api', format='markdown')
)
```

**Métodos requeridos:**
- `generate()`: Método principal que debe retornar la ruta del archivo generado

**Métodos/propiedades útiles heredados:**
- `self.models`: Propiedad que contiene todos los modelos (tablas y vistas)
- `self.config.output_dir`: Directorio de salida configurado
- `self.register_model(model)`: Registra un modelo manualmente
- `self.clear_models()`: Limpia la lista de modelos


Este framework te permite construir aplicaciones robustas con una definición declarativa simple, generación automática de código y herramientas CLI potentes para el desarrollo ágil.
            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "tai-sql",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.10",
    "maintainer_email": null,
    "keywords": null,
    "author": "MateoSaezMata",
    "author_email": "msaez@triplealpha.in",
    "download_url": "https://files.pythonhosted.org/packages/40/4d/e3f21da7a3dd096ee915aacbb04227515dcfaa40113356eb707031f78c1e/tai_sql-0.3.10.tar.gz",
    "platform": null,
    "description": "# \ud83d\ude80 TAI-SQL Framework\n\n**TAI-SQL** es un framework declarativo para Python que simplifica el trabajo con bases de datos relacionales usando SQLAlchemy. Permite definir esquemas de forma intuitiva y generar autom\u00e1ticamente modelos, CRUDs y diagramas ER.\n\n## \ud83d\udce6 Instalaci\u00f3n\n\n### Usando Poetry (Recomendado)\n```bash\npoetry add tai-sql\n```\n\n### Usando pip\n```bash\npip install tai-sql\n```\n\n### Dependencias del sistema\nPara generar diagramas ER, necesitas instalar Graphviz:\n\n```bash\n# Ubuntu/Debian\nsudo apt install graphviz\n\n# macOS\nbrew install graphviz\n\n# Windows\n# Descargar desde: https://graphviz.org/download/\n```\n\n## \ud83d\uddc2\ufe0f Schema\n\nUn **schema** es un archivo Python que define la estructura completa de tu base de datos. Es el punto central donde configuras la conexi\u00f3n, defines tus modelos y especificas qu\u00e9 recursos se generar\u00e1n autom\u00e1ticamente.\n\n### \ud83d\udcc1 Estructura t\u00edpica de un schema\n\n```python\n# schemas/mi_proyecto.py\nfrom __future__ import annotations\nfrom tai_sql import *\nfrom tai_sql.generators import *\n\n# 1\ufe0f\u20e3 Configurar conexi\u00f3n a la base de datos\ndatasource(provider=env('DATABASE_URL'))\n\n# 2\ufe0f\u20e3 Configurar generadores\ngenerate(\n    ModelsGenerator(output_dir='mi_proyecto'),\n    CRUDGenerator(output_dir='mi_proyecto'),\n    ERDiagramGenerator(output_dir='mi_proyecto/diagrams')\n)\n\n# 3\ufe0f\u20e3 Definir modelos (Tablas y Vistas)\nclass Usuario(Table):\n    '''Tabla que almacena informaci\u00f3n de los usuarios del sistema'''\n    __tablename__ = \"usuario\"\n    \n    id: int = column(primary_key=True, autoincrement=True)\n    nombre: str\n    pwd: str = column(encrypt=True)\n    email: str = column(unique=True)\n    \n    posts: List[Post]  # Relaci\u00f3n impl\u00edcita\n\nclass Post(Table):\n    '''Tabla que almacena los posts de los usuarios'''\n    __tablename__ = \"post\"\n    \n    id: int = column(primary_key=True, autoincrement=True)\n    titulo: str = 'Post title'\n    contenido: str\n    timestamp: datetime = column(default=datetime.now)\n    usuario_id: int\n    \n    # Relaci\u00f3n expl\u00edcita\n    usuario: Usuario = relation(\n        fields=['usuario_id'],\n        references=['id'], \n        backref='posts'\n    )\n\nclass UserStats(View):\n    '''Vista que muestra estad\u00edsticas de los usuarios'''\n    __tablename__ = \"user_stats\"\n    __query__ = query('user_stats.sql')\n\n    usuario_id: int\n    nombre_usuario: str\n    post_count: int\n```\n\n### \ud83c\udfaf Concepto clave\n\nEl schema act\u00faa como el **\"blueprint\"** de tu aplicaci\u00f3n:\n- **Define** la estructura de base de datos (tablas, vistas, tipos, etc...)\n- **Configura** la conexi\u00f3n y par\u00e1metros\n- **Especifica** qu\u00e9 c\u00f3digo se generar\u00e1 autom\u00e1ticamente\n- **Centraliza** toda la configuraci\u00f3n en un solo lugar\n\nUna vez definido, el CLI de TAI-SQL usa este schema para:\n- Sincronizar la base de datos (`tai-sql push`)\n- Generar modelos SQLAlchemy, CRUDs y diagramas (`tai-sql generate`)\n\n## \ud83c\udfd7\ufe0f Elementos del Schema\n\nEl esquema es el coraz\u00f3n de TAI-SQL. Define la estructura de tu base de datos y los recursos que se generar\u00e1n autom\u00e1ticamente.\n\n### \ud83d\udcca `datasource()` - Configuraci\u00f3n de la Base de Datos\n\nLa funci\u00f3n `datasource()` configura la conexi\u00f3n a tu base de datos:\n\n```python\nfrom tai_sql import datasource, env, connection_string, params\n\n# \u2705 Opci\u00f3n 1: Variables de entorno (Recomendado para producci\u00f3n)\ndatasource(\n    provider=env('DATABASE_URL')  # postgres://user:pass@host:port/dbname\n)\n\n# \u2705 Opci\u00f3n 2: String de conexi\u00f3n directo (Para desarrollo/testing)\ndatasource(\n    provider=connection_string('postgresql://user:password@localhost/mydb')\n)\n\n# \u2705 Opci\u00f3n 3: Par\u00e1metros individuales (Para desarrollo/testing)\ndatasource(\n    provider=params(\n        drivername='postgresql',\n        username='user',\n        password='password',\n        host='localhost',\n        port=5432,\n        database='mydb'\n    )\n)\n```\n\n**Opciones avanzadas:**\n```python\ndatasource(\n    provider=env('DATABASE_URL'),\n    secret_key_name='SECRET_KEY',  # Variable de entorno para encriptaci\u00f3n\n    pool_size=20,           # Tama\u00f1o del pool de conexiones\n    max_overflow=30,        # Conexiones adicionales permitidas\n    pool_timeout=30,        # Timeout para obtener conexi\u00f3n\n    pool_recycle=3600,      # Reciclar conexiones cada hora\n    echo=True              # Mostrar consultas SQL en desarrollo\n)\n```\n\n### \ud83d\udd27 `generate()` - Configuraci\u00f3n de Generadores\n\nLa funci\u00f3n `generate()` define qu\u00e9 recursos se generar\u00e1n autom\u00e1ticamente:\n\n```python\nfrom tai_sql import generate\nfrom tai_sql.generators import ModelsGenerator, CRUDGenerator, ERDiagramGenerator\n\ngenerate(\n    # Generar modelos SQLAlchemy\n    ModelsGenerator(\n        output_dir='database/database'\n    ),\n    # Generar CRUDs sincronos\n    CRUDGenerator(\n        output_dir='database/database',\n        mode='sync'  # 'sync', 'async', o 'both'\n    ),\n    # Generar diagramas ER\n    ERDiagramGenerator(\n        output_dir='database/diagrams'\n    )\n)\n```\n\n### \ud83d\udccb `Table` - Definici\u00f3n de Tablas\n\nLas tablas son la base de tu modelo de datos:\n\n```python\nfrom __future__ import annotations\nfrom tai_sql import Table, column, relation\nfrom typing import List, Optional\nfrom datetime import date\n\nclass Usuario(Table):\n    '''Tabla que almacena informaci\u00f3n de los usuarios'''\n    __tablename__ = \"usuario\"\n    \n    # Columnas b\u00e1sicas\n    id: int = column(primary_key=True, autoincrement=True)\n    name: str\n    email: str = column(unique=True)\n    fecha_alta: date\n    \n    # Relaciones\n    posts: List[Post] # Impl\u00edcita\n\nclass Post(Table):\n    '''Tabla que almacena la informaci\u00f3n de los posts de los usuarios'''\n    __tablename__ = \"post\"\n    \n    id: int = column(primary_key=True, autoincrement=True)\n    title: str = 'Post title'\n    content: str\n    author_id: int\n    published: Optional[bool]\n    \n    # Relaci\u00f3n expl\u00edcita\n    author: Usuario = relation(\n        fields=['author_id'], \n        references=['id'], \n        backref='posts'\n    )\n```\n\n#### \ud83d\udcdd Documentaci\u00f3n de Tablas\n\nTAI-SQL permite documentar las tablas de dos formas equivalentes para proporcionar contexto y descripci\u00f3n de cada modelo:\n\n```python\n# Opci\u00f3n 1: Usando docstring de la clase\nclass Usuario(Table):\n    '''Tabla que almacena informaci\u00f3n de los usuarios del sistema'''\n    __tablename__ = \"usuario\"\n    \n    id: int = column(primary_key=True, autoincrement=True)\n    name: str\n    email: str\n\n# Opci\u00f3n 2: Usando el metapar\u00e1metro __description__\nclass Post(Table):\n    __tablename__ = \"post\"\n    __description__ = \"Tabla que almacena los posts de los usuarios\"\n    \n    id: int = column(primary_key=True, autoincrement=True)\n    title: str\n    content: str\n```\n\n**Prioridad**\n- El uso del metapar\u00e1metro __description__ tiene preferencia sobre el docstring de la clase.\nDe esta forma si concurren ambos en una tabla, __description__ tendr\u00e1 prioridad.\n\n**Usos de la documentaci\u00f3n:**\n- \ud83d\udcca **Diagramas ER**: Aparece en los diagramas generados por `ERDiagramGenerator`\n\nAmbas formas son equivalentes y permiten que los generadores accedan a la descripci\u00f3n de la tabla para crear documentaci\u00f3n autom\u00e1tica, comentarios en los modelos generados y descripciones en los diagramas ER.\n\n#### \ud83d\udee0\ufe0f Funci\u00f3n `column()` - Configuraci\u00f3n de Columnas\n\nLa funci\u00f3n `column()` permite configurar las propiedades espec\u00edficas de las columnas:\n\n```python\ndef column(\n    primary_key=False,      # Si es clave primaria\n    unique=False,           # Si debe ser \u00fanico\n    default=None,           # Valor por defecto\n    server_now=False,       # Para usar NOW() del servidor\n    index=False,            # Si debe tener \u00edndice\n    autoincrement=False,    # Si es autoincremental\n    encrypt=False           # Si queremos que se encripte\n):\n```\n\n**Ejemplos de uso:**\n\n```python\nclass Producto(Table):\n    __tablename__ = \"producto\"\n    \n    # Clave primaria autoincremental\n    id: int = column(primary_key=True, autoincrement=True)\n    \n    # Campo \u00fanico\n    sku: str = column(unique=True)\n    \n    # Campo con valor por defecto\n    estado: str = \"activo\"\n    \n    # Equivalente a\n    estado: str = column(default=\"activo\")\n    \n    # Campo con \u00edndice para b\u00fasquedas r\u00e1pidas\n    categoria: str = column(index=True)\n    \n    # Campo opcional (nullable autom\u00e1tico por tipo Optional)\n    descripcion: Optional[str]\n    \n    # Campo obligatorio (nullable=False autom\u00e1tico)\n    nombre: str\n\n    # Campo encriptado (necesita una SECRET_KEY)\n    password: str = column(encrypt=True)\n```\n\n**Par\u00e1metros detallados:**\n\n| Par\u00e1metro | Tipo | Descripci\u00f3n | Ejemplo |\n|-----------|------|-------------|---------|\n| `primary_key` | `bool` | Define si la columna es clave primaria | `column(primary_key=True)` |\n| `unique` | `bool` | Garantiza valores \u00fanicos en la columna | `column(unique=True)` |\n| `default` | `Any` | Valor por defecto para nuevos registros | `column(default=\"activo\")` |\n| `server_now` | `bool` | Usa la funci\u00f3n NOW() del servidor de BD | `column(server_now=True)` |\n| `index` | `bool` | Crea un \u00edndice en la columna para b\u00fasquedas r\u00e1pidas | `column(index=True)` |\n| `autoincrement` | `bool` | Incrementa autom\u00e1ticamente el valor (solo integers) | `column(autoincrement=True)` |\n| `encrypt` | `bool` | Encripta autom\u00e1ticamente el contenido de la columna | `column(encrypt=True)` |\n\n#### \ud83d\udd17 Funci\u00f3n `relation()` - Definici\u00f3n de Relaciones\n\nLa funci\u00f3n `relation()` define relaciones expl\u00edcitas entre tablas:\n\n```python\ndef relation(\n    fields: List[str],          # Campos en la tabla actual (foreign keys)\n    references: List[str],      # Campos referenciados en la tabla destino\n    backref: str,              # Nombre de la relaci\u00f3n inversa\n    onDelete='cascade',        # Comportamiento al eliminar\n    onUpdate='cascade'         # Comportamiento al actualizar\n):\n```\n\n**Conceptos importantes:**\n\n1. **Relaciones Expl\u00edcitas vs Impl\u00edcitas:**\n   - **Expl\u00edcita:** Se define usando `relation()` en la tabla que CONTIENE la foreign key\n   - **Impl\u00edcita:** Se declara solo con el tipo en la tabla que NO contiene la foreign key\n\n2. **D\u00f3nde usar `relation()`:**\n   - SOLO en la tabla que tiene la columna foreign key\n   - La tabla \"origen\" muestra la relaci\u00f3n como `List[...]` (impl\u00edcita)\n\n**Ejemplo completo:**\n\n```python\nclass Usuario(Table):\n    __tablename__ = \"usuario\"\n    \n    id: int = column(primary_key=True, autoincrement=True)\n    nombre: str\n    email: str = column(unique=True)\n    \n    # Relaci\u00f3n IMPL\u00cdCITA - Usuario NO tiene foreign key hacia Post\n    # Se muestra autom\u00e1ticamente como List por la relaci\u00f3n inversa\n    posts: List[Post]  # \u2190 No necesita relation()\n\nclass Post(Table):\n    __tablename__ = \"post\"\n    \n    id: int = column(primary_key=True, autoincrement=True)\n    titulo: str\n    contenido: str\n    autor_id: int  # \u2190 Esta ES la foreign key\n    \n    # Relaci\u00f3n EXPL\u00cdCITA - Post S\u00cd tiene foreign key hacia Usuario\n    autor: Usuario = relation(\n        fields=['autor_id'],     # Campo FK en esta tabla\n        references=['id'],       # Campo PK en tabla destino\n        backref='posts'         # Nombre de relaci\u00f3n inversa en Usuario\n    )\n```\n\n**Par\u00e1metros de `relation()`:**\n\n| Par\u00e1metro | Descripci\u00f3n | Ejemplo |\n|-----------|-------------|---------|\n| `fields` | Lista de columnas FK en la tabla actual | `['autor_id']` |\n| `references` | Lista de columnas PK en la tabla destino | `['id']` |\n| `backref` | Nombre de la relaci\u00f3n inversa | `'posts'` |\n| `onDelete` | Acci\u00f3n al eliminar: `'cascade'`, `'restrict'`, `'set null'` | `'cascade'` |\n| `onUpdate` | Acci\u00f3n al actualizar: `'cascade'`, `'restrict'`, `'set null'` | `'cascade'` |\n\n**Regla fundamental:**\n- \u2705 Usa `relation()` SOLO en la tabla que tiene la foreign key\n- \u2705 La tabla \"origen\" autom\u00e1ticamente muestra `List[...]` por la relaci\u00f3n inversa\n- \u274c NO uses `relation()` en ambos lados de la relaci\u00f3n\n\n#### \ud83d\udd10 Encriptaci\u00f3n de Columnas\n\nTAI-SQL soporta encriptaci\u00f3n autom\u00e1tica de columnas para proteger datos sensibles:\n\n```python\nfrom tai_sql import Table, column, datasource\n\n# Configurar datasource con clave de encriptaci\u00f3n\ndatasource(\n    provider=env('DATABASE_URL'),\n    secret_key_name='SECRET_KEY'  # Variable de entorno con la clave secreta\n)\n\nclass Usuario(Table):\n    __tablename__ = \"usuarios\"\n    \n    id: int = column(primary_key=True, autoincrement=True)\n    email: str = column(unique=True)\n    nombre: str\n    \n    # Columnas encriptadas - Los datos se encriptan autom\u00e1ticamente\n    password: str = column(encrypt=True)\n    telefono: Optional[str] = column(encrypt=True)\n    datos_bancarios: Optional[str] = column(encrypt=True)\n\n```\n\n**Configuraci\u00f3n requerida:**\n\n1. **Variable de entorno**: Define una clave secreta segura\n   ```bash\n   export SECRET_KEY=\"tu_clave_secreta_de_al_menos_32_caracteres\"\n   ```\n\n2. **Configuraci\u00f3n en datasource**: Especifica el nombre de la variable\n   ```python\n   datasource(\n       provider=env('DATABASE_URL'),\n       secret_key_name='SECRET_KEY'  # Por defecto es 'SECRET_KEY'\n   )\n   ```\n\n**Caracter\u00edsticas de la encriptaci\u00f3n:**\n\n- \u2705 **Autom\u00e1tica**: Los datos se encriptan al escribir y desencriptan al leer\n- \u2705 **Transparente**: El c\u00f3digo funciona igual que columnas normales\n- \u2705 **Segura**: Usa `cryptography.fernet` con claves de 256 bits\n- \u2705 **Validaci\u00f3n**: Verifica la existencia de la clave secreta antes de generar\n\n**Ejemplo de uso:**\n\n```python\n# El ModelGenerator crea propiedades h\u00edbridas autom\u00e1ticamente\nuser = Usuario(\n    email=\"juan@example.com\",\n    nombre=\"Juan\",\n    password=\"mi_password_secreto\",  # Se encripta autom\u00e1ticamente\n    telefono=\"123-456-7890\"          # Se encripta autom\u00e1ticamente\n)\n\n# Al leer, se desencripta autom\u00e1ticamente\nprint(user.password)  # \"mi_password_secreto\" (desencriptado)\nprint(user.telefono)  # \"123-456-7890\" (desencriptado)\n\n# En la BD se almacena encriptado\nprint(user._password)  # \"gAAAAABh...\" (encriptado)\n```\n\n**Validaciones de seguridad:**\n\n- \u2757 **Clave requerida**: Si hay columnas con `encrypt=True`, la clave secreta debe existir\n- \u2757 **Longitud m\u00ednima**: La clave debe tener al menos 32 caracteres\n- \u2757 **Solo strings**: Solo columnas de tipo string pueden encriptarse\n\n\n### \ud83d\udc41\ufe0f `View` - Definici\u00f3n de Vistas\n\nLas vistas permiten crear consultas complejas reutilizables:\n\n```python\nfrom tai_sql import View, query\n\nclass UserStats(View):\n    '''Estad\u00edsticas de usuarios y sus posts'''\n    __tablename__ = \"user_stats\"\n    __query__ = query('user_stats.sql')  # Archivo SQL en .../views/\n    \n    # Definir las columnas que retorna la vista\n    user_id: int\n    user_name: str\n    post_count: int\n    last_post_date: datetime\n```\n\n**Archivo SQL correspondiente** (`.../views/user_stats.sql`):\n```sql\nSELECT\n    u.id AS user_id,\n    u.name AS user_name,\n    COUNT(p.id) AS post_count,\n    MAX(p.created_at) AS last_post_date\nFROM usuarios u\nLEFT JOIN posts p ON u.id = p.author_id\nWHERE u.active = true\nGROUP BY u.id, u.name\n```\n\n## \ud83c\udfaf Generadores Incluidos\n\n### \ud83d\udcdd ModelsGenerator\n\nGenera modelos SQLAlchemy est\u00e1ndar desde tus definiciones de `Table` y `View`.\n\n```python\nModelsGenerator(\n    output_dir='...'  # Directorio donde se generar\u00e1n los modelos\n)\n```\n\n### \ud83d\udd04 CRUDGenerator\n\nGenera clases CRUD completas con operaciones Create, Read, Update, Delete optimizadas.\n\n```python\nCRUDGenerator(\n    output_dir='...',\n    models_import_path='...',\n    mode='sync'  # 'sync', 'async', o 'both'\n)\n```\n\n**Estructura generada:**\n```\n.../<schema_name>/crud/\n\u251c\u2500\u2500 syn/                    # Si mode='sync' o 'both'\n\u2502   \u251c\u2500\u2500 __init__.py\n\u2502   \u251c\u2500\u2500 session_manager.py\n\u2502   \u2514\u2500\u2500 endpoints.py\n\u2514\u2500\u2500 asyn/                   # Si mode='async' o 'both'\n    \u251c\u2500\u2500 __init__.py\n    \u251c\u2500\u2500 session_manager.py\n    \u2514\u2500\u2500 endpoints.py\n```\n\n**Ejemplo de uso del CRUD generado:**\n\nEl CRUD generado crea una API unificada que expone autom\u00e1ticamente m\u00e9todos para cada tabla definida en tu schema:\n\n```python\nfrom database.main.crud.syn import db_api\n\n# db_api contiene autom\u00e1ticamente un atributo por cada tabla:\n# - db_api.usuario (para la tabla Usuario)\n# - db_api.post (para la tabla Post)  \n# Cada atributo implementa todos los m\u00e9todos CRUD\n\n# \u2705 Operaciones b\u00e1sicas\n# Crear usuario\nuser = db_api.usuario.create(name=\"Juan\", email=\"juan@email.com\", age=25)\n\n# Buscar por ID (si la tabla tiene columna autoincrement)\nuser = db_api.usuario.find_by_id(1)\n\n# Buscar los 10 primeros con filtros\nusers = db_api.usuario.find_many(limit=10, age=25)\n\n# Buscar un registro espec\u00edfico\nuser = db_api.usuario.find(email=\"juan@email.com\")\n\n# Actualizar por ID\ndb_api.usuario.update_by_id(1, name=\"Juan Carlos\", age=26)\n\n# Eliminar por ID\ndb_api.usuario.delete_by_id(1)\n\n\n# \u2705 Operaciones avanzadas\n# Crear m\u00faltiples usuarios\nusers_data = [\n    {\"name\": \"Ana\", \"email\": \"ana@email.com\", \"age\": 28},\n    {\"name\": \"Pedro\", \"email\": \"pedro@email.com\", \"age\": 32}\n]\nusers = db_api.usuario.create_many(users_data)\n\n# Upsert (crear o actualizar)\nuser = db_api.usuario.upsert(email=\"maria@email.com\", name=\"Mar\u00eda\", age=30)\n\n# Operaciones masivas\ndb_api.usuario.update_many(\n    filters={\"age\": 26}, \n    **{\"last_seen\": datetime.now()}\n)\n\n# Contar registros\ntotal_users = db_api.usuario.count()\n\n# Verificar existencia\nexists = db_api.usuario.exists(email=\"juan@email.com\")\n\n# \u2705 Integraci\u00f3n con Pandas\n\n# Obtener como DataFrame\nusers_df = db_api.usuario.as_dataframe()\n\n# Insertar desde DataFrame\nimport pandas as pd\n\nnew_users_df = pd.DataFrame({\n    'name': ['Luis', 'Carmen'],\n    'email': ['luis@email.com', 'carmen@email.com'],\n    'age': [25, 30]\n})\nusers = db_api.usuario.from_df(new_users_df, mode='create')\n```\n\n**Ventajas del patr\u00f3n `db_api`:**\n- \u2705 **Una sola importaci\u00f3n**: Todo el CRUD en un objeto\n- \u2705 **Autocompletado**: Tu IDE sugiere autom\u00e1ticamente todas las tablas disponibles\n- \u2705 **Consistencia**: Todos los m\u00e9todos funcionan igual en todas las tablas\n- \u2705 **Simplicidad**: No necesitas gestionar instancias ni session managers manualmente\n\n**M\u00e9todos disponibles en cada CRUD:**\n\n| M\u00e9todo | Descripci\u00f3n | Ejemplo |\n|--------|-------------|---------|\n| `find(**filters)` | Busca un registro | `find(email=\"test@example.com\")` |\n| `find_many(limit, offset, **filters)` | Busca m\u00faltiples registros | `find_many(10, 0, active=True)` |\n| `find_by_id(id)` | Busca por ID | `find_by_id(1)` |\n| `create(**data)` | Crea un registro | `create(name=\"Juan\", email=\"juan@example.com\")` |\n| `create_many(records)` | Crea m\u00faltiples registros | `create_many([{...}, {...}])` |\n| `update(filters, **data)` | Actualiza un registro | `update({'name': 'Pedro'}, age=25)` |\n| `update_by_id(id, **data)` | Actualiza por ID | `update_by_id(1, name=\"Nuevo nombre\")` |\n| `update_many(filters, **data)` | Actualizaci\u00f3n masiva | `update_many({\"active\": False}, last_seen=datetime.now())` |\n| `upsert(**data)` | Crear o actualizar | `upsert(email=\"test@example.com\", name=\"Juan\")` |\n| `upsert_many(records)` | Upsert m\u00faltiple | `upsert_many([{...}, {...}])` |\n| `delete_by_id(id)` | Elimina por ID | `delete_by_id(1)` |\n| `delete(**filters)` | Elimina con filtros | `delete(active=False)` |\n| `count(**filters)` | Cuenta registros | `count(age__gte=18)` |\n| `exists(**filters)` | Verifica existencia | `exists(email=\"test@example.com\")` |\n| `as_dataframe(**filters)` | Exporta a DataFrame | `as_dataframe(limit=1000)` |\n| `from_dataframe(df, mode)` | Importa desde DataFrame | `from_df(df, mode='upsert')` |\n\n### \ud83d\udcca ERDiagramGenerator\n\nGenera diagramas Entity-Relationship profesionales usando Graphviz.\n\n```python\nERDiagramGenerator(\n    output_dir='docs/diagrams',\n    format='png',           # 'png', 'svg', 'pdf', 'dot'\n    include_views=True,     # Incluir vistas en el diagrama\n    include_columns=True,   # Mostrar detalles de columnas\n    include_relationships=True,  # Mostrar relaciones\n    dpi=300                # Resoluci\u00f3n para formatos bitmap\n)\n```\n\n**Caracter\u00edsticas del diagrama:**\n- \ud83d\udd11 **Primary Keys**: Marcadas con icono de llave\n- \ud83d\udd17 **Foreign Keys**: Marcadas con icono de enlace\n- \u2b50 **Unique**: Columnas \u00fanicas marcadas\n- \u2757 **Not Null**: Columnas obligatorias marcadas\n- \u2b06\ufe0f **Auto Increment**: Columnas auto-incrementales marcadas\n- \ud83d\udc41\ufe0f **Views**: Diferenciadas visualmente de las tablas\n\n## \ud83d\udda5\ufe0f Comandos CLI\n\n### `tai-sql init` - Inicializar Proyecto\n\nCrea un nuevo proyecto TAI-SQL con la estructura completa:\n\n```bash\n# Crear proyecto b\u00e1sico\ntai-sql init\n\n# Crear proyecto con nombre personalizado\ntai-sql init --name mi-proyecto --schema-name mi-esquema\n\n# Estructura generada:\nmi-proyecto/\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 mi_proyecto/             # CRUD/Models Folder\n\u251c\u2500\u2500 schemas/\n\u2502   \u2514\u2500\u2500 mi_esquema.py        # Schema principal\n\u251c\u2500\u2500 views/\n\u2502   \u2514\u2500\u2500 mi_esquema/\n\u2502       \u2514\u2500\u2500 user_stats.sql   # Vista de ejemplo\n\u2514\u2500\u2500 diagrams/                \n    \u2514\u2500\u2500 mi_esquema.png       # ERD Diagram\n```\n**Opciones:**\n- `--name, -n`: Nombre del proyecto (default: `database`)\n- `--schema, -s`: Nombre del primer schema (default: `public`)\n\n### `tai-sql new-schema` - Crear Nuevo Schema\n\nAgrega un nuevo schema a un proyecto existente:\n\n```bash\n# Crear nuevo schema en proyecto existente\ntai-sql new-schema productos\n\n# Con proyecto personalizado\ntai-sql new-schema --project mi-empresa productos\n```\n\n**Caracter\u00edsticas:**\n- \u2705 Detecta autom\u00e1ticamente el proyecto TAI-SQL actual\n- \u2705 Crea archivo de schema con plantilla completa\n- \u2705 Crea directorio de vistas correspondiente\n- \u2705 Actualiza configuraci\u00f3n del proyecto si es necesario\n\n### `tai-sql set-default-schema` - Establecer Schema por Defecto\n\nConfigura qu\u00e9 schema se usar\u00e1 por defecto en los comandos:\n\n```bash\n# Establecer schema por defecto\ntai-sql set-default-schema productos\n\n# Si el schema no existe, muestra opciones disponibles:\n# \u274c El schema 'nonexistent' no existe en el proyecto\n# \n# \ud83d\udcc4 Schemas disponibles:\n#    \u2705 public (actual por defecto)\n#       productos  \n#       ventas\n```\n\n### `tai-sql info` - Informaci\u00f3n del Proyecto\n\nMuestra informaci\u00f3n completa del proyecto actual:\n\n```bash\ntai-sql info\n```\n\n**Informaci\u00f3n mostrada:**\n```bash\n\ud83d\udcc1 Informaci\u00f3n del proyecto:\n   Nombre: mi-proyecto\n   Directorio: /path/to/mi-proyecto\n   Schema por defecto: productos\n\n\ud83d\udcc4 Schemas disponibles:\n   \u2022 public\n   \u2022 productos (\u2705 default, \ud83d\udccc current)\n   \u2022 ventas\n     \u2514\u2500 Estado: Cargado\n\n\ud83d\udd27 Comandos disponibles:\n   tai-sql generate              # Usa schema por defecto\n   tai-sql push                  # Usa schema por defecto\n   tai-sql set-default-schema <nombre>  # Cambiar default\n\n### `tai-sql generate` - Generar Recursos\n\nEjecuta todos los generadores configurados en el schema:\n\n```bash\n# Generar usando schema por defecto\ntai-sql generate\n\n# Generar usando schema espec\u00edfico\ntai-sql generate --schema database/schemas/productos.py\n```\n\n**Proceso de generaci\u00f3n:**\n1. \u2705 Carga y valida el schema\n2. \ud83d\udd0d Descubre modelos (tablas y vistas)\n3. \ud83c\udfd7\ufe0f Ejecuta generadores configurados\n4. \ud83d\udcca Muestra resumen de archivos generados\n\n\n### `tai-sql generate` - Generar Recursos\n\nEjecuta todos los generadores configurados en el schema:\n\n```bash\n# Generar usando schema por defecto\ntai-sql generate\n\n# Generar usando schema espec\u00edfico\ntai-sql generate --schema productos\n\n# Generar para todos los schemas del proyecto\ntai-sql generate --all\n```\n\n**Opciones:**\n- `--schema, -s`: Schema espec\u00edfico a procesar\n- `--all`: Procesar todos los schemas del proyecto\n\n**Proceso de generaci\u00f3n:**\n1. \u2705 Carga y valida el schema\n2. \ud83d\udd0d Descubre modelos (tablas y vistas)\n3. \ud83c\udfd7\ufe0f Ejecuta generadores configurados\n4. \ud83d\udcca Muestra resumen de archivos generados\n\n\n### `tai-sql push` - Sincronizar con Base de Datos\n\nAplica los cambios del schema a la base de datos:\n\n```bash\n# Push b\u00e1sico\ntai-sql push\n\n# Con opciones avanzadas\ntai-sql push --schema public --createdb --force --verbose\n\n# Dry run (mostrar cambios sin aplicar)\ntai-sql push --dry-run\n```\n\n**Opciones disponibles:**\n- `--createdb, -c`: Crear base de datos si no existe\n- `--force, -f`: Aplicar cambios sin confirmaci\u00f3n\n- `--dry-run, -d`: Mostrar DDL sin ejecutar\n- `--verbose, -v`: Mostrar informaci\u00f3n detallada\n\n**Proceso de push:**\n1. \ud83d\udd0d Analiza diferencias entre schema y BD\n2. \ud83d\udccb Genera sentencias DDL necesarias\n3. \u26a0\ufe0f Muestra advertencias de operaciones peligrosas\n4. \u2705 Aplica cambios tras confirmaci\u00f3n\n5. \ud83d\ude80 Ejecuta generadores autom\u00e1ticamente\n\n**Ejemplo de salida:**\n```bash\n\ud83d\ude80 Push schema: database/schemas/main.py\n\n\ud83d\udccb Resumen de cambios:\n   \ud83c\udd95 2 tabla(s) nueva(s): usuarios, posts\n   \u2795 3 columna(s) a a\u00f1adir en 1 tabla(s)\n   \ud83c\udd95 1 vista(s) nueva(s): user_stats\n\n\u00bfDeseas ejecutar estas sentencias en la base de datos? [y/N]: y\n\n\u2705 Esquema sincronizado exitosamente\n\ud83d\ude80 Ejecutando generadores...\n   \u2705 ModelsGenerator completado\n   \u2705 CRUDGenerator completado  \n   \u2705 ERDiagramGenerator completado\n```\n\n\n### `tai-sql ping` - Verificar Conectividad\n\nVerifica la conectividad con el servidor de base de datos:\n\n```bash\n# Verificaci\u00f3n b\u00e1sica (ping al host)\ntai-sql ping\n\n# Verificaci\u00f3n con schema espec\u00edfico\ntai-sql ping --schema productos\n\n# Verificaci\u00f3n completa (incluye ping ICMP, TCP y BD)\ntai-sql ping --full\n\n# Verificar tambi\u00e9n existencia de la base de datos\ntai-sql ping --check-db\n\n# Modo silencioso (solo resultado final)\ntai-sql ping --quiet\n```\n\n**Opciones:**\n- `--schema, -s`: Schema espec\u00edfico para conectividad\n- `--timeout, -t`: Timeout en segundos (default: 5)\n- `--check-db, -d`: Verificar si la base de datos espec\u00edfica existe\n- `--full, -f`: Verificaci\u00f3n completa (ICMP + TCP + BD)\n- `--quiet, -q`: Modo silencioso, solo resultado final\n\n**Tipos de verificaci\u00f3n:**\n\n1. **B\u00e1sica** (default): Solo ping al host\n2. **Full** (`--full`): Ping ICMP + conectividad TCP + conexi\u00f3n BD\n3. **Con BD** (`--check-db`): Incluye verificaci\u00f3n de existencia de BD\n\n**Ejemplo de salida:**\n```bash\n\ud83d\udd27 Informaci\u00f3n de conexi\u00f3n:\n   Motor: postgresql\n   Host: localhost\n   Puerto: 5432\n   Base de datos: mi_proyecto\n   Usuario: postgres\n\n\ud83c\udfd3 Verificaci\u00f3n BASIC\n\n\u2705 Host accesible\n\n\ud83d\uddc4\ufe0f  Verificando existencia de la base de datos...\n\n\u2705 La base de datos existe\n\n\ud83c\udf89 Verificaci\u00f3n de conectividad completada exitosamente\n```\n\n### Gesti\u00f3n Autom\u00e1tica de Schemas\n\n**Resoluci\u00f3n autom\u00e1tica del schema:**\n- Si no especificas `--schema`, los comandos usan autom\u00e1ticamente el schema por defecto\n- Si no hay schema por defecto configurado, el comando te gu\u00eda para establecer uno\n- Todos los comandos muestran qu\u00e9 schema est\u00e1n usando\n\n**Mensajes de ayuda inteligentes:**\n```bash\n# Si no hay schema por defecto:\n\u274c No existe ning\u00fan esquema por defecto\n   Puedes definir uno con: tai-sql set-default-schema <nombre>\n   O usar la opci\u00f3n: --schema <nombre_esquema>\n\n# Si especificas un schema que no existe:\n\u274c El schema 'inexistente' no existe en el proyecto\n\n\ud83d\udcc4 Schemas disponibles:\n   \u2705 public\n      productos\n      ventas\n```\n\n### Workflow T\u00edpico\n\n```bash\n# 1. Crear nuevo proyecto\ntai-sql init --name mi-empresa --schema productos\n\n# 2. Entrar al proyecto\ncd mi-empresa\n\n# 3. Configurar base de datos\nexport DATABASE_URL=\"postgresql://user:pass@localhost/mi_empresa\"\n\n# 4. Editar el schema\n# Editar schemas/productos.py\n\n# 5. Sincronizar con BD (crear BD si no existe)\ntai-sql push --createdb\n\n# 6. Verificar conectividad\ntai-sql ping --full\n\n# 7. Crear schema adicional\ntai-sql new-schema ventas\n\n# 8. Cambiar schema por defecto\ntai-sql set-default-schema ventas\n\n# 9. Ver informaci\u00f3n del proyecto\ntai-sql info\n\n# 10. Generar recursos para todos los schemas\ntai-sql generate --all\n```\n\n### Gesti\u00f3n de Proyectos Multi-Schema\n\nTAI-SQL soporta m\u00faltiples schemas en un mismo proyecto:\n\n```bash\n# Crear schemas adicionales\ntai-sql new-schema productos\ntai-sql new-schema ventas  \ntai-sql new-schema usuarios\n\n# Trabajar con schemas espec\u00edficos\ntai-sql push --schema productos\ntai-sql generate --schema ventas\n\n# O procesar todos a la vez\ntai-sql generate --all\n\n# Cambiar entre schemas por defecto\ntai-sql set-default-schema productos\ntai-sql push  # Usa 'productos' autom\u00e1ticamente\n\ntai-sql set-default-schema ventas  \ntai-sql generate  # Usa 'ventas' autom\u00e1ticamente\n```\n\n**Ventajas del multi-schema:**\n- \u2705 **Modularidad**: Separar l\u00f3gicamente diferentes dominios\n- \u2705 **Escalabilidad**: Cada schema puede tener su propia configuraci\u00f3n\n- \u2705 **Flexibilidad**: Procesar schemas individualmente o en conjunto\n- \u2705 **Organizaci\u00f3n**: Mejor estructura para proyectos complejos\n\n\n## \ud83d\udee0\ufe0f Crear tu Propio Generador\n\nPuedes crear generadores personalizados heredando de `BaseGenerator`:\n\n```python\nfrom tai_sql.generators.base import BaseGenerator\nfrom tai_sql import db\nimport os\n\nclass APIDocsGenerator(BaseGenerator):\n    \"\"\"Generador de documentaci\u00f3n API desde los modelos\"\"\"\n    \n    def __init__(self, output_dir=None, format='markdown'):\n        super().__init__(output_dir or 'docs/api')\n        self.format = format\n    \n    def generate(self) -> str:\n        \"\"\"Genera la documentaci\u00f3n API\"\"\"\n        \n        docs_content = self._create_header()\n        \n        # Procesar cada modelo\n        for model in self.models:\n            if hasattr(model, '__tablename__'):  # Es una tabla\n                docs_content += self._generate_table_docs(model)\n            else:  # Es una vista\n                docs_content += self._generate_view_docs(model)\n        \n        # Guardar archivo\n        output_path = os.path.join(self.config.output_dir, f'api.{self.format}')\n        with open(output_path, 'w', encoding='utf-8') as f:\n            f.write(docs_content)\n        \n        return output_path\n    \n    def _create_header(self) -> str:\n        \"\"\"Crea el header de la documentaci\u00f3n\"\"\"\n        return f\"\"\"# API Documentation\n                    \n            Database: {db.provider.database}\n            Schema: {db.schema_name}\n            Generated: {datetime.now().isoformat()}\n\n            ## Models\n\n        \"\"\"\n    \n    def _generate_table_docs(self, model) -> str:\n        \"\"\"Genera documentaci\u00f3n para una tabla\"\"\"\n        docs = f\"### {model.__name__} (Table)\\n\\n\"\n        docs += f\"**Table name:** `{model.__tablename__}`\\n\\n\"\n        \n        if hasattr(model, '__description__'):\n            docs += f\"**Description:** {model.__description__}\\n\\n\"\n        \n        docs += \"**Columns:**\\n\\n\"\n        docs += \"| Column | Type | Constraints |\\n\"\n        docs += \"|--------|------|-------------|\\n\"\n        \n        for name, column in model.columns.items():\n            constraints = []\n            if column.primary_key:\n                constraints.append(\"PRIMARY KEY\")\n            if not column.nullable:\n                constraints.append(\"NOT NULL\")\n            if column.unique:\n                constraints.append(\"UNIQUE\")\n            if column.autoincrement:\n                constraints.append(\"AUTO INCREMENT\")\n                \n            docs += f\"| {name} | {column.type} | {', '.join(constraints)} |\\n\"\n        \n        docs += \"\\n\"\n        return docs\n    \n    def _generate_view_docs(self, model) -> str:\n        \"\"\"Genera documentaci\u00f3n para una vista\"\"\"\n        docs = f\"### {model.__name__} (View)\\n\\n\"\n        docs += f\"**View name:** `{model.__tablename__}`\\n\\n\"\n        \n        if hasattr(model, '__description__'):\n            docs += f\"**Description:** {model.__description__}\\n\\n\"\n        \n        # Agregar informaci\u00f3n de la vista...\n        return docs\n\n# Uso del generador personalizado\n\ngenerate(\n    ...,\n    APIDocsGenerator(output_dir='docs/api', format='markdown')\n)\n```\n\n**M\u00e9todos requeridos:**\n- `generate()`: M\u00e9todo principal que debe retornar la ruta del archivo generado\n\n**M\u00e9todos/propiedades \u00fatiles heredados:**\n- `self.models`: Propiedad que contiene todos los modelos (tablas y vistas)\n- `self.config.output_dir`: Directorio de salida configurado\n- `self.register_model(model)`: Registra un modelo manualmente\n- `self.clear_models()`: Limpia la lista de modelos\n\n\nEste framework te permite construir aplicaciones robustas con una definici\u00f3n declarativa simple, generaci\u00f3n autom\u00e1tica de c\u00f3digo y herramientas CLI potentes para el desarrollo \u00e1gil.",
    "bugtrack_url": null,
    "license": null,
    "summary": null,
    "version": "0.3.10",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "888783b8373a5def9175418e25e8faec4ce283d11f7c7656f51c66e60b2a6ae0",
                "md5": "e40c0105e0ec66ae3cc74b4d19f86f53",
                "sha256": "c14da4568b8e4396f07e4341997cb62c001f5276e55a78670606317389d50465"
            },
            "downloads": -1,
            "filename": "tai_sql-0.3.10-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e40c0105e0ec66ae3cc74b4d19f86f53",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.10",
            "size": 153570,
            "upload_time": "2025-07-12T17:57:24",
            "upload_time_iso_8601": "2025-07-12T17:57:24.324632Z",
            "url": "https://files.pythonhosted.org/packages/88/87/83b8373a5def9175418e25e8faec4ce283d11f7c7656f51c66e60b2a6ae0/tai_sql-0.3.10-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "404de3f21da7a3dd096ee915aacbb04227515dcfaa40113356eb707031f78c1e",
                "md5": "1a5fb9bf57c35be1eed17aa693c57f7d",
                "sha256": "4ece2ad951bb1073dddd34de5db2e51d176b8d4f7414abba102069104b23a1ab"
            },
            "downloads": -1,
            "filename": "tai_sql-0.3.10.tar.gz",
            "has_sig": false,
            "md5_digest": "1a5fb9bf57c35be1eed17aa693c57f7d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.10",
            "size": 125688,
            "upload_time": "2025-07-12T17:57:25",
            "upload_time_iso_8601": "2025-07-12T17:57:25.540781Z",
            "url": "https://files.pythonhosted.org/packages/40/4d/e3f21da7a3dd096ee915aacbb04227515dcfaa40113356eb707031f78c1e/tai_sql-0.3.10.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-12 17:57:25",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "tai-sql"
}
        
Elapsed time: 0.43142s