Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(...)`

Expand All @@ -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
Expand Down
7 changes: 4 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 37 additions & 20 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -252,7 +252,7 @@ Covers:

Suggested file:

- `examples/13_form_submit.py`
- `plugins/pydantic/examples/13_form_submit.py`

### 14. Dirty tracking

Expand All @@ -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

Expand All @@ -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

Expand All @@ -297,7 +297,7 @@ Covers:

Suggested file:

- `examples/16_form_actionable.py`
- `plugins/pydantic/examples/16_form_actionable.py`

### 17. CRUD-ready form actions

Expand All @@ -312,7 +312,7 @@ Covers:

Suggested file:

- `examples/17_form_crud.py`
- `plugins/pydantic/examples/17_form_crud.py`

### 18. TableHandle basics

Expand All @@ -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

Expand All @@ -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

Expand All @@ -359,7 +359,7 @@ Covers:

Suggested file:

- `examples/20_table_crud.py`
- `plugins/pandas/examples/20_table_crud.py`

## Phase 4: CLI Examples

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -496,3 +512,4 @@ This proposed set covers:
- CRUD helpers
- CLI usage
- plugin-oriented extension concepts

2 changes: 1 addition & 1 deletion docs/layout-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
5 changes: 5 additions & 0 deletions docs/public-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
67 changes: 67 additions & 0 deletions scripts/generate_nicegui_ui_stub.py
Original file line number Diff line number Diff line change
@@ -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())
2 changes: 2 additions & 0 deletions src/nicegui_builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions src/nicegui_builder/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Expand Down
Loading
Loading