Skip to content

Commit

Permalink
Save non-serializable data in tab storage (#4325)
Browse files Browse the repository at this point in the history
This PR fixes #4313 by providing a non-persistent `ObservableDict` for
`app.storage.tab` if Redis is not used.

---------

Co-authored-by: Falko Schindler <[email protected]>
  • Loading branch information
rodja and falkoschindler authored Feb 10, 2025
1 parent 7ad1a4b commit 786e24f
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 5 deletions.
19 changes: 14 additions & 5 deletions nicegui/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Storage:
def __init__(self) -> None:
self._general = Storage._create_persistent_dict('general')
self._users: Dict[str, PersistentDict] = {}
self._tabs: Dict[str, PersistentDict] = {}
self._tabs: Dict[str, ObservableDict] = {}

@staticmethod
def _create_persistent_dict(id: str) -> PersistentDict: # pylint: disable=redefined-builtin
Expand Down Expand Up @@ -163,13 +163,21 @@ def tab(self) -> observables.ObservableDict:
async def _create_tab_storage(self, tab_id: str) -> None:
"""Create tab storage for the given tab ID."""
if tab_id not in self._tabs:
self._tabs[tab_id] = Storage._create_persistent_dict(f'tab-{tab_id}')
await self._tabs[tab_id].initialize()
if Storage.redis_url:
self._tabs[tab_id] = Storage._create_persistent_dict(f'tab-{tab_id}')
tab = self._tabs[tab_id]
assert isinstance(tab, PersistentDict)
await tab.initialize()
else:
self._tabs[tab_id] = ObservableDict()

def copy_tab(self, old_tab_id: str, tab_id: str) -> None:
"""Copy the tab storage to a new tab. (For internal use only.)"""
if old_tab_id in self._tabs:
self._tabs[tab_id] = Storage._create_persistent_dict(f'tab-{tab_id}')
if Storage.redis_url:
self._tabs[tab_id] = Storage._create_persistent_dict(f'tab-{tab_id}')
else:
self._tabs[tab_id] = ObservableDict()
self._tabs[tab_id].update(self._tabs[old_tab_id])

async def prune_tab_storage(self) -> None:
Expand All @@ -178,7 +186,8 @@ async def prune_tab_storage(self) -> None:
for tab_id, tab in list(self._tabs.items()):
if time.time() > tab.last_modified + self.max_tab_storage_age:
tab.clear()
await tab.close()
if isinstance(tab, PersistentDict):
await tab.close()
del self._tabs[tab_id]
await asyncio.sleep(PURGE_INTERVAL)

Expand Down
20 changes: 20 additions & 0 deletions tests/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,23 @@ def f(v):

screen.open('/')
screen.assert_py_logger('ERROR', 'app.storage.user can only be used within a UI context')


def test_client_storage_holds_non_serializable_objects(screen: Screen):
@ui.page('/')
def page():
ui.button('Update storage', on_click=lambda: app.storage.client.update(x=len))

screen.open('/')
screen.click('Update storage')
screen.wait(0.5)


def test_tab_storage_holds_non_serializable_objects(screen: Screen):
@ui.page('/')
def page():
ui.button('Update storage', on_click=lambda: app.storage.tab.update(x=len))

screen.open('/')
screen.click('Update storage')
screen.wait(0.5)

0 comments on commit 786e24f

Please sign in to comment.