Open thadk opened 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;
}
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>}"
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)
We can use .runPython()
instead of . runPythonAsync()
to avoid using await
and async functions.
Is this issue closed, or do we need to do more?
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.
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 })
}
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.