codeforboston / home-energy-analysis-tool

https://www.codeforboston.org/projects/
MIT License
8 stars 25 forks source link

Frontend: Encapsulate Pyodide component from root.tsx #66

Open thadk opened 10 months ago

thadk commented 10 months ago

Experiment with a better encapsulating approach for the Pyodide call and React Hooks into one or more components so that we can add user input forms later around it. The new components should have their own file(s) distinct from root.tsx.

https://github.com/codeforboston/home-energy-analysis-tool/blob/5510bc960aeb661f4242a12c14ae0ad0ca84e628/heat-stack/app/root.tsx#L184-L196

https://github.com/codeforboston/home-energy-analysis-tool/blob/5510bc960aeb661f4242a12c14ae0ad0ca84e628/heat-stack/app/root.tsx#L206-L207

@HassanElsherbini has tentatively volunteered.

HassanElsherbini commented 10 months ago

One approach is to encapsulate the making of python calls into a custom react hook. (avoiding the loading of pyodide on each use is probably more ideal)

Example:

import { useState, useEffect } from 'react';

import * as pyodideModule from 'pyodide'
import engine from '../../../rules-engine/src/rules_engine/engine.py';

const loadPyodide = async () => {
    const pyodide: any = await pyodideModule.loadPyodide({
        // public folder
        indexURL: 'pyodide-env/',
    })

    await pyodide.loadPackage("numpy")
    await pyodide.runPythonAsync(engine);
    return pyodide;
};

export function usePython(code: string){
    const [output, setOutput] = useState<string | null>('(loading python...)');

    useEffect(() => {
        const run = async () => {
            const pyodide: any = await loadPyodide();
            const result = await pyodide.runPythonAsync(code);
            setOutput(result);
        };
        run();
    }, []);

    return output;
}
spghtti commented 10 months ago

Run with DevTools open. Wait 30 seconds for debugger to catch up.

window.pydd = pydd;

Then, resume code


let fuelProxy = await pydd.runPythonAsync(`
fuel = FuelType.GAS
fuel`);

Proxy { : {…}, : {…} }

// Debugging
// let home = await pydd.runPythonAsync(`home = Home(${fuelProxy}, 1)
home`);

let home = await pydd.runPythonAsync(home = Home(fuel, 1) home);

> Proxy { <target>: {…}, <handler>: {…} }
Run the method in JS to initialize billing period, passing trial parameters

// Check args. Tweaked to (potentially) match test home.initialize_billing_periods([[1,2,3], [1, 2, 3]], [4, 5]);

> undefined 

Run python inside the runPythonAsync:

let billingPeriod = await pydd.runPythonAsync(billingPeriod = BillingPeriod([1, 2, 3], 10, home) billingPeriod);

> Proxy { <target>: {…}, <handler>: {…} }

Flipped "inside out" in JS to run python BillingPeriod constructor implicitly:

billingPeriod = await pydd.globals.get("BillingPeriod")([1, 2, 3], 10,pydd.globals.get("home")).toString()


"<class '__main__.BillingPeriod'>" 

`pydd.globals.toString()`

>   "{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '_pyodide_core': <module '_pyodide_core' (built-in)>, 'annotations': _Feature((3, 7, 0, 'beta', 1), None, 16777216), 'sts': <module 'statistics' from '/lib/python311.zip/statistics.py'>, 'Enum': <enum 'Enum'>, 'List': typing.List, 'np': <module 'numpy' from '/lib/python3.11/site-packages/numpy/__init__.py'>, 'hdd': <function hdd at 0x147bd28>, 'period_hdd': <function period_hdd at 0x956b50>, 'average_indoor_temp': <function average_indoor_temp at 0x1473170>, 'average_heat_load': <function average_heat_load at 0x14e8d38>, 'max_heat_load': <function max_heat_load at 0x155a2c8>, 'FuelType': <enum 'FuelType'>, 'Home': <class '__main__.Home'>, 'BillingPeriod': <class '__main__.BillingPeriod'>, 'test_classes': <function test_classes at 0x14a3058>, 'fuel': <FuelType.GAS: 100000>, 'home': <__main__.Home object at 0x10ae028>, 'billingPeriod': <__main__.BillingPeriod object at 0x1fe3f78>}" 
bugzpodder commented 10 months ago

I have being playing around with pyodide in nextjs for a while.

What works for me is a regular async function that takes:

you can use something like swr or react-query to handle the loading/error states. The suggested usePython hook you had only returns a single string and might not be always useful (eg when you want to figure out loading/error state or when you what to return more than just a string)

plocket commented 10 months ago

We can use .runPython() instead of . runPythonAsync() to avoid using await and async functions.

plocket commented 9 months ago

Is this issue closed, or do we need to do more?

thadk commented 9 months ago

This issue will be completed when our instance of pyodide has its own component or React hook. So not quite yet but I think it can be one of the first things we do once we make a simple root page.

bugzpodder commented 9 months ago

briefly looked at the code again, I would just do: https://tanstack.com/query/v5/docs/react/reference/useQuery

async function run(ctx) {
   const pyodide: any = await runPythonScript(); 
   return await pyodide.runPythonAsync(ctx.queryKey[1]); 
}

async function usePython(code) {
  return useQuery({ queryKey: ['pyodide', code], queryFn: run })
}