Como eu (quase) matei o VBA do Excel com o Python

O pessoal do mercado financeiro ama Excel, mas aposto que todo mundo odeia ter que lidar com VBA.

Aqui na empresa não adianta, não consegui convencer que Web é melhor que Excel para ferramentas complexas. Aqueles que desenvolvem ferramentas querem continuar usando apenas Excel e Python, porém, até então, não existe uma maneira fácil de integrar essas duas ferramentas, precisamos passar raiva com VBA de maneira má escrita e macros aterrorizantes.

Então eu reuni todo o ódio que eu tenho por VBA, eu decidi me juntar a ele, para matá-lo de dentro pra fora...

Desenvolvi um proof of concept, aqui está ele:

Video no Youtube

www.youtube.com/watch?v=Be4ytQViWY0

VBA

São basicamente 3 linhas! (depois de desenvolver a biblioteca para tornar isso possível)

Primeiro é definido o que eu chamei de "headers", que é um dicionário contendo a chave sendo como o dado vai transitar pela API (Python) e o valor que seria o nome da coluna para humanos (Excel).

Dim headers As Object

Sub init()
    
    Set headers = Dict( _
        "a", "Variável A", _
        "b", "Variável B", _
        "result", "Resultado" _
    )

End Sub


Sub generate_input()
    init
    
    DataView.fetch "/demo/generate_input", , headers

End Sub


Sub calculate()
    init
    
    Set input_data = DataView.from_table("input", List("a", "b"), headers)
    
    DataView.fetch "/demo/multiply", input_data, headers
    
End Sub

Python (FastAPI)

Ultimamente ando usando o FastAPI, ele já resolve muitas coisas de graça! Junto com ele estou usando o pandera, que é uma lib para validação de dataframes do pandas, e de brinde ele nos entrega uma class muito parecida com o BaseModel do pydantic.

Imports

from fastapi import APIRouter
import numpy
import pandas as pd
import pandera as pa
from common import DataView, NumberFormats

from pandera.typing import DataFrame, Series

A única coisa diferente aqui é a importação do DataView, que é uma classe que eu criei para facilitar na comunicação com a lib no VBA

Definição dos schemas

router = APIRouter(prefix="/demo", tags=["Demo"])


class Input(pa.DataFrameModel):
    a: Series[float] = pa.Field(coerce=True)
    b: Series[float] = pa.Field(coerce=True)


class Output(pa.DataFrameModel):
    result: Series[float] = pa.Field(coerce=True)

Definição das funções

@router.post("/generate_input")
def generate_input():
    df = pd.DataFrame(dict(a=numpy.random.rand(10) * 10, b=numpy.random.rand(10)))

    return DataView(
        df,
        schema=Input,
        table_name="input", # Uma tabela no Excel deve ter esse mesmo nome
        formatters=dict(
            a=NumberFormats.INTEGER,
            b=NumberFormats.DECIMAL_2,
        ),
    )


@router.post("/multiply")
def multiply(df: DataFrame[Input]):
    df["result"] = df["a"] * df["b"]

    return DataView(
        df,
        schema=Output,
        table_name="output", # Uma tabela no Excel deve ter esse mesmo nome
        formatters=dict(
            result=NumberFormats.PERCENT_2,
        ),
    )

It just works

E simples assim, seguindo a vida mais feliz, cortando um belo pedaço de uma linguagem datada.

Finalização

O que acharam dessa gambiarra maravilhosa? Acham que seria útil para vocês? Eu fiz o certo? Comentem seus pensamentos. Obrigado!

Estou lendo do celular, em casa vejo melhor o código. Meus Parabéns, na força do ódio. kkk

hauhauah valeu pelo apoio! Às vezes até o ódio pode nos levar a soluções criativas. Fique à vontade pra perguntar ou discutir a ideia