7bcfb7504f
- Added SQLAlchemy initialization script for backend setup. - Created SQLAlchemy prompts for generating database models with LLMs. - Introduced Docker Compose configuration for PostgreSQL service. - Added SQL script to enable necessary PostgreSQL extensions.
195 lines
21 KiB
JSON
195 lines
21 KiB
JSON
{
|
|
"version": "1",
|
|
"metadata": {
|
|
"marimo_version": "0.14.17"
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "Hbol",
|
|
"code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/plain": ""
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "MJUe",
|
|
"code_hash": "c4f05c85676b46ca0fe7e6ba2c7451cb",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/html": "<span class=\"markdown prose dark:prose-invert\"><h1 id=\"sqlalchemy-2-generacion-de-db_mmr-con-llms\">SQLAlchemy 2: generacion de DB_MMR con LLMs</h1></span>"
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "vblA",
|
|
"code_hash": "7869775eccc51f1498a040ec3dd84bd0",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Aqui podremos hacer que el LLM pueda generar objetos y sus conexiones con la base de datos de forma que generemos un backend s\u00f3lido y consistente. La idea es que con esto generemos la plantilla que podamos pasarle al LLM para que \u00e9l genere el c\u00f3digo de manera organizada</span></span>"
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "bkHC",
|
|
"code_hash": "e0801e8126ea6513ac1888571363f0c0",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/plain": ""
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "lEQa",
|
|
"code_hash": "1e0fb1180758d2b29ee2b3266ab63169",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Definimos la entidad o entidades a generar y su descripcion para generar los campos usando un llm y su plantilla para la bbdd</span></span>"
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "PKri",
|
|
"code_hash": "b34c8a33b26c85ee9344fb55d9b11301",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/plain": ""
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "Xref",
|
|
"code_hash": "030b2a7d1cf6c50221fdfed467780a24",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/plain": ""
|
|
}
|
|
}
|
|
],
|
|
"console": [
|
|
{
|
|
"type": "stream",
|
|
"name": "stdout",
|
|
"text": "\n\ud83d\udccc Nombre de dominio: **productos**\n\n\ud83d\udcdd Explicaci\u00f3n: \nItems para una tienda online\n\n---\n\ud83c\udfaf **Objetivo del prompt** \nGenera una lista de posibles campos para el modelo **productos**, en el siguiente formato:\n\n# Formato sugerido por l\u00ednea:\n# nombre_columna: tipo_python | tipo_sqlalchemy(opcional) | nullable={True/False} | default={valor/opcional} | comentarios\n# - Ya contienen : columnas sys_* (created_at, updated_at, etc.), __json__, etc.\n\nEjemplo esperado:\n\n```\n\nnombre: str | String(120) | nullable=False | comentarios=\"Nombre del productos\"\ndescripcion: str | Text | nullable=True | default=None | comentarios=\"Descripci\u00f3n opcional\"\nactivo: bool | Boolean | nullable=False | default=True | comentarios=\"Si el registro est\u00e1 activo\"\n\n```\n\n---\n\ud83d\udd01 Posibles relaciones en formato:\n```\n\n\"RELACIONES\": \"\",\n\n# Ej.: cliente_id -> FK a ventas.clientes.id (ondelete='RESTRICT'),\n\"RELACIONES\": \"usuario_id -> FK a seguridad.usuarios.id (ondelete='CASCADE')\"\n\n```\n\n---\n\ud83d\udcd1 Posibles \u00edndices y restricciones:\n```\n\n\"INDEX\\_LIST\": \"\", # Opcional\n\"UNIQUE\\_LIST\": \"\", # Opcional\n\n```\n\n\u26a1 Nota: Usa tu criterio para proponer campos, relaciones y restricciones que sean coherentes con el dominio **productos**.\n```\n"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "yeSV",
|
|
"code_hash": "e26c00e88371c15203ce806dc5b41aeb",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/plain": ""
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "SFPL",
|
|
"code_hash": "47a5275bf6221951a2342125bca26d3e",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/plain": ""
|
|
}
|
|
}
|
|
],
|
|
"console": [
|
|
{
|
|
"type": "stream",
|
|
"name": "stdout",
|
|
"text": "Te paso mis clases base para que heredes correctamente:\n\n1) Model_base \n\n- Codigo de la clase Model: \n```\n# Model.py\nfrom sqlalchemy import Column, DateTime, String, Integer, Text, BigInteger, func\nfrom sqlalchemy.ext.declarative import declared_attr, as_declarative\nfrom datetime import datetime\nfrom backend.db.base import Base # tu Base declarativa\n\nclass Model_base(Base):\n __abstract__ = True\n\n # ID autoincremental por defecto en todos los modelos\n id = Column(BigInteger, primary_key=True, autoincrement=True)\n\n @declared_attr\n def sys_created_at(cls):\n # timestamptz + default en BD\n return Column(DateTime(timezone=True), server_default=func.now(), nullable=False)\n\n @declared_attr\n def sys_created_by(cls):\n return Column(String, nullable=True)\n\n @declared_attr\n def sys_updated_at(cls):\n # onupdate lo pone la BD al actualizar\n return Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)\n\n @declared_attr\n def sys_updated_by(cls):\n return Column(String, nullable=True)\n\n @declared_attr\n def sys_version(cls):\n return Column(Integer, default=1, nullable=False)\n\n @declared_attr\n def sys_notes(cls):\n return Column(Text, nullable=True)\n\n @declared_attr\n def sys_deleted_at(cls):\n return Column(DateTime(timezone=True), nullable=True)\n\n def __repr__(self):\n id_val = getattr(self, \"id\", None)\n return f\"<{self.__class__.__name__} id={id_val}>\"\n\n def __str__(self):\n cls = self.__class__.__name__\n id_val = getattr(self, \"id\", None)\n return f\"{cls}(id={id_val})\"\n\n def __json__(self) -> dict:\n out = {}\n if not hasattr(self, \"__table__\"): return out\n for c in self.__table__.columns:\n v = getattr(self, c.name, None)\n out[c.name] = v.isoformat() if isinstance(v, datetime) else v\n return out\n```\n\n- Ruta de import: domains.arquitecture_layer.Repo import Repo_base\n- Contiene: columnas sys_* (created_at, updated_at, etc.), __json__, etc.\n\n2) Mapper_base\n\n- Codigo de la clase Model: \n```\n# Mapper.py\n\nfrom abc import ABC, abstractmethod\nfrom typing import TypeVar, Generic, Type\nimport json\n\nTDominio = TypeVar(\"TDominio\")\nTModelo = TypeVar(\"TModelo\")\n\nclass Mapper_base(ABC, Generic[TDominio, TModelo]):\n # ----------------------------\n # Conversiones individuales\n # ----------------------------\n\n @staticmethod\n @abstractmethod\n def to_model(obj: TDominio) -> TModelo:\n \"\"\"Convierte objeto de dominio a modelo ORM\"\"\"\n pass\n\n @staticmethod\n @abstractmethod\n def from_model(model: TModelo) -> TDominio:\n \"\"\"Convierte modelo ORM a objeto de dominio\"\"\"\n pass\n\n @staticmethod\n @abstractmethod\n def to_dict(obj: TDominio) -> dict:\n \"\"\"Convierte objeto de dominio a diccionario plano\"\"\"\n pass\n\n @staticmethod\n @abstractmethod\n def from_dict(d: dict) -> TDominio:\n \"\"\"Convierte diccionario plano a objeto de dominio\"\"\"\n pass\n\n @classmethod\n def to_json(cls, obj: TDominio) -> str:\n \"\"\"Convierte objeto de dominio a JSON string\"\"\"\n return json.dumps(cls.to_dict(obj), default=str)\n\n @classmethod\n def from_json(cls, json_str: str) -> TDominio:\n \"\"\"Convierte JSON string a objeto de dominio\"\"\"\n return cls.from_dict(json.loads(json_str))\n\n # ----------------------------\n # Conversiones en lote (bulk)\n # ----------------------------\n\n @classmethod\n def to_model_list(cls, objs: list[TDominio]) -> list[TModelo]:\n return [cls.to_model(o) for o in objs]\n\n @classmethod\n def from_model_list(cls, models: list[TModelo]) -> list[TDominio]:\n return [cls.from_model(m) for m in models]\n\n @classmethod\n def to_dict_list(cls, objs: list[TDominio]) -> list[dict]:\n return [cls.to_dict(o) for o in objs]\n\n @classmethod\n def from_dict_list(cls, dicts: list[dict]) -> list[TDominio]:\n return [cls.from_dict(d) for d in dicts]\n\n @classmethod\n def to_json_list(cls, objs: list[TDominio]) -> str:\n return json.dumps(cls.to_dict_list(objs), default=str)\n\n @classmethod\n def from_json_list(cls, json_str: str) -> list[TDominio]:\n return cls.from_dict_list(json.loads(json_str))\n```\n\n- Ruta de import: domains.arquitecture_layer.Mapper import Mapper_base\n\n3) Repo_base\n- Codigo de la clase Model: \n```\n# Repo.py\n\nfrom abc import ABC\nfrom typing import Type, TypeVar, Generic, Optional\nfrom sqlalchemy.orm import Session\nfrom sqlalchemy import func\nfrom datetime import datetime\nfrom datetime import datetime, timezone\n\nfrom .Mapper import Mapper_base # Aseg\u00farate de importar tu ABC base\n\nTModelo = TypeVar(\"TModelo\")\nTDominio = TypeVar(\"TDominio\")\n\nclass Repo_base(ABC, Generic[TModelo, TDominio]):\n def __init__(self, session: Session, modelo: type[TModelo], mapper: type[Mapper_base[TDominio, TModelo]]):\n self.session = session\n self.Modelo = modelo\n self.Mapper = mapper\n\n # ----------------------\n # ADD\n # ----------------------\n\n def add(self, dominio: TDominio, created_by: Optional[str] = None, notes: Optional[str] = None) -> str:\n data = self.Mapper.to_dict(dominio)\n data.update({\n \"sys_created_by\": created_by,\n \"sys_notes\": notes,\n \"sys_version\": 1\n })\n model = self.Modelo(**data)\n self.session.add(model)\n self.session.commit()\n return model.id\n\n def add_many(self, dominios: list[TDominio], created_by: Optional[str] = None, notes: Optional[str] = None) -> list[str]:\n ids = []\n for dominio in dominios:\n data = self.Mapper.to_dict(dominio)\n data.update({\n \"sys_created_by\": created_by,\n \"sys_notes\": notes,\n \"sys_version\": 1\n })\n model = self.Modelo(**data)\n self.session.add(model)\n ids.append(model.id)\n self.session.commit()\n return ids\n\n # ----------------------\n # GET\n # ----------------------\n\n def get_by_id(self, id_: str) -> Optional[TDominio]:\n model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()\n return self.Mapper.from_model(model) if model else None\n\n def get_all(self) -> list[TDominio]:\n models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).all()\n return self.Mapper.from_model_list(models)\n\n def get_paginated(self, offset: int = 0, limit: int = 10) -> list[TDominio]:\n models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).offset(offset).limit(limit).all()\n return self.Mapper.from_model_list(models)\n\n def get_deleted(self) -> list[TDominio]:\n models = self.session.query(self.Modelo).filter(self.Modelo.sys_deleted_at.isnot(None)).all()\n return self.Mapper.from_model_list(models)\n\n # ----------------------\n # UPDATE\n # ----------------------\n\n def update(self, id_: str, new_data: dict, updated_by: Optional[str] = None, notes: Optional[str] = None) -> bool:\n model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()\n if not model:\n return False\n\n for key, value in new_data.items():\n if hasattr(model, key):\n setattr(model, key, value)\n\n model.sys_updated_by = updated_by\n model.sys_notes = notes\n model.sys_version = (model.sys_version or 1) + 1\n self.session.commit()\n return True\n\n def bulk_update(self, updates: list[tuple[str, dict]], updated_by: Optional[str] = None, notes: Optional[str] = None) -> int:\n count = 0\n for id_, data in updates:\n if self.update(id_, data, updated_by=updated_by, notes=notes):\n count += 1\n return count\n\n # ----------------------\n # DELETE\n # ----------------------\n\n def delete_by_id(self, id_: str) -> bool:\n model = self.session.query(self.Modelo).filter_by(id=id_).first()\n if model:\n self.session.delete(model)\n self.session.commit()\n return True\n return False\n\n def delete_all(self) -> int:\n deleted = self.session.query(self.Modelo).delete()\n self.session.commit()\n return deleted\n\n # ----------------------\n # SOFT DELETE\n # ----------------------\n\n def soft_delete(self, id_: str, deleted_by: Optional[str] = None, notes: Optional[str] = None) -> bool:\n model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()\n if model:\n model.sys_deleted_at = datetime.now(timezone.utc) # TZ-aware\n model.sys_updated_by = deleted_by\n model.sys_notes = notes\n model.sys_version = (model.sys_version or 1) + 1\n self.session.commit()\n return True\n return False\n\n def soft_restore(self, id_: str, restored_by: Optional[str] = None, notes: Optional[str] = None) -> bool:\n model = self.session.query(self.Modelo).filter_by(id=id_).first()\n if model and model.sys_deleted_at is not None:\n model.sys_deleted_at = None\n model.sys_updated_by = restored_by\n model.sys_notes = notes\n model.sys_version = (model.sys_version or 1) + 1\n self.session.commit()\n return True\n return False\n\n # ----------------------\n # OTROS\n # ----------------------\n\n def exists(self, id_: str) -> bool:\n return self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first() is not None\n\n def count(self) -> int:\n return self.session.query(self.Modelo).filter_by(sys_deleted_at=None).count()\n\n```\n\n- Ruta de import: from domains.arquitecture_layer.Model import Model_base\n\n\ud83e\udde9 Tarea:\nGenera en **un solo archivo** las 3 piezas siguientes para la entidad **productos**:\n- Dominio (dataclass): productosDom\n- Modelo ORM (SQLAlchemy): productosModel\n- Mapper concreto: productosMapper\n- Repo concreto (especializaci\u00f3n del gen\u00e9rico): productosRepo\n\nAqui puedes ver como hacerlo con este ejemplo: \n\n```python\nfrom pydantic import BaseModel, Field\n\nclass ItemIn(BaseModel):\n nombre: str = Field(min_length=1)\n\nclass ItemOut(ItemIn):\n id: int\n\n##############################\n\nfrom sqlalchemy import Column, String\nfrom backend.db.model_base import Model_base\n\nclass Item(Model_base):\n __tablename__ = \"item\" # opcional; si no, usa el nombre por defecto\n __table_args__ = {\"schema\": \"public\"}\n nombre = Column(String, nullable=False, index=True)\n\n##############################\n\nfrom domains.ejemplo.domain import ItemIn, ItemOut\nfrom domains.ejemplo.model import Item\n\nclass ItemMapper:\n @staticmethod\n def to_model(d: ItemIn) -> Item:\n return Item(nombre=d.nombre)\n\n @staticmethod\n def from_model(m: Item) -> ItemOut:\n return ItemOut(id=m.id, nombre=m.nombre)\n\n @staticmethod\n def to_dict(d: ItemIn) -> dict:\n return {\"nombre\": d.nombre}\n\n @staticmethod\n def from_dict(data: dict) -> ItemIn:\n return ItemIn(**data)\n\n\n#############################\n\n\nfrom typing import Optional\nfrom sqlalchemy.orm import Session\nfrom domains.ejemplo.model import Item\nfrom domains.ejemplo.mapper import ItemMapper\nfrom domains.arquitecture_layer.Repo import Repo_base # tu base\n\nclass ItemRepo(Repo_base[Item, \"ItemIn|ItemOut\"]):\n def __init__(self, session: Session):\n super().__init__(session=session, modelo=Item, mapper=ItemMapper)\n\n```\n\n\n\ud83e\uddf1 Esquema y tabla\n- schema: public (ej. \"ventas\")\n- __tablename__: productos (ej. \"clientes\")\n\n\ud83d\uddc2\ufe0f Campos (excluyendo sys_*, que ya aporta Model_base):\n\n\nid: int | Integer | nullable=False | comentarios=\"PK del producto\"\nsku: str | String(64) | nullable=False | comentarios=\"C\u00f3digo \u00fanico del producto\"\nnombre: str | String(200) | nullable=False | comentarios=\"Nombre del producto\"\ndescripcion: str | Text | nullable=True | default=None | comentarios=\"Descripci\u00f3n detallada\"\nprecio: Decimal | Numeric(12,2) | nullable=False | default=0.00 | comentarios=\"Precio base\"\nstock: int | Integer | nullable=False | default=0 | comentarios=\"Unidades disponibles\"\nactivo: bool | Boolean | nullable=False | default=True | comentarios=\"Si el producto est\u00e1 activo\"\n\n\n\n\ud83d\udd11 Clave primaria\n- Usa el ID autoincremental por defecto si est\u00e1 disponible. Si no, crea una columna id BigInteger autoincremental.\n\n\ud83d\udd17 Restricciones / \u00edndices (opcional)\n- uniques: uq_productos_sku (sku)\n- indexes: idx_productos_nombre (nombre); idx_productos_sku (sku)\n\n\ud83d\udd01 Relaciones (opcional)\n\n\n\ud83d\udd52 Zona horaria\n- Mant\u00e9n timestamps timezone-aware (ya lo hace Model_base).\n\n\ud83c\udfaf Reglas de salida / estilo\n- **Devuelve SOLO c\u00f3digo Python v\u00e1lido** (un \u00fanico archivo), sin comentarios fuera del c\u00f3digo ni explicaciones.\n- Importa exactamente desde mis rutas:\n from domains.arquitecture_layer.Repo import Repo_base import Model_base\n from domains.arquitecture_layer.Mapper import Mapper_base import Mapper_base\n from from domains.arquitecture_layer.Model import Model_base import Repo_base\n- Tipado fuerte (Python 3.10+), `from __future__ import annotations` opcional.\n- Dominio como `@dataclass` -> `productosDom`.\n- Modelo ORM hereda de `Model_base` y (si existe) `IdIntMixin`.\n- Usa `__table_args__ = {\"schema\": \"public\"}`.\n- Tipos SQLAlchemy razonables (String(N), Integer, Numeric(p,s), Boolean, DateTime, JSON, etc.).\n- En `Mapper`:\n - `to_model`, `from_model`, `to_dict`, `from_dict` (respetando tipos; Numeric -> float en dominio).\n- `Repo`:\n - Clase `productosRepo(Repo_base[productosModel, productosDom])` con `__init__(self, session)` que fija `Modelo` y `Mapper`.\n - A\u00f1ade 1\u20133 m\u00e9todos de consulta comunes (ej.: `get_by_nombre`, `buscar_por_rango_gasto`, paginado por pa\u00eds), usando `self.session`.\n- No generes `create_all` ni conexi\u00f3n.\n- No dupliques columnas sys_*.\n- Mant\u00e9n nombre de columnas igual a las llaves del dominio y del dict del mapper.\n\n\ud83d\udce6 Salida esperada (estructura del archivo):\n- imports\n- dataclass de dominio `productosDom`\n- clase ORM `productosModel`\n- clase `productosMapper`\n- clase `productosRepo`\n\n\nGenera ahora el archivo `.py` final.\n"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "RGSE",
|
|
"code_hash": "a1204eb54a53c4ed520511cd637e8d2a",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/html": "<span class=\"markdown prose dark:prose-invert\"><h3 id=\"conexion-con-bbdd\">Conexion con bbdd</h3></span>"
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "Kclp",
|
|
"code_hash": "d19ff2ccfb6db6a077b4575908247605",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/plain": ""
|
|
}
|
|
}
|
|
],
|
|
"console": [
|
|
{
|
|
"type": "stream",
|
|
"name": "stdout",
|
|
"text": "\u2714 Tablas creadas\n"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "emfo",
|
|
"code_hash": "efa869609408ddb6b406bf9d23dcd7bc",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/html": "<span class=\"markdown prose dark:prose-invert\"><h3 id=\"falta-por-generar-los-bloques-de-codigos-o-prompttemplates-que-me-ayuden-a-generar-los-usos-con-repo-y-la-logica-para-el-dominio\">Falta por generar los bloques de codigos o prompt/templates que me ayuden a generar los usos con Repo y la logica para el Dominio</h3></span>"
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
},
|
|
{
|
|
"id": "BYtC",
|
|
"code_hash": "87ee1ef84a53699c468cc44edb65111c",
|
|
"outputs": [
|
|
{
|
|
"type": "data",
|
|
"data": {
|
|
"text/plain": ""
|
|
}
|
|
}
|
|
],
|
|
"console": []
|
|
}
|
|
]
|
|
} |