fh-pydantic-form 0.1.3__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.

Potentially problematic release.


This version of fh-pydantic-form might be problematic. Click here for more details.

@@ -1,327 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: fh-pydantic-form
3
- Version: 0.1.3
4
- Summary: a library to turn any pydantic BaseModel object into a fasthtml/monsterui input form
5
- Project-URL: Homepage, https://github.com/Marcura/fh-pydantic-form
6
- Project-URL: Repository, https://github.com/Marcura/fh-pydantic-form
7
- Project-URL: Documentation, https://github.com/Marcura/fh-pydantic-form
8
- Author-email: Oege Dijk <o.dijk@marcura.com>
9
- Maintainer-email: Oege Dijk <o.dijk@marcura.com>
10
- License-File: LICENSE
11
- Keywords: fasthtml,forms,monsterui,pydantic,ui,web
12
- Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System
19
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
- Requires-Python: >=3.10
21
- Requires-Dist: monsterui>=1.0.19
22
- Requires-Dist: pydantic>=2.0
23
- Requires-Dist: python-fasthtml>=0.12.12
24
- Description-Content-Type: text/markdown
25
-
26
- # fh-pydantic-form
27
-
28
- **Generate HTML forms from Pydantic models for your FastHTML applications.**
29
-
30
- `fh-pydantic-form` simplifies creating web forms for [FastHTML](https://github.com/AnswerDotAI/fasthtml) by automatically generating the necessary HTML input elements based on your Pydantic model definitions. It integrates seamlessly with and leverages [MonsterUI](https://github.com/AnswerDotAI/monsterui) components for styling.
31
-
32
- <details >
33
- <summary>show demo screen recording</summary>
34
- <video src="https://private-user-images.githubusercontent.com/27999937/436237879-feabf388-22af-43e6-b054-f103b8a1b6e6.mp4" controls="controls" style="max-width: 730px;">
35
- </video>
36
- </details>
37
-
38
- ## Purpose
39
-
40
- - **Reduce Boilerplate:** Automatically render form inputs (text, number, checkbox, select, date, time, etc.) based on Pydantic field types and annotations.
41
- - **Data Validation:** Leverage Pydantic's validation rules directly from form submissions.
42
- - **Nested Structures:** Support for nested Pydantic models and lists of models/simple types.
43
- - **Dynamic Lists:** Built-in HTMX endpoints and JavaScript for adding, deleting, and reordering items in lists within the form.
44
- - **Customization:** Easily register custom renderers for specific Pydantic types or fields.
45
-
46
- ## Installation
47
-
48
- You can install `fh-pydantic-form` using either `pip` or `uv`.
49
-
50
- **Using pip:**
51
-
52
- ```bash
53
- pip install fh-pydantic-form
54
- ```
55
-
56
- Using uv:
57
- ```bash
58
- uv add fh-pydantic-form
59
- ```
60
-
61
- This will also install necessary dependencies like `pydantic`, `python-fasthtml`, and `monsterui`.
62
-
63
-
64
- # Basic Usage
65
-
66
-
67
- ```python
68
-
69
- # examples/simple_example.py
70
- import fasthtml.common as fh
71
- import monsterui.all as mui
72
- from pydantic import BaseModel, ValidationError
73
-
74
- # 1. Import the form renderer
75
- from fh_pydantic_form import PydanticForm
76
-
77
- app, rt = fh.fast_app(
78
- hdrs=[
79
- mui.Theme.blue.headers(),
80
- # Add list_manipulation_js() if using list fields
81
- # from fh_pydantic_form import list_manipulation_js
82
- # list_manipulation_js(),
83
- ],
84
- pico=False, # Using MonsterUI, not PicoCSS
85
- live=True, # Enable live reload for development
86
- )
87
-
88
- # 2. Define your Pydantic model
89
- class SimpleModel(BaseModel):
90
- """Model representing a simple form"""
91
- name: str = "Default Name"
92
- age: int
93
- is_active: bool = True
94
-
95
- # 3. Create a form renderer instance
96
- # - 'my_form': Unique name for the form (used for prefixes and routes)
97
- # - SimpleModel: The Pydantic model class
98
- form_renderer = PydanticForm("my_form", SimpleModel)
99
-
100
- # (Optional) Register list manipulation routes if your model has List fields
101
- # form_renderer.register_routes(app)
102
-
103
- # 4. Define routes
104
- @rt("/")
105
- def get():
106
- """Display the form"""
107
- return fh.Div(
108
- mui.Container(
109
- mui.Card(
110
- mui.CardHeader("Simple Pydantic Form"),
111
- mui.CardBody(
112
- # Use MonsterUI Form component for structure
113
- mui.Form(
114
- # Render the inputs using the renderer
115
- form_renderer.render_inputs(),
116
- # Add standard form buttons
117
- mui.Button("Submit", type="submit", cls=mui.ButtonT.primary),
118
- # HTMX attributes for form submission
119
- hx_post="/submit_form",
120
- hx_target="#result", # Target div for response
121
- hx_swap="innerHTML",
122
- # Set a unique ID for the form itself for refresh/reset inclusion
123
- id=f"{form_renderer.name}-form",
124
- )
125
- ),
126
- ),
127
- # Div to display validation results
128
- fh.Div(id="result"),
129
- ),
130
- )
131
-
132
- @rt("/submit_form")
133
- async def post_submit_form(req):
134
- """Handle form submission and validation"""
135
- try:
136
- # 5. Validate the request data against the model
137
- validated_data: SimpleModel = await form_renderer.model_validate_request(req)
138
-
139
- # Success: Display the validated data
140
- return mui.Card(
141
- mui.CardHeader(fh.H3("Validation Successful")),
142
- mui.CardBody(
143
- fh.Pre(
144
- validated_data.model_dump_json(indent=2),
145
- )
146
- ),
147
- cls="mt-4",
148
- )
149
- except ValidationError as e:
150
- # Validation Error: Display the errors
151
- return mui.Card(
152
- mui.CardHeader(fh.H3("Validation Error", cls="text-red-500")),
153
- mui.CardBody(
154
- fh.Pre(
155
- e.json(indent=2),
156
- )
157
- ),
158
- cls="mt-4",
159
- )
160
-
161
- if __name__ == "__main__":
162
- fh.serve()
163
-
164
- ```
165
- ## Key Features
166
-
167
- - **Automatic Field Rendering:** Handles `str`, `int`, `float`, `bool`, `date`, `time`, `Optional`, `Literal`, nested `BaseModel`s, and `List`s out-of-the-box.
168
- - **Sensible Defaults:** Uses appropriate HTML5 input types (`text`, `number`, `date`, `time`, `checkbox`, `select`).
169
- - **Labels & Placeholders:** Generates labels from field names (converting snake_case to Title Case) and basic placeholders.
170
- - **Descriptions as Tooltips:** Uses `Field(description=...)` from Pydantic to create tooltips (`uk-tooltip` via UIkit).
171
- - **Required Fields:** Automatically adds the `required` attribute based on field definitions (considering `Optional` and defaults).
172
- - **Disabled Fields:** Disable the whole form with `disabled=True` or disable specific fields with `disabled_fields`
173
- - **Collapsible Nested Models:** Renders nested Pydantic models in collapsible details/summary elements for better form organization and space management.
174
- - **List Manipulation:**
175
- - Renders lists of simple types or models in accordion-style cards with an enhanced UI.
176
- - Provides HTMX endpoints (registered via `register_routes`) for adding and deleting list items.
177
- - Includes JavaScript (`list_manipulation_js()`) for client-side reordering (moving items up/down).
178
- - **Form Refresh & Reset:**
179
- - Provides HTMX-powered "Refresh" and "Reset" buttons (`form_renderer.refresh_button()`, `form_renderer.reset_button()`).
180
- - Refresh updates list item summaries or other dynamic parts without full page reload.
181
- - Reset reverts the form to its initial values.
182
- - **Custom Renderers:** Register your own `BaseFieldRenderer` subclasses for specific Pydantic types or complex field logic using `FieldRendererRegistry` or by passing `custom_renderers` during `PydanticForm` initialization.
183
- - **Form Data Parsing:** Includes logic (`form_renderer.parse` and `form_renderer.model_validate_request`) to correctly parse submitted form data (handling prefixes, list indices, nested structures, boolean checkboxes, etc.) back into a dictionary suitable for Pydantic validation.
184
-
185
- ## disabled fields
186
-
187
- You can disable the full form with `PydanticForm("my_form", FormModel, disabled=True)` or disable specific fields with `PydanticForm("my_form", FormModel, disabled_fields=["field1", "field3"])`.
188
-
189
-
190
- ## Manipulating lists fields
191
-
192
- When you have `BaseModels` with fields that are e.g. `List[str]` or even `List[BaseModel]` you want to be able to easily edit the list by adding, deleting and moving items. For this we need a little bit of javascript and register some additional routes:
193
-
194
- ```python
195
- from fh_pydantic_form import PydanticForm, list_manipulation_js
196
-
197
- app, rt = fh.fast_app(
198
- hdrs=[
199
- mui.Theme.blue.headers(),
200
- list_manipulation_js(),
201
- ],
202
- pico=False,
203
- live=True,
204
- )
205
-
206
-
207
- class ListModel(BaseModel):
208
- name: str = ""
209
- tags: List[str] = Field(["tag1", "tag2"])
210
-
211
-
212
- form_renderer = PydanticForm("list_model", ListModel)
213
- form_renderer.register_routes(app)
214
- ```
215
-
216
- ## Refreshing and resetting the form
217
-
218
- You can set the initial values of the form by passing an instantiated BaseModel:
219
-
220
- ```python
221
- form_renderer = PydanticForm("my_form", ListModel, initial_values=ListModel(name="John", tags=["happy", "joy"]))
222
- ```
223
-
224
- You can reset the form back to these initial values by adding a `form_render.reset_button()` to your UI:
225
-
226
- ```python
227
- mui.Form(
228
- form_renderer.render_inputs(),
229
- fh.Div(
230
- mui.Button("Validate and Show JSON",cls=mui.ButtonT.primary,),
231
- form_renderer.refresh_button(),
232
- form_renderer.reset_button(),
233
- ),
234
- hx_post="/submit_form",
235
- hx_target="#result",
236
- hx_swap="innerHTML",
237
- )
238
- ```
239
-
240
- The refresh button 🔄 refreshes the list item labels. These are rendered initially to summarize the underlying item, but do not automatically update after editing unless refreshed. You can also use the 🔄 icon next to the list field label.
241
-
242
-
243
- ## Custom renderers
244
-
245
- The library is extensible by adding your own input renderers for your types. This can be used to override e.g. the default BaseModelFieldRenderer for nested BaseModels, but also to register types that are not (yet) supported (but submit a PR then as well!)
246
-
247
- You can register a renderer based on type, type str, or a predicate function:
248
-
249
- ```python
250
- from fh_pydantic_form import FieldRendererRegistry
251
-
252
- from fh_pydantic_form.field_renderers import BaseFieldRenderer
253
-
254
- class CustomDetail(BaseModel):
255
- value: str = "Default value"
256
- confidence: Literal["HIGH", "MEDIUM", "LOW"] = "MEDIUM"
257
-
258
- def __str__(self) -> str:
259
- return f"{self.value} ({self.confidence})"
260
-
261
-
262
- class CustomDetailFieldRenderer(BaseFieldRenderer):
263
- """display value input and dropdown side by side"""
264
-
265
- def render_input(self):
266
- value_input = fh.Div(
267
- mui.Input(
268
- value=self.value.get("value", ""),
269
- id=f"{self.field_name}_value",
270
- name=f"{self.field_name}_value",
271
- placeholder=f"Enter {self.original_field_name.replace('_', ' ')} value",
272
- cls="uk-input w-full",
273
- ),
274
- cls="flex-grow", # apply some custom css
275
- )
276
-
277
- confidence_options_ft = [
278
- fh.Option(
279
- opt, value=opt, selected=(opt == self.value.get("confidence", "MEDIUM"))
280
- )
281
- for opt in ["HIGH", "MEDIUM", "LOW"]
282
- ]
283
-
284
- confidence_select = mui.Select(
285
- *confidence_options_ft,
286
- id=f"{self.field_name}_confidence",
287
- name=f"{self.field_name}_confidence",
288
- cls_wrapper="w-[110px] min-w-[110px] flex-shrink-0", # apply some custom css
289
- )
290
-
291
- return fh.Div(
292
- value_input,
293
- confidence_select,
294
- cls="flex items-start gap-2 w-full", # apply some custom css
295
- )
296
-
297
-
298
- # these are all equivalent. You can either register the type directly
299
- FieldRendererRegistry.register_type_renderer(CustomDetail, CustomDetailFieldRender)
300
- # or just by the name of the type
301
- FieldRendererRegistry.register_type_name_renderer("CustomDetail", CustomDetailFieldRender)
302
- # or register I predicate function
303
- FieldRendererRegistry.register_type_renderer_with_predicate(lambda: x: isinstance(x, CustomDetail), CustomDetailFieldRender)
304
- ```
305
-
306
- You can also pass these directly to the `PydanticForm` with the custom_renderers argument:
307
-
308
- ```python
309
-
310
- form_renderer = PydanticForm(
311
- form_name="main_form",
312
- model_class=ComplexSchema,
313
- initial_values=initial_values,
314
- custom_renderers=[
315
- (CustomDetail, CustomDetailFieldRenderer)
316
- ], # Register Detail renderer
317
- )
318
- ```
319
-
320
- ## Contributing
321
-
322
- Contributions are welcome! Please feel free to open an issue or submit a pull request.
323
-
324
-
325
-
326
-
327
-
@@ -1,11 +0,0 @@
1
- fh_pydantic_form/__init__.py,sha256=oifnGdBOS1XaO9Q8gcZ1SJ98Xzz9TfbZH9kvHg9_beo,3056
2
- fh_pydantic_form/field_renderers.py,sha256=zgGEgR09Oop_-lyYvcHw52L7Uz4X26dWcxyFqUCBSN0,39461
3
- fh_pydantic_form/form_parser.py,sha256=3EGy4YHbLskH76V0ieTG8zmQ0b02ty8bOZf9AEg89ZY,20629
4
- fh_pydantic_form/form_renderer.py,sha256=gv172TwuihNBIgUD7VBCPo2tJ3FuAAlHZkzXzQQyT3w,28990
5
- fh_pydantic_form/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- fh_pydantic_form/registry.py,sha256=sufK-85ST3rc3Vu0XmjjjdTqTAqgHr_ZbMGU0xRgTK8,4996
7
- fh_pydantic_form/type_helpers.py,sha256=ZCU8m4xxFk_ofMsAwxb8CunZhsDBsrEFjpJdtncreT0,1322
8
- fh_pydantic_form-0.1.3.dist-info/METADATA,sha256=7xzIYoU9WIg-29glyEx4f7fkO2kG365zU6Ow810abP8,12555
9
- fh_pydantic_form-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- fh_pydantic_form-0.1.3.dist-info/licenses/LICENSE,sha256=AOi2eNK3D2aDycRHfPRiuACZ7WPBsKHTV2tTYNl7cls,577
11
- fh_pydantic_form-0.1.3.dist-info/RECORD,,