fh-pydantic-form 0.3.4__tar.gz → 0.3.6__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.
Potentially problematic release.
This version of fh-pydantic-form might be problematic. Click here for more details.
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/PKG-INFO +1 -1
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/RELEASE_NOTES.md +13 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/pyproject.toml +1 -1
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/__init__.py +3 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/defaults.py +6 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/field_renderers.py +62 -1
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/form_renderer.py +14 -2
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/.github/workflows/build.yaml +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/.github/workflows/publish.yaml +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/.gitignore +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/.pre-commit-config.yaml +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/LICENSE +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/README.md +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/color_utils.py +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/comparison_form.py +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/constants.py +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/form_parser.py +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/list_path.py +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/py.typed +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/registry.py +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/type_helpers.py +0 -0
- {fh_pydantic_form-0.3.4 → fh_pydantic_form-0.3.6}/src/fh_pydantic_form/ui_style.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fh-pydantic-form
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: a library to turn any pydantic BaseModel object into a fasthtml/monsterui input form
|
|
5
5
|
Project-URL: Homepage, https://github.com/Marcura/fh-pydantic-form
|
|
6
6
|
Project-URL: Repository, https://github.com/Marcura/fh-pydantic-form
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## Version 0.3.6 (2025-07-21)
|
|
4
|
+
|
|
5
|
+
- **NEW**: can now pass new metrics_dict to `.with_initial_values()` helper method.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
## Version 0.3.5 (2025-07-17)
|
|
9
|
+
|
|
10
|
+
- **NEW**: Added support for `decimal.Decimal` fields with dedicated field renderer
|
|
11
|
+
- **FIXED**: Scientific notation display issues in decimal values
|
|
12
|
+
- **IMPROVED**: MyPy type checking compliance
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
3
16
|
## Version 0.3.4 (2025-07-15)
|
|
4
17
|
|
|
5
18
|
- **NEW**: Added support for Optional[List[..]] types in form fields
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import decimal
|
|
2
3
|
from enum import Enum
|
|
3
4
|
from typing import Literal, get_origin
|
|
4
5
|
|
|
@@ -17,6 +18,7 @@ from fh_pydantic_form.field_renderers import (
|
|
|
17
18
|
BaseModelFieldRenderer,
|
|
18
19
|
BooleanFieldRenderer,
|
|
19
20
|
DateFieldRenderer,
|
|
21
|
+
DecimalFieldRenderer,
|
|
20
22
|
EnumFieldRenderer,
|
|
21
23
|
ListFieldRenderer,
|
|
22
24
|
LiteralFieldRenderer,
|
|
@@ -55,6 +57,7 @@ def register_default_renderers() -> None:
|
|
|
55
57
|
FieldRendererRegistry.register_type_renderer(bool, BooleanFieldRenderer)
|
|
56
58
|
FieldRendererRegistry.register_type_renderer(int, NumberFieldRenderer)
|
|
57
59
|
FieldRendererRegistry.register_type_renderer(float, NumberFieldRenderer)
|
|
60
|
+
FieldRendererRegistry.register_type_renderer(decimal.Decimal, DecimalFieldRenderer)
|
|
58
61
|
FieldRendererRegistry.register_type_renderer(datetime.date, DateFieldRenderer)
|
|
59
62
|
FieldRendererRegistry.register_type_renderer(datetime.time, TimeFieldRenderer)
|
|
60
63
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import datetime as _dt
|
|
4
|
+
import decimal
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from typing import Any, Literal, get_args, get_origin
|
|
6
7
|
|
|
@@ -25,6 +26,7 @@ _SIMPLE_DEFAULTS = {
|
|
|
25
26
|
int: 0,
|
|
26
27
|
float: 0.0,
|
|
27
28
|
bool: False,
|
|
29
|
+
decimal.Decimal: decimal.Decimal("0"),
|
|
28
30
|
_dt.date: lambda: _today(), # callable - gets current date (late-bound)
|
|
29
31
|
_dt.time: lambda: _dt.time(0, 0), # callable - midnight
|
|
30
32
|
}
|
|
@@ -52,6 +54,10 @@ def default_for_annotation(annotation: Any) -> Any:
|
|
|
52
54
|
if _is_optional_type(annotation):
|
|
53
55
|
return None
|
|
54
56
|
|
|
57
|
+
# List[T] → []
|
|
58
|
+
if origin is list:
|
|
59
|
+
return []
|
|
60
|
+
|
|
55
61
|
# Literal[...] → first literal value
|
|
56
62
|
if origin is Literal:
|
|
57
63
|
return _first_literal_choice(annotation)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
3
|
from datetime import date, time
|
|
4
|
+
from decimal import Decimal
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from typing import (
|
|
6
7
|
Any,
|
|
@@ -510,7 +511,7 @@ class BaseFieldRenderer(MetricsRendererMixin):
|
|
|
510
511
|
label_text_span = fh.Span(label_text, **span_attrs)
|
|
511
512
|
|
|
512
513
|
# Prepare label attributes
|
|
513
|
-
label_attrs = {"
|
|
514
|
+
label_attrs = {"for": self.field_name}
|
|
514
515
|
|
|
515
516
|
# Build label classes with tokenized gap
|
|
516
517
|
label_gap_class = spacing("label_gap", self.spacing)
|
|
@@ -705,6 +706,66 @@ class NumberFieldRenderer(BaseFieldRenderer):
|
|
|
705
706
|
return mui.Input(**input_attrs)
|
|
706
707
|
|
|
707
708
|
|
|
709
|
+
class DecimalFieldRenderer(BaseFieldRenderer):
|
|
710
|
+
"""Renderer for decimal.Decimal fields"""
|
|
711
|
+
|
|
712
|
+
def __init__(self, *args, **kwargs):
|
|
713
|
+
"""Initialize decimal field renderer, passing all arguments to parent"""
|
|
714
|
+
super().__init__(*args, **kwargs)
|
|
715
|
+
|
|
716
|
+
def render_input(self) -> FT:
|
|
717
|
+
"""
|
|
718
|
+
Render input element for decimal fields
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
A NumberInput component appropriate for decimal values
|
|
722
|
+
"""
|
|
723
|
+
# Determine if field is required
|
|
724
|
+
has_default = get_default(self.field_info) is not _UNSET
|
|
725
|
+
is_field_required = not self.is_optional and not has_default
|
|
726
|
+
|
|
727
|
+
placeholder_text = f"Enter {self.original_field_name.replace('_', ' ')}"
|
|
728
|
+
if self.is_optional:
|
|
729
|
+
placeholder_text += " (Optional)"
|
|
730
|
+
|
|
731
|
+
input_cls_parts = ["w-full"]
|
|
732
|
+
input_spacing_cls = spacing_many(
|
|
733
|
+
["input_size", "input_padding", "input_line_height", "input_font_size"],
|
|
734
|
+
self.spacing,
|
|
735
|
+
)
|
|
736
|
+
if input_spacing_cls:
|
|
737
|
+
input_cls_parts.append(input_spacing_cls)
|
|
738
|
+
|
|
739
|
+
# Convert Decimal value to string for display
|
|
740
|
+
if isinstance(self.value, Decimal):
|
|
741
|
+
# Use format to avoid scientific notation
|
|
742
|
+
display_value = format(self.value, "f")
|
|
743
|
+
# Normalize zero values to display as "0"
|
|
744
|
+
if self.value == 0:
|
|
745
|
+
display_value = "0"
|
|
746
|
+
elif self.value is not None:
|
|
747
|
+
display_value = str(self.value)
|
|
748
|
+
else:
|
|
749
|
+
display_value = ""
|
|
750
|
+
|
|
751
|
+
input_attrs = {
|
|
752
|
+
"value": display_value,
|
|
753
|
+
"id": self.field_name,
|
|
754
|
+
"name": self.field_name,
|
|
755
|
+
"type": "number",
|
|
756
|
+
"placeholder": placeholder_text,
|
|
757
|
+
"required": is_field_required,
|
|
758
|
+
"cls": " ".join(input_cls_parts),
|
|
759
|
+
"step": "any", # Allow arbitrary decimal precision
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
# Only add the disabled attribute if the field should actually be disabled
|
|
763
|
+
if self.disabled:
|
|
764
|
+
input_attrs["disabled"] = True
|
|
765
|
+
|
|
766
|
+
return mui.Input(**input_attrs)
|
|
767
|
+
|
|
768
|
+
|
|
708
769
|
class BooleanFieldRenderer(BaseFieldRenderer):
|
|
709
770
|
"""Renderer for boolean fields"""
|
|
710
771
|
|
|
@@ -349,6 +349,13 @@ class PydanticForm(Generic[ModelType]):
|
|
|
349
349
|
for field_type, renderer_cls in custom_renderers:
|
|
350
350
|
registry.register_type_renderer(field_type, renderer_cls)
|
|
351
351
|
|
|
352
|
+
@property
|
|
353
|
+
def form_name(self) -> str:
|
|
354
|
+
"""
|
|
355
|
+
LLMs like to hallucinate this property, so might as well make it real.
|
|
356
|
+
"""
|
|
357
|
+
return self.name
|
|
358
|
+
|
|
352
359
|
def _compact_wrapper(self, inner: FT) -> FT:
|
|
353
360
|
"""
|
|
354
361
|
Wrap inner markup in a wrapper div.
|
|
@@ -364,7 +371,9 @@ class PydanticForm(Generic[ModelType]):
|
|
|
364
371
|
self.values_dict = self.initial_values_dict.copy()
|
|
365
372
|
|
|
366
373
|
def with_initial_values(
|
|
367
|
-
self,
|
|
374
|
+
self,
|
|
375
|
+
initial_values: Optional[Union[ModelType, Dict[str, Any]]] = None,
|
|
376
|
+
metrics_dict: Optional[Dict[str, Any]] = None,
|
|
368
377
|
) -> "PydanticForm":
|
|
369
378
|
"""
|
|
370
379
|
Create a new PydanticForm instance with the same configuration but different initial values.
|
|
@@ -376,6 +385,7 @@ class PydanticForm(Generic[ModelType]):
|
|
|
376
385
|
Args:
|
|
377
386
|
initial_values: New initial values as BaseModel instance or dict.
|
|
378
387
|
Same format as the constructor accepts.
|
|
388
|
+
metrics_dict: Optional metrics dictionary for field-level visual feedback
|
|
379
389
|
|
|
380
390
|
Returns:
|
|
381
391
|
A new PydanticForm instance with identical configuration but updated initial values
|
|
@@ -391,7 +401,9 @@ class PydanticForm(Generic[ModelType]):
|
|
|
391
401
|
label_colors=self.label_colors,
|
|
392
402
|
exclude_fields=self.exclude_fields,
|
|
393
403
|
spacing=self.spacing,
|
|
394
|
-
metrics_dict=
|
|
404
|
+
metrics_dict=metrics_dict
|
|
405
|
+
if metrics_dict is not None
|
|
406
|
+
else self.metrics_dict,
|
|
395
407
|
)
|
|
396
408
|
|
|
397
409
|
return clone
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|