Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: run the app all at once #240

Merged
merged 2 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions component/model/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,9 @@ class Recipe(HasTraits):
recipe_session_path = Unicode("", allow_none=True).tag(sync=True)
"""The path to the recipe session file. This value will come from the recipe view, it will be used by the export csv function and to create the names of the assets to export"""

def __init__(self):
def __init__(self, **delete_aoi):
super().__init__()
self.seplan_aoi = None
self.benefit_model = None
self.constraint_model = None
self.cost_model = None
self.seplan = None

def load_model(self, **delete_aoi):
"""Define all the models required by the module."""

self.seplan_aoi = SeplanAoi(**delete_aoi)
self.benefit_model = cmod.BenefitModel()
self.constraint_model = cmod.ConstraintModel()
Expand All @@ -52,8 +45,6 @@ def load_model(self, **delete_aoi):
self.constraint_model.observe(self.update_changes, "new_changes")
self.cost_model.observe(self.update_changes, "new_changes")

return self

def update_changes(self, change):
"""Increment the new_changes counter by 1."""
self.new_changes += 1
Expand All @@ -66,6 +57,9 @@ def load(self, recipe_path: str):
with recipe_path.open() as f:
data = json.loads(f.read())

# Remove all the "updated" keys from the data in the second level
[validation.remove_key(data, key) for key in ["updated", "new_changes"]]

# load the aoi_model
self.seplan_aoi.import_data(data["aoi"])
self.benefit_model.import_data(data["benefits"])
Expand Down
13 changes: 13 additions & 0 deletions component/scripts/validation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""functions to read and validate a recipe and return meaningful errors."""

import json
from pathlib import Path
from typing import Optional
Expand Down Expand Up @@ -79,3 +80,15 @@ def validate_recipe(
raise ValidationError(e)

return recipe_path


def remove_key(data, key_to_remove):
"""Remove a key from a dictionary."""
if isinstance(data, dict):
if key_to_remove in data:
del data[key_to_remove]
for key, value in list(data.items()):
remove_key(value, key_to_remove)
elif isinstance(data, list):
for item in data:
remove_key(item, key_to_remove)
8 changes: 1 addition & 7 deletions component/tile/custom_aoi_tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,12 @@
class AoiTile(sw.Layout):
"""Overwrite the map of the tile to replace it with a customMap."""

def __init__(self):
def __init__(self, recipe: Recipe):
self.class_ = "d-block custom_map"
self._metadata = {"mount_id": "aoi_tile"}

super().__init__()

def build(self, recipe: Recipe, build_alert: AlertState):
"""Build the custom aoi tile."""
build_alert.set_state("new", "aoi", "building")

self.map_ = SepalMap(gee=True)
self.map_.dc.hide()
self.map_.min_zoom = 3
Expand All @@ -47,8 +43,6 @@ def build(self, recipe: Recipe, build_alert: AlertState):
# bind an extra js behaviour
self.view.observe(self._check_lmic, "updated")

build_alert.set_state("new", "aoi", "done")

def _check_lmic(self, _):
"""Every time a new aoi is set check if it fits the LMIC country list."""
# check over the lmic country number
Expand Down
7 changes: 1 addition & 6 deletions component/tile/dashboard_tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,13 @@


class DashboardTile(sw.Layout):
def __init__(self):
def __init__(self, recipe: Recipe):
super().__init__()

self._metadata = {"mount_id": "dashboard_tile"}
self.class_ = "d-block"
self.summary_stats = None

def build(self, recipe: Recipe, build_alert: AlertState):
"""Build the dashboard tile."""
build_alert.set_state("new", "dashboard", "building")
self.recipe = recipe

dash_toolbar = DashToolBar(recipe.seplan)
Expand Down Expand Up @@ -58,8 +55,6 @@ def build(self, recipe: Recipe, build_alert: AlertState):
dash_toolbar.btn_dashboard.on_event("click", self._dashboard)
dash_toolbar.btn_download.on_event("click", self.csv_export)

build_alert.set_state("new", "dashboard", "done")

self.recipe.dash_model.observe(self.reset, "reset_count")

def csv_export(self, *_):
Expand Down
11 changes: 1 addition & 10 deletions component/tile/map_tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


class MapTile(sw.Layout):
def __init__(self, app_model: AppModel = None):
def __init__(self, recipe: Recipe, app_model: AppModel = None):
"""Define the map tile layout.

Args:
Expand All @@ -24,13 +24,6 @@ def __init__(self, app_model: AppModel = None):

super().__init__()

def build(
self,
recipe: Recipe,
build_alert: AlertState,
):
build_alert.set_state("new", "map", "building")

self.recipe = recipe
self.colors = []
self.alert = Alert()
Expand Down Expand Up @@ -58,8 +51,6 @@ def build(
# # add js behaviour
self.map_toolbar.btn_compute.on_event("click", self._compute)

build_alert.set_state("new", "map", "done")

self.recipe.seplan_aoi.observe(self._update_aoi, "updated")

# This will open the info dialog when the map_tile drawer is clicked
Expand Down
8 changes: 1 addition & 7 deletions component/tile/questionnaire_tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,13 @@


class QuestionnaireTile(sw.Layout):
def __init__(self):
def __init__(self, recipe: Recipe):
# name the tile
self._metadata = {"mount_id": "questionnaire_tile"}
self.class_ = "d-block"

super().__init__()

def build(self, recipe: Recipe, build_alert: AlertState):
"""Build the questionnaire tile."""
build_alert.set_state("new", "questionnaire", "building")

self.alert = Alert()
alert_dialog = AlertDialog(self.alert)

Expand Down Expand Up @@ -74,5 +70,3 @@ def build(self, recipe: Recipe, build_alert: AlertState):
)

self.set_children([alert_dialog, preview_map] + [tabs], position="last")

build_alert.set_state("new", "questionnaire", "done")
86 changes: 8 additions & 78 deletions component/tile/recipe_tile.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import concurrent.futures
from datetime import datetime
from pathlib import Path
from typing import Literal
Expand Down Expand Up @@ -134,11 +133,11 @@ class RecipeView(sw.Card):
app_model: AppModel
"""It will be used to listen the on_save trait and trigger the save button here"""

def __init__(self, app_model: AppModel = None):
def __init__(self, recipe: Recipe = None, app_model: AppModel = None):
self.attributes = {"_metadata": "recipe_tile"}

super().__init__()
self.recipe = Recipe()
self.recipe = recipe or Recipe()
self.alert = AlertState()
self.alert_dialog = AlertDialog(self.alert)

Expand Down Expand Up @@ -205,12 +204,6 @@ def session_path_handler(self, change):

self.card_save.recipe_name = str(Path(change["new"]).stem)

def new_recipe(self):
"""Initialize a new recipe."""

self.recipe.load_model()
self.create_view += 1

@switch("disabled", on_widgets=["card_new", "card_load", "card_save"])
@switch("loading", on_widgets=["card_new"])
def new_event(self, *_):
Expand All @@ -220,14 +213,9 @@ def new_event(self, *_):
if not self.card_new.recipe_name:
raise ValueError(cm.recipe.error.no_name)

# consider first when theres is not a recipe loaded
if not self.recipe.seplan_aoi:
print("no seplan")
self.new_recipe()
else:
self.alert.set_state("reset", "all", "building")
self.recipe.reset()
self.alert.set_state("reset", "all", "done")
self.alert.set_state("reset", "all", "building")
self.recipe.reset()
self.alert.set_state("reset", "all", "done")

# update current session path
self.recipe_session_path = self.recipe.get_recipe_path(
Expand All @@ -253,9 +241,6 @@ def load_event(self, *_):

self.load_dialog.v_model = False

if not self.recipe.seplan_aoi:
self.new_recipe()

self.alert.set_state("load", "all", "building")

self.recipe.load(recipe_path=self.load_dialog.load_recipe_path)
Expand Down Expand Up @@ -368,31 +353,18 @@ def cancel(self, *args):


class RecipeTile(sw.Layout):
def __init__(
self,
app_model: AppModel,
aoi_tile: AoiTile,
questionnaire_tile: QuestionnaireTile,
map_tile: MapTile,
dashboard_tile: DashboardTile,
):
def __init__(self, recipe: Recipe, app_model: AppModel):
self._metadata = {"mount_id": "recipe_tile"}
self.class_ = "d-block pa-2"

super().__init__()

self.recipe = recipe
self.app_model = app_model
self.recipe_view = RecipeView(app_model=app_model)

self.aoi_tile = aoi_tile
self.questionnaire_tile = questionnaire_tile
self.map_tile = map_tile
self.dashboard_tile = dashboard_tile
self.recipe_view = RecipeView(recipe=recipe, app_model=app_model)

self.children = [self.recipe_view]

self.recipe_view.observe(self.render, "create_view")

directional_link(
(self.recipe_view, "recipe_session_path"), (self.app_model, "recipe_name")
)
Expand All @@ -402,47 +374,5 @@ def __init__(
(self.recipe_view.recipe, "new_changes"), (self.app_model, "new_changes")
)

def render(self, *_):
"""Render all the different tiles.

This element is intended to be used only once, when the app has to start.
"""
try:
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
futures = [
executor.submit(
self.aoi_tile.build,
self.recipe_view.recipe,
self.recipe_view.alert,
),
executor.submit(
self.questionnaire_tile.build,
self.recipe_view.recipe,
self.recipe_view.alert,
),
executor.submit(
self.map_tile.build,
self.recipe_view.recipe,
self.recipe_view.alert,
),
executor.submit(
self.dashboard_tile.build,
self.recipe_view.recipe,
self.recipe_view.alert,
),
]

# Check if any future has raised an exception
for future in concurrent.futures.as_completed(futures):
e = future.exception()
if e:
raise e # Rethrow the first exception encountered

# concurrent.futures.wait(futures)
except Exception as e:
# If something fails, I want to return the recipe to its original state
self.recipe_view.recipe.__init__()
raise e

# This trait will let know the app drawers that the app is ready to be used
self.app_model.ready = True
4 changes: 0 additions & 4 deletions component/widget/custom_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,6 @@ def __init__(self, *args, **kwargs) -> None:

self.attributes = {"id": kwargs["card"]}

# Only hide the drawers that are not the main ones
if self.attributes["id"] not in (["recipe_tile", "about_tile"]):
self.viz = False

self.observe(self.add_notif, "alert")

def add_notif(self, change: dict) -> None:
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ voila
toml

# Fixes authentication process
git+https://github.com/12rambau/sepal_ui.git@sepal_pre_release
sepal_ui==2.18.1

# https://github.com/openforis/sepal/issues/321
ipyleaflet==0.18.2
Expand Down
23 changes: 16 additions & 7 deletions ui.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"from component.tile.map_tile import MapTile\n",
"from component.tile.questionnaire_tile import QuestionnaireTile\n",
"from component.tile.recipe_tile import RecipeTile\n",
"from component.model.recipe import Recipe\n",
"\n",
"from component.message import cm"
]
Expand Down Expand Up @@ -54,6 +55,16 @@
"disclaimer_tile = sw.TileDisclaimer()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "184f41b0",
"metadata": {},
"outputs": [],
"source": [
"recipe = Recipe()"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -63,13 +74,11 @@
},
"outputs": [],
"source": [
"aoi_tile = AoiTile()\n",
"questionnaire_tile = QuestionnaireTile()\n",
"map_tile = MapTile(app_model)\n",
"dashboard_tile = DashboardTile()\n",
"recipe_tile = RecipeTile(\n",
" app_model, aoi_tile, questionnaire_tile, map_tile, dashboard_tile\n",
")"
"recipe_tile = RecipeTile(recipe=recipe, app_model=app_model)\n",
"aoi_tile = AoiTile(recipe=recipe)\n",
"questionnaire_tile = QuestionnaireTile(recipe=recipe)\n",
"map_tile = MapTile(recipe=recipe, app_model=app_model)\n",
"dashboard_tile = DashboardTile(recipe=recipe)"
]
},
{
Expand Down
Loading