Browse Source

UI context properties (#2905)

* provide ui.context

* use ui.context instead of page injector

* add `context` to `__all__`

* remove unnecessary file

* introduce a context class

* deprecate getter methods in favor of properties

* fix pytests

* remove context imports and use ui.context instead

* replace some outdated calls to get_client()

---------

Co-authored-by: Rodja Trappe <rodja@zauberzeug.com>
Falko Schindler 1 year ago
parent
commit
f7c08cfd97

+ 1 - 1
examples/chat_app/main.py

@@ -36,7 +36,7 @@ async def main():
         ui.markdown('simple chat app built with [NiceGUI](https://nicegui.io)') \
         ui.markdown('simple chat app built with [NiceGUI](https://nicegui.io)') \
             .classes('text-xs self-end mr-8 m-[-1em] text-primary')
             .classes('text-xs self-end mr-8 m-[-1em] text-primary')
 
 
-    await ui.context.get_client().connected()  # chat_messages(...) uses run_javascript which is only possible after connecting
+    await ui.context.client.connected()  # chat_messages(...) uses run_javascript which is only possible after connecting
     with ui.column().classes('w-full max-w-2xl mx-auto items-stretch'):
     with ui.column().classes('w-full max-w-2xl mx-auto items-stretch'):
         chat_messages(user_id)
         chat_messages(user_id)
 
 

+ 1 - 1
examples/descope_auth/user.py

@@ -61,7 +61,7 @@ class page(ui.page):
                     const sessionToken = sdk.getSessionToken()
                     const sessionToken = sdk.getSessionToken()
                 </script>
                 </script>
             ''')
             ''')
-            await ui.context.get_client().connected()
+            await ui.context.client.connected()
             if await self._is_logged_in():
             if await self._is_logged_in():
                 if self.path == self.LOGIN_PATH:
                 if self.path == self.LOGIN_PATH:
                     self._refresh()
                     self._refresh()

+ 1 - 1
examples/download_text_as_file/main.py

@@ -21,7 +21,7 @@ async def index():
     ui.button('Download', on_click=lambda: ui.download(download_path))
     ui.button('Download', on_click=lambda: ui.download(download_path))
 
 
     # cleanup the download route after the client disconnected
     # cleanup the download route after the client disconnected
-    await ui.context.get_client().disconnected()
+    await ui.context.client.disconnected()
     app.routes[:] = [route for route in app.routes if route.path != download_path]
     app.routes[:] = [route for route in app.routes if route.path != download_path]
 
 
 ui.run()
 ui.run()

+ 1 - 1
examples/infinite_scroll/main.py

@@ -9,7 +9,7 @@ async def page():
     async def check():
     async def check():
         if await ui.run_javascript('window.pageYOffset >= document.body.offsetHeight - 2 * window.innerHeight'):
         if await ui.run_javascript('window.pageYOffset >= document.body.offsetHeight - 2 * window.innerHeight'):
             ui.image(f'https://picsum.photos/640/360?{time.time()}')
             ui.image(f'https://picsum.photos/640/360?{time.time()}')
-    await ui.context.get_client().connected()
+    await ui.context.client.connected()
     ui.timer(0.1, check)
     ui.timer(0.1, check)
 
 
 
 

+ 2 - 1
nicegui/__init__.py

@@ -1,7 +1,8 @@
-from . import context, elements, run, ui
+from . import elements, run, ui
 from .api_router import APIRouter
 from .api_router import APIRouter
 from .app.app import App
 from .app.app import App
 from .client import Client
 from .client import Client
+from .context import context
 from .nicegui import app
 from .nicegui import app
 from .tailwind import Tailwind
 from .tailwind import Tailwind
 from .version import __version__
 from .version import __version__

+ 36 - 14
nicegui/context.py

@@ -2,27 +2,49 @@ from __future__ import annotations
 
 
 from typing import TYPE_CHECKING, List
 from typing import TYPE_CHECKING, List
 
 
+from .logging import log
 from .slot import Slot
 from .slot import Slot
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from .client import Client
     from .client import Client
 
 
 
 
-def get_slot_stack() -> List[Slot]:
-    """Return the slot stack of the current asyncio task."""
-    return Slot.get_stack()
+class Context:
 
 
+    def get_slot_stack(self) -> List[Slot]:
+        """Return the slot stack of the current asyncio task. (DEPRECATED, use context.slot_stack instead)"""
+        log.warning('context.get_slot_stack() is deprecated, use context.slot_stack instead')
+        return self.slot_stack
 
 
-def get_slot() -> Slot:
-    """Return the current slot."""
-    slot_stack = get_slot_stack()
-    if not slot_stack:
-        raise RuntimeError('The current slot cannot be determined because the slot stack for this task is empty.\n'
-                           'This may happen if you try to create UI from a background task.\n'
-                           'To fix this, enter the target slot explicitly using `with container_element:`.')
-    return slot_stack[-1]
+    def get_slot(self) -> Slot:
+        """Return the current slot. (DEPRECATED, use context.slot instead)"""
+        log.warning('context.get_slot() is deprecated, use context.slot instead')
+        return self.slot
 
 
+    def get_client(self) -> Client:
+        """Return the current client. (DEPRECATED, use context.client instead)"""
+        log.warning('context.get_client() is deprecated, use context.client instead')
+        return self.client
 
 
-def get_client() -> Client:
-    """Return the current client."""
-    return get_slot().parent.client
+    @property
+    def slot_stack(self) -> List[Slot]:
+        """Return the slot stack of the current asyncio task."""
+        return Slot.get_stack()
+
+    @property
+    def slot(self) -> Slot:
+        """Return the current slot."""
+        slot_stack = self.slot_stack
+        if not slot_stack:
+            raise RuntimeError('The current slot cannot be determined because the slot stack for this task is empty.\n'
+                               'This may happen if you try to create UI from a background task.\n'
+                               'To fix this, enter the target slot explicitly using `with container_element:`.')
+        return slot_stack[-1]
+
+    @property
+    def client(self) -> Client:
+        """Return the current client."""
+        return self.slot.parent.client
+
+
+context = Context()

+ 4 - 3
nicegui/element.py

@@ -9,8 +9,9 @@ from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Iterator, List,
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from . import context, core, events, helpers, json, storage
+from . import core, events, helpers, json, storage
 from .awaitable_response import AwaitableResponse, NullResponse
 from .awaitable_response import AwaitableResponse, NullResponse
+from .context import context
 from .dependencies import Component, Library, register_library, register_resource, register_vue_component
 from .dependencies import Component, Library, register_library, register_resource, register_vue_component
 from .elements.mixins.visibility import Visibility
 from .elements.mixins.visibility import Visibility
 from .event_listener import EventListener
 from .event_listener import EventListener
@@ -72,7 +73,7 @@ class Element(Visibility):
         :param _client: client for this element (for internal use only)
         :param _client: client for this element (for internal use only)
         """
         """
         super().__init__()
         super().__init__()
-        self.client = _client or context.get_client()
+        self.client = _client or context.client
         self.id = self.client.next_element_id
         self.id = self.client.next_element_id
         self.client.next_element_id += 1
         self.client.next_element_id += 1
         self.tag = tag if tag else self.component.tag if self.component else 'div'
         self.tag = tag if tag else self.component.tag if self.component else 'div'
@@ -92,7 +93,7 @@ class Element(Visibility):
 
 
         self.client.elements[self.id] = self
         self.client.elements[self.id] = self
         self.parent_slot: Optional[Slot] = None
         self.parent_slot: Optional[Slot] = None
-        slot_stack = context.get_slot_stack()
+        slot_stack = context.slot_stack
         if slot_stack:
         if slot_stack:
             self.parent_slot = slot_stack[-1]
             self.parent_slot = slot_stack[-1]
             self.parent_slot.children.append(self)
             self.parent_slot.children.append(self)

+ 2 - 2
nicegui/elements/carousel.py

@@ -2,7 +2,7 @@ from __future__ import annotations
 
 
 from typing import Any, Callable, Optional, Union, cast
 from typing import Any, Callable, Optional, Union, cast
 
 
-from .. import context
+from ..context import context
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -62,7 +62,7 @@ class CarouselSlide(DisableableElement):
         :param name: name of the slide (will be the value of the `ui.carousel` element, auto-generated if `None`)
         :param name: name of the slide (will be the value of the `ui.carousel` element, auto-generated if `None`)
         """
         """
         super().__init__(tag='q-carousel-slide')
         super().__init__(tag='q-carousel-slide')
-        self.carousel = cast(ValueElement, context.get_slot().parent)
+        self.carousel = cast(ValueElement, context.slot.parent)
         name = name or f'slide_{len(self.carousel.default_slot.children)}'
         name = name or f'slide_{len(self.carousel.default_slot.children)}'
         self._props['name'] = name
         self._props['name'] = name
         self._classes.append('nicegui-carousel-slide')
         self._classes.append('nicegui-carousel-slide')

+ 2 - 2
nicegui/elements/notification.py

@@ -1,6 +1,6 @@
 from typing import Any, Literal, Optional, Union
 from typing import Any, Literal, Optional, Union
 
 
-from .. import context
+from ..context import context
 from ..element import Element
 from ..element import Element
 from .timer import Timer
 from .timer import Timer
 
 
@@ -57,7 +57,7 @@ class Notification(Element, component='notification.js'):
 
 
         Note: You can pass additional keyword arguments according to `Quasar's Notify API <https://quasar.dev/quasar-plugins/notify#notify-api>`_.
         Note: You can pass additional keyword arguments according to `Quasar's Notify API <https://quasar.dev/quasar-plugins/notify#notify-api>`_.
         """
         """
-        with context.get_client().layout:
+        with context.client.layout:
             super().__init__()
             super().__init__()
         self._props['options'] = {
         self._props['options'] = {
             'message': str(message),
             'message': str(message),

+ 2 - 2
nicegui/elements/query.py

@@ -2,7 +2,7 @@ from typing import Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from .. import context
+from ..context import context
 from ..element import Element
 from ..element import Element
 
 
 
 
@@ -64,7 +64,7 @@ class Query:
 
 
         :param selector: the CSS selector (e.g. "body", "#my-id", ".my-class", "div > p")
         :param selector: the CSS selector (e.g. "body", "#my-id", ".my-class", "div > p")
         """
         """
-        for element in context.get_client().elements.values():
+        for element in context.client.elements.values():
             if isinstance(element, QueryElement) and element._props['selector'] == selector:  # pylint: disable=protected-access
             if isinstance(element, QueryElement) and element._props['selector'] == selector:  # pylint: disable=protected-access
                 self.element = element
                 self.element = element
                 break
                 break

+ 2 - 2
nicegui/elements/stepper.py

@@ -2,7 +2,7 @@ from __future__ import annotations
 
 
 from typing import Any, Callable, Optional, Union, cast
 from typing import Any, Callable, Optional, Union, cast
 
 
-from .. import context
+from ..context import context
 from ..element import Element
 from ..element import Element
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -69,7 +69,7 @@ class Step(DisableableElement):
         self._classes.append('nicegui-step')
         self._classes.append('nicegui-step')
         if icon:
         if icon:
             self._props['icon'] = icon
             self._props['icon'] = icon
-        self.stepper = cast(ValueElement, context.get_slot().parent)
+        self.stepper = cast(ValueElement, context.slot.parent)
         if self.stepper.value is None:
         if self.stepper.value is None:
             self.stepper.value = name
             self.stepper.value = name
 
 

+ 2 - 2
nicegui/elements/tabs.py

@@ -2,7 +2,7 @@ from __future__ import annotations
 
 
 from typing import Any, Callable, Optional, Union
 from typing import Any, Callable, Optional, Union
 
 
-from .. import context
+from ..context import context
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -44,7 +44,7 @@ class Tab(DisableableElement):
         self._props['label'] = label if label is not None else name
         self._props['label'] = label if label is not None else name
         if icon:
         if icon:
             self._props['icon'] = icon
             self._props['icon'] = icon
-        self.tabs = context.get_slot().parent
+        self.tabs = context.slot.parent
 
 
 
 
 class TabPanels(ValueElement):
 class TabPanels(ValueElement):

+ 3 - 2
nicegui/functions/download.py

@@ -1,7 +1,8 @@
 from pathlib import Path
 from pathlib import Path
 from typing import Optional, Union
 from typing import Optional, Union
 
 
-from .. import context, core, helpers
+from .. import core, helpers
+from ..context import context
 
 
 
 
 def download(src: Union[str, Path, bytes], filename: Optional[str] = None, media_type: str = '') -> None:
 def download(src: Union[str, Path, bytes], filename: Optional[str] = None, media_type: str = '') -> None:
@@ -18,4 +19,4 @@ def download(src: Union[str, Path, bytes], filename: Optional[str] = None, media
             src = core.app.add_static_file(local_file=src, single_use=True)
             src = core.app.add_static_file(local_file=src, single_use=True)
         else:
         else:
             src = str(src)
             src = str(src)
-    context.get_client().download(src, filename, media_type)
+    context.client.download(src, filename, media_type)

+ 3 - 3
nicegui/functions/html.py

@@ -1,5 +1,5 @@
-from .. import context
 from ..client import Client
 from ..client import Client
+from ..context import context
 
 
 
 
 def add_head_html(code: str, *, shared: bool = False) -> None:
 def add_head_html(code: str, *, shared: bool = False) -> None:
@@ -11,7 +11,7 @@ def add_head_html(code: str, *, shared: bool = False) -> None:
     if shared:
     if shared:
         Client.shared_head_html += code + '\n'
         Client.shared_head_html += code + '\n'
     else:
     else:
-        client = context.get_client()
+        client = context.client
         if client.has_socket_connection:
         if client.has_socket_connection:
             client.run_javascript(f'document.head.insertAdjacentHTML("beforeend", {code!r});')
             client.run_javascript(f'document.head.insertAdjacentHTML("beforeend", {code!r});')
         client._head_html += code + '\n'  # pylint: disable=protected-access
         client._head_html += code + '\n'  # pylint: disable=protected-access
@@ -26,7 +26,7 @@ def add_body_html(code: str, *, shared: bool = False) -> None:
     if shared:
     if shared:
         Client.shared_body_html += code + '\n'
         Client.shared_body_html += code + '\n'
     else:
     else:
-        client = context.get_client()
+        client = context.client
         if client.has_socket_connection:
         if client.has_socket_connection:
             client.run_javascript(f'document.querySelector("#app").insertAdjacentHTML("beforebegin", {code!r});')
             client.run_javascript(f'document.querySelector("#app").insertAdjacentHTML("beforebegin", {code!r});')
         client._body_html += code + '\n'  # pylint: disable=protected-access
         client._body_html += code + '\n'  # pylint: disable=protected-access

+ 2 - 2
nicegui/functions/javascript.py

@@ -1,7 +1,7 @@
 from typing import Optional
 from typing import Optional
 
 
-from .. import context
 from ..awaitable_response import AwaitableResponse
 from ..awaitable_response import AwaitableResponse
+from ..context import context
 
 
 
 
 def run_javascript(code: str, *,
 def run_javascript(code: str, *,
@@ -21,4 +21,4 @@ def run_javascript(code: str, *,
 
 
     :return: AwaitableResponse that can be awaited to get the result of the JavaScript code
     :return: AwaitableResponse that can be awaited to get the result of the JavaScript code
     """
     """
-    return context.get_client().run_javascript(code, respond=respond, timeout=timeout, check_interval=check_interval)
+    return context.client.run_javascript(code, respond=respond, timeout=timeout, check_interval=check_interval)

+ 2 - 2
nicegui/functions/navigate.py

@@ -1,7 +1,7 @@
 from typing import Any, Callable, Union
 from typing import Any, Callable, Union
 
 
-from .. import context
 from ..client import Client
 from ..client import Client
+from ..context import context
 from ..element import Element
 from ..element import Element
 from .javascript import run_javascript
 from .javascript import run_javascript
 
 
@@ -64,4 +64,4 @@ class Navigate:
             path = f'#c{target.id}'
             path = f'#c{target.id}'
         elif callable(target):
         elif callable(target):
             path = Client.page_routes[target]
             path = Client.page_routes[target]
-        context.get_client().open(path, new_tab)
+        context.client.open(path, new_tab)

+ 2 - 2
nicegui/functions/notify.py

@@ -1,6 +1,6 @@
 from typing import Any, Literal, Optional, Union
 from typing import Any, Literal, Optional, Union
 
 
-from .. import context
+from ..context import context
 
 
 ARG_MAP = {
 ARG_MAP = {
     'close_button': 'closeBtn',
     'close_button': 'closeBtn',
@@ -49,5 +49,5 @@ def notify(message: Any, *,
     options = {ARG_MAP.get(key, key): value for key, value in locals().items() if key != 'kwargs' and value is not None}
     options = {ARG_MAP.get(key, key): value for key, value in locals().items() if key != 'kwargs' and value is not None}
     options['message'] = str(message)
     options['message'] = str(message)
     options.update(kwargs)
     options.update(kwargs)
-    client = context.get_client()
+    client = context.client
     client.outbox.enqueue_message('notify', options, client.id)
     client.outbox.enqueue_message('notify', options, client.id)

+ 3 - 3
nicegui/functions/on.py

@@ -1,6 +1,6 @@
 from typing import Any, Callable, Optional, Sequence, Union
 from typing import Any, Callable, Optional, Sequence, Union
 
 
-from .. import context
+from ..context import context
 
 
 
 
 def on(type: str,  # pylint: disable=redefined-builtin
 def on(type: str,  # pylint: disable=redefined-builtin
@@ -19,5 +19,5 @@ def on(type: str,  # pylint: disable=redefined-builtin
     :param leading_events: whether to trigger the event handler immediately upon the first event occurrence (default: `True`)
     :param leading_events: whether to trigger the event handler immediately upon the first event occurrence (default: `True`)
     :param trailing_events: whether to trigger the event handler after the last event occurrence (default: `True`)
     :param trailing_events: whether to trigger the event handler after the last event occurrence (default: `True`)
     """
     """
-    context.get_client().layout.on(type, handler, args,
-                                   throttle=throttle, leading_events=leading_events, trailing_events=trailing_events)
+    context.client.layout.on(type, handler, args,
+                             throttle=throttle, leading_events=leading_events, trailing_events=trailing_events)

+ 3 - 2
nicegui/functions/page_title.py

@@ -1,4 +1,5 @@
-from .. import context, json
+from .. import json
+from ..context import context
 
 
 
 
 def page_title(title: str) -> None:
 def page_title(title: str) -> None:
@@ -8,7 +9,7 @@ def page_title(title: str) -> None:
 
 
     :param title: page title
     :param title: page title
     """
     """
-    client = context.get_client()
+    client = context.client
     client.title = title
     client.title = title
     if client.has_socket_connection:
     if client.has_socket_connection:
         client.run_javascript(f'document.title = {json.dumps(title)}')
         client.run_javascript(f'document.title = {json.dumps(title)}')

+ 5 - 5
nicegui/page_layout.py

@@ -1,6 +1,6 @@
 from typing import Literal, Optional
 from typing import Literal, Optional
 
 
-from . import context
+from .context import context
 from .element import Element
 from .element import Element
 from .elements.mixins.value_element import ValueElement
 from .elements.mixins.value_element import ValueElement
 from .functions.html import add_body_html
 from .functions.html import add_body_html
@@ -45,7 +45,7 @@ class Header(ValueElement):
         :param add_scroll_padding: whether to automatically prevent link targets from being hidden behind the header (default: `True`)
         :param add_scroll_padding: whether to automatically prevent link targets from being hidden behind the header (default: `True`)
         """
         """
         _check_current_slot(self)
         _check_current_slot(self)
-        with context.get_client().layout:
+        with context.client.layout:
             super().__init__(tag='q-header', value=value, on_value_change=None)
             super().__init__(tag='q-header', value=value, on_value_change=None)
         self._classes.append('nicegui-header')
         self._classes.append('nicegui-header')
         self._props['bordered'] = bordered
         self._props['bordered'] = bordered
@@ -109,7 +109,7 @@ class Drawer(Element):
         :param bottom_corner: whether the drawer expands into the bottom corner (default: `False`)
         :param bottom_corner: whether the drawer expands into the bottom corner (default: `False`)
         """
         """
         _check_current_slot(self)
         _check_current_slot(self)
-        with context.get_client().layout:
+        with context.client.layout:
             super().__init__('q-drawer')
             super().__init__('q-drawer')
         if value is None:
         if value is None:
             self._props['show-if-above'] = True
             self._props['show-if-above'] = True
@@ -228,7 +228,7 @@ class Footer(ValueElement):
         :param wrap: whether the footer should wrap its content (default: `True`)
         :param wrap: whether the footer should wrap its content (default: `True`)
         """
         """
         _check_current_slot(self)
         _check_current_slot(self)
-        with context.get_client().layout:
+        with context.client.layout:
             super().__init__(tag='q-footer', value=value, on_value_change=None)
             super().__init__(tag='q-footer', value=value, on_value_change=None)
         self.classes('nicegui-footer')
         self.classes('nicegui-footer')
         self._props['bordered'] = bordered
         self._props['bordered'] = bordered
@@ -271,7 +271,7 @@ class PageSticky(Element):
 
 
 
 
 def _check_current_slot(element: Element) -> None:
 def _check_current_slot(element: Element) -> None:
-    parent = context.get_slot().parent
+    parent = context.slot.parent
     if parent != parent.client.content:
     if parent != parent.client.content:
         log.warning(f'Found top level layout element "{element.__class__.__name__}" inside element "{parent.__class__.__name__}". '
         log.warning(f'Found top level layout element "{element.__class__.__name__}" inside element "{parent.__class__.__name__}". '
                     'Top level layout elements should not be nested but must be direct children of the page content. '
                     'Top level layout elements should not be nested but must be direct children of the page content. '

+ 6 - 5
nicegui/storage.py

@@ -15,7 +15,8 @@ from starlette.middleware.sessions import SessionMiddleware
 from starlette.requests import Request
 from starlette.requests import Request
 from starlette.responses import Response
 from starlette.responses import Response
 
 
-from . import background_tasks, context, core, json, observables
+from . import background_tasks, core, json, observables
+from .context import context
 from .logging import log
 from .logging import log
 from .observables import ObservableDict
 from .observables import ObservableDict
 
 
@@ -148,7 +149,7 @@ class Storage:
     @staticmethod
     @staticmethod
     def _is_in_auto_index_context() -> bool:
     def _is_in_auto_index_context() -> bool:
         try:
         try:
-            return context.get_client().is_auto_index_client
+            return context.client.is_auto_index_client
         except RuntimeError:
         except RuntimeError:
             return False  # no client
             return False  # no client
 
 
@@ -167,7 +168,7 @@ class Storage:
         if self._is_in_auto_index_context():
         if self._is_in_auto_index_context():
             raise RuntimeError('app.storage.client can only be used with page builder functions '
             raise RuntimeError('app.storage.client can only be used with page builder functions '
                                '(https://nicegui.io/documentation/page)')
                                '(https://nicegui.io/documentation/page)')
-        return context.get_client().storage
+        return context.client.storage
 
 
     @property
     @property
     def tab(self) -> observables.ObservableDict:
     def tab(self) -> observables.ObservableDict:
@@ -175,7 +176,7 @@ class Storage:
         if self._is_in_auto_index_context():
         if self._is_in_auto_index_context():
             raise RuntimeError('app.storage.tab can only be used with page builder functions '
             raise RuntimeError('app.storage.tab can only be used with page builder functions '
                                '(https://nicegui.io/documentation/page)')
                                '(https://nicegui.io/documentation/page)')
-        client = context.get_client()
+        client = context.client
         if not client.has_socket_connection:
         if not client.has_socket_connection:
             raise RuntimeError('app.storage.tab can only be used with a client connection; '
             raise RuntimeError('app.storage.tab can only be used with a client connection; '
                                'see https://nicegui.io/documentation/page#wait_for_client_connection to await it')
                                'see https://nicegui.io/documentation/page#wait_for_client_connection to await it')
@@ -197,7 +198,7 @@ class Storage:
         self._general.clear()
         self._general.clear()
         self._users.clear()
         self._users.clear()
         try:
         try:
-            client = context.get_client()
+            client = context.client
         except RuntimeError:
         except RuntimeError:
             pass  # no client, could be a pytest
             pass  # no client, could be a pytest
         else:
         else:

+ 1 - 1
nicegui/ui.py

@@ -122,7 +122,7 @@ __all__ = [
     'run_with',
     'run_with',
 ]
 ]
 
 
-from . import context
+from .context import context
 from .element import Element as element
 from .element import Element as element
 from .elements.aggrid import AgGrid as aggrid
 from .elements.aggrid import AgGrid as aggrid
 from .elements.audio import Audio as audio
 from .elements.audio import Audio as audio

+ 2 - 2
tests/test_auto_context.py

@@ -2,7 +2,7 @@ import asyncio
 
 
 from selenium.webdriver.common.by import By
 from selenium.webdriver.common.by import By
 
 
-from nicegui import Client, background_tasks, ui
+from nicegui import background_tasks, ui
 from nicegui.testing import Screen
 from nicegui.testing import Screen
 
 
 
 
@@ -52,7 +52,7 @@ def test_autoupdate_after_connected(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
         ui.label('before connected')
         ui.label('before connected')
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         ui.label('after connected')
         ui.label('after connected')
         await asyncio.sleep(1)
         await asyncio.sleep(1)
         ui.label('one')
         ui.label('one')

+ 1 - 1
tests/test_interactive_image.py

@@ -20,7 +20,7 @@ def test_set_source_in_tab(screen: Screen):
                 img = ui.interactive_image()
                 img = ui.interactive_image()
             with ui.tab_panel('B'):
             with ui.tab_panel('B'):
                 ui.label('Tab B')
                 ui.label('Tab B')
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         img.set_source('https://picsum.photos/id/29/640/360')
         img.set_source('https://picsum.photos/id/29/640/360')
 
 
     screen.open('/')
     screen.open('/')

+ 1 - 1
tests/test_javascript.py

@@ -16,7 +16,7 @@ def test_run_javascript_on_value_change(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
         ui.radio(['A', 'B'], on_change=lambda e: ui.run_javascript(f'document.title = "Page {e.value}"'))
         ui.radio(['A', 'B'], on_change=lambda e: ui.run_javascript(f'document.title = "Page {e.value}"'))
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         ui.run_javascript('document.title = "Initial Title"')
         ui.run_javascript('document.title = "Initial Title"')
 
 
     screen.open('/')
     screen.open('/')

+ 1 - 1
tests/test_lifecycle.py

@@ -35,7 +35,7 @@ def test_connect_disconnect_is_called_for_each_client(screen: Screen):
 
 
     @ui.page('/', reconnect_timeout=0)
     @ui.page('/', reconnect_timeout=0)
     def page():
     def page():
-        ui.label(f'client id: {ui.context.get_client().id}')
+        ui.label(f'client id: {ui.context.client.id}')
     app.on_connect(lambda: events.append('connect'))
     app.on_connect(lambda: events.append('connect'))
     app.on_disconnect(lambda: events.append('disconnect'))
     app.on_disconnect(lambda: events.append('disconnect'))
 
 

+ 9 - 9
tests/test_page.py

@@ -120,7 +120,7 @@ def test_wait_for_connected(screen: Screen):
     async def page():
     async def page():
         nonlocal label
         nonlocal label
         label = ui.label()
         label = ui.label()
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         await load()
         await load()
 
 
     screen.open('/')
     screen.open('/')
@@ -132,9 +132,9 @@ def test_wait_for_disconnect(screen: Screen):
 
 
     @ui.page('/', reconnect_timeout=0)
     @ui.page('/', reconnect_timeout=0)
     async def page():
     async def page():
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         events.append('connected')
         events.append('connected')
-        await ui.context.get_client().disconnected()
+        await ui.context.client.disconnected()
         events.append('disconnected')
         events.append('disconnected')
 
 
     screen.open('/')
     screen.open('/')
@@ -149,7 +149,7 @@ def test_wait_for_disconnect_without_awaiting_connected(screen: Screen):
 
 
     @ui.page('/', reconnect_timeout=0)
     @ui.page('/', reconnect_timeout=0)
     async def page():
     async def page():
-        await ui.context.get_client().disconnected()
+        await ui.context.client.disconnected()
         events.append('disconnected')
         events.append('disconnected')
 
 
     screen.open('/')
     screen.open('/')
@@ -163,7 +163,7 @@ def test_adding_elements_after_connected(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
         ui.label('before')
         ui.label('before')
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         ui.label('after')
         ui.label('after')
 
 
     screen.open('/')
     screen.open('/')
@@ -185,7 +185,7 @@ def test_exception(screen: Screen):
 def test_exception_after_connected(screen: Screen):
 def test_exception_after_connected(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         ui.label('this is shown')
         ui.label('this is shown')
         raise RuntimeError('some exception')
         raise RuntimeError('some exception')
 
 
@@ -207,7 +207,7 @@ def test_adding_elements_during_onconnect(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     def page():
     def page():
         ui.label('Label 1')
         ui.label('Label 1')
-        ui.context.get_client().on_connect(lambda: ui.label('Label 2'))
+        ui.context.client.on_connect(lambda: ui.label('Label 2'))
 
 
     screen.open('/')
     screen.open('/')
     screen.should_contain('Label 2')
     screen.should_contain('Label 2')
@@ -219,7 +219,7 @@ def test_async_connect_handler(screen: Screen):
         async def run_js():
         async def run_js():
             result.text = await ui.run_javascript('41 + 1')
             result.text = await ui.run_javascript('41 + 1')
         result = ui.label()
         result = ui.label()
-        ui.context.get_client().on_connect(run_js)
+        ui.context.client.on_connect(run_js)
 
 
     screen.open('/')
     screen.open('/')
     screen.should_contain('42')
     screen.should_contain('42')
@@ -292,7 +292,7 @@ def test_returning_custom_response_async(screen: Screen):
 def test_warning_about_to_late_responses(screen: Screen):
 def test_warning_about_to_late_responses(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         ui.label('NiceGUI page')
         ui.label('NiceGUI page')
         return PlainTextResponse('custom response')
         return PlainTextResponse('custom response')
 
 

+ 6 - 6
tests/test_storage.py

@@ -4,7 +4,7 @@ from pathlib import Path
 import httpx
 import httpx
 import pytest
 import pytest
 
 
-from nicegui import Client, app, background_tasks, context, ui
+from nicegui import app, background_tasks, context, ui
 from nicegui import storage as storage_module
 from nicegui import storage as storage_module
 from nicegui.testing import Screen
 from nicegui.testing import Screen
 
 
@@ -50,7 +50,7 @@ def test_browser_storage_supports_asyncio(screen: Screen):
 def test_browser_storage_modifications_after_page_load_are_forbidden(screen: Screen):
 def test_browser_storage_modifications_after_page_load_are_forbidden(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         try:
         try:
             app.storage.browser['test'] = 'data'
             app.storage.browser['test'] = 'data'
         except TypeError as e:
         except TypeError as e:
@@ -65,7 +65,7 @@ def test_user_storage_modifications(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     async def page(delayed: bool = False):
     async def page(delayed: bool = False):
         if delayed:
         if delayed:
-            await ui.context.get_client().connected()
+            await ui.context.client.connected()
         app.storage.user['count'] = app.storage.user.get('count', 0) + 1
         app.storage.user['count'] = app.storage.user.get('count', 0) + 1
         ui.label().bind_text_from(app.storage.user, 'count')
         ui.label().bind_text_from(app.storage.user, 'count')
 
 
@@ -170,7 +170,7 @@ def test_rapid_storage(screen: Screen):
 def test_tab_storage_is_local(screen: Screen):
 def test_tab_storage_is_local(screen: Screen):
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
-        await context.get_client().connected()
+        await context.client.connected()
         app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
         app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
         ui.label().bind_text_from(app.storage.tab, 'count')
         ui.label().bind_text_from(app.storage.tab, 'count')
 
 
@@ -194,7 +194,7 @@ def test_tab_storage_is_auto_removed(screen: Screen):
 
 
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
-        await context.get_client().connected()
+        await context.client.connected()
         app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
         app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
         ui.label().bind_text_from(app.storage.tab, 'count')
         ui.label().bind_text_from(app.storage.tab, 'count')
 
 
@@ -213,7 +213,7 @@ def test_clear_tab_storage(screen: Screen):
 
 
     @ui.page('/')
     @ui.page('/')
     async def page():
     async def page():
-        await context.get_client().connected()
+        await context.client.connected()
         app.storage.tab['test'] = '123'
         app.storage.tab['test'] = '123'
         ui.button('clear', on_click=app.storage.clear)
         ui.button('clear', on_click=app.storage.clear)
 
 

+ 2 - 2
website/documentation/content/generic_events_documentation.py

@@ -1,4 +1,4 @@
-from nicegui import context, ui
+from nicegui import ui
 
 
 from . import doc
 from . import doc
 
 
@@ -116,7 +116,7 @@ async def custom_events() -> None:
     #     </script>
     #     </script>
     # ''')
     # ''')
     # END OF DEMO
     # END OF DEMO
-    await context.get_client().connected()
+    await ui.context.client.connected()
     ui.run_javascript('''
     ui.run_javascript('''
         document.addEventListener('visibilitychange', () => {
         document.addEventListener('visibilitychange', () => {
             if (document.visibilityState === 'visible') {
             if (document.visibilityState === 'visible') {

+ 2 - 2
website/documentation/content/page_documentation.py

@@ -45,10 +45,10 @@ def wait_for_connected_demo():
     @ui.page('/wait_for_connection')
     @ui.page('/wait_for_connection')
     async def wait_for_connection():
     async def wait_for_connection():
         ui.label('This text is displayed immediately.')
         ui.label('This text is displayed immediately.')
-        await ui.context.get_client().connected()
+        await ui.context.client.connected()
         await asyncio.sleep(2)
         await asyncio.sleep(2)
         ui.label('This text is displayed 2 seconds after the page has been fully loaded.')
         ui.label('This text is displayed 2 seconds after the page has been fully loaded.')
-        ui.label(f'The IP address {ui.context.get_client().ip} was obtained from the websocket.')
+        ui.label(f'The IP address {ui.context.client.ip} was obtained from the websocket.')
 
 
     ui.link('wait for connection', wait_for_connection)
     ui.link('wait for connection', wait_for_connection)
 
 

+ 3 - 3
website/documentation/content/query_documentation.py

@@ -1,4 +1,4 @@
-from nicegui import context, ui
+from nicegui import ui
 
 
 from . import doc
 from . import doc
 
 
@@ -22,7 +22,7 @@ def main_demo() -> None:
 def background_image():
 def background_image():
     # ui.query('body').classes('bg-gradient-to-t from-blue-400 to-blue-100')
     # ui.query('body').classes('bg-gradient-to-t from-blue-400 to-blue-100')
     # END OF DEMO
     # END OF DEMO
-    context.get_slot_stack()[-1].parent.classes('bg-gradient-to-t from-blue-400 to-blue-100')
+    ui.context.slot_stack[-1].parent.classes('bg-gradient-to-t from-blue-400 to-blue-100')
 
 
 
 
 @doc.demo('Modify default page padding', '''
 @doc.demo('Modify default page padding', '''
@@ -31,7 +31,7 @@ def background_image():
 ''')
 ''')
 def remove_padding():
 def remove_padding():
     # ui.query('.nicegui-content').classes('p-0')
     # ui.query('.nicegui-content').classes('p-0')
-    context.get_slot_stack()[-1].parent.classes(remove='p-4')  # HIDE
+    ui.context.slot_stack[-1].parent.classes(remove='p-4')  # HIDE
     # with ui.column().classes('h-screen w-full bg-gray-400 justify-between'):
     # with ui.column().classes('h-screen w-full bg-gray-400 justify-between'):
     with ui.column().classes('h-full w-full bg-gray-400 justify-between'):  # HIDE
     with ui.column().classes('h-full w-full bg-gray-400 justify-between'):  # HIDE
         ui.label('top left')
         ui.label('top left')

+ 1 - 1
website/documentation/content/storage_documentation.py

@@ -122,7 +122,7 @@ def tab_storage():
 
 
     # @ui.page('/')
     # @ui.page('/')
     # async def index():
     # async def index():
-    #     await ui.context.get_client().connected()
+    #     await ui.context.client.connected()
     with ui.column():  # HIDE
     with ui.column():  # HIDE
         app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
         app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
         ui.label(f'Tab reloaded {app.storage.tab["count"]} times')
         ui.label(f'Tab reloaded {app.storage.tab["count"]} times')

+ 2 - 2
website/main_page.py

@@ -1,4 +1,4 @@
-from nicegui import context, ui
+from nicegui import ui
 
 
 from . import documentation, example_card, svg
 from . import documentation, example_card, svg
 from .examples import examples
 from .examples import examples
@@ -8,7 +8,7 @@ from .style import example_link, features, heading, link_target, section_heading
 
 
 def create() -> None:
 def create() -> None:
     """Create the content of the main page."""
     """Create the content of the main page."""
-    context.get_client().content.classes('p-0 gap-0')
+    ui.context.client.content.classes('p-0 gap-0')
     add_head_html()
     add_head_html()
     add_header()
     add_header()
 
 

+ 2 - 2
website/style.py

@@ -1,7 +1,7 @@
 import re
 import re
 from typing import List, Optional
 from typing import List, Optional
 
 
-from nicegui import context, ui
+from nicegui import ui
 
 
 from .examples import Example
 from .examples import Example
 
 
@@ -73,7 +73,7 @@ def subheading(text: str, *, link: Optional[str] = None, major: bool = False, an
             ui.label(text).classes(classes)
             ui.label(text).classes(classes)
         with ui.link(target=f'#{name}').classes('absolute').style('transform: translateX(-150%)'):
         with ui.link(target=f'#{name}').classes('absolute').style('transform: translateX(-150%)'):
             ui.icon('link', size='sm').classes('opacity-10 hover:opacity-80')
             ui.icon('link', size='sm').classes('opacity-10 hover:opacity-80')
-    drawers = [element for element in context.get_client().elements.values() if isinstance(element, ui.left_drawer)]
+    drawers = [element for element in ui.context.client.elements.values() if isinstance(element, ui.left_drawer)]
     if drawers:
     if drawers:
         menu = drawers[0]
         menu = drawers[0]
         with menu:
         with menu: