Saltar a contenido

El schema

Un schema es un fichero schemas/<nombre>.py dentro de un proyecto con .taisqlproject, y es la única fuente de verdad: la base de datos, el cliente Python y los diagramas salen de él.

Anatomía de un fichero de schema

El orden importa, y es siempre este:

# -*- coding: utf-8 -*-
from __future__ import annotations          # obligatorio: los schemas usan forward references
from tai_sql import *
from tai_sql.generators import *

datasource(...)      # 1. con qué base de datos habla y cómo se interpreta lo declarado
generate(...)        # 2. qué recursos produce

# 3. Enumerados   4. Tablas   5. Vistas

from __future__ import annotations no es opcional

Sin él, una relación que apunte a una clase declarada más abajo revienta al importar.

La referencia normativa del estilo es la plantilla que produce tai-sql new-schema (default.py), que además trae plantillas de dominio para autenticación y chatbots.

datasource() — con qué base de datos habla el schema

datasource(
    provider=env('MAIN_DATABASE_URL'),   # recomendado: la URL sale del entorno
    schema='public',                     # nombre del schema EN la base de datos
    syntax='v2',                         # cómo se interpreta lo que declaras ('v1' | 'v2')
    operative_fields=False,              # columnas de auditoría automáticas
    secret_key_name='SECRET_KEY',        # variable con la clave de cifrado
    default_vector_dimensions=384,       # dimensiones por defecto de una vector_column()
)

Declara dos cosas y solo dos: con qué base de datos habla el schema (provider) y cómo se interpreta lo que declara (syntax, operative_fields, secret_key_name, default_vector_dimensions) — más la configuración de backup, que es un inquilino aparte.

Las tres formas de declarar el provider

provider=env('MAIN_DATABASE_URL')                          # variable de entorno
provider=connection_string('postgresql://u:p@h:5432/db')   # cadena literal
provider=params(host='localhost', port=5432, database='db', username='u', password='p')

Usa env() salvo que haya una razón muy concreta

El provider no es «el origen del CLI»: es el del schema, y lo heredan todos sus consumidores. Con env() lo que se hereda es la indirección (os.getenv(...)), así que el CLI en tu máquina y el cliente desplegado en producción pueden apuntar a bases de datos distintas con una sola declaración. Con una cadena literal, la contraseña queda escrita en el repositorio.

Campos operativos

datasource(..., operative_fields=True)

Añade a todas las tablas created_at, updated_at, created_by y updated_by, y las mantiene el framework: no las declares tú. El usuario que se escribe en created_by / updated_by sale del contexto del cliente generado (set_username()).

Se puede activar para una tabla suelta con __operative_fields__ = True en su cuerpo.

Los parámetros de pool

datasource() acepta además siete parámetros (pool_size, max_overflow, pool_timeout, pool_recycle, pool_pre_ping, ssl, sqlalchemy_logs) que configuran el cliente generado, no las conexiones de tai-sql. Se siguen aceptando por compatibilidad, pero su hogar es el generador que produce el fichero donde acaban —PythonClientGenerator(pool_size=10)— y lo que declare el generador manda.

Backup

backup_storage, backup_format, backup_retention y restore_provider configuran tai-sql backup.

generate() — qué recursos produce el schema

generate(
    PythonClientGenerator(
        output_dir='database',                # o una lista de directorios
        mode='both',                          # 'sync' | 'async' | 'both'
        max_depth=5,                          # profundidad al resolver relaciones anidadas
        logger_name='tai-sql',
        pool_size=10, max_overflow=5, pool_timeout=30,   # del cliente generado
        pool_recycle=3600, pool_pre_ping=True, ssl=True, sqlalchemy_logs=False,
    ),
    ERDiagramGenerator(
        output_dir='diagrams',
        format='html',                        # 'html' | 'svg' | 'png' | 'pdf' | 'dot' | 'eps' | 'ps'
        theme='classic',                      # 'classic' | 'modern' | 'dark' | 'minimal' | 'corporate'
        include_views=True,
        include_columns=True,
        include_relationships=True,
        include_triggers=True,
        include_constraints=True,
        include_enums=True,
        include_operative_fields=False,
        assets='inline',                      # solo html: 'inline' (offline) | 'cdn'
        lines='ortho', size='auto', dpi=300,  # solo formatos Graphviz
    ),
)

El cliente sale en <output_dir>/<schema>/ y expone <schema>_sync_api y <schema>_async_api. Se puede declarar el mismo generador dos veces con formatos o destinos distintos, y también escribir el tuyo.

Las dos sintaxis: v1 y v2

Las dos funcionan y las dos se soportan; las elige datasource(syntax=...). Mira el syntax= del proyecto antes de escribir nada y usa la que ya se esté usando: mezclarlas en el mismo fichero funciona, pero se lee fatal.

Qué declaras v1 (legacy) v2 (recomendada)
Columna id: int = column(primary_key=True) id: col[bigint] = column(primary_key=True)
Opcional email: Optional[str] email: col[str \| None]
Uno a muchos posts: List[Post] posts: onetomany[Post]
Muchos a uno autor: Autor = relation(...) autor: manytoone[Autor] = relation(...)
Uno a uno (no se puede expresar) perfil: onetoone[Perfil]
Calculada @calculated_column + -> float -> col[float], sin decorador

La v2 dice la cardinalidad en el tipo, así que no hay que deducirla. En v1 se deduce —un hint escalar es many-to-one y un List[X] es one-to-many—, y por eso v1 no sabe expresar un uno a uno.

Compatibilidad

Lo que exporta tai_sql es la API con la que escribes tus schemas, y no se rompe: las dos sintaxis conviven, y los cambios son aditivos o pasan por una deprecación explícita.

Qué sigue