diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fc72e2..8e72713 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,11 @@ jobs: python -m pip install --upgrade pip python -m pip install -e ".[all]" + - name: Verify NiceGUI ui stub is up to date + run: | + python scripts/generate_nicegui_ui_stub.py + git diff --exit-code -- typings/nicegui/ui.pyi + - name: Run tests run: python -m pytest tests diff --git a/README.md b/README.md index 534df2a..def5170 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ It currently provides three main entry points: When `nicegui_builder` is imported, it also attaches those entry points to NiceGUI's `ui` object at runtime: - `ui.builder(...)` +- `ui.datetime_input(...)` - `ui.form_builder(...)` - `ui.table_builder(...)` @@ -39,12 +40,22 @@ from nicegui_builder import ui That exported `ui` is the same runtime object as `nicegui.ui`, but `nicegui_builder` ships typing metadata for the added methods. For this workspace, VS Code/Pylance can also enrich `from nicegui import ui` directly through the local stub path configured in [`.vscode/settings.json`](.vscode/settings.json). -That setup uses [`typings/nicegui/ui.pyi`](typings/nicegui/ui.pyi) to expose `ui.builder(...)`, `ui.form_builder(...)`, and `ui.table_builder(...)` to code completion without modifying NiceGUI itself. +That setup uses the generated stub [`typings/nicegui/ui.pyi`](typings/nicegui/ui.pyi) to expose: + +- `ui.builder(...)` +- `ui.datetime_input(...)` +- `ui.form_builder(...)` +- `ui.table_builder(...)` + +to code completion without modifying NiceGUI itself. +The local [`typings/nicegui/py.typed`](typings/nicegui/py.typed) file marks those stubs as `partial`, so the editor can merge our additions with NiceGUI's own native signatures instead of replacing them. +The stub is generated by [`scripts/generate_nicegui_ui_stub.py`](scripts/generate_nicegui_ui_stub.py), and CI checks that the committed file stays aligned with the installed NiceGUI version used by the test environment. Patch alert: - the runtime methods are attached dynamically by `nicegui_builder` -- the editor completion for `from nicegui import ui` comes from local workspace stubs, not from NiceGUI upstream +- `ui.datetime_input(...)` is also attached dynamically by `nicegui_builder` +- the editor completion for `from nicegui import ui` comes from local partial workspace stubs, not from NiceGUI upstream - outside a workspace that loads those stubs, code completion may fall back to whatever the editor infers from the installed NiceGUI package alone ## Install diff --git a/docs/architecture.md b/docs/architecture.md index f096542..3f666c5 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -390,13 +390,14 @@ Some conveniences in the project are intentionally implemented as thin patches r Current examples: -- `nicegui_builder` attaches `builder`, `form`, and `table` to NiceGUI's runtime `ui` object -- the workspace can provide editor completion for those added methods through local stubs in [`typings/nicegui/ui.pyi`](../typings/nicegui/ui.pyi) +- `nicegui_builder` attaches `builder`, `datetime_input`, `form_builder`, and `table_builder` to NiceGUI's runtime `ui` object +- the workspace can provide editor completion for those added methods through local partial stubs in [`typings/nicegui/ui.pyi`](../typings/nicegui/ui.pyi) +- those stubs are generated from NiceGUI's own `ui` module by [`scripts/generate_nicegui_ui_stub.py`](../scripts/generate_nicegui_ui_stub.py) These integrations are useful and deliberate, but they should still be understood as package-owned glue: - runtime behavior is provided by `nicegui-builder` -- editor behavior depends on the local typing setup +- editor behavior depends on the local typing setup and generated stub - neither mechanism implies that NiceGUI itself natively declares those methods ## Practical Reading Order diff --git a/docs/examples.md b/docs/examples.md index 2d9f82c..1eea6cd 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -87,7 +87,7 @@ Covers: Suggested file: -- `examples/03_pydantic_form_basic.py` +- `plugins/pydantic/examples/03_pydantic_form_basic.py` ### 4. First automatic Pandas table @@ -102,7 +102,7 @@ Covers: Suggested file: -- `examples/04_pandas_table_basic.py` +- `plugins/pandas/examples/04_pandas_table_basic.py` ## Phase 2: Core Usage Patterns @@ -122,7 +122,7 @@ Covers: Suggested file: -- `examples/05_pydantic_form_yaml.py` +- `plugins/pydantic/examples/05_pydantic_form_yaml.py` ### 6. Pydantic automatic flavors @@ -137,7 +137,7 @@ Covers: Suggested file: -- `examples/06_pydantic_form_flavors.py` +- `plugins/pydantic/examples/06_pydantic_form_flavors.py` ### 7. Pydantic field mapping variants @@ -152,7 +152,7 @@ Covers: Suggested file: -- `examples/07_pydantic_field_variants.py` +- `plugins/pydantic/examples/07_pydantic_field_variants.py` ### 8. Pydantic structured fields @@ -168,7 +168,7 @@ Covers: Suggested file: -- `examples/08_pydantic_structured_fields.py` +- `plugins/pydantic/examples/08_pydantic_structured_fields.py` ### 9. Datetime split input @@ -185,7 +185,7 @@ Covers: Suggested file: -- `examples/09_pydantic_datetime_split.py` +- `plugins/pydantic/examples/09_pydantic_datetime_split.py` ### 10. Filtered Pandas table @@ -202,7 +202,7 @@ Covers: Suggested file: -- `examples/10_pandas_table_filters.py` +- `plugins/pandas/examples/10_pandas_table_filters.py` ## Phase 3: Handles And Runtime Interaction @@ -221,7 +221,7 @@ Covers: Suggested file: -- `examples/11_form_handle_basics.py` +- `plugins/pydantic/examples/11_form_handle_basics.py` ### 12. Form validation and error handling @@ -237,7 +237,7 @@ Covers: Suggested file: -- `examples/12_form_validation.py` +- `plugins/pydantic/examples/12_form_validation.py` ### 13. Form submit and model reconstruction @@ -252,7 +252,7 @@ Covers: Suggested file: -- `examples/13_form_submit.py` +- `plugins/pydantic/examples/13_form_submit.py` ### 14. Dirty tracking @@ -267,7 +267,7 @@ Covers: Suggested file: -- `examples/14_form_dirty_state.py` +- `plugins/pydantic/examples/14_form_dirty_state.py` ### 15. Live form helpers @@ -282,7 +282,7 @@ Covers: Suggested file: -- `examples/15_form_live_helpers.py` +- `plugins/pydantic/examples/15_form_live_helpers.py` ### 16. Action bar and actionable flavor @@ -297,7 +297,7 @@ Covers: Suggested file: -- `examples/16_form_actionable.py` +- `plugins/pydantic/examples/16_form_actionable.py` ### 17. CRUD-ready form actions @@ -312,7 +312,7 @@ Covers: Suggested file: -- `examples/17_form_crud.py` +- `plugins/pydantic/examples/17_form_crud.py` ### 18. TableHandle basics @@ -327,7 +327,7 @@ Covers: Suggested file: -- `examples/18_table_handle_basics.py` +- `plugins/pandas/examples/18_table_handle_basics.py` ### 19. Table pagination, selection, and export @@ -343,7 +343,7 @@ Covers: Suggested file: -- `examples/19_table_pagination_selection_export.py` +- `plugins/pandas/examples/19_table_pagination_selection_export.py` ### 20. CRUD-ready table actions @@ -359,7 +359,7 @@ Covers: Suggested file: -- `examples/20_table_crud.py` +- `plugins/pandas/examples/20_table_crud.py` ## Phase 4: CLI Examples @@ -430,7 +430,7 @@ Covers: Suggested file: -- `examples/24_field_context_resolution.py` +- `plugins/pydantic/examples/24_field_context_resolution.py` ### 25. Plugin-local map customization @@ -445,7 +445,7 @@ Covers: Suggested file: -- `examples/25_plugin_mapping_customization.py` +- `plugins/pydantic/examples/25_plugin_mapping_customization.py` ### 26. Writing a minimal custom plugin @@ -463,6 +463,22 @@ Suggested file: - `examples/26_custom_plugin_minimal.py` +### 27. Direct datetime_input component + +Goal: + +- show `ui.datetime_input(...)` without going through a plugin entry point + +Covers: + +- direct component usage +- one logical datetime value with internal date/time controls +- structured `container` configuration + +Suggested file: + +- `examples/27_datetime_input_direct.py` + ## Recommended Initial Implementation Order If these examples are implemented in a later pass, the most useful first wave would be: @@ -496,3 +512,4 @@ This proposed set covers: - CRUD helpers - CLI usage - plugin-oriented extension concepts + diff --git a/docs/layout-schema.md b/docs/layout-schema.md index 4d0ed24..1cec594 100644 --- a/docs/layout-schema.md +++ b/docs/layout-schema.md @@ -286,4 +286,4 @@ Reach for expansion nodes when a plugin should resolve the final widget for you. - [`src/nicegui_builder/builder.py`](../src/nicegui_builder/builder.py) - [`src/nicegui_builder/core/models.py`](../src/nicegui_builder/core/models.py) - [`src/nicegui_builder/examples/demo_basic_builder.yml`](../src/nicegui_builder/examples/demo_basic_builder.yml) -- [`src/nicegui_builder/examples/24_field_context_resolution.py`](../src/nicegui_builder/examples/24_field_context_resolution.py) +- [`src/nicegui_builder/plugins/pydantic/examples/24_field_context_resolution.py`](../src/nicegui_builder/plugins/pydantic/examples/24_field_context_resolution.py) diff --git a/docs/public-api.md b/docs/public-api.md index 9ba975a..f8ab7dc 100644 --- a/docs/public-api.md +++ b/docs/public-api.md @@ -42,12 +42,17 @@ Examples: The package also re-exports `ui` and patches the imported NiceGUI `ui` object at runtime with: - `ui.builder(...)` +- `ui.datetime_input(...)` - `ui.form_builder(...)` - `ui.table_builder(...)` That behavior is supported by `nicegui-builder`, but it is still a patch-style integration layered on top of NiceGUI's own `ui` module. It should be treated as a convenience contract of this package, not as an upstream NiceGUI guarantee. +For editor completion in this repository, the package also ships local partial stubs under [`typings/nicegui`](../typings/nicegui). +Those stubs are intentionally marked `partial` so they extend NiceGUI's own type information rather than replacing the native signatures. +The main workspace stub [`typings/nicegui/ui.pyi`](../typings/nicegui/ui.pyi) is generated from NiceGUI's own `ui` module by [`scripts/generate_nicegui_ui_stub.py`](../scripts/generate_nicegui_ui_stub.py) and is checked by CI to avoid drift. + ## Advanced / Experimental The following namespaces remain available for advanced use, but are not yet treated as stable contracts: diff --git a/pyproject.toml b/pyproject.toml index 0557795..de10a59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,8 @@ include-package-data = true nicegui_builder = [ "py.typed", "*.pyi", + "example_models/*.yml", + "example_models/*.yaml", "examples/*.yml", "examples/*.yaml", "plugins/pydantic/*.yml", diff --git a/scripts/generate_nicegui_ui_stub.py b/scripts/generate_nicegui_ui_stub.py new file mode 100644 index 0000000..5b157a8 --- /dev/null +++ b/scripts/generate_nicegui_ui_stub.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import inspect +from pathlib import Path + +import nicegui.ui as nicegui_ui + + +ROOT = Path(__file__).resolve().parents[1] +TARGET = ROOT / "typings" / "nicegui" / "ui.pyi" + +ADDED_NAMES = [ + "builder", + "datetime_input", + "form_builder", + "table_builder", +] + +ADDED_IMPORTS = [ + "from nicegui_builder import builder as builder", + "from nicegui_builder import form as form_builder", + "from nicegui_builder import table as table_builder", + "from nicegui_builder.core.datetime_inputs import DateTimeInput as datetime_input", +] + + +def inject_names(source_text: str) -> str: + marker = "__all__ = [" + start = source_text.index(marker) + end = source_text.index("]\n\n", start) + 2 + block = source_text[start:end] + for name in ADDED_NAMES: + needle = f" '{name}',\n" + if needle not in block: + block = block[:-2] + needle + "]\n" + return source_text[:start] + block + source_text[end:] + + +def inject_imports(source_text: str) -> str: + marker = "from .context import context\n" + imports = "\n".join(ADDED_IMPORTS) + "\n\n" + if imports in source_text: + return source_text + position = source_text.index(marker) + return source_text[:position] + imports + source_text[position:] + + +def absolutize_relative_imports(source_text: str) -> str: + lines = [] + for line in source_text.splitlines(): + if line.startswith("from ."): + lines.append(f"from nicegui{line[5:]}") + continue + lines.append(line) + return "\n".join(lines) + "\n" + + +def main() -> int: + source_text = inspect.getsource(nicegui_ui) + target_text = inject_imports(inject_names(source_text)) + target_text = absolutize_relative_imports(target_text) + TARGET.write_text(target_text, encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/nicegui_builder/__init__.py b/src/nicegui_builder/__init__.py index 66dcd1f..1919acb 100644 --- a/src/nicegui_builder/__init__.py +++ b/src/nicegui_builder/__init__.py @@ -15,12 +15,14 @@ TableSpec, ViewHandle, ) +from .core.datetime_inputs import DateTimeInput from .form import form from .table import table def _attach_to_ui() -> None: ui.builder = builder + ui.datetime_input = DateTimeInput ui.form_builder = form ui.table_builder = table diff --git a/src/nicegui_builder/__init__.pyi b/src/nicegui_builder/__init__.pyi index 9ef5e1c..c79406c 100644 --- a/src/nicegui_builder/__init__.pyi +++ b/src/nicegui_builder/__init__.pyi @@ -14,10 +14,12 @@ from .core import ( TableSpec, ViewHandle, ) +from .core.datetime_inputs import DateTimeInput class _ExtendedUI(Protocol): def builder(self, layout: Any) -> Any: ... + datetime_input: type[DateTimeInput] def form_builder(self, source: Any, flavor: str = "") -> Any: ... def table_builder(self, source: Any, variant: str = "std") -> Any: ... def __getattr__(self, name: str) -> Any: ... diff --git a/src/nicegui_builder/_example_runtime.py b/src/nicegui_builder/_example_runtime.py new file mode 100644 index 0000000..87cd724 --- /dev/null +++ b/src/nicegui_builder/_example_runtime.py @@ -0,0 +1,27 @@ +"""Shared runtime setup for bundled examples.""" + +from __future__ import annotations + +import logging + +from nicegui import run as nicegui_run + + +def patch_nicegui_process_pool_setup() -> None: + original_setup = nicegui_run.setup + + if getattr(original_setup, "_nicegui_builder_examples_patched", False): + return + + def safe_setup() -> None: + try: + original_setup() + except PermissionError as exc: + logging.warning( + "NiceGUI process pool is unavailable for bundled examples; " + "continuing without CPU-bound worker support: %s", + exc, + ) + + safe_setup._nicegui_builder_examples_patched = True # type: ignore[attr-defined] + nicegui_run.setup = safe_setup diff --git a/src/nicegui_builder/cli.py b/src/nicegui_builder/cli.py index 0666ae0..94816af 100644 --- a/src/nicegui_builder/cli.py +++ b/src/nicegui_builder/cli.py @@ -1,4 +1,5 @@ import argparse +from dataclasses import dataclass from importlib import import_module from pathlib import Path import sys @@ -13,6 +14,13 @@ from .form import form +@dataclass(frozen=True) +class ExampleSpec: + name: str + module: str + group: str + + def _build_windows_keypress_watcher(stop_event: threading.Event, message: str): try: import msvcrt @@ -105,27 +113,77 @@ def run_ui_app(root, *, port: int = 8080, host: str | None = None, reload: bool keyboard_shutdown.set() +def _example_group_sort_key(group: str) -> tuple[int, str]: + if group == "core": + return (0, group) + return (1, group) + + +def _iter_example_packages() -> list[tuple[str, str, Path]]: + root = Path(__file__).parent + packages = [("core", "nicegui_builder.examples", root / "examples")] + + plugins_dir = root / "plugins" + for plugin_dir in sorted(path for path in plugins_dir.iterdir() if path.is_dir() and not path.name.startswith("__")): + examples_dir = plugin_dir / "examples" + if examples_dir.is_dir(): + packages.append( + ( + plugin_dir.name, + f"nicegui_builder.plugins.{plugin_dir.name}.examples", + examples_dir, + ) + ) + return packages + + +def list_example_specs() -> list[ExampleSpec]: + specs = [] + for group, package_name, examples_dir in _iter_example_packages(): + for path in sorted(examples_dir.glob("*.py")): + if path.name == "__init__.py" or path.name.startswith("_"): + continue + specs.append( + ExampleSpec( + name=path.stem, + module=f"{package_name}.{path.stem}", + group=group, + ) + ) + return sorted(specs, key=lambda spec: (_example_group_sort_key(spec.group), spec.name)) + + def list_examples() -> list[str]: - examples_dir = Path(__file__).parent / "examples" - return sorted( - path.stem - for path in examples_dir.glob("*.py") - if path.name != "__init__.py" and not path.name.startswith("_") - ) + return [spec.name for spec in list_example_specs()] -def resolve_example_name(name: str) -> str: - examples = list_examples() - if name in examples: - return name +def list_examples_grouped() -> list[tuple[str, list[ExampleSpec]]]: + grouped: dict[str, list[ExampleSpec]] = {} + for spec in list_example_specs(): + grouped.setdefault(spec.group, []).append(spec) + return [ + (group, grouped[group]) + for group in sorted(grouped, key=lambda group: _example_group_sort_key(group)) + ] + + +def resolve_example_spec(name: str) -> ExampleSpec: + examples = list_example_specs() + exact = next((spec for spec in examples if spec.name == name), None) + if exact is not None: + return exact for example in examples: - if example.startswith(name): + if example.name.startswith(name): return example raise LookupError(f"unknown example: {name}") +def resolve_example_name(name: str) -> str: + return resolve_example_spec(name).name + + def load_object(dotted_path: str): if ":" not in dotted_path: raise ValueError("source must use the format 'module:object'") @@ -139,9 +197,9 @@ def load_object(dotted_path: str): def run_example(name: str, *, port: int = 8080, host: str | None = None, reload: bool = False): - resolved_name = resolve_example_name(name) + resolved = resolve_example_spec(name) - module = import_module(f"nicegui_builder.examples.{resolved_name}") + module = import_module(resolved.module) build_ui = getattr(module, "build_ui", None) if build_ui is not None: return run_ui_app(build_ui, port=port, host=host, reload=reload) @@ -227,8 +285,10 @@ def main(argv: list[str] | None = None): if args.command == "examples": if args.examples_command == "list": - for example in list_examples(): - print(example) + for group, examples in list_examples_grouped(): + print(f"[{group}]") + for example in examples: + print(example.name) return 0 if args.examples_command == "run": diff --git a/src/nicegui_builder/core/datetime_inputs.py b/src/nicegui_builder/core/datetime_inputs.py index fd7676e..f943703 100644 --- a/src/nicegui_builder/core/datetime_inputs.py +++ b/src/nicegui_builder/core/datetime_inputs.py @@ -2,7 +2,6 @@ import locale from typing import Callable -from nicegui import ui from nicegui.elements.mixins.value_element import ValueElement from .context import builder_ctx, component_refs, ensure_builder_runtime @@ -368,6 +367,3 @@ def build_split_datetime_node( "children": [], } - -if not hasattr(ui, "datetime_input"): - ui.datetime_input = DateTimeInput diff --git a/src/nicegui_builder/examples/models/Contest.yml b/src/nicegui_builder/example_models/Contest.yml similarity index 100% rename from src/nicegui_builder/examples/models/Contest.yml rename to src/nicegui_builder/example_models/Contest.yml diff --git a/src/nicegui_builder/examples/models/Participant.yml b/src/nicegui_builder/example_models/Participant.yml similarity index 100% rename from src/nicegui_builder/examples/models/Participant.yml rename to src/nicegui_builder/example_models/Participant.yml diff --git a/src/nicegui_builder/examples/models/Registration.yml b/src/nicegui_builder/example_models/Registration.yml similarity index 100% rename from src/nicegui_builder/examples/models/Registration.yml rename to src/nicegui_builder/example_models/Registration.yml diff --git a/src/nicegui_builder/example_models/__init__.py b/src/nicegui_builder/example_models/__init__.py new file mode 100644 index 0000000..a5186e5 --- /dev/null +++ b/src/nicegui_builder/example_models/__init__.py @@ -0,0 +1,12 @@ +from .contest import Contest, ContestCategory +from .participant import Participant, SockColor +from .registration import Registration, RegistrationStatus + +__all__ = [ + "Contest", + "ContestCategory", + "Participant", + "Registration", + "RegistrationStatus", + "SockColor", +] diff --git a/src/nicegui_builder/examples/models/contest.py b/src/nicegui_builder/example_models/contest.py similarity index 100% rename from src/nicegui_builder/examples/models/contest.py rename to src/nicegui_builder/example_models/contest.py diff --git a/src/nicegui_builder/examples/models/participant.py b/src/nicegui_builder/example_models/participant.py similarity index 100% rename from src/nicegui_builder/examples/models/participant.py rename to src/nicegui_builder/example_models/participant.py diff --git a/src/nicegui_builder/examples/models/registration.py b/src/nicegui_builder/example_models/registration.py similarity index 100% rename from src/nicegui_builder/examples/models/registration.py rename to src/nicegui_builder/example_models/registration.py diff --git a/src/nicegui_builder/examples/01_basic_builder.py b/src/nicegui_builder/examples/01_basic_builder.py index 151c458..79ef3ba 100644 --- a/src/nicegui_builder/examples/01_basic_builder.py +++ b/src/nicegui_builder/examples/01_basic_builder.py @@ -6,7 +6,7 @@ from nicegui import ui -from nicegui_builder import builder +import nicegui_builder LAYOUT = [ @@ -35,7 +35,7 @@ def build_ui(): - builder(LAYOUT) + ui.builder(LAYOUT) def main(*, port: int = 8080, host: str | None = None, reload: bool = False): diff --git a/src/nicegui_builder/examples/02_inline_builder.py b/src/nicegui_builder/examples/02_inline_builder.py index c714b09..0481e59 100644 --- a/src/nicegui_builder/examples/02_inline_builder.py +++ b/src/nicegui_builder/examples/02_inline_builder.py @@ -6,7 +6,7 @@ from nicegui import ui -from nicegui_builder import builder +import nicegui_builder LAYOUT = [ @@ -54,7 +54,7 @@ def build_ui(): - builder(LAYOUT) + ui.builder(LAYOUT) def main(*, port: int = 8080, host: str | None = None, reload: bool = False): diff --git a/src/nicegui_builder/examples/23_cli_form_run.py b/src/nicegui_builder/examples/23_cli_form_run.py index 9f23d41..5697f65 100644 --- a/src/nicegui_builder/examples/23_cli_form_run.py +++ b/src/nicegui_builder/examples/23_cli_form_run.py @@ -14,8 +14,8 @@ def build_ui(): You can ask the CLI to render a plugin-supported source directly: ```bash -nicegui-builder form run nicegui_builder.examples.models:Participant -nicegui-builder form run nicegui_builder.examples.models:Contest --flavor actionable +nicegui-builder form run nicegui_builder.example_models:Participant +nicegui-builder form run nicegui_builder.example_models:Contest --flavor actionable ``` This is ideal for trying a model quickly, @@ -30,3 +30,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/26_custom_plugin_minimal.py b/src/nicegui_builder/examples/26_custom_plugin_minimal.py index 54aa4ee..4122c50 100644 --- a/src/nicegui_builder/examples/26_custom_plugin_minimal.py +++ b/src/nicegui_builder/examples/26_custom_plugin_minimal.py @@ -8,7 +8,7 @@ from nicegui import ui -from nicegui_builder import form +import nicegui_builder from nicegui_builder.core.models import FieldSpec, WidgetSpec from nicegui_builder.plugins import plugin_registry @@ -117,7 +117,7 @@ def build_ui(): "This tiny plugin handles one whimsical source type and proves the extension point can stay approachable." ).classes("text-body2 text-grey-7") - handle = form(LostSockPass()) + handle = ui.form_builder(LostSockPass()) with ui.row().classes("gap-2"): ui.button( diff --git a/src/nicegui_builder/examples/27_datetime_input_direct.py b/src/nicegui_builder/examples/27_datetime_input_direct.py new file mode 100644 index 0000000..8dacf1f --- /dev/null +++ b/src/nicegui_builder/examples/27_datetime_input_direct.py @@ -0,0 +1,71 @@ +"""Direct use of the shared datetime_input component. + +Sometimes the festival desk does not want a whole form. +It just wants one civilized datetime control and a few buttons to poke at it. +""" + +from datetime import datetime + +from nicegui import ui + +import nicegui_builder + + +def build_ui(): + ui.label("Direct datetime_input usage").classes("text-h5") + ui.label( + "This example uses ui.datetime_input(...) directly, without form_builder or pandas." + ).classes("text-body2 text-grey-7") + + with ui.card().classes("w-full max-w-3xl mx-auto gap-3"): + control = ui.datetime_input( + value=datetime(2026, 6, 14, 18, 45), + container={ + "methods": "grid", + "params": {"columns": 2}, + "classes": "w-full gap-2", + "props": "", + }, + date_options={ + "label": "Festival date", + "classes": "col", + "props": "clearable", + }, + time_options={ + "label": "Festival time", + "classes": "col", + "props": "clearable", + }, + ) + + ui.label( + "The component keeps one logical datetime value while exposing .container, .date, and .time." + ).classes("text-caption text-grey-7") + + with ui.row().classes("gap-2"): + ui.button( + "Show value", + on_click=lambda: ui.notify(str(control.value)), + ) + ui.button( + "Set 21:30", + on_click=lambda: control.set_value(datetime(2026, 6, 14, 21, 30)), + ) + ui.button( + "Clear", + on_click=lambda: control.set_value(None), + ) + ui.button( + "Show child refs", + on_click=lambda: ui.notify( + f"date={control.date.value!r} time={control.time.value!r}" + ), + ) + + +def main(*, port: int = 8080, host: str | None = None, reload: bool = False): + ui.run(root=build_ui, port=port, host=host, reload=reload) + + +if __name__ in {"__main__", "__mp_main__"}: + main() diff --git a/src/nicegui_builder/examples/__init__.py b/src/nicegui_builder/examples/__init__.py index 93547e8..1429a1b 100644 --- a/src/nicegui_builder/examples/__init__.py +++ b/src/nicegui_builder/examples/__init__.py @@ -1,39 +1,6 @@ -"""Shared setup for bundled examples. +"""Shared setup for bundled core examples.""" -The examples should be runnable directly with: +from nicegui_builder._example_runtime import patch_nicegui_process_pool_setup - python -m nicegui_builder.examples.03_pydantic_form_basic -On some Windows setups, NiceGUI's process-pool startup fails with -``PermissionError: [WinError 5] Access is denied``. The examples do not rely on -CPU-bound helpers, so we gracefully disable that pool for the examples package. -""" - -from __future__ import annotations - -import logging - -from nicegui import run as nicegui_run - - -def _patch_nicegui_process_pool_setup() -> None: - original_setup = nicegui_run.setup - - if getattr(original_setup, "_nicegui_builder_examples_patched", False): - return - - def safe_setup() -> None: - try: - original_setup() - except PermissionError as exc: - logging.warning( - "NiceGUI process pool is unavailable for bundled examples; " - "continuing without CPU-bound worker support: %s", - exc, - ) - - safe_setup._nicegui_builder_examples_patched = True # type: ignore[attr-defined] - nicegui_run.setup = safe_setup - - -_patch_nicegui_process_pool_setup() +patch_nicegui_process_pool_setup() diff --git a/src/nicegui_builder/examples/demo_basic_builder.py b/src/nicegui_builder/examples/demo_basic_builder.py index f1b4e7d..2cac5c6 100644 --- a/src/nicegui_builder/examples/demo_basic_builder.py +++ b/src/nicegui_builder/examples/demo_basic_builder.py @@ -2,14 +2,14 @@ import yaml from nicegui import ui -from nicegui_builder import builder +import nicegui_builder def build_ui(): with p.Path("src/nicegui_builder/examples/demo_basic_builder.yml").open(encoding="utf-8") as file: layout = yaml.safe_load(file) - builder(layout) + ui.builder(layout) def main(*, port: int = 8080, host: str | None = None, reload: bool = False): diff --git a/src/nicegui_builder/examples/models/__init__.py b/src/nicegui_builder/examples/models/__init__.py index a5186e5..d55a79f 100644 --- a/src/nicegui_builder/examples/models/__init__.py +++ b/src/nicegui_builder/examples/models/__init__.py @@ -1,6 +1,11 @@ -from .contest import Contest, ContestCategory -from .participant import Participant, SockColor -from .registration import Registration, RegistrationStatus +from nicegui_builder.example_models import ( + Contest, + ContestCategory, + Participant, + Registration, + RegistrationStatus, + SockColor, +) __all__ = [ "Contest", diff --git a/src/nicegui_builder/examples/vscode/launch.json b/src/nicegui_builder/examples/vscode/launch.json index 8ab219a..0b877d9 100644 --- a/src/nicegui_builder/examples/vscode/launch.json +++ b/src/nicegui_builder/examples/vscode/launch.json @@ -23,7 +23,7 @@ "name": "Example 03: First Pydantic Form", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.03_pydantic_form_basic", + "module": "nicegui_builder.plugins.pydantic.examples.03_pydantic_form_basic", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -32,7 +32,7 @@ "name": "Example 04: First Pandas Table", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.04_pandas_table_basic", + "module": "nicegui_builder.plugins.pandas.examples.04_pandas_table_basic", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -41,7 +41,7 @@ "name": "Example 05: Pydantic Form with YAML", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.05_pydantic_form_yaml", + "module": "nicegui_builder.plugins.pydantic.examples.05_pydantic_form_yaml", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -50,7 +50,7 @@ "name": "Example 06: Pydantic Form Flavors", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.06_pydantic_form_flavors", + "module": "nicegui_builder.plugins.pydantic.examples.06_pydantic_form_flavors", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -59,7 +59,7 @@ "name": "Example 07: Pydantic Field Variants", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.07_pydantic_field_variants", + "module": "nicegui_builder.plugins.pydantic.examples.07_pydantic_field_variants", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -68,7 +68,7 @@ "name": "Example 08: Pydantic Structured Fields", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.08_pydantic_structured_fields", + "module": "nicegui_builder.plugins.pydantic.examples.08_pydantic_structured_fields", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -77,7 +77,7 @@ "name": "Example 09: Datetime Split", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.09_pydantic_datetime_split", + "module": "nicegui_builder.plugins.pydantic.examples.09_pydantic_datetime_split", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -86,7 +86,7 @@ "name": "Example 10: Pandas Table Filters", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.10_pandas_table_filters", + "module": "nicegui_builder.plugins.pandas.examples.10_pandas_table_filters", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -95,7 +95,7 @@ "name": "Example 11: FormHandle Basics", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.11_form_handle_basics", + "module": "nicegui_builder.plugins.pydantic.examples.11_form_handle_basics", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -104,7 +104,7 @@ "name": "Example 12: Form Validation", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.12_form_validation", + "module": "nicegui_builder.plugins.pydantic.examples.12_form_validation", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -113,7 +113,7 @@ "name": "Example 13: Form Submit", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.13_form_submit", + "module": "nicegui_builder.plugins.pydantic.examples.13_form_submit", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -122,7 +122,7 @@ "name": "Example 14: Dirty State", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.14_form_dirty_state", + "module": "nicegui_builder.plugins.pydantic.examples.14_form_dirty_state", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -131,7 +131,7 @@ "name": "Example 15: Live Helpers", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.15_form_live_helpers", + "module": "nicegui_builder.plugins.pydantic.examples.15_form_live_helpers", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -140,7 +140,7 @@ "name": "Example 16: Actionable Form", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.16_form_actionable", + "module": "nicegui_builder.plugins.pydantic.examples.16_form_actionable", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -149,7 +149,7 @@ "name": "Example 17: Form CRUD", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.17_form_crud", + "module": "nicegui_builder.plugins.pydantic.examples.17_form_crud", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -158,7 +158,7 @@ "name": "Example 18: TableHandle Basics", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.18_table_handle_basics", + "module": "nicegui_builder.plugins.pandas.examples.18_table_handle_basics", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -167,7 +167,7 @@ "name": "Example 19: Table Pagination Selection Export", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.19_table_pagination_selection_export", + "module": "nicegui_builder.plugins.pandas.examples.19_table_pagination_selection_export", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -176,7 +176,7 @@ "name": "Example 20: Table CRUD", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.20_table_crud", + "module": "nicegui_builder.plugins.pandas.examples.20_table_crud", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -212,7 +212,7 @@ "name": "Example 24: Field Context Resolution", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.24_field_context_resolution", + "module": "nicegui_builder.plugins.pydantic.examples.24_field_context_resolution", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -221,7 +221,7 @@ "name": "Example 25: Plugin Mapping Customization", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.25_plugin_mapping_customization", + "module": "nicegui_builder.plugins.pydantic.examples.25_plugin_mapping_customization", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -248,7 +248,7 @@ "name": "Example Demo: Pydantic Builder", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.demo_pydantic_builder", + "module": "nicegui_builder.plugins.pydantic.examples.demo_pydantic_builder", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false @@ -257,10 +257,11 @@ "name": "Example Demo: Contact Address", "type": "debugpy", "request": "launch", - "module": "nicegui_builder.examples.example_contact_address", + "module": "nicegui_builder.plugins.pydantic.examples.example_contact_address", "cwd": "${workspaceFolder}", "console": "integratedTerminal", "justMyCode": false } ] } + diff --git a/src/nicegui_builder/examples/04_pandas_table_basic.py b/src/nicegui_builder/plugins/pandas/examples/04_pandas_table_basic.py similarity index 95% rename from src/nicegui_builder/examples/04_pandas_table_basic.py rename to src/nicegui_builder/plugins/pandas/examples/04_pandas_table_basic.py index fdccf79..0cb7de6 100644 --- a/src/nicegui_builder/examples/04_pandas_table_basic.py +++ b/src/nicegui_builder/plugins/pandas/examples/04_pandas_table_basic.py @@ -9,7 +9,7 @@ from nicegui import ui import pandas as pd -from nicegui_builder import table +import nicegui_builder def build_ui(): @@ -37,7 +37,7 @@ def build_ui(): ) ui.label("Today's contests").classes("text-h6") - table(contests) + ui.table_builder(contests) def main(*, port: int = 8080, host: str | None = None, reload: bool = False): diff --git a/src/nicegui_builder/examples/10_pandas_table_filters.py b/src/nicegui_builder/plugins/pandas/examples/10_pandas_table_filters.py similarity index 97% rename from src/nicegui_builder/examples/10_pandas_table_filters.py rename to src/nicegui_builder/plugins/pandas/examples/10_pandas_table_filters.py index 646756f..e05734b 100644 --- a/src/nicegui_builder/examples/10_pandas_table_filters.py +++ b/src/nicegui_builder/plugins/pandas/examples/10_pandas_table_filters.py @@ -9,7 +9,7 @@ from nicegui import ui import pandas as pd -from nicegui_builder import table +import nicegui_builder def _registrations_dataframe() -> pd.DataFrame: @@ -55,7 +55,7 @@ def build_ui(): "Active filters can be toggled with a checkbox or removed, with comma-separated input for in/not in and two values for between." ).classes("text-body2 text-grey-6") - handle = table(_registrations_dataframe(), variant="filters") + handle = ui.table_builder(_registrations_dataframe(), variant="filters") with ui.row().classes("gap-2"): ui.button( diff --git a/src/nicegui_builder/examples/18_table_handle_basics.py b/src/nicegui_builder/plugins/pandas/examples/18_table_handle_basics.py similarity index 97% rename from src/nicegui_builder/examples/18_table_handle_basics.py rename to src/nicegui_builder/plugins/pandas/examples/18_table_handle_basics.py index 575ace3..fe2420c 100644 --- a/src/nicegui_builder/examples/18_table_handle_basics.py +++ b/src/nicegui_builder/plugins/pandas/examples/18_table_handle_basics.py @@ -9,7 +9,7 @@ from nicegui import ui import pandas as pd -from nicegui_builder import table +import nicegui_builder def _contest_rows() -> pd.DataFrame: @@ -69,7 +69,7 @@ def build_ui(): "Rows can be read, replaced, and sorted without rethinking the entire festival." ).classes("text-body2 text-grey-7") - handle = table(_contest_rows()) + handle = ui.table_builder(_contest_rows()) with ui.row().classes("gap-2"): ui.button( diff --git a/src/nicegui_builder/examples/19_table_pagination_selection_export.py b/src/nicegui_builder/plugins/pandas/examples/19_table_pagination_selection_export.py similarity index 97% rename from src/nicegui_builder/examples/19_table_pagination_selection_export.py rename to src/nicegui_builder/plugins/pandas/examples/19_table_pagination_selection_export.py index 9feb247..1049ff0 100644 --- a/src/nicegui_builder/examples/19_table_pagination_selection_export.py +++ b/src/nicegui_builder/plugins/pandas/examples/19_table_pagination_selection_export.py @@ -8,7 +8,7 @@ from nicegui import ui import pandas as pd -from nicegui_builder import table +import nicegui_builder def _registration_rows() -> pd.DataFrame: @@ -54,7 +54,7 @@ def build_ui(): "A few runtime helpers turn the registration board into a surprisingly capable little control panel." ).classes("text-body2 text-grey-7") - handle = table(_registration_rows()) + handle = ui.table_builder(_registration_rows()) with ui.row().classes("gap-2"): ui.button( diff --git a/src/nicegui_builder/examples/20_table_crud.py b/src/nicegui_builder/plugins/pandas/examples/20_table_crud.py similarity index 98% rename from src/nicegui_builder/examples/20_table_crud.py rename to src/nicegui_builder/plugins/pandas/examples/20_table_crud.py index 2b89b3e..36716a7 100644 --- a/src/nicegui_builder/examples/20_table_crud.py +++ b/src/nicegui_builder/plugins/pandas/examples/20_table_crud.py @@ -9,7 +9,7 @@ from nicegui import ui import pandas as pd -from nicegui_builder import table +import nicegui_builder def _contest_rows() -> pd.DataFrame: @@ -106,7 +106,7 @@ def build_ui(): "Selection is used by 'Delete selected'. 'Export CSV' previews the full current table." ).classes("text-body2 text-grey-7") - handle = table(_contest_rows()) + handle = ui.table_builder(_contest_rows()) with ui.row().classes("gap-2"): ui.button( diff --git a/src/nicegui_builder/plugins/pandas/examples/__init__.py b/src/nicegui_builder/plugins/pandas/examples/__init__.py new file mode 100644 index 0000000..562c3a2 --- /dev/null +++ b/src/nicegui_builder/plugins/pandas/examples/__init__.py @@ -0,0 +1,6 @@ +"""Bundled examples specific to the pandas plugin.""" + +from nicegui_builder._example_runtime import patch_nicegui_process_pool_setup + + +patch_nicegui_process_pool_setup() diff --git a/src/nicegui_builder/examples/03_pydantic_form_basic.py b/src/nicegui_builder/plugins/pydantic/examples/03_pydantic_form_basic.py similarity index 82% rename from src/nicegui_builder/examples/03_pydantic_form_basic.py rename to src/nicegui_builder/plugins/pydantic/examples/03_pydantic_form_basic.py index 8f65e8f..1d86b5c 100644 --- a/src/nicegui_builder/examples/03_pydantic_form_basic.py +++ b/src/nicegui_builder/plugins/pydantic/examples/03_pydantic_form_basic.py @@ -7,13 +7,13 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Participant +import nicegui_builder +from nicegui_builder.example_models import Participant def build_ui(): ui.label("New participant intake").classes("text-h6") - form(Participant) + ui.form_builder(Participant) def main(*, port: int = 8080, host: str | None = None, reload: bool = False): @@ -22,3 +22,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/05_pydantic_form_yaml.py b/src/nicegui_builder/plugins/pydantic/examples/05_pydantic_form_yaml.py similarity index 81% rename from src/nicegui_builder/examples/05_pydantic_form_yaml.py rename to src/nicegui_builder/plugins/pydantic/examples/05_pydantic_form_yaml.py index 6d69e76..e07661a 100644 --- a/src/nicegui_builder/examples/05_pydantic_form_yaml.py +++ b/src/nicegui_builder/plugins/pydantic/examples/05_pydantic_form_yaml.py @@ -6,13 +6,13 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Participant +import nicegui_builder +from nicegui_builder.example_models import Participant def build_ui(): ui.label("Participant registration with custom YAML layout").classes("text-h6") - form(Participant) + ui.form_builder(Participant) def main(*, port: int = 8080, host: str | None = None, reload: bool = False): @@ -21,3 +21,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/06_pydantic_form_flavors.py b/src/nicegui_builder/plugins/pydantic/examples/06_pydantic_form_flavors.py similarity index 87% rename from src/nicegui_builder/examples/06_pydantic_form_flavors.py rename to src/nicegui_builder/plugins/pydantic/examples/06_pydantic_form_flavors.py index 8bf2664..e0158fd 100644 --- a/src/nicegui_builder/examples/06_pydantic_form_flavors.py +++ b/src/nicegui_builder/plugins/pydantic/examples/06_pydantic_form_flavors.py @@ -8,8 +8,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Contest, Participant, Registration +import nicegui_builder +from nicegui_builder.example_models import Contest, Participant, Registration def _sample_participant() -> Participant: @@ -57,31 +57,31 @@ def build_ui(): "Standard flavor", "The default layout is calm, practical, and only mildly suspicious.", ): - form(Participant) + ui.form_builder(Participant) with _section( "Compact flavor", "Useful when the registration desk is small and the gossip is large.", ): - form(Participant, flavor="compact") + ui.form_builder(Participant, flavor="compact") with _section( "Detail flavor", "A read-only view for moments when the committee wants facts, not improvisation.", ): - form(_sample_contest(), flavor="detail") + ui.form_builder(_sample_contest(), flavor="detail") with _section( "Filters flavor", "For when the desk staff wants search boxes instead of philosophical commitment.", ): - form(Registration, flavor="filters") + ui.form_builder(Registration, flavor="filters") with _section( "Actionable flavor", "Built-in status, actions, and a pleasant sense of administrative authority.", ): - handle = form(_sample_participant(), flavor="actionable") + handle = ui.form_builder(_sample_participant(), flavor="actionable") handle.action_bar( "Save participant", lambda model: ui.notify(f"Saved {model.display_name}"), @@ -100,3 +100,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/07_pydantic_field_variants.py b/src/nicegui_builder/plugins/pydantic/examples/07_pydantic_field_variants.py similarity index 96% rename from src/nicegui_builder/examples/07_pydantic_field_variants.py rename to src/nicegui_builder/plugins/pydantic/examples/07_pydantic_field_variants.py index a8d0ce1..728b97b 100644 --- a/src/nicegui_builder/examples/07_pydantic_field_variants.py +++ b/src/nicegui_builder/plugins/pydantic/examples/07_pydantic_field_variants.py @@ -10,7 +10,7 @@ from nicegui import ui from pydantic import BaseModel, Field -from nicegui_builder import form +import nicegui_builder class SockMood(str, Enum): @@ -53,7 +53,7 @@ def build_ui(): "Here the layout explicitly requests widget variants instead of relying on the plugin defaults." ).classes("text-body2 text-grey-7") - handle = form(VariantShowcase, flavor="actionable") + handle = ui.form_builder(VariantShowcase, flavor="actionable") handle.set_values( { "secret_code": "velvet-ankle-42", @@ -89,3 +89,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/08_pydantic_structured_fields.py b/src/nicegui_builder/plugins/pydantic/examples/08_pydantic_structured_fields.py similarity index 96% rename from src/nicegui_builder/examples/08_pydantic_structured_fields.py rename to src/nicegui_builder/plugins/pydantic/examples/08_pydantic_structured_fields.py index a7b39a5..b68495b 100644 --- a/src/nicegui_builder/examples/08_pydantic_structured_fields.py +++ b/src/nicegui_builder/plugins/pydantic/examples/08_pydantic_structured_fields.py @@ -7,7 +7,7 @@ from nicegui import ui from pydantic import BaseModel, Field -from nicegui_builder import form +import nicegui_builder class SupplyCrate(BaseModel): @@ -43,7 +43,7 @@ def build_ui(): "Nested models, lists, and dict-like values are grouped into their own generated sections." ).classes("text-body2 text-grey-7") - handle = form(ParadePlan()) + handle = ui.form_builder(ParadePlan()) with ui.row().classes("gap-2"): ui.button( @@ -62,3 +62,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/09_pydantic_datetime_split.py b/src/nicegui_builder/plugins/pydantic/examples/09_pydantic_datetime_split.py similarity index 92% rename from src/nicegui_builder/examples/09_pydantic_datetime_split.py rename to src/nicegui_builder/plugins/pydantic/examples/09_pydantic_datetime_split.py index 3c28af8..05ba62a 100644 --- a/src/nicegui_builder/examples/09_pydantic_datetime_split.py +++ b/src/nicegui_builder/plugins/pydantic/examples/09_pydantic_datetime_split.py @@ -8,8 +8,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Contest +import nicegui_builder +from nicegui_builder.example_models import Contest def _sample_contest() -> Contest: @@ -28,7 +28,7 @@ def build_ui(): ).classes("text-body2 text-grey-7") with ui.card().classes("w-full max-w-3xl mx-auto gap-3"): - handle = form(_sample_contest()) + handle = ui.form_builder(_sample_contest()) with ui.row().classes("gap-2"): ui.button( @@ -51,3 +51,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/11_form_handle_basics.py b/src/nicegui_builder/plugins/pydantic/examples/11_form_handle_basics.py similarity index 94% rename from src/nicegui_builder/examples/11_form_handle_basics.py rename to src/nicegui_builder/plugins/pydantic/examples/11_form_handle_basics.py index e518497..88a11fd 100644 --- a/src/nicegui_builder/examples/11_form_handle_basics.py +++ b/src/nicegui_builder/plugins/pydantic/examples/11_form_handle_basics.py @@ -6,8 +6,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Participant, SockColor +import nicegui_builder +from nicegui_builder.example_models import Participant, SockColor def build_ui(): @@ -16,7 +16,7 @@ def build_ui(): "A few runtime helpers are enough to make the registration desk feel improbably organized." ).classes("text-body2 text-grey-7") - handle = form( + handle = ui.form_builder( Participant( display_name="Bernice Freeheel", email="bernice@example.com", @@ -64,3 +64,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/12_form_validation.py b/src/nicegui_builder/plugins/pydantic/examples/12_form_validation.py similarity index 92% rename from src/nicegui_builder/examples/12_form_validation.py rename to src/nicegui_builder/plugins/pydantic/examples/12_form_validation.py index b83f02e..9425c71 100644 --- a/src/nicegui_builder/examples/12_form_validation.py +++ b/src/nicegui_builder/plugins/pydantic/examples/12_form_validation.py @@ -7,8 +7,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Participant +import nicegui_builder +from nicegui_builder.example_models import Participant def _apply_errors(handle): @@ -22,7 +22,7 @@ def build_ui(): "Try clearing the required fields, then use the controls below to inspect and display validation errors." ).classes("text-body2 text-grey-7") - handle = form(Participant, flavor="actionable") + handle = ui.form_builder(Participant, flavor="actionable") handle.action_bar( "Validate gently", lambda values: ui.notify(f"Looks valid enough for a sock festival: {values['display_name']}"), @@ -64,3 +64,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/13_form_submit.py b/src/nicegui_builder/plugins/pydantic/examples/13_form_submit.py similarity index 94% rename from src/nicegui_builder/examples/13_form_submit.py rename to src/nicegui_builder/plugins/pydantic/examples/13_form_submit.py index b022954..53d8023 100644 --- a/src/nicegui_builder/examples/13_form_submit.py +++ b/src/nicegui_builder/plugins/pydantic/examples/13_form_submit.py @@ -6,8 +6,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Registration +import nicegui_builder +from nicegui_builder.example_models import Registration def _notify_values(values: dict[str, object]): @@ -24,7 +24,7 @@ def build_ui(): "The same form can submit plain values or a reconstructed Pydantic model, depending on the desk's mood." ).classes("text-body2 text-grey-7") - handle = form( + handle = ui.form_builder( Registration( participant_name="Odette of the Triumphant Heel", contest_title="Midnight Parade of Respectable Nonsense", @@ -65,3 +65,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/14_form_dirty_state.py b/src/nicegui_builder/plugins/pydantic/examples/14_form_dirty_state.py similarity index 93% rename from src/nicegui_builder/examples/14_form_dirty_state.py rename to src/nicegui_builder/plugins/pydantic/examples/14_form_dirty_state.py index 5d371ff..08fce97 100644 --- a/src/nicegui_builder/examples/14_form_dirty_state.py +++ b/src/nicegui_builder/plugins/pydantic/examples/14_form_dirty_state.py @@ -6,8 +6,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Participant +import nicegui_builder +from nicegui_builder.example_models import Participant def _show_changes(handle): @@ -24,7 +24,7 @@ def build_ui(): "Change a field, and the form will cheerfully admit that it is no longer in its original state." ).classes("text-body2 text-grey-7") - handle = form( + handle = ui.form_builder( Participant( display_name="Marshall Greatdisagreement", email="marshall@example.com", @@ -56,3 +56,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/15_form_live_helpers.py b/src/nicegui_builder/plugins/pydantic/examples/15_form_live_helpers.py similarity index 91% rename from src/nicegui_builder/examples/15_form_live_helpers.py rename to src/nicegui_builder/plugins/pydantic/examples/15_form_live_helpers.py index ac8acdb..1f24f45 100644 --- a/src/nicegui_builder/examples/15_form_live_helpers.py +++ b/src/nicegui_builder/plugins/pydantic/examples/15_form_live_helpers.py @@ -6,8 +6,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Participant +import nicegui_builder +from nicegui_builder.example_models import Participant def build_ui(): @@ -16,7 +16,7 @@ def build_ui(): "Dirty tracking, live validation, and strategic buttons all reacting as the form changes." ).classes("text-body2 text-grey-7") - handle = form(Participant, flavor="actionable") + handle = ui.form_builder(Participant, flavor="actionable") handle.live_validation(as_model=True, mode="change") with ui.row().classes("gap-2 items-center"): @@ -53,3 +53,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/16_form_actionable.py b/src/nicegui_builder/plugins/pydantic/examples/16_form_actionable.py similarity index 91% rename from src/nicegui_builder/examples/16_form_actionable.py rename to src/nicegui_builder/plugins/pydantic/examples/16_form_actionable.py index 769048f..1482685 100644 --- a/src/nicegui_builder/examples/16_form_actionable.py +++ b/src/nicegui_builder/plugins/pydantic/examples/16_form_actionable.py @@ -7,8 +7,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Contest +import nicegui_builder +from nicegui_builder.example_models import Contest def _save_contest(model: Contest): @@ -21,7 +21,7 @@ def build_ui(): "This layout exposes built-in status, action, and error areas so the handle can do more of the stage work." ).classes("text-body2 text-grey-7") - handle = form(Contest, flavor="actionable") + handle = ui.form_builder(Contest, flavor="actionable") handle.action_bar( "Save contest", @@ -53,3 +53,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/17_form_crud.py b/src/nicegui_builder/plugins/pydantic/examples/17_form_crud.py similarity index 94% rename from src/nicegui_builder/examples/17_form_crud.py rename to src/nicegui_builder/plugins/pydantic/examples/17_form_crud.py index f1493cc..dd142de 100644 --- a/src/nicegui_builder/examples/17_form_crud.py +++ b/src/nicegui_builder/plugins/pydantic/examples/17_form_crud.py @@ -6,8 +6,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Registration +import nicegui_builder +from nicegui_builder.example_models import Registration def _create_registration(model: Registration): @@ -29,7 +29,7 @@ def build_ui(): "The registration desk has discovered buttons that sound official and therefore improve morale." ).classes("text-body2 text-grey-7") - handle = form( + handle = ui.form_builder( Registration( participant_name="Alice Two-Left-Socks", contest_title="Interpretive Heel Rotation", @@ -63,3 +63,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/24_field_context_resolution.py b/src/nicegui_builder/plugins/pydantic/examples/24_field_context_resolution.py similarity index 95% rename from src/nicegui_builder/examples/24_field_context_resolution.py rename to src/nicegui_builder/plugins/pydantic/examples/24_field_context_resolution.py index 32587b0..da168ce 100644 --- a/src/nicegui_builder/examples/24_field_context_resolution.py +++ b/src/nicegui_builder/plugins/pydantic/examples/24_field_context_resolution.py @@ -9,8 +9,8 @@ from nicegui import ui -from nicegui_builder import form -from nicegui_builder.examples.models import Participant +import nicegui_builder +from nicegui_builder.example_models import Participant from nicegui_builder.plugins import plugin_registry @@ -62,7 +62,7 @@ def build_ui(): ui.label("Declarative layout snippet").classes("text-subtitle1") ui.markdown(f"```yaml\n{LAYOUT_SNIPPET}\n```").classes("w-full") - form(sample) + ui.form_builder(sample) with ui.grid(columns=2).classes("w-full max-w-4xl mx-auto gap-4"): with ui.card().classes("gap-2"): @@ -80,3 +80,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/25_plugin_mapping_customization.py b/src/nicegui_builder/plugins/pydantic/examples/25_plugin_mapping_customization.py similarity index 96% rename from src/nicegui_builder/examples/25_plugin_mapping_customization.py rename to src/nicegui_builder/plugins/pydantic/examples/25_plugin_mapping_customization.py index 4e2d56c..1ff0d48 100644 --- a/src/nicegui_builder/examples/25_plugin_mapping_customization.py +++ b/src/nicegui_builder/plugins/pydantic/examples/25_plugin_mapping_customization.py @@ -6,7 +6,7 @@ from nicegui import ui -from nicegui_builder.examples.models import Contest, Participant, Registration +from nicegui_builder.example_models import Contest, Participant, Registration from nicegui_builder.plugins.pydantic.mapping import ( get_defaults_from_map, resolve_map_type, @@ -73,3 +73,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/plugins/pydantic/examples/__init__.py b/src/nicegui_builder/plugins/pydantic/examples/__init__.py new file mode 100644 index 0000000..4404447 --- /dev/null +++ b/src/nicegui_builder/plugins/pydantic/examples/__init__.py @@ -0,0 +1,7 @@ +"""Bundled examples specific to the pydantic plugin.""" + +from nicegui_builder._example_runtime import patch_nicegui_process_pool_setup + + +patch_nicegui_process_pool_setup() + diff --git a/src/nicegui_builder/examples/demo_pydantic_builder.py b/src/nicegui_builder/plugins/pydantic/examples/demo_pydantic_builder.py similarity index 84% rename from src/nicegui_builder/examples/demo_pydantic_builder.py rename to src/nicegui_builder/plugins/pydantic/examples/demo_pydantic_builder.py index 1bd1ff7..d794c0c 100644 --- a/src/nicegui_builder/examples/demo_pydantic_builder.py +++ b/src/nicegui_builder/plugins/pydantic/examples/demo_pydantic_builder.py @@ -1,7 +1,7 @@ from pydantic import BaseModel from nicegui import ui -from nicegui_builder import form +import nicegui_builder class DemoPydanticBuilder(BaseModel): @@ -11,7 +11,7 @@ class DemoPydanticBuilder(BaseModel): def build_ui(): - form(DemoPydanticBuilder) + ui.form_builder(DemoPydanticBuilder) def main(*, port: int = 8080, host: str | None = None, reload: bool = False): @@ -20,3 +20,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = False): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/src/nicegui_builder/examples/example_contact_address.py b/src/nicegui_builder/plugins/pydantic/examples/example_contact_address.py similarity index 96% rename from src/nicegui_builder/examples/example_contact_address.py rename to src/nicegui_builder/plugins/pydantic/examples/example_contact_address.py index 44648f2..50fc042 100644 --- a/src/nicegui_builder/examples/example_contact_address.py +++ b/src/nicegui_builder/plugins/pydantic/examples/example_contact_address.py @@ -1,6 +1,6 @@ from nicegui import ui from pydantic import BaseModel, Field -from nicegui_builder import form +import nicegui_builder import typing as t Civility = t.Literal["Mr.", "Miss", "Ms.", "Sir"] @@ -63,7 +63,7 @@ class ContactAddress(BaseModel): def build_ui(): ui.label("Hello from NiceGUI 👋") - form(contact_address) + ui.form_builder(contact_address) def main(*, port: int = 8080, host: str | None = None, reload: bool = True): @@ -72,3 +72,4 @@ def main(*, port: int = 8080, host: str | None = None, reload: bool = True): if __name__ in {"__main__", "__mp_main__"}: main() + diff --git a/tests/test_nicegui_builder/examples/test_sanity.py b/tests/test_nicegui_builder/examples/test_sanity.py index 49044c0..940bcd1 100644 --- a/tests/test_nicegui_builder/examples/test_sanity.py +++ b/tests/test_nicegui_builder/examples/test_sanity.py @@ -5,7 +5,7 @@ from nicegui import app from nicegui import ui -from nicegui_builder.cli import list_examples +from nicegui_builder.cli import list_example_specs def _configure_test_app() -> None: @@ -27,18 +27,18 @@ def _configure_test_app() -> None: ) -@pytest.mark.parametrize("example_name", list_examples()) -def test_example_build_ui_returns_http_200(example_name: str) -> None: +@pytest.mark.parametrize("example_spec", list_example_specs()) +def test_example_build_ui_returns_http_200(example_spec) -> None: _configure_test_app() - module = import_module(f"nicegui_builder.examples.{example_name}") + module = import_module(example_spec.module) build_ui = getattr(module, "build_ui", None) - assert build_ui is not None, f"{example_name} must expose build_ui()" + assert build_ui is not None, f"{example_spec.name} must expose build_ui()" ui.page("/")(build_ui) with TestClient(app) as client: response = client.get("/") - assert response.status_code == 200, example_name + assert response.status_code == 200, example_spec.name diff --git a/tests/test_nicegui_builder/plugins/test_registry.py b/tests/test_nicegui_builder/plugins/test_registry.py index 7564c3b..62a7f7f 100644 --- a/tests/test_nicegui_builder/plugins/test_registry.py +++ b/tests/test_nicegui_builder/plugins/test_registry.py @@ -2,6 +2,7 @@ from nicegui import ui import nicegui_builder +from nicegui_builder.core.datetime_inputs import DateTimeInput from nicegui_builder.plugins import plugin_registry import nicegui_builder.plugins as plugins_module from nicegui_builder.plugins.registry import PluginRegistry @@ -82,6 +83,7 @@ def fake_import(name): def test_package_attaches_builder_form_and_table_builders_to_ui(): assert ui.builder is nicegui_builder.builder + assert ui.datetime_input is DateTimeInput assert ui.form_builder is nicegui_builder.form assert ui.table_builder is nicegui_builder.table @@ -89,5 +91,6 @@ def test_package_attaches_builder_form_and_table_builders_to_ui(): def test_package_exports_nicegui_ui_with_builder_extensions(): assert nicegui_builder.ui is ui assert nicegui_builder.ui.builder is nicegui_builder.builder + assert nicegui_builder.ui.datetime_input is DateTimeInput assert nicegui_builder.ui.form_builder is nicegui_builder.form assert nicegui_builder.ui.table_builder is nicegui_builder.table diff --git a/tests/test_nicegui_builder/test_cli.py b/tests/test_nicegui_builder/test_cli.py index 27b5562..a759b24 100644 --- a/tests/test_nicegui_builder/test_cli.py +++ b/tests/test_nicegui_builder/test_cli.py @@ -11,17 +11,27 @@ def test_list_examples_includes_known_examples(): assert "demo_basic_builder" in examples assert "demo_pydantic_builder" in examples + assert "04_pandas_table_basic" in examples + + +def test_list_example_specs_include_plugin_groups(): + specs = cli.list_example_specs() + grouped = {spec.name: spec.group for spec in specs} + + assert grouped["01_basic_builder"] == "core" + assert grouped["03_pydantic_form_basic"] == "pydantic" + assert grouped["04_pandas_table_basic"] == "pandas" def test_load_object_resolves_module_paths(): - target = cli.load_object("nicegui_builder.examples.demo_pydantic_builder:DemoPydanticBuilder") + target = cli.load_object("nicegui_builder.plugins.pydantic.examples.demo_pydantic_builder:DemoPydanticBuilder") assert target.__name__ == "DemoPydanticBuilder" def test_load_object_rejects_invalid_path(): try: - cli.load_object("nicegui_builder.examples.demo_pydantic_builder") + cli.load_object("nicegui_builder.plugins.pydantic.examples.demo_pydantic_builder") except ValueError as exc: assert "module:object" in str(exc) else: @@ -29,32 +39,64 @@ def test_load_object_rejects_invalid_path(): def test_resolve_example_name_accepts_full_name(monkeypatch): - monkeypatch.setattr(cli, "list_examples", lambda: ["01_basic_builder", "03_pydantic_form_basic"]) + monkeypatch.setattr( + cli, + "list_example_specs", + lambda: [ + cli.ExampleSpec("01_basic_builder", "pkg.core.01_basic_builder", "core"), + cli.ExampleSpec("03_pydantic_form_basic", "pkg.pydantic.03_pydantic_form_basic", "pydantic"), + ], + ) assert cli.resolve_example_name("01_basic_builder") == "01_basic_builder" def test_resolve_example_name_accepts_numeric_shortcut(monkeypatch): - monkeypatch.setattr(cli, "list_examples", lambda: ["01_basic_builder", "03_pydantic_form_basic"]) + monkeypatch.setattr( + cli, + "list_example_specs", + lambda: [ + cli.ExampleSpec("01_basic_builder", "pkg.core.01_basic_builder", "core"), + cli.ExampleSpec("03_pydantic_form_basic", "pkg.pydantic.03_pydantic_form_basic", "pydantic"), + ], + ) assert cli.resolve_example_name("01") == "01_basic_builder" def test_resolve_example_name_accepts_partial_prefix(monkeypatch): - monkeypatch.setattr(cli, "list_examples", lambda: ["01_basic_builder", "03_pydantic_form_basic"]) + monkeypatch.setattr( + cli, + "list_example_specs", + lambda: [ + cli.ExampleSpec("01_basic_builder", "pkg.core.01_basic_builder", "core"), + cli.ExampleSpec("03_pydantic_form_basic", "pkg.pydantic.03_pydantic_form_basic", "pydantic"), + ], + ) assert cli.resolve_example_name("01_") == "01_basic_builder" assert cli.resolve_example_name("01_basic") == "01_basic_builder" def test_resolve_example_name_returns_first_match_for_ambiguous_shortcut(monkeypatch): - monkeypatch.setattr(cli, "list_examples", lambda: ["01_alpha", "01_beta"]) + monkeypatch.setattr( + cli, + "list_example_specs", + lambda: [ + cli.ExampleSpec("01_alpha", "pkg.core.01_alpha", "core"), + cli.ExampleSpec("01_beta", "pkg.pydantic.01_beta", "pydantic"), + ], + ) assert cli.resolve_example_name("01") == "01_alpha" def test_resolve_example_name_raises_for_unknown_example(monkeypatch): - monkeypatch.setattr(cli, "list_examples", lambda: ["01_basic_builder"]) + monkeypatch.setattr( + cli, + "list_example_specs", + lambda: [cli.ExampleSpec("01_basic_builder", "pkg.core.01_basic_builder", "core")], + ) try: cli.resolve_example_name("99") @@ -115,7 +157,7 @@ def fake_run(**kwargs): monkeypatch.setattr(cli.ui, "run", fake_run) result = cli.run_form( - "nicegui_builder.examples.demo_pydantic_builder:DemoPydanticBuilder", + "nicegui_builder.plugins.pydantic.examples.demo_pydantic_builder:DemoPydanticBuilder", flavor="compact", port=8081, ) @@ -154,7 +196,7 @@ def fake_run(**kwargs): monkeypatch.setattr(cli.ui, "run", fake_run) result = cli.run_form( - "nicegui_builder.examples.models:Contest", + "nicegui_builder.example_models:Contest", flavor="actionable", port=8082, ) @@ -186,7 +228,7 @@ def fake_run(**kwargs): monkeypatch.setattr(cli.ui, "run", fake_run) result = cli.run_form( - "nicegui_builder.examples.models:Contest", + "nicegui_builder.example_models:Contest", flavor="actionable", port=8083, ) @@ -209,7 +251,7 @@ def set(self): calls["stopped"] = True monkeypatch.setattr(cli, "enable_any_key_shutdown", lambda *args, **kwargs: DummyEvent()) - monkeypatch.setattr(cli, "resolve_example_name", lambda name: "demo") + monkeypatch.setattr(cli, "resolve_example_spec", lambda name: cli.ExampleSpec("demo", "pkg.demo", "core")) monkeypatch.setattr(cli, "import_module", lambda name: module) def fake_run(**kwargs): calls["run_kwargs"] = kwargs @@ -239,7 +281,7 @@ def fake_main(**kwargs): module = types.SimpleNamespace(main=fake_main) - monkeypatch.setattr(cli, "resolve_example_name", lambda name: "demo") + monkeypatch.setattr(cli, "resolve_example_spec", lambda name: cli.ExampleSpec("demo", "pkg.demo", "core")) monkeypatch.setattr(cli, "import_module", lambda name: module) result = cli.run_example("demo", port=9002, host="127.0.0.1", reload=True) @@ -255,7 +297,7 @@ def fake_main(**kwargs): def test_run_example_raises_when_example_exposes_no_entrypoint(monkeypatch): module = types.SimpleNamespace() - monkeypatch.setattr(cli, "resolve_example_name", lambda name: "demo") + monkeypatch.setattr(cli, "resolve_example_spec", lambda name: cli.ExampleSpec("demo", "pkg.demo", "core")) monkeypatch.setattr(cli, "import_module", lambda name: module) try: @@ -576,12 +618,19 @@ def fake_import(name, *args, **kwargs): def test_cli_main_lists_examples(monkeypatch, capsys): - monkeypatch.setattr(cli, "list_examples", lambda: ["alpha", "beta"]) + monkeypatch.setattr( + cli, + "list_examples_grouped", + lambda: [ + ("core", [cli.ExampleSpec("alpha", "pkg.alpha", "core")]), + ("pydantic", [cli.ExampleSpec("beta", "pkg.beta", "pydantic")]), + ], + ) result = cli.main(["examples", "list"]) assert result == 0 - assert capsys.readouterr().out.splitlines() == ["alpha", "beta"] + assert capsys.readouterr().out.splitlines() == ["[core]", "alpha", "[pydantic]", "beta"] def test_cli_main_runs_example(monkeypatch): @@ -635,3 +684,4 @@ def error(self, message): assert parser.message == "unknown command" assert result == 2 + diff --git a/typings/nicegui/py.typed b/typings/nicegui/py.typed new file mode 100644 index 0000000..b648ac9 --- /dev/null +++ b/typings/nicegui/py.typed @@ -0,0 +1 @@ +partial diff --git a/typings/nicegui/ui.pyi b/typings/nicegui/ui.pyi index 43bece2..5ee130a 100644 --- a/typings/nicegui/ui.pyi +++ b/typings/nicegui/ui.pyi @@ -1,145 +1,283 @@ -from typing import Any +__all__ = [ + 'add_body_html', + 'add_css', + 'add_head_html', + 'add_sass', + 'add_scss', + 'aggrid', + 'altair', + 'anywidget', + 'audio', + 'avatar', + 'badge', + 'button', + 'button_group', + 'card', + 'card_actions', + 'card_section', + 'carousel', + 'carousel_slide', + 'chat_message', + 'checkbox', + 'chip', + 'circular_progress', + 'clipboard', + 'code', + 'codemirror', + 'color_input', + 'color_picker', + 'colors', + 'column', + 'context', + 'context_menu', + 'dark_mode', + 'date', + 'date_input', + 'dialog', + 'download', + 'drawer', + 'dropdown_button', + 'echart', + 'editor', + 'element', + 'expansion', + 'fab', + 'fab_action', + 'footer', + 'fullscreen', + 'grid', + 'header', + 'highchart', + 'html', + 'icon', + 'image', + 'input', + 'input_chips', + 'interactive_image', + 'item', + 'item_label', + 'item_section', + 'joystick', + 'json_editor', + 'keyboard', + 'knob', + 'label', + 'leaflet', + 'left_drawer', + 'line_plot', + 'linear_progress', + 'link', + 'link_target', + 'list', + 'log', + 'markdown', + 'matplotlib', + 'menu', + 'menu_item', + 'mermaid', + 'navigate', + 'notification', + 'notify', + 'number', + 'on', + 'on_exception', + 'page', + 'page_scroller', + 'page_sticky', + 'page_title', + 'pagination', + 'parallax', + 'plotly', + 'pyplot', + 'query', + 'radio', + 'range', + 'rating', + 'refreshable', + 'refreshable_method', + 'restructured_text', + 'right_drawer', + 'row', + 'run', + 'run_javascript', + 'run_with', + 'scene', + 'scene_view', + 'scroll_area', + 'select', + 'separator', + 'skeleton', + 'slide_item', + 'slider', + 'space', + 'spinner', + 'splitter', + 'state', + 'step', + 'stepper', + 'stepper_navigation', + 'sub_pages', + 'switch', + 'tab', + 'tab_panel', + 'tab_panels', + 'table', + 'tabs', + 'teleport', + 'textarea', + 'time', + 'time_input', + 'timeline', + 'timeline_entry', + 'timer', + 'toggle', + 'tooltip', + 'tree', + 'update', + 'upload', + 'video', + 'xterm', + 'builder', + 'datetime_input', + 'form_builder', + 'table_builder', +] -add_body_html: Any -add_css: Any -add_head_html: Any -add_sass: Any -add_scss: Any -aggrid: Any -altair: Any -anywidget: Any -audio: Any -avatar: Any -badge: Any -button: Any -button_group: Any -card: Any -card_actions: Any -card_section: Any -carousel: Any -carousel_slide: Any -chat_message: Any -checkbox: Any -chip: Any -circular_progress: Any -clipboard: Any -code: Any -codemirror: Any -color_input: Any -color_picker: Any -colors: Any -column: Any -context: Any -context_menu: Any -dark_mode: Any -date: Any -date_input: Any -dialog: Any -download: Any -drawer: Any -dropdown_button: Any -echart: Any -editor: Any -element: Any -expansion: Any -fab: Any -fab_action: Any -footer: Any -fullscreen: Any -grid: Any -header: Any -highchart: Any -html: Any -icon: Any -image: Any -input: Any -input_chips: Any -interactive_image: Any -item: Any -item_label: Any -item_section: Any -joystick: Any -json_editor: Any -keyboard: Any -knob: Any -label: Any -leaflet: Any -left_drawer: Any -line_plot: Any -linear_progress: Any -link: Any -link_target: Any -list: Any -log: Any -markdown: Any -matplotlib: Any -menu: Any -menu_item: Any -mermaid: Any -navigate: Any -notification: Any -notify: Any -number: Any -on: Any -on_exception: Any -page: Any -page_scroller: Any -page_sticky: Any -page_title: Any -pagination: Any -plotly: Any -pyplot: Any -query: Any -radio: Any -range: Any -rating: Any -refreshable: Any -refreshable_method: Any -restructured_text: Any -right_drawer: Any -row: Any -run: Any -run_javascript: Any -run_with: Any -scene: Any -scene_view: Any -scroll_area: Any -select: Any -separator: Any -skeleton: Any -slide_item: Any -slider: Any -space: Any -spinner: Any -splitter: Any -state: Any -step: Any -stepper: Any -stepper_navigation: Any -sub_pages: Any -switch: Any -tab: Any -tab_panel: Any -tab_panels: Any -table: Any -tabs: Any -teleport: Any -textarea: Any -time: Any -time_input: Any -timeline: Any -timeline_entry: Any -timer: Any -toggle: Any -tooltip: Any -tree: Any -update: Any -upload: Any -video: Any -xterm: Any +from nicegui_builder import builder as builder +from nicegui_builder import form as form_builder +from nicegui_builder import table as table_builder +from nicegui_builder.core.datetime_inputs import DateTimeInput as datetime_input -def builder(layout: Any) -> Any: ... -def form_builder(source: Any, flavor: str = "") -> Any: ... -def table_builder(source: Any, variant: str = "std") -> Any: ... - -__all__: list[str] +from nicegui.context import context +from nicegui.element import Element as element +from nicegui.elements.aggrid import AgGrid as aggrid +from nicegui.elements.altair import Altair as altair +from nicegui.elements.anywidget import AnyWidget as anywidget +from nicegui.elements.audio import Audio as audio +from nicegui.elements.avatar import Avatar as avatar +from nicegui.elements.badge import Badge as badge +from nicegui.elements.button import Button as button +from nicegui.elements.button_dropdown import DropdownButton as dropdown_button +from nicegui.elements.button_group import ButtonGroup as button_group +from nicegui.elements.card import Card as card +from nicegui.elements.card import CardActions as card_actions +from nicegui.elements.card import CardSection as card_section +from nicegui.elements.carousel import Carousel as carousel +from nicegui.elements.carousel import CarouselSlide as carousel_slide +from nicegui.elements.chat_message import ChatMessage as chat_message +from nicegui.elements.checkbox import Checkbox as checkbox +from nicegui.elements.chip import Chip as chip +from nicegui.elements.code import Code as code +from nicegui.elements.codemirror import CodeMirror as codemirror +from nicegui.elements.color_input import ColorInput as color_input +from nicegui.elements.color_picker import ColorPicker as color_picker +from nicegui.elements.colors import Colors as colors +from nicegui.elements.column import Column as column +from nicegui.elements.context_menu import ContextMenu as context_menu +from nicegui.elements.dark_mode import DarkMode as dark_mode +from nicegui.elements.date import Date as date +from nicegui.elements.date_input import DateInput as date_input +from nicegui.elements.dialog import Dialog as dialog +from nicegui.elements.drawer import Drawer as drawer +from nicegui.elements.drawer import LeftDrawer as left_drawer +from nicegui.elements.drawer import RightDrawer as right_drawer +from nicegui.elements.echart import EChart as echart +from nicegui.elements.editor import Editor as editor +from nicegui.elements.expansion import Expansion as expansion +from nicegui.elements.fab import Fab as fab +from nicegui.elements.fab import FabAction as fab_action +from nicegui.elements.footer import Footer as footer +from nicegui.elements.fullscreen import Fullscreen as fullscreen +from nicegui.elements.grid import Grid as grid +from nicegui.elements.header import Header as header +from nicegui.elements.highchart import highchart +from nicegui.elements.html import Html as html +from nicegui.elements.icon import Icon as icon +from nicegui.elements.image import Image as image +from nicegui.elements.input import Input as input # pylint: disable=redefined-builtin +from nicegui.elements.input_chips import InputChips as input_chips +from nicegui.elements.interactive_image import InteractiveImage as interactive_image +from nicegui.elements.item import Item as item +from nicegui.elements.item import ItemLabel as item_label +from nicegui.elements.item import ItemSection as item_section +from nicegui.elements.joystick import Joystick as joystick +from nicegui.elements.json_editor import JsonEditor as json_editor +from nicegui.elements.keyboard import Keyboard as keyboard +from nicegui.elements.knob import Knob as knob +from nicegui.elements.label import Label as label +from nicegui.elements.leaflet import Leaflet as leaflet +from nicegui.elements.line_plot import LinePlot as line_plot +from nicegui.elements.link import Link as link +from nicegui.elements.link import LinkTarget as link_target +from nicegui.elements.list import List as list # pylint: disable=redefined-builtin +from nicegui.elements.log import Log as log +from nicegui.elements.markdown import Markdown as markdown +from nicegui.elements.menu import Menu as menu +from nicegui.elements.menu import MenuItem as menu_item +from nicegui.elements.mermaid import Mermaid as mermaid +from nicegui.elements.notification import Notification as notification +from nicegui.elements.number import Number as number +from nicegui.elements.page_scroller import PageScroller as page_scroller +from nicegui.elements.page_sticky import PageSticky as page_sticky +from nicegui.elements.pagination import Pagination as pagination +from nicegui.elements.parallax import Parallax as parallax +from nicegui.elements.plotly import Plotly as plotly +from nicegui.elements.progress import CircularProgress as circular_progress +from nicegui.elements.progress import LinearProgress as linear_progress +from nicegui.elements.pyplot import Matplotlib as matplotlib +from nicegui.elements.pyplot import Pyplot as pyplot +from nicegui.elements.query import Query as query +from nicegui.elements.radio import Radio as radio +from nicegui.elements.range import Range as range # pylint: disable=redefined-builtin +from nicegui.elements.rating import Rating as rating +from nicegui.elements.restructured_text import ReStructuredText as restructured_text +from nicegui.elements.row import Row as row +from nicegui.elements.scene import Scene as scene +from nicegui.elements.scene import SceneView as scene_view +from nicegui.elements.scroll_area import ScrollArea as scroll_area +from nicegui.elements.select import Select as select +from nicegui.elements.separator import Separator as separator +from nicegui.elements.skeleton import Skeleton as skeleton +from nicegui.elements.slide_item import SlideItem as slide_item +from nicegui.elements.slider import Slider as slider +from nicegui.elements.space import Space as space +from nicegui.elements.spinner import Spinner as spinner +from nicegui.elements.splitter import Splitter as splitter +from nicegui.elements.stepper import Step as step +from nicegui.elements.stepper import Stepper as stepper +from nicegui.elements.stepper import StepperNavigation as stepper_navigation +from nicegui.elements.sub_pages import SubPages as sub_pages +from nicegui.elements.switch import Switch as switch +from nicegui.elements.table import Table as table +from nicegui.elements.tabs import Tab as tab +from nicegui.elements.tabs import TabPanel as tab_panel +from nicegui.elements.tabs import TabPanels as tab_panels +from nicegui.elements.tabs import Tabs as tabs +from nicegui.elements.teleport import Teleport as teleport +from nicegui.elements.textarea import Textarea as textarea +from nicegui.elements.time import Time as time +from nicegui.elements.time_input import TimeInput as time_input +from nicegui.elements.timeline import Timeline as timeline +from nicegui.elements.timeline import TimelineEntry as timeline_entry +from nicegui.elements.timer import Timer as timer +from nicegui.elements.toggle import Toggle as toggle +from nicegui.elements.tooltip import Tooltip as tooltip +from nicegui.elements.tree import Tree as tree +from nicegui.elements.upload import Upload as upload +from nicegui.elements.video import Video as video +from nicegui.elements.xterm import Xterm as xterm +from nicegui.functions import clipboard +from nicegui.functions.download import download +from nicegui.functions.html import add_body_html, add_head_html +from nicegui.functions.javascript import run_javascript +from nicegui.functions.navigate import navigate +from nicegui.functions.notify import notify +from nicegui.functions.on import on +from nicegui.functions.on_exception import on_exception +from nicegui.functions.page_title import page_title +from nicegui.functions.refreshable import refreshable, refreshable_method, state +from nicegui.functions.style import add_css, add_sass, add_scss +from nicegui.functions.update import update +from nicegui.page import page +from nicegui.ui_run import run +from nicegui.ui_run_with import run_with