Skip to content

Commit

Permalink
Merge branch 'main' into message-retransmission
Browse files Browse the repository at this point in the history
  • Loading branch information
falkoschindler committed Jul 27, 2024
2 parents 230adce + 6c5fceb commit dbb0f16
Show file tree
Hide file tree
Showing 31 changed files with 815 additions and 396 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.8
FROM --platform=linux/amd64 python:3.8

ENV POETRY_VERSION=1.6.1 \
POETRY_NO_INTERACTION=1 \
Expand Down
6 changes: 3 additions & 3 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ authors:
given-names: Rodja
orcid: https://orcid.org/0009-0009-4735-6227
title: 'NiceGUI: Web-based user interfaces with Python. The nice way.'
version: v1.4.27
date-released: '2024-06-13'
version: v1.4.29
date-released: '2024-07-09'
url: https://github.com/zauberzeug/nicegui
doi: 10.5281/zenodo.11637764
doi: 10.5281/zenodo.12697345
62 changes: 33 additions & 29 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,6 @@ To view the log output, use the command

### Formatting

We use [pre-commit](https://github.com/pre-commit/pre-commit) to make sure the coding style is enforced.
You first need to install pre-commit and the corresponding git commit hooks by running the following commands:

```bash
python3 -m pip install pre-commit
pre-commit install
```

After that you can make sure your code satisfies the coding style by running the following command:

```bash
pre-commit run --all-files
```

These checks will also run automatically before every commit.

### Formatting

We use [autopep8](https://github.com/hhatto/autopep8) with a 120 character line length to format our code.
Before submitting a pull request, please run

Expand All @@ -109,24 +91,46 @@ There are cases where one or the other arrangement of, e.g., function arguments
Then we like the flexibility to either put all arguments on separate lines or only put the lengthy event handler
on a second line and leave the other arguments as they are.

### Imports
### Linting

We use [ruff](https://docs.astral.sh/ruff/) to automatically sort imports:
We use [pre-commit](https://github.com/pre-commit/pre-commit) to make sure the coding style is enforced.
You first need to install pre-commit and the corresponding git commit hooks by running the following commands:

```bash
ruff check . --fix
python3 -m pip install pre-commit
pre-commit install
```

### Single vs Double Quotes

Regarding single or double quotes: [PEP 8](https://peps.python.org/pep-0008/) doesn't give any recommendation, so we simply chose single quotes and sticked with it.
On qwerty keyboards it's a bit easier to type, is visually less cluttered, and it works well for strings containing double quotes from the English language.
After that you can make sure your code satisfies the coding style by running the following command:

### F-Strings
```bash
pre-commit run --all-files
```

We use f-strings where ever possible because they are generally more readable - once you get used to them.
There are only a few places in the code base where performance really matters and f-strings might not be the best choice.
These places should be marked with a `# NOTE: ...` comment when diverging from f-string usage.
> [!TIP]
> The command may fail with
>
> > RuntimeError: failed to find interpreter for Builtin discover of python_spec='python3.8'
>
> You will need to install Python 3.8 and make sure it is available in your `PATH`.
These checks will also run automatically before every commit:

- Run `ruff check . --fix` to check the code and sort imports.
- Remove trailing whitespace.
- Fix end of files.
- Enforce single quotes.

> [!NOTE]
>
> **Regarding single or double quotes:** > [PEP 8](https://peps.python.org/pep-0008/) doesn't give any recommendation, so we simply chose single quotes and sticked with it.
> On qwerty keyboards it's a bit easier to type, is visually less cluttered, and it works well for strings containing double quotes from the English language.
> [!NOTE]
>
> **We use f-strings** where ever possible because they are generally more readable - once you get used to them.
> There are only a few places in the code base where performance really matters and f-strings might not be the best choice.
> These places should be marked with a `# NOTE: ...` comment when diverging from f-string usage.
## Running tests

Expand Down
8 changes: 5 additions & 3 deletions nicegui/app/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import inspect
import os
import platform
import signal
import urllib
from enum import Enum
from pathlib import Path
Expand Down Expand Up @@ -124,12 +127,11 @@ def shutdown(self) -> None:
"""Shut down NiceGUI.
This will programmatically stop the server.
Only possible when auto-reload is disabled.
"""
if self.config.reload:
raise RuntimeError('calling shutdown() is not supported when auto-reload is enabled')
if self.native.main_window:
self.native.main_window.destroy()
if self.config.reload:
os.kill(os.getppid(), getattr(signal, 'CTRL_C_EVENT' if platform.system() == 'Windows' else 'SIGINT'))
else:
Server.instance.should_exit = True

Expand Down
3 changes: 2 additions & 1 deletion nicegui/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from fastapi.templating import Jinja2Templates
from typing_extensions import Self

from . import background_tasks, binding, core, helpers, json
from . import background_tasks, binding, core, helpers, json, storage
from .awaitable_response import AwaitableResponse
from .dependencies import generate_resources
from .element import Element
Expand Down Expand Up @@ -249,6 +249,7 @@ def handle_handshake(self) -> None:
if self._disconnect_task:
self._disconnect_task.cancel()
self._disconnect_task = None
storage.request_contextvar.set(self.request)
for t in self.connect_handlers:
self.safe_invoke(t)
for t in core.app._connect_handlers: # pylint: disable=protected-access
Expand Down
33 changes: 29 additions & 4 deletions nicegui/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,18 @@ def run_method(self, name: str, *args: Any, timeout: float = 1, check_interval:
return self.client.run_javascript(f'return runMethod({self.id}, "{name}", {json.dumps(args)})',
timeout=timeout, check_interval=check_interval)

def get_computed_prop(self, prop_name: str, *, timeout: float = 1) -> AwaitableResponse:
"""Return a computed property.
This function should be awaited so that the computed property is properly returned.
:param prop_name: name of the computed prop
:param timeout: maximum time to wait for a response (default: 1 second)
"""
if not core.loop:
return NullResponse()
return self.client.run_javascript(f'return getComputedProp({self.id}, "{prop_name}")', timeout=timeout)

def _collect_descendants(self, *, include_self: bool = False) -> List[Element]:
elements: List[Element] = [self] if include_self else []
for child in self:
Expand All @@ -498,19 +510,32 @@ def clear(self) -> None:
slot.children.clear()
self.update()

def move(self, target_container: Optional[Element] = None, target_index: int = -1):
def move(self,
target_container: Optional[Element] = None,
target_index: int = -1, *,
target_slot: Optional[str] = None) -> None:
"""Move the element to another container.
:param target_container: container to move the element to (default: the parent container)
:param target_index: index within the target slot (default: append to the end)
:param target_slot: slot within the target container (default: default slot)
"""
assert self.parent_slot is not None
self.parent_slot.children.remove(self)
self.parent_slot.parent.update()
target_container = target_container or self.parent_slot.parent
target_index = target_index if target_index >= 0 else len(target_container.default_slot.children)
target_container.default_slot.children.insert(target_index, self)
self.parent_slot = target_container.default_slot

if target_slot is None:
self.parent_slot = target_container.default_slot
elif target_slot in target_container.slots:
self.parent_slot = target_container.slots[target_slot]
else:
raise ValueError(f'Slot "{target_slot}" does not exist in the target container. '
f'Add it first using `add_slot("{target_slot}")`.')

target_index = target_index if target_index >= 0 else len(self.parent_slot.children)
self.parent_slot.children.insert(target_index, self)

target_container.update()

def remove(self, element: Union[Element, int]) -> None:
Expand Down
38 changes: 38 additions & 0 deletions nicegui/elements/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export default {
template: `
<q-editor
ref="qRef"
v-bind="$attrs"
v-model="inputValue"
>
<template v-for="(_, slot) in $slots" v-slot:[slot]="slotProps">
<slot :name="slot" v-bind="slotProps || {}" />
</template>
</q-input>
`,
props: {
value: String,
},
data() {
return {
inputValue: this.value,
emitting: true,
};
},
watch: {
value(newValue) {
this.emitting = false;
this.inputValue = newValue;
this.$nextTick(() => (this.emitting = true));
},
inputValue(newValue) {
if (!this.emitting) return;
this.$emit("update:value", newValue);
},
},
methods: {
updateValue() {
this.inputValue = this.value;
},
},
};
12 changes: 9 additions & 3 deletions nicegui/elements/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from .mixins.value_element import ValueElement


class Editor(ValueElement, DisableableElement):
LOOPBACK = None
class Editor(ValueElement, DisableableElement, component='editor.js'):
VALUE_PROP: str = 'value'
LOOPBACK = False

def __init__(self,
*,
Expand All @@ -21,7 +22,12 @@ def __init__(self,
:param value: initial value
:param on_change: callback to be invoked when the value changes
"""
super().__init__(tag='q-editor', value=value, on_value_change=on_change)
super().__init__(value=value, on_value_change=on_change)
self._classes.append('nicegui-editor')
if placeholder is not None:
self._props['placeholder'] = placeholder

def _handle_value_change(self, value: Any) -> None:
super()._handle_value_change(value)
if self._send_update_on_value_change:
self.run_method('updateValue')
1 change: 1 addition & 0 deletions nicegui/elements/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def sanitize(self) -> None:
if self.precision is not None:
value = float(round(value, self.precision))
self.set_value(float(self.format % value) if self.format else value)
self.update()

def _event_args_to_value(self, e: GenericEventArguments) -> Any:
if not e.args:
Expand Down
12 changes: 12 additions & 0 deletions nicegui/elements/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,18 @@ def update_rows(self, rows: List[Dict], *, clear_selection: bool = True) -> None
self.selected.clear()
self.update()

async def get_filtered_sorted_rows(self, *, timeout: float = 1) -> List[Dict]:
"""Asynchronously return the filtered and sorted rows of the table."""
return await self.get_computed_prop('filteredSortedRows', timeout=timeout)

async def get_computed_rows(self, *, timeout: float = 1) -> List[Dict]:
"""Asynchronously return the computed rows of the table."""
return await self.get_computed_prop('computedRows', timeout=timeout)

async def get_computed_rows_number(self, *, timeout: float = 1) -> int:
"""Asynchronously return the number of computed rows of the table."""
return await self.get_computed_prop('computedRowsNumber', timeout=timeout)

class row(Element):

def __init__(self) -> None:
Expand Down
7 changes: 5 additions & 2 deletions nicegui/json/builtin_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
has_numpy = False


def dumps(obj: Any, sort_keys: bool = False, separators: Optional[Tuple[str, str]] = None):
def dumps(obj: Any,
sort_keys: bool = False,
separators: Optional[Tuple[str, str]] = None, *,
indent: bool = False) -> str:
"""Serializes a Python object to a JSON-encoded string.
This implementation uses Python's default json module, but extends it in order to support NumPy arrays.
Expand All @@ -22,7 +25,7 @@ def dumps(obj: Any, sort_keys: bool = False, separators: Optional[Tuple[str, str
obj,
sort_keys=sort_keys,
separators=separators,
indent=None,
indent=2 if indent else None,
allow_nan=False,
ensure_ascii=False,
cls=NumpyJsonEncoder)
Expand Down
9 changes: 8 additions & 1 deletion nicegui/json/orjson_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
ORJSON_OPTS = orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NON_STR_KEYS


def dumps(obj: Any, sort_keys: bool = False, separators: Optional[Tuple[str, str]] = None):
def dumps(obj: Any,
sort_keys: bool = False,
separators: Optional[Tuple[str, str]] = None, *,
indent: bool = False) -> str:
"""Serializes a Python object to a JSON-encoded string.
By default, this function supports serializing NumPy arrays, which Python's json module does not.
Expand All @@ -33,6 +36,10 @@ def dumps(obj: Any, sort_keys: bool = False, separators: Optional[Tuple[str, str
if sort_keys:
opts |= orjson.OPT_SORT_KEYS

# flag for pretty-printing with indentation
if indent:
opts |= orjson.OPT_INDENT_2

return orjson.dumps(obj, option=opts, default=_orjson_converter).decode('utf-8')


Expand Down
13 changes: 13 additions & 0 deletions nicegui/static/nicegui.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ function runMethod(target, method_name, args) {
}
}

function getComputedProp(target, prop_name) {
if (typeof target === "object" && prop_name in target) {
return target[prop_name];
}
const element = getElement(target);
if (element === null || element === undefined) return;
if (prop_name in element) {
return element[prop_name];
} else if (prop_name in (element.$refs.qRef || [])) {
return element.$refs.qRef[prop_name];
}
}

function emitEvent(event_name, ...args) {
getElement(0).$emit(event_name, ...args);
}
Expand Down
Loading

0 comments on commit dbb0f16

Please sign in to comment.