fh-pydantic-form 0.3.4__py3-none-any.whl → 0.3.6__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,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 = {"For": self.field_name}
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, initial_values: Optional[Union[ModelType, Dict[str, Any]]] = None
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=self.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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fh-pydantic-form
3
- Version: 0.3.4
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,17 +1,17 @@
1
- fh_pydantic_form/__init__.py,sha256=rFuRyXVlitbFQfR4BxRLZR_CxCMJwE_QbQyt1h_4568,4479
1
+ fh_pydantic_form/__init__.py,sha256=uPN0pwErHoe69tDoCDdIYD_iCslMKRgfYw6pvL4McHE,4608
2
2
  fh_pydantic_form/color_utils.py,sha256=M0HSXX0i-lSHkcsgesxw7d3PEAnLsZ46i_STymZAM_k,18271
3
3
  fh_pydantic_form/comparison_form.py,sha256=Y-OKAjTxMiixBC05SK1ofZ_A3zkvjUFp8tTE-_wUn60,21660
4
4
  fh_pydantic_form/constants.py,sha256=-N9wzkibFNn-V6cO8iWTQ7_xBvwSr2hBdq-m3apmW4M,169
5
- fh_pydantic_form/defaults.py,sha256=Pwv46v7e43cykx4Pt01e4nw-6FBkHmPvTZK36ZTZqgA,6068
6
- fh_pydantic_form/field_renderers.py,sha256=yJuV1bMVhL_7o1tZDyw20DJzy3-OQbq_Ygc_tHpsLsU,81459
5
+ fh_pydantic_form/defaults.py,sha256=9vV0f4PapTOgqNsIxoW6rEbpYO66O4uiKvpd6hzR1-M,6189
6
+ fh_pydantic_form/field_renderers.py,sha256=twAqHvWkDL6M-HRcdi_JrR2BgzkPmMyF_vojId5ABa0,83574
7
7
  fh_pydantic_form/form_parser.py,sha256=DsOMiCXRRHswL37Vqv7bvMv3Q7nQdXp0oP3_ZoIJfVc,26172
8
- fh_pydantic_form/form_renderer.py,sha256=0OrOA0--KT1r-HX37AGeDkC2rmbUSbJ4fgL04wG7pI8,36224
8
+ fh_pydantic_form/form_renderer.py,sha256=G_pO3rQrv3lCpiqfZfPkT96duDLp466CWU1PTOy9U4E,36619
9
9
  fh_pydantic_form/list_path.py,sha256=AA8bmDmaYy4rlGIvQOOZ0fP2tgcimNUB2Re5aVGnYc8,5182
10
10
  fh_pydantic_form/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  fh_pydantic_form/registry.py,sha256=b5zIjOpfmCUCs2njgp4PdDu70ioDIAfl49oU-Nf2pg4,4810
12
12
  fh_pydantic_form/type_helpers.py,sha256=JUzHT8YrWj2_g7f_Wr2GL9i3BgP1zZftFrrO8xDPeis,7409
13
13
  fh_pydantic_form/ui_style.py,sha256=UPK5OBwUVVTLnfvQ-yKukz2vbKZaT_GauaNB7OGc-Uw,3848
14
- fh_pydantic_form-0.3.4.dist-info/METADATA,sha256=gKrPm2PdFdKvU2d3WvyjoioH-LRe3K0Nj4SXGa7vlhs,38420
15
- fh_pydantic_form-0.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- fh_pydantic_form-0.3.4.dist-info/licenses/LICENSE,sha256=AOi2eNK3D2aDycRHfPRiuACZ7WPBsKHTV2tTYNl7cls,577
17
- fh_pydantic_form-0.3.4.dist-info/RECORD,,
14
+ fh_pydantic_form-0.3.6.dist-info/METADATA,sha256=cdQwkkDEOFsqTvZVbGTIZjhgRk_vx4PK0MKtds79oYU,38420
15
+ fh_pydantic_form-0.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ fh_pydantic_form-0.3.6.dist-info/licenses/LICENSE,sha256=AOi2eNK3D2aDycRHfPRiuACZ7WPBsKHTV2tTYNl7cls,577
17
+ fh_pydantic_form-0.3.6.dist-info/RECORD,,