Skip to main content

No project description provided

Project description

🚀 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)

poetry add tai-sql

Usando pip

pip install tai-sql

Dependencias del sistema

Para generar diagramas ER, necesitas instalar Graphviz:

# Ubuntu/Debian
sudo apt install graphviz

# macOS
brew install graphviz

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

🏗️ 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:

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:

datasource(
    provider=env('DATABASE_URL'),
    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:

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',
        models_import_path='database.models',
        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:

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

class Usuario(Table):
    __tablename__ = "usuario"
    __description__ = "Tabla que almacena información de los usuarios"
    
    # 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):
    __tablename__ = "post"
    __description__ = "Tabla que almacena la información de los posts de los usuarios"
    
    id: int = column(primary_key=True, autoincrement=True)
    title: str
    content: str
    author_id: int
    published: Optional[bool] = column(default=False)
    
    # Relación explícita
    author: Usuario = relation(
        fields=['author_id'], 
        references=['id'], 
        backref='posts'
    )

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

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

def column(
    primary_key=False,      # Si es clave primaria
    unique=False,           # Si debe ser único
    default=None,           # Valor por defecto
    default_factory=None,   # Función para generar valor por defecto
    server_now=False,       # Para usar NOW() del servidor
    index=False,            # Si debe tener índice
    autoincrement=False     # Si es autoincremental
):

Ejemplos de uso:

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 = 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

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")
default_factory callable Función que genera el valor por defecto column(default_factory=datetime.now)
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)

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

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

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:

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

👁️ View - Definición de Vistas

Las vistas permiten crear consultas complejas reutilizables:

from tai_sql import View, query

class UserStats(View):
    __tablename__ = "user_stats"
    __query__ = query('user_stats.sql')  # Archivo SQL en .../views/
    __description__ = "Estadísticas de usuarios y sus posts"
    
    # 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):

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.

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

🔄 CRUDGenerator

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

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:

from database.main.crud 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_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.

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:

# 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/                # ERD folder

tai-sql new-schema - Crear Nuevo Schema

Agrega un nuevo schema a un proyecto existente:

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

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

tai-sql generate - Generar Recursos

Ejecuta todos los generadores configurados en el schema:

# 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 push - Sincronizar con Base de Datos

Aplica los cambios del schema a la base de datos:

# Push básico
tai-sql push

# Con opciones avanzadas
tai-sql push --schema schemas/productos.py --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:

🚀 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 createdb - Crear Base de Datos

Crea la base de datos especificada en el schema:

# Crear BD del schema por defecto
tai-sql createdb

# Crear BD de schema específico
tai-sql createdb --schema schemas/productos.py

🛠️ Crear tu Propio Generador

Puedes crear generadores personalizados heredando de BaseGenerator:

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.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tai_sql-0.2.0.tar.gz (93.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tai_sql-0.2.0-py3-none-any.whl (106.7 kB view details)

Uploaded Python 3

File details

Details for the file tai_sql-0.2.0.tar.gz.

File metadata

  • Download URL: tai_sql-0.2.0.tar.gz
  • Upload date:
  • Size: 93.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.11.0 Linux/6.11.0-1015-azure

File hashes

Hashes for tai_sql-0.2.0.tar.gz
Algorithm Hash digest
SHA256 49485057e5b3ce4b9950a598b27d7696d5409558a72381d60ecaa40826cdb357
MD5 816164bd69825f805aa741ca38bd26b3
BLAKE2b-256 9a9a21719539038719b8e6e2ea4bbdb29b4b5cec73f56de597e31e56c5a42bb3

See more details on using hashes here.

File details

Details for the file tai_sql-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: tai_sql-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 106.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.11.0 Linux/6.11.0-1015-azure

File hashes

Hashes for tai_sql-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dc23a0609926f111fc47ead40e41bd9cad0eab66bb97bf1da1d9cabf4db88ce9
MD5 445e1f03ef74e46f2f2f5fcd6385fdbb
BLAKE2b-256 0a7ed66c93334b7a20245a9f7277843c4105bb4df865a79b87950eeab1dc3612

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page