import os from dotenv import load_dotenv from sqlalchemy import Table, Column, String, Text, MetaData from pgvector.sqlalchemy import Vector from sqlalchemy.orm import registry, Session from src.TextManager.nota import Nota from src.ConexionSql.Base_conexion import ConexionBase from typing import Tuple import re from src.ArquitectureLayer.Mapper import Mapper_base from src.ArquitectureLayer.Model import Model_base from src.ArquitectureLayer.Repo import Repo_base from src.Credenciales.postgres_credencial import PostgresCredencial # Asegúrate de tener esta clase implementada correctamente titulo = os.getenv('DB_TITLE') usuario = os.getenv('DB_USER') passwrd = os.getenv('DB_PASSWORD') host = os.getenv('DB_HOST') port = os.getenv('DB_PORT') db_name = os.getenv('DB_NAME') db_credencial = PostgresCredencial( titulo=titulo, user=usuario, password=passwrd, host=host, port=port, dbname=db_name ) # from entrypoint.init_db import db_credencial from src.Logger.logger_db import LoggerDB, logger LoggerDB(db_credencial, "logger_textos", created_by="sistema") from src.base import Base # Este es tu declarative_base() # ---------------------- # Cargar .env # ---------------------- from entrypoint import ENV_PATH load_dotenv(ENV_PATH) # ---------------------- # REGISTRO DINÁMICO PARA TABLAS # ---------------------- mapper_registry = registry() # ---------------------- # FUNCIONES AUXILIARES # ---------------------- def generar_tabla_nota_para_biblioteca(biblioteca_nombre: str, vector_dim: int, metadata: MetaData) -> Tuple[Table, type]: """ Genera una tabla dinámica y modelo ORM para una biblioteca dada, con campos vectoriales y campos del sistema. """ try: logger.info(f"Generando tabla para biblioteca: '{biblioteca_nombre}' con dimensión de vector: {vector_dim}") # Nombre SQL-safe nombre_tabla = re.sub(r"[^a-zA-Z0-9_]", "_", biblioteca_nombre.strip().lower()) logger.debug(f"Nombre de tabla SQL-safe: '{nombre_tabla}'") # Modelo ORM dinámico class NotaModel(Base, Model_base): __tablename__ = nombre_tabla __table_args__ = {"extend_existing": True} id = Column(String, primary_key=True) titulo = Column(String, nullable=False) tags = Column(String) conexiones = Column(String) texto = Column(Text) resumen = Column(Text) vector = Column(Vector(vector_dim), nullable=True) vector_resumen = Column(Vector(vector_dim), nullable=True) logger.info(f"Modelo ORM 'NotaModel' creado para la tabla '{nombre_tabla}'") logger.debug(f"Columnas del modelo: {[c.name for c in NotaModel.__table__.columns]}") logger.debug(f"Tipos de columnas: {[str(c.type) for c in NotaModel.__table__.columns]}") logger.debug(f"Claves primarias: {[c.name for c in NotaModel.__table__.primary_key]}") return NotaModel.__table__, NotaModel except Exception as e: logger.error(f"Error al generar la tabla y modelo ORM para '{biblioteca_nombre}': {e}") raise # ---------------------- # MAPPER # ---------------------- class NotaMapper(Mapper_base[Nota, object]): # Usa `object` si el modelo es dinámico @staticmethod def to_model(nota: Nota): return { "id": nota.id, "titulo": nota.titulo, "tags": ",".join(nota.tags), "conexiones": ",".join(nota.conexiones), "texto": nota.texto, "resumen": nota.resumen, "vector": nota.vector, "vector_resumen": nota.vector_resumen } @staticmethod def from_model(model) -> Nota: return Nota( id=model.id, titulo=model.titulo, tags=model.tags.split(",") if model.tags else [], conexiones=model.conexiones.split(",") if model.conexiones else [], texto=model.texto, resumen=model.resumen, vector=list(model.vector) if model.vector is not None else None, vector_resumen=list(model.vector_resumen) if model.vector_resumen is not None else None ) @staticmethod def to_dict(nota: Nota) -> dict: return { "id": nota.id, "titulo": nota.titulo, "tags": ",".join(nota.tags), "conexiones": ",".join(nota.conexiones), "texto": nota.texto, "resumen": nota.resumen, "vector": nota.vector, "vector_resumen": nota.vector_resumen } @staticmethod def from_dict(data: dict) -> Nota: return Nota( id=data["id"], titulo=data["titulo"], tags=data["tags"].split(",") if data.get("tags") else [], conexiones=data["conexiones"].split(",") if data.get("conexiones") else [], texto=data["texto"], resumen=data["resumen"], vector=data.get("vector"), vector_resumen=data.get("vector_resumen") ) # ---------------------- # REPO # ---------------------- class NotaRepo(Repo_base): def __init__(self, session: Session, modelo_nota: type): if modelo_nota is None: raise ValueError("No se puede instanciar NotaRepo sin un modelo válido de nota.") super().__init__(session=session, modelo=modelo_nota, mapper=NotaMapper) # ------------------------ # Métodos personalizados # ------------------------ def get_by_tag(self, tag: str) -> list[Nota]: query = self.session.query(self.Modelo).filter(self.Modelo.tags.contains([tag])) models = query.all() return self.Mapper.from_model_list(models) def search_by_text(self, texto: str) -> list[Nota]: query = self.session.query(self.Modelo).filter(self.Modelo.texto.ilike(f'%{texto}%')) models = query.all() return self.Mapper.from_model_list(models) def get_paginated(self, offset: int = 0, limit: int = 10) -> list[Nota]: models = self.session.query(self.Modelo).offset(offset).limit(limit).all() return self.Mapper.from_model_list(models) def update(self, id_: str, nota_actualizada: Nota) -> bool: model = self.session.get(self.Modelo, id_) if not model: return False # Campos de dominio model.titulo = nota_actualizada.titulo model.texto = nota_actualizada.texto model.tags = nota_actualizada.tags model.conexiones = nota_actualizada.conexiones model.resumen = nota_actualizada.resumen # Actualización de campos de sistema model.sys_version = (model.sys_version or 1) + 1 self.session.commit() return True