posit-dev / shinylive

Run Shiny on Python and R (compiled to wasm) in the browser
https://shinylive.io/py/examples/
MIT License
193 stars 15 forks source link

Shinylive Python Question: Importing from subfolders #162

Open darrida opened 1 month ago

darrida commented 1 month ago

Curious if it's possible to import modules from local directory-based packages or folders. I feel like I was able to do this a couple of versions back, but I'm unable to now (it's also possible I'm just incorrectly remembering using regular py-shiny as well.

With larger apps/utilities, folders really help for organizing code (i.e., right now I have a utility that contains 28 other module files in the same directory as app.py), but I'm unable to figure out how to successfully import them (I've tried absolute imports, relative imports, regular folders, package folders, etc).

Any direction is helpful. Thanks!

(btw - really enjoying Shinylive)

wch commented 1 month ago

Hi, can you provide a simple example app?

darrida commented 1 month ago

Sure thing. I will try to get something to you tomorrow.

darrida commented 1 month ago

My apologies for not providing an example app yet. I did discover the issue, and figured out why I felt like I "remembered" that it used to work.

On personal projects I use a MacBook, and importing from modules organized into folders works fine in Shinylive.

At work I use a Windows laptop, and that is where importing from folders runs into issues.

The crux of it is this:

When I change the "\\" to a "/" in app.js after exporting the app, importing from modules in folders works (where before the browser error message says the module doesn't exist).

I didn't dig into your repo to see if the path is being generated in Python or something else like Javascript, but it seems like it's the kind of behavior created by pathlib.Path when run on Windows and expecting unix formatted paths (i.e., We develop on Windows and run on Linux servers. In our internal tooling we sometimes have to use PurePosixPath on paths where the string is passed to the linux server after being formatted on our local Windows laptops).

The crude tool I hurriedly threw together today as a temporary workaround is just a script that finds those locations in app.js and "fixes" the path to be a unix format. When I export the shinylive app, then run this script, my app with lots of modules nested in subfolders works.

import re
from pathlib import Path

filepath = Path(__file__).resolve().parent / "staging" / "app-name" / "app.json"

with open(filepath) as f:
    text = f.read()

def fix_path_slashes(match_obj):
    return re.sub(r"\\\\", "/", match_obj.group(1))

matches = re.findall(r'("name": "\S*.py", "content":)', text)
matches = [x for x in matches if "\\" in x]
if matches:
    print("FIXING:")
    for m in matches:
        print(">", m.replace('"name": "', "").replace('", "content":', ""))
    text = re.sub(r'("name": "\S*.py", "content":)', fix_path_slashes, text)

    with open(filepath, "w") as f:
        f.write(text)