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

Provide app.storage.client as a location to store volatile data which only matters for the current connection #2820

Merged
merged 29 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
db6b065
Implemented app.storage.session which enables the user to store data …
Alyxion Apr 4, 2024
d9403b4
Merge branch 'main' into feature/per_session_data
Alyxion Apr 4, 2024
3fedd36
Replaced Client.state by ObservableDict
Alyxion Apr 5, 2024
8fc9208
Merge branch 'main' into feature/per_session_data
Alyxion Apr 5, 2024
ea8dad5
Renamed app.storage.session to app.storage.client.
Alyxion Apr 6, 2024
e627934
Merge remote-tracking branch 'origin/feature/per_session_data' into f…
Alyxion Apr 6, 2024
df3335a
Merge branch 'main' into feature/per_session_data
Alyxion Apr 6, 2024
9dd4227
Exchanged quotes
Alyxion Apr 6, 2024
ca99fcf
Merge remote-tracking branch 'origin/feature/per_session_data' into f…
Alyxion Apr 6, 2024
08d73bd
Added documentation for app.storage.client
Alyxion Apr 7, 2024
06d6393
Removed imports, simplified client availability check
Alyxion Apr 7, 2024
bb6f44b
Merge branch 'main' into feature/per_session_data
Alyxion Apr 7, 2024
040ebb8
Updated documentation
Alyxion Apr 8, 2024
8013ea0
Merge remote-tracking branch 'origin/feature/per_session_data' into f…
Alyxion Apr 8, 2024
f02d9e9
Removed connection test_clear from
Alyxion Apr 8, 2024
c57a93f
Removed random import, not required for demo anymore
Alyxion Apr 8, 2024
0714c51
Merge remote-tracking branch 'nicegui/main' into feature/per_session_…
Alyxion Apr 8, 2024
ac4ecb1
Resolved merging conflicts with tab extension
Alyxion Apr 8, 2024
edca313
Merge fix
Alyxion Apr 8, 2024
6e85268
Merge fix
Alyxion Apr 8, 2024
bad5cec
minimal updates to documentation
rodja Apr 8, 2024
c01d0d1
code review
falkoschindler Apr 8, 2024
26ba191
Removed line duplication
Alyxion Apr 8, 2024
9ade1f9
improve clearing of client storage
falkoschindler Apr 9, 2024
44f70c7
Merge branch 'feature/per_session_data' of github.com:Alyxion/nicegui…
falkoschindler Apr 9, 2024
6563825
fix typo
falkoschindler Apr 9, 2024
ff58c03
add overview table
falkoschindler Apr 9, 2024
7f856b4
renaming
falkoschindler Apr 9, 2024
0f72368
review documentation
falkoschindler Apr 9, 2024
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
8 changes: 8 additions & 0 deletions nicegui/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

from . import background_tasks, binding, core, helpers, json
from .awaitable_response import AwaitableResponse
from .context import get_client
from .dependencies import generate_resources
from .element import Element
from .favicon import get_favicon_url
from .javascript_request import JavaScriptRequest
from .logging import log
from .observables import ObservableDict
from .outbox import Outbox
from .version import __version__

Expand Down Expand Up @@ -72,6 +74,7 @@ def __init__(self, page: page, *, shared: bool = False) -> None:
self._body_html = ''

self.page = page
self.state = ObservableDict()

self.connect_handlers: List[Union[Callable[..., Any], Awaitable]] = []
self.disconnect_handlers: List[Union[Callable[..., Any], Awaitable]] = []
Expand All @@ -83,6 +86,11 @@ def is_auto_index_client(self) -> bool:
"""Return True if this client is the auto-index client."""
return self is self.auto_index_client

@staticmethod
def current_client() -> Optional[Client]:
Alyxion marked this conversation as resolved.
Show resolved Hide resolved
"""Returns the current client if obtainable from the current context."""
return get_client()

@property
def ip(self) -> Optional[str]:
"""Return the IP address of the client, or None if the client is not connected."""
Expand Down
18 changes: 18 additions & 0 deletions nicegui/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
from starlette.responses import Response

from . import background_tasks, context, core, json, observables
from .context import get_slot_stack
from .logging import log
from .observables import ObservableDict

request_contextvar: contextvars.ContextVar[Optional[Request]] = contextvars.ContextVar('request_var', default=None)

Expand Down Expand Up @@ -149,10 +151,26 @@ def general(self) -> Dict:
"""General storage shared between all users that is persisted on the server (where NiceGUI is executed)."""
return self._general

@property
def client(self) -> ObservableDict:
"""Client storage that is persisted on the server (where NiceGUI is executed) on a per client
connection basis.

The data is lost when the client disconnects through reloading the page, closing the tab or
navigating away from the page. It can be used to store data that is only relevant for the current view such
as filter settings on a dashboard or in-page navigation. As the data is not persisted it also allows the
storage of data structures such as database connections, pandas tables, numpy arrays, user specific ML models
or other living objects that are not serializable to JSON.
"""
client = context.get_client()
Alyxion marked this conversation as resolved.
Show resolved Hide resolved
return client.state

def clear(self) -> None:
"""Clears all storage."""
self._general.clear()
self._users.clear()
if get_slot_stack():
self.client.clear()
rodja marked this conversation as resolved.
Show resolved Hide resolved
for filepath in self.path.glob('storage-*.json'):
filepath.unlink()

Expand Down
23 changes: 23 additions & 0 deletions tests/test_client_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from nicegui import ui, app
from nicegui.testing import Screen


def test_session_state(screen: Screen):
app.storage.client['counter'] = 123

def increment():
app.storage.client['counter'] = app.storage.client['counter'] + 1

ui.button('Increment').on_click(increment)
ui.label().bind_text(app.storage.client, 'counter')

screen.open('/')
screen.should_contain('123')
screen.click('Increment')
screen.wait_for('124')


def test_clear(screen: Screen):
app.storage.client['counter'] = 123
app.storage.client.clear()
assert 'counter' not in app.storage.client