import marimo __generated_with = "0.15.5" app = marimo.App(width="columns") @app.cell def _(): import marimo as mo return (mo,) @app.cell def _(): from backend.db.session import engine import pandas as pd return engine, pd @app.cell def _(engine, mo, pd): # Query directo a information_schema query_tables = """ SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name; """ tables_df = pd.read_sql(query_tables, engine) # Dropdown para elegir tabla table_selector = mo.ui.dropdown( options=tables_df["table_name"].tolist(), label="Selecciona una tabla", allow_select_none= False, value=tables_df["table_name"][0] ) table_selector return (table_selector,) @app.cell(hide_code=True) def _(mo): mo.md(r"""## Leer Datos""") return @app.cell def _( delete_status, engine, final_status, insert_button, inserted_data, mo, pd, soft_delete_status, soft_restore_status, table_selector, ): inserted_data final_status delete_status soft_delete_status soft_restore_status insert_button query_data = f'SELECT * FROM "{table_selector.value}" LIMIT 1000;' df_preview = pd.read_sql(query_data, engine) mo.ui.table(df_preview) # editor visual return @app.cell def _(mo): get_obj_id_input = mo.ui.number(label="ID a recuperar", value=1) get_obj_button = mo.ui.run_button(label="Recuperar objeto", kind="neutral") mo.hstack([get_obj_id_input, get_obj_button]) return get_obj_button, get_obj_id_input @app.cell def _(get_obj_button, get_obj_id_input, mo, repo, session, table_selector): obj_status = "ℹ️ Introduce un ID y pulsa *Recuperar*." obj_python = None if get_obj_button.value and table_selector.value: try: # Usamos el repo → devuelve un dominio (no un dict) _fetched_obj = repo.get_by_id(get_obj_id_input.value) if _fetched_obj: obj_status = f"✅ Objeto recuperado con ID {get_obj_id_input.value}" obj_python = _fetched_obj else: obj_status = f"⚠️ No se encontró registro con ID {get_obj_id_input.value}" except Exception as exc: session.rollback() obj_status = f"❌ Error al recuperar: {exc!s}" mo.vstack([ mo.md(f"**Estado:** {obj_status}"), mo.inspect(obj_python) ]) return @app.cell(hide_code=True) def _(mo): mo.md(r"""## Crear datos""") return @app.cell(hide_code=True) def _(engine, pd, table_selector): from sqlalchemy import text table_selector query_columns = text(""" SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = :table_name AND column_name NOT LIKE 'sys_%' ORDER BY ordinal_position; """) columns_df = pd.read_sql(query_columns, engine, params={"table_name": table_selector.value}) columns_df = columns_df[ ~( (columns_df["column_name"] == "id") & (columns_df["column_default"].notnull()) & (columns_df["column_default"].str.contains("nextval")) ) ] return (columns_df,) @app.cell def _(columns_df, mo, table_selector): # Generamos inputs dinámicamente con soporte para fechas ui_inputs = {} for _, row in columns_df.iterrows(): col, dtype = row["column_name"], row["data_type"] if dtype in ("integer", "numeric", "bigint", "smallint"): ui_inputs[col] = mo.ui.number(label=col) elif dtype in ("boolean",): ui_inputs[col] = mo.ui.checkbox(label=col) elif dtype in ("date", "timestamp", "timestamp without time zone", "timestamp with time zone"): ui_inputs[col] = mo.ui.date(label=col) # 👉 usar date picker de Marimo else: ui_inputs[col] = mo.ui.text(label=col) # Botón run_button_add = mo.ui.run_button( label=f"Agregar fila a {table_selector.value}", kind="neutral" ) # Layout de inputs + botón mo.vstack(list(ui_inputs.values()) + [run_button_add]) return run_button_add, ui_inputs @app.cell def _(MapperCls, mo, repo, run_button_add, table_selector, ui_inputs): _status_message = "ℹ️ Completa los campos y pulsa *Agregar fila*." inserted_id = None inserted_data = None if run_button_add.value and table_selector.value: try: # Recoger inputs y normalizar fechas a ISO raw_data = {} for _col, ui in ui_inputs.items(): value = ui.value if hasattr(value, "isoformat"): # 👉 si es date/datetime raw_data[_col] = value.isoformat() elif value == "" or value is None: # 👉 convertir '' → None raw_data[_col] = None else: raw_data[_col] = value # Convertir dict → dominio con el mapper correcto dominio_obj = MapperCls.from_dict(raw_data) # Insertar en la tabla usando el repo correcto inserted_id = repo.add(dominio_obj, created_by="admin") _status_message = f"✅ Registro insertado en {table_selector.value} con ID: {inserted_id}" inserted_data = raw_data except Exception as exc: _status_message = f"❌ Error al insertar: {exc!s}" mo.vstack([ mo.md(f"**Estado:** {_status_message}"), mo.json(inserted_data) if inserted_data else mo.md("") ]) return (inserted_data,) @app.cell def _(mo): mo.md(r"""Para añadir un objeto dentro de la base de datos:""") return @app.cell def _(mo): from domains.nota import notaDom # Aqui genera tu objeto de python # objeto_python = notaDom( # nombre="Prueba", # descripcion="Registro de prueba insertado desde Python", # activo=True, # ) insert_button = mo.ui.run_button(label="Insertar objeto en la bbdd", kind="neutral") mo.vstack([ # mo.inspect(objeto_python), insert_button]) return (insert_button,) @app.cell def _(ejemploRepo, insert_button, mo, objeto_python, session): insert_status = "ℹ️ Pulsa el botón para insertar el objeto." nuevo_id = None if insert_button.value: try: # Instancia del repo repo_objeto = ejemploRepo(session) # Inserción en la BD nuevo_id = repo_objeto.add( objeto_python, created_by="admin", notes="Inserción desde código Python" ) insert_status = f"✅ Registro insertado con ID: {nuevo_id}" except Exception as exc: session.rollback() insert_status = f"❌ Error al insertar: {exc!s}" mo.md(insert_status) return @app.cell(hide_code=True) def _(mo): mo.md(r"""## Actualizar datos""") return @app.cell(hide_code=True) def _(mo): update_id_input = mo.ui.number(label="ID a actualizar", value=1) update_fetch_button = mo.ui.run_button(label="Buscar registro", kind="neutral") confirm_update_button = mo.ui.run_button(label="Confirmar actualización", kind="success") mo.hstack([update_id_input, update_fetch_button, confirm_update_button]) return confirm_update_button, update_fetch_button, update_id_input @app.cell def _( MapperCls, mo, repo, session, table_selector, update_fetch_button, update_id_input, ): # Valores iniciales _update_status = "ℹ️ Introduce un ID y pulsa *Buscar*." update_dic = None if update_fetch_button.value and table_selector.value: try: fetched_obj = repo.get_by_id(update_id_input.value) if fetched_obj: fetched_dict = MapperCls.to_dict(fetched_obj) update_dic = { col: ( mo.ui.checkbox(value=bool(fetched_dict[col]), label=col) if isinstance(fetched_dict[col], bool) else mo.ui.number(value=fetched_dict[col], label=col) if isinstance(fetched_dict[col], (int, float)) else mo.ui.date(value=fetched_dict[col], label=col) if hasattr(fetched_dict[col], "isoformat") else mo.ui.text(value=str(fetched_dict[col]) if fetched_dict[col] is not None else "", label=col) ) for col in fetched_dict.keys() if not col.startswith("sys_") and col != "id" } _update_status = f"✅ Registro encontrado con ID: {update_id_input.value}" else: _update_status = f"⚠️ No se encontró registro con ID: {update_id_input.value}" except Exception as exc: session.rollback() _update_status = f"❌ Error al buscar: {exc!s}" mo.vstack([ mo.md(f"**Estado:** {_update_status}"), mo.vstack(list(update_dic.values())) if update_dic else mo.md("") ]) return (update_dic,) @app.cell(hide_code=True) def _(confirm_update_button, mo, repo, session, update_dic, update_id_input): # Estado por defecto final_status = "ℹ️ Esperando confirmación de actualización." if confirm_update_button.value and update_dic is not None: try: updated_data = {} for _col, _ui in update_dic.items(): val = _ui.value # Normalización: "" → None if val == "": updated_data[_col] = None # Normalización: fechas → ISO string elif hasattr(val, "isoformat"): updated_data[_col] = val.isoformat() else: updated_data[_col] = val ok = repo.update( update_id_input.value, updated_data, updated_by="admin", notes="Actualización desde UI" ) if ok: final_status = f"✅ Registro con ID {update_id_input.value} actualizado correctamente." else: final_status = f"⚠️ No se pudo actualizar el registro con ID {update_id_input.value}." except Exception as exc: session.rollback() final_status = f"❌ Error al actualizar: {exc!s}" mo.md(f"**Estado final:** {final_status}") return (final_status,) @app.cell(hide_code=True) def _(mo): mo.md(r"""## Eliminar datos""") return @app.cell def _(mo): delete_id_input = mo.ui.number(label="ID a eliminar", value=1) delete_button = mo.ui.run_button(label="Hard delete", kind="danger") mo.hstack([delete_id_input, delete_button]) return delete_button, delete_id_input @app.cell def _(delete_button, delete_id_input, mo, repo, session, table_selector): delete_status = "ℹ️ Introduce un ID y pulsa *Eliminar*." if delete_button.value and table_selector.value: try: _ok = repo.delete_by_id(delete_id_input.value) if _ok: delete_status = f"✅ Registro con ID {delete_id_input.value} eliminado permanentemente." else: delete_status = f"⚠️ No se encontró registro con ID {delete_id_input.value}." except Exception as exc: session.rollback() delete_status = f"❌ Error al eliminar: {exc!s}" mo.md(f"**Estado:** {delete_status}") return (delete_status,) @app.cell def _(mo): soft_delete_id_input = mo.ui.number(label="ID a eliminar (soft)", value=1) soft_delete_button = mo.ui.run_button(label="Soft delete", kind="warn") mo.hstack([soft_delete_id_input, soft_delete_button]) return soft_delete_button, soft_delete_id_input @app.cell def _( mo, repo, session, soft_delete_button, soft_delete_id_input, table_selector, ): soft_delete_status = "ℹ️ Introduce un ID y pulsa *Soft delete*." if soft_delete_button.value and table_selector.value: try: _ok = repo.soft_delete( soft_delete_id_input.value, deleted_by="admin", notes="Eliminado desde UI" ) if _ok: soft_delete_status = f"✅ Registro con ID {soft_delete_id_input.value} marcado como eliminado (soft delete)." else: soft_delete_status = f"⚠️ No se encontró registro con ID {soft_delete_id_input.value} o ya estaba eliminado." except Exception as exc: session.rollback() soft_delete_status = f"❌ Error al aplicar soft delete: {exc!s}" mo.md(f"**Estado:** {soft_delete_status}") return (soft_delete_status,) @app.cell def _(mo): soft_restore_id_input = mo.ui.number(label="ID a restaurar (soft)", value=1) soft_restore_button = mo.ui.run_button(label="Restaurar registro", kind="success") mo.hstack([soft_restore_id_input, soft_restore_button]) return soft_restore_button, soft_restore_id_input @app.cell def _( mo, repo, session, soft_restore_button, soft_restore_id_input, table_selector, ): soft_restore_status = "ℹ️ Introduce un ID y pulsa *Restaurar*." if soft_restore_button.value and table_selector.value: try: _ok = repo.soft_restore( soft_restore_id_input.value, restored_by="admin", notes="Restaurado desde UI" ) if _ok: soft_restore_status = f"✅ Registro con ID {soft_restore_id_input.value} restaurado correctamente." else: soft_restore_status = f"⚠️ No se encontró registro con ID {soft_restore_id_input.value} o no estaba eliminado." except Exception as exc: session.rollback() soft_restore_status = f"❌ Error al restaurar: {exc!s}" mo.md(f"**Estado:** {soft_restore_status}") return (soft_restore_status,) @app.cell def _(): # # Arregla transacciones rotas # try: # _dominio_obj = MapperCls.from_dict(raw_data) # _inserted_id = repo.add(_dominio_obj, created_by="admin") # _status_message = f"✅ Insertado con ID {_inserted_id}" # except Exception as exc: # session.rollback() # 👈 limpia la transacción rota # _status_message = f"❌ Error al insertar: {exc!s}" return @app.cell(hide_code=True) def _(table_selector): import importlib from backend.db.session import SessionLocal def load_classes(entidad: str): """ Importa dinámicamente Repo, Mapper y Dom según convención: - Módulo: domains.{entidad} (minúsculas) - Clases: {EntidadCapitalizada}Repo, {EntidadCapitalizada}Mapper, {EntidadCapitalizada}Dom """ entidad = entidad.lower() # siempre minúscula para el módulo module_name = f"domains.{entidad}" module = importlib.import_module(module_name) # Nombre base: capitalizar primera letra base_name = entidad.lower() repo_class = getattr(module, f"{base_name}Repo") mapper_class = getattr(module, f"{base_name}Mapper") dom_class = getattr(module, f"{base_name}Dom") return repo_class, mapper_class, dom_class # 👉 Uso interactivo en Marimo session = SessionLocal() if table_selector.value: # valor que escribiste en el textarea RepoCls, MapperCls, DomCls = load_classes(table_selector.value) repo = RepoCls(session) return MapperCls, repo, session if __name__ == "__main__": app.run()