pydantic-ui 0.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydantic_ui-0.1.5/.gitignore +54 -0
- pydantic_ui-0.1.5/PKG-INFO +623 -0
- pydantic_ui-0.1.5/README.md +584 -0
- pydantic_ui-0.1.5/pydantic_ui/__init__.py +20 -0
- pydantic_ui-0.1.5/pydantic_ui/app.py +468 -0
- pydantic_ui-0.1.5/pydantic_ui/config.py +169 -0
- pydantic_ui-0.1.5/pydantic_ui/controller.py +235 -0
- pydantic_ui-0.1.5/pydantic_ui/events.py +87 -0
- pydantic_ui-0.1.5/pydantic_ui/handlers.py +240 -0
- pydantic_ui-0.1.5/pydantic_ui/models.py +100 -0
- pydantic_ui-0.1.5/pydantic_ui/schema.py +433 -0
- pydantic_ui-0.1.5/pydantic_ui/sessions.py +197 -0
- pydantic_ui-0.1.5/pydantic_ui/static/assets/index-Bo-ezZQn.css +1 -0
- pydantic_ui-0.1.5/pydantic_ui/static/assets/index-Bs1Tm71i.js +6790 -0
- pydantic_ui-0.1.5/pydantic_ui/static/index.html +14 -0
- pydantic_ui-0.1.5/pydantic_ui/static/logo.png +0 -0
- pydantic_ui-0.1.5/pydantic_ui/utils.py +194 -0
- pydantic_ui-0.1.5/pyproject.toml +128 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
frontend/coverage/*
|
|
2
|
+
examples/basic/user_input/*
|
|
3
|
+
# Python
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
*.so
|
|
8
|
+
.Python
|
|
9
|
+
build/
|
|
10
|
+
develop-eggs/
|
|
11
|
+
dist/
|
|
12
|
+
downloads/
|
|
13
|
+
eggs/
|
|
14
|
+
.eggs/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
venv/
|
|
25
|
+
ENV/
|
|
26
|
+
env/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
|
|
34
|
+
# Node
|
|
35
|
+
node_modules/
|
|
36
|
+
frontend/dist/
|
|
37
|
+
|
|
38
|
+
# Build artifacts (but keep the static folder structure)
|
|
39
|
+
pydantic_ui/static/*
|
|
40
|
+
!pydantic_ui/static/.gitkeep
|
|
41
|
+
|
|
42
|
+
# OS
|
|
43
|
+
.DS_Store
|
|
44
|
+
Thumbs.db
|
|
45
|
+
|
|
46
|
+
# Test
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
.coverage
|
|
49
|
+
htmlcov/
|
|
50
|
+
|
|
51
|
+
# Misc
|
|
52
|
+
*.log
|
|
53
|
+
.env
|
|
54
|
+
.env.local
|
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pydantic-ui
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Dynamic UI for editing deeply nested Pydantic models
|
|
5
|
+
Project-URL: Homepage, https://github.com/idling-mind/pydantic-ui
|
|
6
|
+
Project-URL: Documentation, https://github.com/idling-mind/pydantic-ui#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/idling-mind/pydantic-ui
|
|
8
|
+
Project-URL: Issues, https://github.com/idling-mind/pydantic-ui/issues
|
|
9
|
+
Author-email: Najeem Muhammed <najeem@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: editor,fastapi,form,pydantic,react,ui
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Framework :: Pydantic :: 2
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: fastapi>=0.100.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0.0
|
|
27
|
+
Requires-Dist: uvicorn>=0.23.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: coverage>=7.4.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: faker>=22.0.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: httpx>=0.27.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# Pydantic UI
|
|
41
|
+
|
|
42
|
+
A dynamic, modern UI for editing deeply nested Pydantic models with FastAPI integration.
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+

|
|
46
|
+

|
|
47
|
+

|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- 🌳 **Tree Navigation**: Sidebar showing hierarchical structure of nested models
|
|
54
|
+
- 🎨 **Dynamic Renderers**: Auto-detect appropriate input components based on field types
|
|
55
|
+
- ⚙️ **Customizable**: Override default renderers with sliders, dropdowns, date pickers, etc.
|
|
56
|
+
- 🌓 **Theme Support**: Light and dark mode with system preference detection
|
|
57
|
+
- ✅ **Validation**: Real-time validation using Pydantic validators
|
|
58
|
+
- 📦 **Easy Integration**: Mount as a FastAPI router in your existing application
|
|
59
|
+
- 🔘 **Action Buttons**: Custom action buttons with Python callbacks
|
|
60
|
+
- 📡 **Real-time Updates**: Server-Sent Events (SSE) for live UI updates
|
|
61
|
+
- 💾 **Session Management**: Per-user session state with data persistence
|
|
62
|
+
- 📋 **Copy/Paste**: Clipboard support for tree nodes
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install pydantic-ui
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from fastapi import FastAPI
|
|
74
|
+
from pydantic import BaseModel, Field
|
|
75
|
+
from typing import Annotated
|
|
76
|
+
from pydantic_ui import create_pydantic_ui, FieldConfig, Renderer
|
|
77
|
+
|
|
78
|
+
# Define your Pydantic model
|
|
79
|
+
class Address(BaseModel):
|
|
80
|
+
street: str
|
|
81
|
+
city: str
|
|
82
|
+
zipcode: str
|
|
83
|
+
|
|
84
|
+
class Person(BaseModel):
|
|
85
|
+
name: str = Field(min_length=1, max_length=100)
|
|
86
|
+
age: Annotated[int, FieldConfig(
|
|
87
|
+
renderer=Renderer.SLIDER,
|
|
88
|
+
props={"min": 0, "max": 120}
|
|
89
|
+
)]
|
|
90
|
+
email: str
|
|
91
|
+
address: Address
|
|
92
|
+
tags: list[str] = []
|
|
93
|
+
|
|
94
|
+
# Create FastAPI app and mount pydantic-ui
|
|
95
|
+
app = FastAPI()
|
|
96
|
+
|
|
97
|
+
app.include_router(
|
|
98
|
+
create_pydantic_ui(Person, prefix="/editor"),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
import uvicorn
|
|
103
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Then visit `http://localhost:8000/editor` to see the UI.
|
|
107
|
+
|
|
108
|
+
## Public API
|
|
109
|
+
|
|
110
|
+
The package exports the following from `pydantic_ui`:
|
|
111
|
+
|
|
112
|
+
| Export | Description |
|
|
113
|
+
|--------|-------------|
|
|
114
|
+
| `create_pydantic_ui` | Factory function to create a FastAPI router for a Pydantic model |
|
|
115
|
+
| `UIConfig` | Global UI configuration class |
|
|
116
|
+
| `FieldConfig` | Per-field UI configuration class |
|
|
117
|
+
| `Renderer` | Enum of available field renderers |
|
|
118
|
+
| `ActionButton` | Configuration for custom action buttons |
|
|
119
|
+
| `PydanticUIController` | Controller for programmatic UI interaction |
|
|
120
|
+
|
|
121
|
+
## UI Configuration
|
|
122
|
+
|
|
123
|
+
### Global Configuration (`UIConfig`)
|
|
124
|
+
|
|
125
|
+
All available options for `UIConfig`:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from pydantic_ui import create_pydantic_ui, UIConfig
|
|
129
|
+
|
|
130
|
+
ui_config = UIConfig(
|
|
131
|
+
# Basic Settings
|
|
132
|
+
title="Data Editor", # Title shown in header (default: "Data Editor")
|
|
133
|
+
description="", # Description below title
|
|
134
|
+
|
|
135
|
+
# Logo/Branding
|
|
136
|
+
logo_text=None, # Short text for logo (e.g., "P", "UI").
|
|
137
|
+
# If not set, uses first letter of title
|
|
138
|
+
logo_url=None, # URL to logo image. Overrides logo_text if set
|
|
139
|
+
|
|
140
|
+
# Theme
|
|
141
|
+
theme="system", # "light", "dark", or "system" (default: "system")
|
|
142
|
+
|
|
143
|
+
# Form Behavior
|
|
144
|
+
read_only=False, # Make entire form read-only (default: False)
|
|
145
|
+
show_validation=True, # Show validation errors (default: True)
|
|
146
|
+
auto_save=False, # Auto-save changes (default: False)
|
|
147
|
+
auto_save_delay=1000, # Delay in ms before auto-saving (default: 1000)
|
|
148
|
+
|
|
149
|
+
# Tree Panel
|
|
150
|
+
collapsible_tree=True, # Allow tree nodes to collapse (default: True)
|
|
151
|
+
show_types=True, # Show type badges in tree (default: True)
|
|
152
|
+
|
|
153
|
+
# Footer
|
|
154
|
+
show_save_reset=False, # Show Save/Reset buttons in footer (default: False)
|
|
155
|
+
footer_text="Powered by Pydantic UI", # Footer text (empty string hides footer)
|
|
156
|
+
footer_url="https://github.com/idling-mind/pydantic-ui", # Footer link URL
|
|
157
|
+
|
|
158
|
+
# Layout
|
|
159
|
+
responsive_columns={ # Responsive column breakpoints
|
|
160
|
+
640: 1, # 1 column up to 640px
|
|
161
|
+
1000: 2, # 2 columns from 640-1000px
|
|
162
|
+
1600: 3 # 3 columns above 1000px
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
# Custom Actions (see Action Buttons section)
|
|
166
|
+
actions=[], # List of ActionButton configurations
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
app.include_router(
|
|
170
|
+
create_pydantic_ui(
|
|
171
|
+
Person,
|
|
172
|
+
ui_config=ui_config,
|
|
173
|
+
prefix="/editor"
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Per-Field Configuration (`FieldConfig`)
|
|
179
|
+
|
|
180
|
+
Use `Annotated` with `FieldConfig` to customize individual fields:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from typing import Annotated
|
|
184
|
+
from pydantic_ui import FieldConfig, Renderer
|
|
185
|
+
|
|
186
|
+
class Settings(BaseModel):
|
|
187
|
+
# Slider for numeric values
|
|
188
|
+
volume: Annotated[int, FieldConfig(
|
|
189
|
+
renderer=Renderer.SLIDER,
|
|
190
|
+
label="Volume Level", # Custom label (defaults to field name)
|
|
191
|
+
help_text="Adjust the volume", # Help text below field (alias: description)
|
|
192
|
+
placeholder="Enter value", # Placeholder text
|
|
193
|
+
props={"min": 0, "max": 100, "step": 5} # Renderer-specific props
|
|
194
|
+
)] = 50
|
|
195
|
+
|
|
196
|
+
# Dropdown for enum-like fields
|
|
197
|
+
theme: Annotated[str, FieldConfig(
|
|
198
|
+
renderer=Renderer.SELECT,
|
|
199
|
+
props={"options": ["light", "dark", "auto"]}
|
|
200
|
+
)] = "auto"
|
|
201
|
+
|
|
202
|
+
# Toggle instead of checkbox
|
|
203
|
+
notifications: Annotated[bool, FieldConfig(
|
|
204
|
+
renderer=Renderer.TOGGLE
|
|
205
|
+
)] = True
|
|
206
|
+
|
|
207
|
+
# Text area for long text
|
|
208
|
+
bio: Annotated[str, FieldConfig(
|
|
209
|
+
renderer=Renderer.TEXT_AREA,
|
|
210
|
+
props={"rows": 5, "placeholder": "Tell us about yourself..."}
|
|
211
|
+
)] = ""
|
|
212
|
+
|
|
213
|
+
# Hidden fields (not shown in UI)
|
|
214
|
+
internal_id: Annotated[str, FieldConfig(hidden=True)]
|
|
215
|
+
|
|
216
|
+
# Read-only fields (visible but not editable)
|
|
217
|
+
created_at: Annotated[str, FieldConfig(read_only=True)]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Field Configs via Path (Alternative Method)
|
|
221
|
+
|
|
222
|
+
You can also configure fields by path without using `Annotated`:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
field_configs = {
|
|
226
|
+
# Direct field path
|
|
227
|
+
"server.name": FieldConfig(
|
|
228
|
+
label="Application Name",
|
|
229
|
+
placeholder="Enter your app name",
|
|
230
|
+
),
|
|
231
|
+
|
|
232
|
+
# Array item fields using [] syntax
|
|
233
|
+
"users.[].age": FieldConfig(
|
|
234
|
+
label="User Age",
|
|
235
|
+
renderer=Renderer.SLIDER,
|
|
236
|
+
props={"min": 0, "max": 120, "step": 1},
|
|
237
|
+
),
|
|
238
|
+
|
|
239
|
+
# Nested paths
|
|
240
|
+
"database.password": FieldConfig(
|
|
241
|
+
label="Database Password",
|
|
242
|
+
props={"type": "password"},
|
|
243
|
+
),
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
pydantic_ui_router = create_pydantic_ui(
|
|
247
|
+
model=AppConfig,
|
|
248
|
+
field_configs=field_configs,
|
|
249
|
+
prefix="/config",
|
|
250
|
+
)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Available Renderers
|
|
254
|
+
|
|
255
|
+
| Renderer | Enum Value | Description | Props |
|
|
256
|
+
|----------|------------|-------------|-------|
|
|
257
|
+
| Auto | `Renderer.AUTO` | Auto-detect based on type | - |
|
|
258
|
+
| Text Input | `Renderer.TEXT_INPUT` | Single-line text input | `placeholder`, `maxLength` |
|
|
259
|
+
| Text Area | `Renderer.TEXT_AREA` | Multi-line text input | `rows`, `placeholder` |
|
|
260
|
+
| Number Input | `Renderer.NUMBER_INPUT` | Numeric input | `min`, `max`, `step` |
|
|
261
|
+
| Slider | `Renderer.SLIDER` | Range slider | `min`, `max`, `step`, `marks` |
|
|
262
|
+
| Checkbox | `Renderer.CHECKBOX` | Checkbox | - |
|
|
263
|
+
| Toggle | `Renderer.TOGGLE` | Toggle switch | - |
|
|
264
|
+
| Select | `Renderer.SELECT` | Dropdown select | `options` |
|
|
265
|
+
| Multi-Select | `Renderer.MULTI_SELECT` | Multi-select dropdown | `options` |
|
|
266
|
+
| Date Picker | `Renderer.DATE_PICKER` | Date picker | `format` |
|
|
267
|
+
| DateTime Picker | `Renderer.DATETIME_PICKER` | DateTime picker | `format` |
|
|
268
|
+
| Color Picker | `Renderer.COLOR_PICKER` | Color picker | - |
|
|
269
|
+
| File Upload | `Renderer.FILE_UPLOAD` | File upload | - |
|
|
270
|
+
| File Select | `Renderer.FILE_SELECT` | File selector | - |
|
|
271
|
+
| Password | `Renderer.PASSWORD` | Password input | - |
|
|
272
|
+
| Email | `Renderer.EMAIL` | Email input | - |
|
|
273
|
+
| URL | `Renderer.URL` | URL input | - |
|
|
274
|
+
|
|
275
|
+
## Action Buttons
|
|
276
|
+
|
|
277
|
+
Add custom action buttons to the UI header that trigger Python callbacks:
|
|
278
|
+
|
|
279
|
+
### Defining Action Buttons
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from pydantic_ui import UIConfig, ActionButton
|
|
283
|
+
|
|
284
|
+
ui_config = UIConfig(
|
|
285
|
+
title="App Settings",
|
|
286
|
+
actions=[
|
|
287
|
+
ActionButton(
|
|
288
|
+
id="validate", # Unique identifier (required)
|
|
289
|
+
label="Validate", # Button label (required)
|
|
290
|
+
variant="secondary", # "default", "secondary", "outline",
|
|
291
|
+
# "ghost", "destructive"
|
|
292
|
+
icon="check-circle", # Lucide icon name (optional)
|
|
293
|
+
tooltip="Run validation", # Tooltip on hover (optional)
|
|
294
|
+
disabled=False, # Whether button is disabled
|
|
295
|
+
confirm=None, # Confirmation message before action
|
|
296
|
+
# If set, shows dialog before triggering
|
|
297
|
+
),
|
|
298
|
+
ActionButton(
|
|
299
|
+
id="reset",
|
|
300
|
+
label="Reset All",
|
|
301
|
+
variant="destructive",
|
|
302
|
+
icon="refresh-cw",
|
|
303
|
+
confirm="Are you sure you want to reset all settings?"
|
|
304
|
+
),
|
|
305
|
+
ActionButton(
|
|
306
|
+
id="save",
|
|
307
|
+
label="Save",
|
|
308
|
+
variant="default",
|
|
309
|
+
icon="save",
|
|
310
|
+
),
|
|
311
|
+
],
|
|
312
|
+
)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Registering Action Handlers
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
from pydantic_ui import create_pydantic_ui, PydanticUIController
|
|
319
|
+
|
|
320
|
+
router = create_pydantic_ui(model=AppSettings, ui_config=ui_config, prefix="/settings")
|
|
321
|
+
app.include_router(router)
|
|
322
|
+
|
|
323
|
+
@router.action("validate")
|
|
324
|
+
async def handle_validate(data: dict, controller: PydanticUIController):
|
|
325
|
+
"""Handler receives current data and a controller for UI interaction."""
|
|
326
|
+
errors = []
|
|
327
|
+
|
|
328
|
+
# Custom validation logic
|
|
329
|
+
if data.get("environment") == "production" and data.get("server", {}).get("debug"):
|
|
330
|
+
errors.append({
|
|
331
|
+
"path": "server.debug",
|
|
332
|
+
"message": "Debug mode should not be enabled in production"
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
if errors:
|
|
336
|
+
await controller.show_validation_errors(errors)
|
|
337
|
+
await controller.show_toast("Validation failed", "error")
|
|
338
|
+
else:
|
|
339
|
+
await controller.clear_validation_errors()
|
|
340
|
+
await controller.show_toast("All validations passed!", "success")
|
|
341
|
+
|
|
342
|
+
return {"valid": len(errors) == 0}
|
|
343
|
+
|
|
344
|
+
@router.action("save")
|
|
345
|
+
async def handle_save(data: dict, controller: PydanticUIController):
|
|
346
|
+
"""Save handler with Pydantic validation."""
|
|
347
|
+
from pydantic import ValidationError
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
validated = AppSettings.model_validate(data)
|
|
351
|
+
# Save to database, file, etc.
|
|
352
|
+
await controller.show_toast("Settings saved!", "success")
|
|
353
|
+
return {"saved": True}
|
|
354
|
+
except ValidationError as e:
|
|
355
|
+
await controller.show_toast(f"Validation error: {e}", "error")
|
|
356
|
+
return {"saved": False}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Controller Methods (`PydanticUIController`)
|
|
360
|
+
|
|
361
|
+
The controller provides methods for programmatic UI interaction:
|
|
362
|
+
|
|
363
|
+
### Validation Errors
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
# Show validation errors
|
|
367
|
+
await controller.show_validation_errors([
|
|
368
|
+
{"path": "users[0].age", "message": "Age must be positive"},
|
|
369
|
+
{"path": "name", "message": "Name is required"}
|
|
370
|
+
])
|
|
371
|
+
|
|
372
|
+
# Clear all validation errors
|
|
373
|
+
await controller.clear_validation_errors()
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Toast Notifications
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
# Show toast notification
|
|
380
|
+
await controller.show_toast(
|
|
381
|
+
message="Operation completed!",
|
|
382
|
+
type="success", # "success", "error", "warning", "info"
|
|
383
|
+
duration=5000 # ms (0 for persistent)
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Broadcast toast to ALL connected sessions
|
|
387
|
+
await controller.broadcast_toast("Server restarting...", "warning")
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Data Updates
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
# Push new data to the UI
|
|
394
|
+
new_data = AppSettings(name="Updated", ...)
|
|
395
|
+
await controller.push_data(new_data) # Accepts BaseModel or dict
|
|
396
|
+
|
|
397
|
+
# Get current data from session
|
|
398
|
+
current_data = controller.get_current_data()
|
|
399
|
+
|
|
400
|
+
# Get validated model instance (returns None if invalid)
|
|
401
|
+
model_instance = controller.get_model_instance()
|
|
402
|
+
|
|
403
|
+
# Tell UI to refresh from server
|
|
404
|
+
await controller.refresh()
|
|
405
|
+
|
|
406
|
+
# Broadcast refresh to all sessions
|
|
407
|
+
await controller.broadcast_refresh()
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Confirmation Dialogs
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
# Request user confirmation (async - waits for response)
|
|
414
|
+
confirmed = await controller.request_confirmation(
|
|
415
|
+
message="Delete all users?",
|
|
416
|
+
title="Confirm Deletion", # Dialog title
|
|
417
|
+
confirm_text="Delete", # Confirm button text
|
|
418
|
+
cancel_text="Cancel", # Cancel button text
|
|
419
|
+
variant="destructive" # "default" or "destructive"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if confirmed:
|
|
423
|
+
# User clicked confirm
|
|
424
|
+
delete_all_users()
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Data Handlers
|
|
428
|
+
|
|
429
|
+
### Custom Data Loading and Saving
|
|
430
|
+
|
|
431
|
+
Use decorators to set custom data loader/saver:
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
from pydantic_ui import create_pydantic_ui
|
|
435
|
+
|
|
436
|
+
router = create_pydantic_ui(Person, prefix="/editor")
|
|
437
|
+
app.include_router(router)
|
|
438
|
+
|
|
439
|
+
@router.data_loader
|
|
440
|
+
async def load_person() -> Person:
|
|
441
|
+
"""Load data from your database or file."""
|
|
442
|
+
return await database.get_person(id=1)
|
|
443
|
+
|
|
444
|
+
@router.data_saver
|
|
445
|
+
async def save_person(data: Person) -> None:
|
|
446
|
+
"""Save data to your database or file."""
|
|
447
|
+
await database.update_person(id=1, data=data)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Initial Data via Parameter
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
initial_person = Person(
|
|
454
|
+
name="John Doe",
|
|
455
|
+
age=30,
|
|
456
|
+
email="john@example.com",
|
|
457
|
+
address=Address(street="123 Main St", city="NYC", zipcode="10001"),
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
app.include_router(
|
|
461
|
+
create_pydantic_ui(
|
|
462
|
+
Person,
|
|
463
|
+
initial_data=initial_person,
|
|
464
|
+
prefix="/editor"
|
|
465
|
+
),
|
|
466
|
+
)
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Inline Data Loader/Saver
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
app.include_router(
|
|
473
|
+
create_pydantic_ui(
|
|
474
|
+
Person,
|
|
475
|
+
data_loader=lambda: load_from_db(),
|
|
476
|
+
data_saver=lambda data: save_to_db(data),
|
|
477
|
+
prefix="/editor"
|
|
478
|
+
),
|
|
479
|
+
)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Factory Function Reference
|
|
483
|
+
|
|
484
|
+
Complete signature for `create_pydantic_ui`:
|
|
485
|
+
|
|
486
|
+
```python
|
|
487
|
+
def create_pydantic_ui(
|
|
488
|
+
model: type[BaseModel], # The Pydantic model class (required)
|
|
489
|
+
*,
|
|
490
|
+
ui_config: UIConfig | None = None, # Global UI configuration
|
|
491
|
+
field_configs: dict[str, FieldConfig] | None = None, # Per-field configs by path
|
|
492
|
+
initial_data: BaseModel | None = None, # Initial data to populate form
|
|
493
|
+
data_loader: Callable[[], BaseModel | dict] | None = None, # Data loader function
|
|
494
|
+
data_saver: Callable[[BaseModel], None] | None = None, # Data saver function
|
|
495
|
+
prefix: str = "", # URL prefix for router
|
|
496
|
+
) -> APIRouter:
|
|
497
|
+
...
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The returned router has additional attributes:
|
|
501
|
+
|
|
502
|
+
| Attribute | Description |
|
|
503
|
+
|-----------|-------------|
|
|
504
|
+
| `router.controller` | `PydanticUIController` instance |
|
|
505
|
+
| `@router.action(id)` | Decorator to register action handlers |
|
|
506
|
+
| `@router.data_loader` | Decorator to set custom data loader |
|
|
507
|
+
| `@router.data_saver` | Decorator to set custom data saver |
|
|
508
|
+
|
|
509
|
+
## API Endpoints
|
|
510
|
+
|
|
511
|
+
When mounted at `/editor`, the following endpoints are available:
|
|
512
|
+
|
|
513
|
+
| Method | Endpoint | Description |
|
|
514
|
+
|--------|----------|-------------|
|
|
515
|
+
| GET | `/editor/` | Serve the React UI |
|
|
516
|
+
| GET | `/editor/api/schema` | Get the model schema |
|
|
517
|
+
| GET | `/editor/api/data` | Get current session data |
|
|
518
|
+
| POST | `/editor/api/data` | Update entire data (validates with Pydantic) |
|
|
519
|
+
| PATCH | `/editor/api/data` | Partial update (path + value) |
|
|
520
|
+
| POST | `/editor/api/validate` | Validate data without saving |
|
|
521
|
+
| GET | `/editor/api/config` | Get UI configuration |
|
|
522
|
+
| GET | `/editor/api/session` | Get or create session ID |
|
|
523
|
+
| GET | `/editor/api/events` | SSE endpoint for real-time events |
|
|
524
|
+
| GET | `/editor/api/events/poll` | Polling fallback for events |
|
|
525
|
+
| POST | `/editor/api/actions/{id}` | Trigger action handler |
|
|
526
|
+
| POST | `/editor/api/confirmation/{id}` | Handle confirmation response |
|
|
527
|
+
|
|
528
|
+
## Supported Pydantic Types
|
|
529
|
+
|
|
530
|
+
The UI automatically handles these types:
|
|
531
|
+
|
|
532
|
+
| Type | Renderer |
|
|
533
|
+
|------|----------|
|
|
534
|
+
| `str` | Text Input |
|
|
535
|
+
| `int`, `float` | Number Input (Slider if min/max defined) |
|
|
536
|
+
| `bool` | Toggle |
|
|
537
|
+
| `datetime`, `date` | Date/DateTime Picker |
|
|
538
|
+
| `Enum`, `StrEnum` | Select Dropdown |
|
|
539
|
+
| `Literal["a", "b"]` | Select Dropdown |
|
|
540
|
+
| `list[T]` | Array Editor with add/remove |
|
|
541
|
+
| `dict[str, T]` | JSON Editor or Key-Value |
|
|
542
|
+
| `Optional[T]` / `T \| None` | Nullable field |
|
|
543
|
+
| Nested `BaseModel` | Nested object navigation |
|
|
544
|
+
|
|
545
|
+
## Examples
|
|
546
|
+
|
|
547
|
+
See the [examples](./examples/) directory for complete examples:
|
|
548
|
+
|
|
549
|
+
- **[simple.py](./examples/basic/simple.py)** - Basic usage with field configs
|
|
550
|
+
- **[main.py](./examples/basic/main.py)** - Full configuration example
|
|
551
|
+
- **[callbacks.py](./examples/basic/callbacks.py)** - Action buttons, validation, toasts, confirmations
|
|
552
|
+
|
|
553
|
+
### Running Examples
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
# Run the simple example
|
|
557
|
+
cd examples/basic
|
|
558
|
+
python simple.py
|
|
559
|
+
# Visit http://localhost:8000/config
|
|
560
|
+
|
|
561
|
+
# Run the callbacks example
|
|
562
|
+
python callbacks.py
|
|
563
|
+
# Visit http://localhost:8000/settings
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
## Development
|
|
567
|
+
|
|
568
|
+
### Setup
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
# Clone the repository
|
|
572
|
+
git clone https://github.com/idling-mind/pydantic-ui.git
|
|
573
|
+
cd pydantic-ui
|
|
574
|
+
|
|
575
|
+
# Install dependencies
|
|
576
|
+
pip install -e ".[dev]"
|
|
577
|
+
|
|
578
|
+
# Install frontend dependencies
|
|
579
|
+
cd frontend
|
|
580
|
+
npm install
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Running Tests
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
# Backend tests
|
|
587
|
+
pytest
|
|
588
|
+
|
|
589
|
+
# Frontend tests
|
|
590
|
+
cd frontend
|
|
591
|
+
npm test
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Building
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
# Build frontend and copy to package
|
|
598
|
+
cd frontend
|
|
599
|
+
npm run build:package
|
|
600
|
+
|
|
601
|
+
# Or use the PowerShell script (Windows)
|
|
602
|
+
./scripts/build-test.ps1
|
|
603
|
+
|
|
604
|
+
# Build Python package
|
|
605
|
+
python -m build
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Build Script Options
|
|
609
|
+
|
|
610
|
+
```powershell
|
|
611
|
+
# Full build and run
|
|
612
|
+
./scripts/build-test.ps1
|
|
613
|
+
|
|
614
|
+
# Skip frontend build (use existing)
|
|
615
|
+
./scripts/build-test.ps1 -SkipBuild
|
|
616
|
+
|
|
617
|
+
# Run specific example on different port
|
|
618
|
+
./scripts/build-test.ps1 -Example callbacks -Port 3000 -OpenBrowser
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## License
|
|
622
|
+
|
|
623
|
+
MIT License - see [LICENSE](LICENSE) for details.
|