{ "version": "1", "metadata": { "marimo_version": "0.15.5" }, "cells": [ { "id": "Hbol", "code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e", "outputs": [ { "type": "data", "data": { "text/plain": "" } } ], "console": [] }, { "id": "MJUe", "code_hash": "a40093b1686243c3c7cfe538710216ac", "outputs": [ { "type": "data", "data": { "text/plain": "" } } ], "console": [] }, { "id": "vblA", "code_hash": "e01ef05d140a83f20a1373bbc701f873", "outputs": [ { "type": "data", "data": { "text/plain": "" } } ], "console": [ { "type": "stream", "name": "stdout", "text": "\u2139\ufe0f /home/lucas/DataProyects/myrag/backend/.env ya tiene todas las claves necesarias.\n\u2705 Creado /home/lucas/DataProyects/myrag/backend/db/base.py\n\u2705 Creado /home/lucas/DataProyects/myrag/backend/db/session.py\n" } ] }, { "id": "bkHC", "code_hash": "9a66ffddaebf2517492a50f3cec46737", "outputs": [ { "type": "data", "data": { "text/plain": "" } } ], "console": [ { "type": "stream", "name": "stdout", "text": "\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Repo.py \u2192 domains/arquitecture_layer/Repo.py\n\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Mapper.py \u2192 domains/arquitecture_layer/Mapper.py\n\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Model.py \u2192 domains/arquitecture_layer/Model.py\n\u2705 Creado domains/arquitecture_layer/__init__.py\n" } ] }, { "id": "lEQa", "code_hash": "151311bdb1d09f9b69b11b735701f9e4", "outputs": [ { "type": "data", "data": { "text/plain": "" } } ], "console": [] }, { "id": "PKri", "code_hash": "0ec08d2138de5b6833360c8a32834465", "outputs": [ { "type": "data", "data": { "text/html": "
{\n "CAMPOS": [\n "columna: tipo_python | tipo_sqlalchemy | nullable={True/False} | default={valor/opcional} | comentarios='texto'"\n ],\n "RELACIONES": [\n "campo_1 -> FK a public.campo_2 (ondelete='CASCADE/RESTRICT/SET NULL')"\n ],\n "INDEX_LIST": [\n "nombre_indice"\n ],\n "UNIQUE_LIST": [\n "nombre_unico"\n ]\n}\ntrue/false/null en min\u00fascula si incluyes valores l\u00f3gicos o nulos.nombre: str | String(120) | nullable=False | comentarios="Nombre del prueba_simple"\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# Ejemplos.: \ncliente_id -> FK a public.clientes.id (ondelete='RESTRICT'),\nusuario_id -> FK a public.usuarios.id (ondelete='CASCADE')\n\n---\n\n"RELACIONES": "",\n"INDEX\\_LIST": "", # Opcional\n"UNIQUE\\_LIST": "", # Opcional\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# 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# 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()\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"} # Usa el schema al final para evitar el error SchemaItem' object, such as a 'Column' or a 'Constraint' expected\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)\nfrom __future__ import annotations opcional.\n- Dominio No puede ser @dataclass -> prueba_simpleDom.\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 prueba_simpleRepo(Repo_base[prueba_simpleModel, prueba_simpleDom]) 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\ud83d\udce6 Salida esperada (estructura del archivo):\n- imports\n- dataclass de dominio prueba_simpleDom\n- clase ORM prueba_simpleModel\n- clase prueba_simpleMapper\n- clase prueba_simpleRepo\nGenera ahora el archivo .py final.```python\nfrom sqlalchemy import Column, String, Text, Boolean\nfrom sqlalchemy.orm import Session\nfrom domains.arquitecture_layer.Model import Model_base\nfrom domains.arquitecture_layer.Mapper import Mapper_base\nfrom domains.arquitecture_layer.Repo import Repo_base\n\nclass prueba_simpleDom:\n def __init__(self, nombre: str, descripcion: str = None, activo: bool = True):\n self.nombre = nombre\n self.descripcion = descripcion\n self.activo = activo\n\nclass prueba_simpleModel(Model_base):\n __tablename__ = "prueba_simple"\n __table_args__ = (\n {"schema": "public"}\n )\n\n nombre = Column(String(120), nullable=False, comment='Nombre del prueba_simple')\n descripcion = Column(Text, nullable=True, default=None, comment='Descripci\u00f3n opcional')\n activo = Column(Boolean, nullable=False, default=True, comment='Si el registro est\u00e1 activo')\n\nclass prueba_simpleMapper(Mapper_base[prueba_simpleDom, prueba_simpleModel]):\n @staticmethod\n def to_model(obj: prueba_simpleDom) -> prueba_simpleModel:\n return prueba_simpleModel(\n nombre=obj.nombre,\n descripcion=obj.descripcion,\n activo=obj.activo\n )\n\n @staticmethod\n def from_model(model: prueba_simpleModel) -> prueba_simpleDom:\n return prueba_simpleDom(\n nombre=model.nombre,\n descripcion=model.descripcion,\n activo=model.activo\n )\n\n @staticmethod\n def to_dict(obj: prueba_simpleDom) -> dict:\n return {\n "nombre": obj.nombre,\n "descripcion": obj.descripcion,\n "activo": obj.activo\n }\n\n @staticmethod\n def from_dict(data: dict) -> prueba_simpleDom:\n return prueba_simpleDom(\n nombre=data["nombre"],\n descripcion=data.get("descripcion"),\n activo=data.get("activo", True)\n )\n\nclass prueba_simpleRepo(Repo_base[prueba_simpleModel, prueba_simpleDom]):\n def __init__(self, session: Session):\n super().__init__(session=session, modelo=prueba_simpleModel, mapper=prueba_simpleMapper)\n\n def get_by_nombre(self, nombre: str) -> list[prueba_simpleDom]:\n models = self.session.query(self.Modelo).filter_by(nombre=nombre, sys_deleted_at=None).all()\n return self.Mapper.from_model_list(models)\n\n def get_activos(self) -> list[prueba_simpleDom]:\n models = self.session.query(self.Modelo).filter_by(activo=True, sys_deleted_at=None).all()\n return self.Mapper.from_model_list(models)\n\n def get_inactivos(self) -> list[prueba_simpleDom]:\n models = self.session.query(self.Modelo).filter_by(activo=False, sys_deleted_at=None).all()\n return self.Mapper.from_model_list(models)\n