Skip to content

Commit

Permalink
Merge branch '1.4' into run_javascript
Browse files Browse the repository at this point in the history
# Conflicts:
#	website/documentation_tools.py
  • Loading branch information
falkoschindler committed Oct 19, 2023
2 parents 10d4b85 + b5ee25f commit f34f013
Show file tree
Hide file tree
Showing 17 changed files with 173 additions and 93 deletions.
2 changes: 1 addition & 1 deletion development.dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM python:3.8-slim

RUN apt update && apt install curl -y
RUN apt update && apt install curl build-essential -y

# We use Poetry for dependency management
RUN curl -sSL https://install.python-poetry.org | python3 - && \
Expand Down
6 changes: 3 additions & 3 deletions fly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ kill_timeout = "5s"
dockerfile = "fly.dockerfile"

[deploy]
strategy = "bluegreen"
strategy = "canary"

[processes]
app = ""
Expand All @@ -37,8 +37,8 @@ kill_timeout = "5s"
port = 443
handlers = ["tls", "http"]
[services.concurrency]
type = "connections"
hard_limit = 80
type = "requests"
hard_limit = 60
soft_limit = 30

[[services.tcp_checks]]
Expand Down
8 changes: 8 additions & 0 deletions nicegui/elements/context_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@ def __init__(self) -> None:
super().__init__('q-menu')
self._props['context-menu'] = True
self._props['touch-position'] = True

def open(self) -> None:
"""Open the context menu."""
self.run_method('show')

def close(self) -> None:
"""Close the context menu."""
self.run_method('hide')
3 changes: 2 additions & 1 deletion nicegui/elements/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .. import globals # pylint: disable=redefined-builtin
from ..events import ClickEventArguments, handle_event
from .context_menu import ContextMenu
from .mixins.text_element import TextElement
from .mixins.value_element import ValueElement

Expand Down Expand Up @@ -65,6 +66,6 @@ def __init__(self,
def handle_click(_) -> None:
handle_event(on_click, ClickEventArguments(sender=self, client=self.client))
if auto_close:
assert isinstance(self.menu, Menu)
assert isinstance(self.menu, (Menu, ContextMenu))
self.menu.close()
self.on('click', handle_click, [])
1 change: 1 addition & 0 deletions nicegui/elements/timer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default {};
23 changes: 9 additions & 14 deletions nicegui/functions/timer.py → nicegui/elements/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

from .. import background_tasks, globals, helpers # pylint: disable=redefined-builtin
from ..binding import BindableProperty
from ..slot import Slot
from ..element import Element


class Timer:
class Timer(Element, component='timer.js'):
active = BindableProperty()
interval = BindableProperty()

Expand All @@ -28,10 +28,10 @@ def __init__(self,
:param active: whether the callback should be executed or not (can be changed during runtime)
:param once: whether the callback is only executed once after a delay specified by `interval` (default: `False`)
"""
super().__init__()
self.interval = interval
self.callback: Optional[Callable[..., Any]] = callback
self.active = active
self.slot: Optional[Slot] = globals.get_slot()
self._is_canceled: bool = False

coroutine = self._run_once if once else self._run_in_loop
Expand All @@ -57,8 +57,7 @@ async def _run_once(self) -> None:
try:
if not await self._connected():
return
assert self.slot is not None
with self.slot:
with self.parent_slot:
await asyncio.sleep(self.interval)
if self.active and not self._should_stop():
await self._invoke_callback()
Expand All @@ -69,8 +68,7 @@ async def _run_in_loop(self) -> None:
try:
if not await self._connected():
return
assert self.slot is not None
with self.slot:
with self.parent_slot:
while not self._should_stop():
try:
start = time.time()
Expand Down Expand Up @@ -101,27 +99,24 @@ async def _connected(self, timeout: float = 60.0) -> bool:
See https://github.com/zauberzeug/nicegui/issues/206 for details.
Returns True if the client is connected, False if the client is not connected and the timer should be cancelled.
"""
assert self.slot is not None
if self.slot.parent.client.shared:
if self.client.shared:
return True

# ignore served pages which do not reconnect to backend (e.g. monitoring requests, scrapers etc.)
try:
await self.slot.parent.client.connected(timeout=timeout)
await self.client.connected(timeout=timeout)
return True
except TimeoutError:
globals.log.error(f'Timer cancelled because client is not connected after {timeout} seconds')
return False

def _should_stop(self) -> bool:
assert self.slot is not None
return (
self.slot.parent.is_deleted or
self.slot.parent.client.id not in globals.clients or
self.is_deleted or
self.client.id not in globals.clients or
self._is_canceled or
globals.state in {globals.State.STOPPING, globals.State.STOPPED}
)

def _cleanup(self) -> None:
self.slot = None
self.callback = None
36 changes: 32 additions & 4 deletions nicegui/functions/refreshable.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from dataclasses import dataclass
from typing import Any, Awaitable, Callable, Dict, List, Tuple, Union
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Awaitable, Callable, ClassVar, Dict, List, Optional, Tuple, Union

from typing_extensions import Self

Expand All @@ -11,13 +13,20 @@

@dataclass(**KWONLY_SLOTS)
class RefreshableTarget:
container: Element
container: RefreshableContainer
refreshable: refreshable
instance: Any
args: Tuple[Any, ...]
kwargs: Dict[str, Any]

current_target: ClassVar[Optional[RefreshableTarget]] = None
locals: List[Any] = field(default_factory=list)
next_index: int = 0

def run(self, func: Callable[..., Any]) -> Union[None, Awaitable]:
"""Run the function and return the result."""
RefreshableTarget.current_target = self
self.next_index = 0
# pylint: disable=no-else-return
if is_coroutine_function(func):
async def wait_for_result() -> None:
Expand Down Expand Up @@ -67,7 +76,8 @@ def refresh(*args: Any, _instance=self.instance, **kwargs: Any) -> None:

def __call__(self, *args: Any, **kwargs: Any) -> Union[None, Awaitable]:
self.prune()
target = RefreshableTarget(container=RefreshableContainer(), instance=self.instance, args=args, kwargs=kwargs)
target = RefreshableTarget(container=RefreshableContainer(), refreshable=self, instance=self.instance,
args=args, kwargs=kwargs)
self.targets.append(target)
return target.run(self.func)

Expand Down Expand Up @@ -106,3 +116,21 @@ def prune(self) -> None:
for target in self.targets
if target.container.client.id in globals.clients and target.container.id in target.container.client.elements
]


def state(value: Any) -> Tuple[Any, Callable[[Any], None]]:
target = RefreshableTarget.current_target
assert target is not None

if target.next_index >= len(target.locals):
target.locals.append(value)
else:
value = target.locals[target.next_index]

def set_value(new_value: Any, index=target.next_index) -> None:
target.locals[index] = new_value
target.refreshable.refresh()

target.next_index += 1

return value, set_value
3 changes: 1 addition & 2 deletions nicegui/storage.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import contextvars
import json
import uuid
from collections.abc import MutableMapping
from pathlib import Path
Expand All @@ -12,7 +11,7 @@
from starlette.requests import Request
from starlette.responses import Response

from . import background_tasks, globals, observables # pylint: disable=redefined-builtin
from . import background_tasks, globals, json, observables # pylint: disable=redefined-builtin

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

Expand Down
7 changes: 4 additions & 3 deletions nicegui/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
'tabs',
'textarea',
'time',
'timer',
'timeline',
'timeline_entry',
'toggle',
Expand All @@ -84,7 +85,7 @@
'notify',
'open',
'refreshable',
'timer',
'state',
'update',
'page',
'drawer',
Expand Down Expand Up @@ -170,6 +171,7 @@
from .elements.time import Time as time
from .elements.timeline import Timeline as timeline
from .elements.timeline import TimelineEntry as timeline_entry
from .elements.timer import Timer as timer
from .elements.toggle import Toggle as toggle
from .elements.tooltip import Tooltip as tooltip
from .elements.tree import Tree as tree
Expand All @@ -180,8 +182,7 @@
from .functions.javascript import run_javascript
from .functions.notify import notify
from .functions.open import open # pylint: disable=redefined-builtin
from .functions.refreshable import refreshable
from .functions.timer import Timer as timer
from .functions.refreshable import refreshable, state
from .functions.update import update
from .page import page
from .page_layout import Drawer as drawer
Expand Down
Loading

0 comments on commit f34f013

Please sign in to comment.