fh-pydantic-form 0.3.3__py3-none-any.whl → 0.3.4__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.

@@ -87,13 +87,16 @@ def register_default_renderers() -> None:
87
87
 
88
88
  # Register list renderer for List[*] types
89
89
  def is_list_field(field_info):
90
- """Check if field is a list type"""
90
+ """Check if field is a list type, including Optional[List[...]]"""
91
91
  annotation = getattr(field_info, "annotation", None)
92
- return (
93
- annotation is not None
94
- and hasattr(annotation, "__origin__")
95
- and annotation.__origin__ is list
96
- )
92
+ if annotation is None:
93
+ return False
94
+
95
+ # Handle Optional[List[...]] by unwrapping the Optional
96
+ underlying_type = _get_underlying_type_if_optional(annotation)
97
+
98
+ # Check if the underlying type is a list
99
+ return get_origin(underlying_type) is list
97
100
 
98
101
  FieldRendererRegistry.register_type_renderer_with_predicate(
99
102
  is_list_field, ListFieldRenderer
@@ -1523,12 +1523,15 @@ class ListFieldRenderer(BaseFieldRenderer):
1523
1523
  annotation = getattr(self.field_info, "annotation", None)
1524
1524
  item_type = None # Initialize here to avoid UnboundLocalError
1525
1525
 
1526
+ # Handle Optional[List[...]] by unwrapping the Optional first
1527
+ base_annotation = _get_underlying_type_if_optional(annotation)
1528
+
1526
1529
  if (
1527
- annotation is not None
1528
- and hasattr(annotation, "__origin__")
1529
- and annotation.__origin__ is list
1530
+ base_annotation is not None
1531
+ and hasattr(base_annotation, "__origin__")
1532
+ and base_annotation.__origin__ is list
1530
1533
  ):
1531
- item_type = annotation.__args__[0]
1534
+ item_type = base_annotation.__args__[0]
1532
1535
 
1533
1536
  if not item_type:
1534
1537
  logger.error(f"Cannot determine item type for list field {self.field_name}")
@@ -1600,10 +1603,20 @@ class ListFieldRenderer(BaseFieldRenderer):
1600
1603
  if self.disabled:
1601
1604
  add_button_attrs["disabled"] = "true"
1602
1605
 
1606
+ # Differentiate message for Optional[List] vs required List
1607
+ if self.is_optional:
1608
+ empty_message = (
1609
+ "No items in this optional list. Click 'Add Item' if needed."
1610
+ )
1611
+ else:
1612
+ empty_message = (
1613
+ "No items in this required list. Click 'Add Item' to create one."
1614
+ )
1615
+
1603
1616
  empty_state = mui.Alert(
1604
1617
  fh.Div(
1605
1618
  mui.UkIcon("info", cls="mr-2"),
1606
- "No items in this list. Click 'Add Item' to create one.",
1619
+ empty_message,
1607
1620
  mui.Button("Add Item", **add_button_attrs),
1608
1621
  cls="flex flex-col items-start",
1609
1622
  ),
@@ -7,8 +7,8 @@ from typing import (
7
7
  Optional,
8
8
  Tuple,
9
9
  Union,
10
- get_origin,
11
10
  get_args,
11
+ get_origin,
12
12
  )
13
13
 
14
14
  from fh_pydantic_form.type_helpers import (
@@ -438,7 +438,7 @@ def _parse_list_fields(
438
438
  list_field_defs: Dict[str, Dict[str, Any]],
439
439
  base_prefix: str = "",
440
440
  exclude_fields: Optional[List[str]] = None,
441
- ) -> Dict[str, List[Any]]:
441
+ ) -> Dict[str, Optional[List[Any]]]:
442
442
  """
443
443
  Parse list fields from form data by analyzing keys and reconstructing ordered lists.
444
444
 
@@ -490,7 +490,7 @@ def _parse_list_fields(
490
490
  list_items_temp[field_name][idx_str][subfield] = value
491
491
 
492
492
  # Build final lists based on tracked order
493
- final_lists = {}
493
+ final_lists: Dict[str, Optional[List[Any]]] = {}
494
494
  for field_name, ordered_indices in list_item_indices_ordered.items():
495
495
  field_def = list_field_defs[field_name]
496
496
  item_type = field_def["item_type"]
@@ -544,8 +544,12 @@ def _parse_list_fields(
544
544
  if field_name in final_lists:
545
545
  continue
546
546
 
547
- # User submitted form with zero items → honour intent with empty list
548
- final_lists[field_name] = []
547
+ # User submitted form with zero items → honour intent with None for Optional[List]
548
+ field_info = field_def["field_info"]
549
+ if _is_optional_type(field_info.annotation):
550
+ final_lists[field_name] = None # Use None for empty Optional[List]
551
+ else:
552
+ final_lists[field_name] = [] # Regular empty list for required fields
549
553
 
550
554
  return final_lists
551
555
 
@@ -435,7 +435,7 @@ class PydanticForm(Generic[ModelType]):
435
435
  try:
436
436
  default_factory = field_info.default_factory
437
437
  if callable(default_factory):
438
- initial_value = default_factory()
438
+ initial_value = default_factory() # type: ignore[call-arg]
439
439
  else:
440
440
  initial_value = None
441
441
  logger.warning(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fh-pydantic-form
3
- Version: 0.3.3
3
+ Version: 0.3.4
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=uNDN6UXIM25U7NazFi0Y9ivAeA8plERrRBk7TOd6P6M,4313
1
+ fh_pydantic_form/__init__.py,sha256=rFuRyXVlitbFQfR4BxRLZR_CxCMJwE_QbQyt1h_4568,4479
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
5
  fh_pydantic_form/defaults.py,sha256=Pwv46v7e43cykx4Pt01e4nw-6FBkHmPvTZK36ZTZqgA,6068
6
- fh_pydantic_form/field_renderers.py,sha256=wX8XhesFH7Pt8l0stYR4FVQciVo2GBxADGnvwofu6YU,80944
7
- fh_pydantic_form/form_parser.py,sha256=7GTOBNQSfJltDHZnM12FxTJj0X_IMWoDV3lJbDF3EpY,25879
8
- fh_pydantic_form/form_renderer.py,sha256=3v4NPFQJ37m__kNo-sbNqaItR2AvcDzn5KRt44qCBTo,36198
6
+ fh_pydantic_form/field_renderers.py,sha256=yJuV1bMVhL_7o1tZDyw20DJzy3-OQbq_Ygc_tHpsLsU,81459
7
+ fh_pydantic_form/form_parser.py,sha256=DsOMiCXRRHswL37Vqv7bvMv3Q7nQdXp0oP3_ZoIJfVc,26172
8
+ fh_pydantic_form/form_renderer.py,sha256=0OrOA0--KT1r-HX37AGeDkC2rmbUSbJ4fgL04wG7pI8,36224
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.3.dist-info/METADATA,sha256=oLRfQeZIxBjls-FwIN8yXgcJOLDJmw-UXLeeo6R7kmg,38420
15
- fh_pydantic_form-0.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- fh_pydantic_form-0.3.3.dist-info/licenses/LICENSE,sha256=AOi2eNK3D2aDycRHfPRiuACZ7WPBsKHTV2tTYNl7cls,577
17
- fh_pydantic_form-0.3.3.dist-info/RECORD,,
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,,