PyPb: llamando a Python desde PowerBuilder 2025 R2 (con un Excel de propina)

Esta semana me he puesto a trastear con una de las novedades beta de PowerBuilder 2025 R2: PyPb, el puente que ha publicado Appeon para llamar a Python directamente desde PowerScript, sin escribir una sola línea de C# ni montar ninguna API web. Y os adelanto la conclusión: engancha.

¿Qué es PyPb y por qué me interesa?

PyPb es una capa de objetos sobre Python.NET. Traducido: desde un objeto de PowerBuilder puedo importar un módulo de Python, llamar a sus funciones, instanciar clases y convertir los resultados a tipos nativos de PB. Todo en proceso, sin tuberías raras ni ejecutables externos. De repente, todo el ecosistema de Python (pandas, numpy, OpenCV, openpyxl…) queda a un of_import de distancia.

Pero ojo: el objetivo de mi ejemplo no es “Python contra PowerBuilder”. El objetivo es tener una clase reutilizable que me permita integrar cualquier cosa de Python con comodidad. A esa clase la he llamado n_cst_pyton, y es la verdadera protagonista.

La pieza central: n_cst_pyton

Es una fachada fina sobre PyPb, con el manejo de errores al estilo de toda la vida (0/-1 + of_lasterror()) y sin excepciones saliendo por ahí. Arrancar Python y pedirle la versión queda así de tonto:

n_cst_pyton lnv_py
string ls_ver

lnv_py = CREATE n_cst_pyton
lnv_py.of_init("…\python.runtime\python313.dll")          // arranca el runtime
lnv_py.of_invoke("platform", "python_version", ls_ver)    // import + llamada
// ls_ver  ->  "3.13.1"
DESTROY lnv_py

Los métodos que expone:

Método Para qué
of_init(dll) Arranca (o reutiliza) el runtime de Python
of_import(modulo, ref mod) Importa un módulo (con caché)
of_invoke(modulo, func, ref res) Atajo: importa + llama función → string
of_run(sentencia, ref res) Ejecuta una expresión Python suelta
of_exec_req(sentencia, req, ref res) Ejecuta Python con variables locales
of_lasterror() El último error capturado

Que el cliente no instale nada: Python embebido

Aquí está, para mí, lo más importante de cara a producción. En vez de pedirle al usuario que se instale Python, empaqueto un Python “embeddable” portable dentro del proyecto (carpeta python.runtime\) y le meto las dependencias con pip install openpyxl -t python.runtime. Junto a eso van los assemblies de PyPb (bin.pypb.appeon\), y listo: doble clic y funciona, sin instalar nada, sin pip y sin internet en la máquina del cliente. Una distribución fija, consistente y testeable.

El caso práctico: un DataWindow de facturas a Excel

¿Por qué Excel? Porque PowerBuilder 2025 trae como mejora la función SaveDisplayedDataAs(…, XLSX!), que ya genera un .xlsx de verdad con los valores tal como se muestran. Así que monté un DataWindow con 300 facturas cargadas desde un JSON, y le puse dos botones:

Nativo · SaveDisplayedDataAs(XLSX!) Python · openpyxl
.xlsx real ✅ ✅
Cabeceras mostradas, moneda, totales ✅ ✅
Respeta columnas ocultas/reordenadas ✅ ✅
Colores / negrita / fuentes ❌ ✅
Autofiltro / cabecera inmovilizada ❌ ✅
Líneas de código 1 un módulo .py + la fachada

La moraleja no es “quién gana”. El nativo te da un Excel correcto en una línea, y es lo que usaré el 90% de las veces. Python entra cuando quiero un informe presentable: cabecera con color, filas tipo cebra, importes en euros, fila de totales en negrita, autofiltro y cabecera congelada. Lo que el nativo, hoy por hoy, no hace.

Para pasarle los datos del grid a Python uso dw_1.ExportJson(), de modo que el Excel sale fiel a lo que se ve.

Lo que he aprendido por el camino (los “pero”)

Como buena beta, PyPb tiene sus aristas. Apunto las que me han mordido, por si os ahorran un rato:

  • Ojo con la autoinstanciación. Según el objeto sea o no autoinstanciable, puede que necesites un CREATE antes de usarlo; si te toca uno que no lo es y se te olvida, te comes un Null object reference al primer método. Conviene tenerlo presente.
  • of_executestatement usa eval() de Python, o sea solo expresiones. Una asignación como ws.title = x revienta con SyntaxError. Para asignar, setattr(ws, 'title', x) (que es una expresión) o tirar de métodos.
  • of_set pasa un System.String de .NET, no un str de Python. Lo pillé porque openpyxl valida el título con expresiones regulares y se quejaba: “expected string or bytes-like object, got 'String'”. La solución fiable es pasar el valor como argumento con nombre de un invocation request.
  • PyPb mantiene un único contexto de Python por proceso y lo reutiliza. No mezcléis runtimes distintos.

La curiosidad de la sesión: PyPb y el Excel nativo se pelean por .NET

Esta merece sección propia porque me tuvo un rato rascándome la cabeza. Resulta que:

  • Python primero, luego nativo: todo bien, y a partir de ahí en cualquier orden.
  • Nativo primero, luego Python: ¡error! El botón de Python petaba con Could not create instance of .NET Assembly: Load bin.pypb.appeon\…dll failed.

Mi primera teoría fue que SaveDisplayedDataAs cambiaba el directorio de trabajo (PyPb carga su DLL por ruta relativa), así que probé a restaurarlo… y no funcionó. No era el directorio. La causa real: PowerBuilder carga el runtime .NET una sola vez por proceso, y manda quien lo inicialice primero. Si el export XLSX nativo lo arranca antes, PyPb ya no puede cargar su assembly.

La solución, sencilla una vez entendido el porqué: pre-cargar PyPb al arrancar la app, antes de abrir nada.

// En el open del objeto aplicación, antes de open(w_main)
n_cst_pyton lnv_pyinit
lnv_pyinit = CREATE n_cst_pyton
lnv_pyinit.of_init(gs_appdir + "\python.runtime\python313.dll")
DESTROY lnv_pyinit

El contexto de Python es global y persiste tras el DESTROY, así que después los botones funcionan en cualquier orden. Pequeños detalles que hacen que la herramienta acabe comportándose como uno espera.

Recursos

En resumen: PyPb todavía es beta y se le notan las costuras, pero el potencial es enorme. Con una clase como n_cst_pyton bien resuelta, integrar Python en PowerBuilder deja de ser “matar moscas a cañonazos” y pasa a ser una herramienta más en la caja. Y lo de empaquetar el runtime para que el cliente no instale nada… eso me ha encantado.

¡Nos vemos en el próximo artículo! Y recuerda: en PowerBuilder, los límites solo están en nuestra imaginación. 🚀

Comentarios