widgetti / solara

A Pure Python, React-style Framework for Scaling Your Jupyter and Web Apps
https://solara.dev
MIT License
1.9k stars 139 forks source link

How to add TileLayer's from an external package? #733

Open dfguerrerom opened 2 months ago

dfguerrerom commented 2 months ago

I'm trying to access to a dictionary of TileLayer's that I have created in an external repository where I collect several sources, I do that by:

from sepal_ui.mapping.basemaps import basemap_tiles

That basemap_tiles is just a dictionary containing a key with a known name and a value with its corresponding TileLayer, the problem happens when I try to create a new Map from ipyleaflet that initializes and adds one of those custom basemaps:


import solara
from ipyleaflet import Map, TileLayer
from sepal_ui.mapping.basemaps import basemap_tiles

class MyMap(Map):
    def __init__(self):
        super().__init__(scroll_wheel_zoom=True, center=(48.85, 2.35), zoom=12)

        layer = basemap_tiles["Esri.WorldStreetMap"]
        self.add_layer(layer)

@solara.component
def Page():
    MyMap.element()

This raises the following error:

 RuntimeError(f"Widget {type(self)} has been closed, the stacktrace when the widget was closed is:\n{closed_stack[id(self)]}")

After carefully digging into the error, I came up with a simply solution and probably also the root of the problem...

If I redefine all the TileLayer elements but now within the context of my sol.py page, the problem is solved:

basemap_tiles = {k: eval(str(v)) for k, v in basemap_tiles.items()}

I assume that Solara in someway will "wrap" all the custom widgets to be compatible with it, my question is: how and how is done this process? is there a way to also wrap widgets from custom packages?

lopezvoliver commented 2 months ago

I am also facing this issue more generally trying to import any widget or layer. Here's a minimal reproducible example:

import solara
import ipyleaflet
import ipywidgets

imported_widget = ipywidgets.Label(value="LABEL")   # same error if we import it from a module, e.g.:
# from .mymodule import imported_widget

class Map(ipyleaflet.Map):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        widget_ctrl = ipyleaflet.WidgetControl(widget=imported_widget, position="topright")
        self.add(widget_ctrl)

@solara.component
def Page():
    Map.element()

Currently I have to define all widgets and layers inside the class, so the file is getting huge. I tried restructuring the code using modules for my reusable widgets, but this issue makes it impossible to do so.