Saltar a contenido

Tu primer proyecto

De cero a un cliente generado funcionando. Necesitas tai-sql instalado y una base de datos PostgreSQL a la que puedas conectarte.

1. Crear el proyecto

tai-sql init -n mi_proyecto -s public
cd mi_proyecto

Eso deja esto:

mi_proyecto/
├── .taisqlproject          # {"name": "mi_proyecto", "default_schema": "public"}
├── pyproject.toml          # dependencias del CLIENTE GENERADO
├── README.md
├── __init__.py
├── schemas/
│   └── public.py           # el schema: la única fuente de verdad
├── views/
│   └── public/
│       └── user_stats.sql  # el SQL de cada vista, un fichero por vista
└── diagrams/

schemas/public.py viene con un ejemplo completo —un blog con usuarios, posts, comentarios, un enumerado, un trigger, un feed() y una vista— que sirve de plantilla. Bórralo y escribe el tuyo cuando hayas visto cómo funciona.

2. Configurar la conexión

export MAIN_DATABASE_URL="postgresql://usuario:clave@localhost:5432/mibase"

El nombre de la variable es el que diga el env(...) del schema. tai-sql no carga .env por su cuenta: si usas uno, cárgalo tú antes (por ejemplo con direnv, o set -a; . .env; set +a).

tai-sql ping -d     # comprueba servidor y base de datos

Si la base de datos no existe, tai-sql push la crea.

3. Instalar las dependencias del proyecto

tai-sql install

4. Escribir el schema

Abre schemas/public.py y déjalo así:

# -*- coding: utf-8 -*-
from __future__ import annotations
from tai_sql import *
from tai_sql.generators import *

datasource(
    provider=env('MAIN_DATABASE_URL'),
    schema='public',
    syntax='v2',
    operative_fields=True,      # created_at / updated_at / created_by / updated_by
)

generate(
    PythonClientGenerator(output_dir='database'),
    ERDiagramGenerator(output_dir='diagrams', format='html'),
)


class Estado(Enum):
    """Estado de publicación de un post."""
    BORRADOR = 'borrador'
    PUBLICADO = 'publicado'


class Usuario(Table):
    """Usuarios del sistema."""
    __tablename__ = 'usuario'

    id: col[int] = column(primary_key=True, autoincrement=True)
    nombre: col[str] = column(description='Nombre visible')
    email: col[str] = column(unique=True)

    posts: onetomany[Post]

    def feed(self) -> List[Usuario]:
        """Datos iniciales. Se ejecutan con: tai-sql feed"""
        return [Usuario(nombre='Ana', email='ana@example.com')]


class Post(Table):
    """Posts publicados."""
    __tablename__ = 'post'

    id: col[bigint] = column(primary_key=True, autoincrement=True)
    titulo: col[str]
    contenido: col[text]
    estado: col[Estado] = column(default=Estado.BORRADOR)
    publicado_en: col[datetime | None]
    autor_id: col[int]

    autor: manytoone[Usuario] = relation(
        fields=['autor_id'], references=['id'], backref='posts'
    )

    @on_update(timing='before', fields=['estado'],
               when=lambda t: t.new.estado == 'publicado')
    def sellar_publicacion(self, t: TriggerAPI[Post]):
        """Al pasar a publicado, sella la fecha."""
        t.new.publicado_en = datetime.now()

from __future__ import annotations no es opcional

Sin él, posts: onetomany[Post] revienta al importar, porque Post todavía no existe en esa línea. Es el error de schema más común.

5. Ver qué implicaría, y aplicarlo

tai-sql push --dry-run --verbose

Eso imprime el DDL exacto que se ejecutaría, sin tocar nada. Cuando te convenza:

tai-sql push

push sincroniza la base de datos y, al terminar, regenera el cliente y el diagrama.

6. Poblar con los datos iniciales

tai-sql feed --dry-run     # qué insertaría
tai-sql feed

7. Usar el cliente

from database.public import public_sync_api, UsuarioCreate, PostCreate, set_username

set_username('ana')     # va a created_by / updated_by

usuario = public_sync_api.usuario.upsert(
    UsuarioCreate(nombre='Ana', email='ana@example.com'),
    match_fields=['email'],
)

public_sync_api.post.create(
    PostCreate(titulo='Hola mundo', contenido='...', autor_id=usuario.id)
)

for post in public_sync_api.post.find_many(includes=['autor'], limit=10):
    print(post.titulo, '→', post.autor.nombre)

Y el diagrama está en diagrams/public.html: ábrelo con doble clic.

El ciclo, a partir de aquí

vim schemas/public.py               # 1. cambias el schema
tai-sql push --dry-run --verbose    # 2. miras qué implicaría
tai-sql push                        # 3. lo aplicas (regenera el cliente al terminar)

Si solo quieres regenerar el cliente sin tocar la base de datos, tai-sql generate.

Nunca edites el código generado

Todo lo que hay bajo el output_dir es desechable: se borra y se regenera. Si el cliente no hace lo que necesitas, el cambio va en el schema.

¿Y si la base de datos ya existe?

Entonces no empiezas escribiendo el schema, sino leyéndolo:

tai-sql pull

Ver tai-sql pull.