Skip to content

Commit

Permalink
Merge branch '2.0' into dependabot/pip/fastapi-0.112.2
Browse files Browse the repository at this point in the history
# Conflicts:
#	poetry.lock
#	pyproject.toml
  • Loading branch information
falkoschindler committed Aug 30, 2024
2 parents 0f401e1 + 108035a commit 8920aed
Show file tree
Hide file tree
Showing 86 changed files with 1,537 additions and 804 deletions.
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.36
date-released: '2024-08-15'
version: 1.4.37
date-released: '2024-08-22'
url: https://github.com/zauberzeug/nicegui
doi: 10.5281/zenodo.13325243
doi: 10.5281/zenodo.13358977
3 changes: 2 additions & 1 deletion examples/chat_with_ai/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
langchain>=0.0.142
langchain>=0.2
langchain-community
langchain_openai
nicegui
2 changes: 2 additions & 0 deletions examples/node_module_integration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
package-lock.json
30 changes: 30 additions & 0 deletions examples/node_module_integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Use Bundled Node Modules as Third-party Dependencies

This example demonstrates how to use multiple third-party node modules as dependencies in a NiceGUI app.
The app uses the [is-odd](https://www.npmjs.com/package/is-odd) node modules to check if a number is even or odd.
We chose this package to demonstrate a very simple node module which has a dependency itself,
namely the [is-number](https://www.npmjs.com/package/is-number) package.
Using NPM, we can easily install both packages and bundle them into a single file which can be used in the app.
The package.json file defines the is-odd dependency and some dev dependencies for bundling the node module,
the webpack.config.js file specifies the entry point for the node module,
and number_checker.js as well as number_checker.py define a new UI element to be used in the NiceGUI app main.py.

1. First, install all third-party node modules (assuming you have NPM installed):

```bash
npm install
```

This will create a node_modules directory containing the is-odd and is-number modules as well as some dev dependencies.

2. Now bundle the node module:

```bash
npm run build
```

3. Finally, you can run the app as usual:

```bash
python3 main.py
```
19 changes: 19 additions & 0 deletions examples/node_module_integration/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python3
from number_checker import NumberChecker

from nicegui import ui


@ui.page('/')
def page():
number_checker = NumberChecker()
number = ui.number(value=42.0)

async def check():
even = await number_checker.is_even(number.value)
ui.notify(f'{number.value} is {"even" if even else "odd"}')

ui.button('Check', on_click=check)


ui.run()
13 changes: 13 additions & 0 deletions examples/node_module_integration/number_checker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
async mounted() {
await import("is-odd");
},
methods: {
isOdd(number) {
return isOdd(number);
},
isEven(number) {
return !isOdd(number);
},
},
};
19 changes: 19 additions & 0 deletions examples/node_module_integration/number_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from nicegui import ui


class NumberChecker(ui.element, component='number_checker.js', dependencies=['dist/is-odd.js']):

def __init__(self) -> None:
"""NumberChecker
A number checker based on the `is-odd <https://www.npmjs.com/package/is-odd>`_ NPM package.
"""
super().__init__()

async def is_odd(self, number: int) -> bool:
"""Check if a number is odd."""
return await self.run_method('isOdd', number)

async def is_even(self, number: int) -> bool:
"""Check if a number is even."""
return await self.run_method('isEven', number)
15 changes: 15 additions & 0 deletions examples/node_module_integration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"scripts": {
"build": "webpack --config webpack.config.js"
},
"dependencies": {
"is-odd": "^3.0.1"
},
"devDependencies": {
"@babel/core": "^7.24.5",
"@babel/preset-env": "^7.24.5",
"babel-loader": "^9.1.3",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
}
}
26 changes: 26 additions & 0 deletions examples/node_module_integration/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const path = require("path");

module.exports = {
entry: "is-odd/index.js",
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "is-odd.js",
library: "isOdd",
libraryTarget: "umd",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
};
2 changes: 2 additions & 0 deletions examples/signature_pad/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
package-lock.json
20 changes: 20 additions & 0 deletions examples/signature_pad/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use Node Modules as Third-party Dependencies

This example demonstrates how to use third-party node modules as dependencies in a NiceGUI app.
The app uses the [signature_pad](https://www.npmjs.com/package/signature_pad) node module to create a signature pad.
In package.json, the signature_pad module is listed as a dependency,
while signature_pad.js and signature_pad.py define the new UI element which can be used in main.py.

1. First, install the third-party node modules (assuming you have NPM installed):

```bash
npm install
```

This will create a node_modules directory containing the signature_pad module.

2. Now you can run the app as usual:

```bash
python3 main.py
```
9 changes: 9 additions & 0 deletions examples/signature_pad/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env python3
from signature_pad import SignaturePad

from nicegui import ui

pad = SignaturePad().classes('border')
ui.button('Clear', on_click=pad.clear)

ui.run()
5 changes: 5 additions & 0 deletions examples/signature_pad/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"signature_pad": "^4.1.7"
}
}
16 changes: 16 additions & 0 deletions examples/signature_pad/signature_pad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SignaturePad from "signature_pad";

export default {
template: "<canvas></canvas>",
props: {
options: Array,
},
mounted() {
this.pad = new SignaturePad(this.$el, this.options);
},
methods: {
clear() {
this.pad.clear();
},
},
};
20 changes: 20 additions & 0 deletions examples/signature_pad/signature_pad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import Dict, Optional

from nicegui import ui


class SignaturePad(ui.element,
component='signature_pad.js',
dependencies=['node_modules/signature_pad/dist/signature_pad.min.js']):

def __init__(self, options: Optional[Dict] = None) -> None:
"""SignaturePad
An element that integrates the `Signature Pad library <https://szimek.github.io/signature_pad/>`_.
"""
super().__init__()
self._props['options'] = options or {}

def clear(self):
"""Clear the signature."""
self.run_method('clear')
4 changes: 2 additions & 2 deletions examples/table_and_slots/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
with table.row():
with table.cell():
ui.button(on_click=lambda: (
table.add_rows({'id': time.time(), 'name': new_name.value, 'age': new_age.value}),
table.add_row({'id': time.time(), 'name': new_name.value, 'age': new_age.value}),
new_name.set_value(None),
new_age.set_value(None),
), icon='add').props('flat fab-mini')
Expand All @@ -35,7 +35,7 @@
new_age = ui.number('Age')

ui.label().bind_text_from(table, 'selected', lambda val: f'Current selection: {val}')
ui.button('Remove', on_click=lambda: table.remove_rows(*table.selected)) \
ui.button('Remove', on_click=lambda: table.remove_rows(table.selected)) \
.bind_visibility_from(table, 'selected', backward=lambda val: bool(val))

ui.run()
2 changes: 1 addition & 1 deletion nicegui/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class State(Enum):
class App(FastAPI):

def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
super().__init__(**kwargs, docs_url=None, redoc_url=None, openapi_url=None)
self.native = NativeConfig()
self.storage = Storage()
self.urls = ObservableSet()
Expand Down
45 changes: 45 additions & 0 deletions nicegui/classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import TYPE_CHECKING, Generic, List, Optional, TypeVar

if TYPE_CHECKING:
from .element import Element

T = TypeVar('T', bound='Element')


class Classes(list, Generic[T]):

def __init__(self, *args, element: T, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.element = element

def __call__(self,
add: Optional[str] = None, *,
remove: Optional[str] = None,
replace: Optional[str] = None) -> T:
"""Apply, remove, or replace HTML classes.
This allows modifying the look of the element or its layout using `Tailwind <https://tailwindcss.com/>`_ or `Quasar <https://quasar.dev/>`_ classes.
Removing or replacing classes can be helpful if predefined classes are not desired.
:param add: whitespace-delimited string of classes
:param remove: whitespace-delimited string of classes to remove from the element
:param replace: whitespace-delimited string of classes to use instead of existing ones
"""
new_classes = self.update_list(self, add, remove, replace)
if self != new_classes:
self[:] = new_classes
self.element.update()
return self.element

@staticmethod
def update_list(classes: List[str],
add: Optional[str] = None,
remove: Optional[str] = None,
replace: Optional[str] = None) -> List[str]:
"""Update a list of classes."""
class_list = classes if replace is None else []
class_list = [c for c in class_list if c not in (remove or '').split()]
class_list += (add or '').split()
class_list += (replace or '').split()
return list(dict.fromkeys(class_list)) # NOTE: remove duplicates while preserving order
26 changes: 5 additions & 21 deletions nicegui/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ def is_auto_index_client(self) -> bool:

@property
def ip(self) -> Optional[str]:
"""Return the IP address of the client, or None if the client is not connected."""
return self.environ['asgi.scope']['client'][0] if self.environ else None # pylint: disable=unsubscriptable-object
"""Return the IP address of the client, or None if it is an
`auto-index page <https://nicegui.io/documentation/section_pages_routing#auto-index_page>`_."""
return self.request.client.host if self.request is not None and self.request.client is not None else None

@property
def has_socket_connection(self) -> bool:
Expand Down Expand Up @@ -180,11 +181,7 @@ async def disconnected(self, check_interval: float = 0.1) -> None:
await asyncio.sleep(check_interval)
self.is_waiting_for_disconnect = False

def run_javascript(self, code: str, *,
respond: Optional[bool] = None, # DEPRECATED
timeout: float = 1.0,
check_interval: float = 0.01, # DEPRECATED
) -> AwaitableResponse:
def run_javascript(self, code: str, *, timeout: float = 1.0) -> AwaitableResponse:
"""Execute JavaScript on the client.
The client connection must be established before this method is called.
Expand All @@ -198,19 +195,6 @@ def run_javascript(self, code: str, *,
:return: AwaitableResponse that can be awaited to get the result of the JavaScript code
"""
if respond is True:
helpers.warn_once('The "respond" argument of run_javascript() has been removed. '
'Now the method always returns an AwaitableResponse that can be awaited. '
'Please remove the "respond=True" argument.')
if respond is False:
raise ValueError('The "respond" argument of run_javascript() has been removed. '
'Now the method always returns an AwaitableResponse that can be awaited. '
'Please remove the "respond=False" argument and call the method without awaiting.')
if check_interval != 0.01:
helpers.warn_once('The "check_interval" argument of run_javascript() and similar methods has been removed. '
'Now the method automatically returns when receiving a response without checking regularly in an interval. '
'Please remove the "check_interval" argument.')

request_id = str(uuid.uuid4())
target_id = self._temporary_socket_id or self.id

Expand Down Expand Up @@ -282,7 +266,7 @@ def handle_event(self, msg: Dict) -> None:

def handle_javascript_response(self, msg: Dict) -> None:
"""Store the result of a JavaScript command."""
JavaScriptRequest.resolve(msg['request_id'], msg['result'])
JavaScriptRequest.resolve(msg['request_id'], msg.get('result'))

def safe_invoke(self, func: Union[Callable[..., Any], Awaitable]) -> None:
"""Invoke the potentially async function in the client context and catch any exceptions."""
Expand Down
16 changes: 0 additions & 16 deletions nicegui/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from typing import TYPE_CHECKING, List

from . import helpers
from .slot import Slot

if TYPE_CHECKING:
Expand All @@ -11,21 +10,6 @@

class Context:

def get_slot_stack(self) -> List[Slot]:
"""Return the slot stack of the current asyncio task. (DEPRECATED, use context.slot_stack instead)"""
helpers.warn_once('context.get_slot_stack() is deprecated, use context.slot_stack instead')
return self.slot_stack

def get_slot(self) -> Slot:
"""Return the current slot. (DEPRECATED, use context.slot instead)"""
helpers.warn_once('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)"""
helpers.warn_once('context.get_client() is deprecated, use context.client instead')
return self.client

@property
def slot_stack(self) -> List[Slot]:
"""Return the slot stack of the current asyncio task."""
Expand Down
Loading

0 comments on commit 8920aed

Please sign in to comment.