Name | tai-sql JSON |
Version |
0.3.10
JSON |
| download |
home_page | None |
Summary | None |
upload_time | 2025-07-12 17:57:25 |
maintainer | None |
docs_url | None |
author | MateoSaezMata |
requires_python | <4.0,>=3.10 |
license | None |
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"
}