fh-pydantic-form 0.2.3__py3-none-any.whl → 0.2.5__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.
- fh_pydantic_form/constants.py +12 -0
- fh_pydantic_form/defaults.py +24 -2
- fh_pydantic_form/field_renderers.py +1 -1
- fh_pydantic_form/form_parser.py +71 -31
- fh_pydantic_form/form_renderer.py +25 -41
- fh_pydantic_form/type_helpers.py +79 -13
- {fh_pydantic_form-0.2.3.dist-info → fh_pydantic_form-0.2.5.dist-info}/METADATA +1 -1
- fh_pydantic_form-0.2.5.dist-info/RECORD +15 -0
- fh_pydantic_form-0.2.3.dist-info/RECORD +0 -14
- {fh_pydantic_form-0.2.3.dist-info → fh_pydantic_form-0.2.5.dist-info}/WHEEL +0 -0
- {fh_pydantic_form-0.2.3.dist-info → fh_pydantic_form-0.2.5.dist-info}/licenses/LICENSE +0 -0
fh_pydantic_form/defaults.py
CHANGED
|
@@ -2,11 +2,16 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import datetime as _dt
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from typing import Any, get_args, get_origin
|
|
5
|
+
from typing import Any, Literal, get_args, get_origin
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel
|
|
8
8
|
|
|
9
|
-
from .
|
|
9
|
+
from fh_pydantic_form.constants import _UNSET
|
|
10
|
+
from fh_pydantic_form.type_helpers import (
|
|
11
|
+
_is_optional_type,
|
|
12
|
+
_is_skip_json_schema_field,
|
|
13
|
+
get_default,
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
def _today():
|
|
@@ -122,6 +127,23 @@ def default_dict_for_model(model_cls: type[BaseModel]) -> dict[str, Any]:
|
|
|
122
127
|
continue
|
|
123
128
|
# --------------------------------------------------------------------
|
|
124
129
|
|
|
130
|
+
# Check if this is a SkipJsonSchema field - if so, always get its default
|
|
131
|
+
if _is_skip_json_schema_field(field):
|
|
132
|
+
default_val = get_default(field)
|
|
133
|
+
if default_val is not _UNSET:
|
|
134
|
+
# Handle BaseModel defaults by converting to dict
|
|
135
|
+
if hasattr(default_val, "model_dump"):
|
|
136
|
+
out[name] = default_val.model_dump()
|
|
137
|
+
# Convert enum instances to their values
|
|
138
|
+
elif isinstance(default_val, Enum):
|
|
139
|
+
out[name] = default_val.value
|
|
140
|
+
else:
|
|
141
|
+
out[name] = default_val
|
|
142
|
+
else:
|
|
143
|
+
# No default for SkipJsonSchema field - use smart default
|
|
144
|
+
out[name] = default_for_annotation(field.annotation)
|
|
145
|
+
continue
|
|
146
|
+
|
|
125
147
|
# 1. Check for model-supplied default or factory
|
|
126
148
|
default_val = get_default(field) # returns _UNSET if no default
|
|
127
149
|
if default_val is not _UNSET:
|
|
@@ -15,9 +15,9 @@ from fastcore.xml import FT
|
|
|
15
15
|
from pydantic import ValidationError
|
|
16
16
|
from pydantic.fields import FieldInfo
|
|
17
17
|
|
|
18
|
+
from fh_pydantic_form.constants import _UNSET
|
|
18
19
|
from fh_pydantic_form.registry import FieldRendererRegistry
|
|
19
20
|
from fh_pydantic_form.type_helpers import (
|
|
20
|
-
_UNSET,
|
|
21
21
|
_get_underlying_type_if_optional,
|
|
22
22
|
_is_optional_type,
|
|
23
23
|
get_default,
|
fh_pydantic_form/form_parser.py
CHANGED
|
@@ -7,6 +7,8 @@ from typing import (
|
|
|
7
7
|
Optional,
|
|
8
8
|
Tuple,
|
|
9
9
|
Union,
|
|
10
|
+
get_origin,
|
|
11
|
+
get_args,
|
|
10
12
|
)
|
|
11
13
|
|
|
12
14
|
from fh_pydantic_form.type_helpers import (
|
|
@@ -14,6 +16,7 @@ from fh_pydantic_form.type_helpers import (
|
|
|
14
16
|
_is_enum_type,
|
|
15
17
|
_is_literal_type,
|
|
16
18
|
_is_optional_type,
|
|
19
|
+
_is_skip_json_schema_field,
|
|
17
20
|
)
|
|
18
21
|
|
|
19
22
|
logger = logging.getLogger(__name__)
|
|
@@ -32,17 +35,16 @@ def _identify_list_fields(model_class) -> Dict[str, Dict[str, Any]]:
|
|
|
32
35
|
list_fields = {}
|
|
33
36
|
for field_name, field_info in model_class.model_fields.items():
|
|
34
37
|
annotation = getattr(field_info, "annotation", None)
|
|
35
|
-
if
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
38
|
+
if annotation is not None:
|
|
39
|
+
# Handle Optional[List[...]] by unwrapping the Optional
|
|
40
|
+
base_ann = _get_underlying_type_if_optional(annotation)
|
|
41
|
+
if get_origin(base_ann) is list:
|
|
42
|
+
item_type = get_args(base_ann)[0]
|
|
43
|
+
list_fields[field_name] = {
|
|
44
|
+
"item_type": item_type,
|
|
45
|
+
"is_model_type": hasattr(item_type, "model_fields"),
|
|
46
|
+
"field_info": field_info, # Store for later use if needed
|
|
47
|
+
}
|
|
46
48
|
return list_fields
|
|
47
49
|
|
|
48
50
|
|
|
@@ -77,6 +79,11 @@ def _parse_non_list_fields(
|
|
|
77
79
|
if field_name in exclude_fields:
|
|
78
80
|
continue
|
|
79
81
|
|
|
82
|
+
# Skip SkipJsonSchema fields - they should not be parsed from form data
|
|
83
|
+
if _is_skip_json_schema_field(field_info):
|
|
84
|
+
logger.debug(f"Skipping SkipJsonSchema field during parsing: {field_name}")
|
|
85
|
+
continue
|
|
86
|
+
|
|
80
87
|
# Create full key with prefix
|
|
81
88
|
full_key = f"{base_prefix}{field_name}"
|
|
82
89
|
|
|
@@ -91,11 +98,21 @@ def _parse_non_list_fields(
|
|
|
91
98
|
|
|
92
99
|
# Handle Literal fields (including Optional[Literal[...]])
|
|
93
100
|
elif _is_literal_type(annotation):
|
|
94
|
-
|
|
101
|
+
if full_key in form_data: # User sent it
|
|
102
|
+
result[field_name] = _parse_literal_field(
|
|
103
|
+
full_key, form_data, field_info
|
|
104
|
+
)
|
|
105
|
+
elif _is_optional_type(annotation): # Optional but omitted
|
|
106
|
+
result[field_name] = None
|
|
107
|
+
# otherwise leave the key out – defaults will be injected later
|
|
95
108
|
|
|
96
109
|
# Handle Enum fields (including Optional[Enum])
|
|
97
110
|
elif _is_enum_type(annotation):
|
|
98
|
-
|
|
111
|
+
if full_key in form_data: # User sent it
|
|
112
|
+
result[field_name] = _parse_enum_field(full_key, form_data, field_info)
|
|
113
|
+
elif _is_optional_type(annotation): # Optional but omitted
|
|
114
|
+
result[field_name] = None
|
|
115
|
+
# otherwise leave the key out – defaults will be injected later
|
|
99
116
|
|
|
100
117
|
# Handle nested model fields (including Optional[NestedModel])
|
|
101
118
|
elif (
|
|
@@ -112,9 +129,14 @@ def _parse_non_list_fields(
|
|
|
112
129
|
# Get the nested model class (unwrap Optional if needed)
|
|
113
130
|
nested_model_class = _get_underlying_type_if_optional(annotation)
|
|
114
131
|
|
|
115
|
-
# Parse the nested model - pass the base_prefix
|
|
132
|
+
# Parse the nested model - pass the base_prefix and exclude_fields
|
|
116
133
|
nested_value = _parse_nested_model_field(
|
|
117
|
-
field_name,
|
|
134
|
+
field_name,
|
|
135
|
+
form_data,
|
|
136
|
+
nested_model_class,
|
|
137
|
+
field_info,
|
|
138
|
+
base_prefix,
|
|
139
|
+
exclude_fields,
|
|
118
140
|
)
|
|
119
141
|
|
|
120
142
|
# Only assign if we got a non-None value or the field is not optional
|
|
@@ -126,8 +148,13 @@ def _parse_non_list_fields(
|
|
|
126
148
|
|
|
127
149
|
# Handle simple fields
|
|
128
150
|
else:
|
|
129
|
-
|
|
130
|
-
|
|
151
|
+
if full_key in form_data: # User sent it
|
|
152
|
+
result[field_name] = _parse_simple_field(
|
|
153
|
+
full_key, form_data, field_info
|
|
154
|
+
)
|
|
155
|
+
elif _is_optional_type(annotation): # Optional but omitted
|
|
156
|
+
result[field_name] = None
|
|
157
|
+
# otherwise leave the key out – defaults will be injected later
|
|
131
158
|
|
|
132
159
|
return result
|
|
133
160
|
|
|
@@ -249,6 +276,7 @@ def _parse_nested_model_field(
|
|
|
249
276
|
nested_model_class,
|
|
250
277
|
field_info,
|
|
251
278
|
parent_prefix: str = "",
|
|
279
|
+
exclude_fields: Optional[List[str]] = None,
|
|
252
280
|
) -> Optional[Dict[str, Any]]:
|
|
253
281
|
"""
|
|
254
282
|
Parse a nested Pydantic model field from form data.
|
|
@@ -282,6 +310,13 @@ def _parse_nested_model_field(
|
|
|
282
310
|
sub_key = f"{current_prefix}{sub_field_name}"
|
|
283
311
|
annotation = getattr(sub_field_info, "annotation", None)
|
|
284
312
|
|
|
313
|
+
# Skip SkipJsonSchema fields - they should not be parsed from form data
|
|
314
|
+
if _is_skip_json_schema_field(sub_field_info):
|
|
315
|
+
logger.debug(
|
|
316
|
+
f"Skipping SkipJsonSchema field in nested model during parsing: {sub_field_name}"
|
|
317
|
+
)
|
|
318
|
+
continue
|
|
319
|
+
|
|
285
320
|
# Handle based on field type, with Optional unwrapping
|
|
286
321
|
is_optional = _is_optional_type(annotation)
|
|
287
322
|
base_type = _get_underlying_type_if_optional(annotation)
|
|
@@ -323,6 +358,7 @@ def _parse_nested_model_field(
|
|
|
323
358
|
form_data,
|
|
324
359
|
nested_list_defs,
|
|
325
360
|
current_prefix, # ← prefix for this nested model
|
|
361
|
+
exclude_fields, # Pass through exclude_fields
|
|
326
362
|
)
|
|
327
363
|
# Merge without clobbering keys already set in step 1
|
|
328
364
|
for lf_name, lf_val in list_results.items():
|
|
@@ -404,6 +440,7 @@ def _parse_list_fields(
|
|
|
404
440
|
form_data: Dict[str, Any],
|
|
405
441
|
list_field_defs: Dict[str, Dict[str, Any]],
|
|
406
442
|
base_prefix: str = "",
|
|
443
|
+
exclude_fields: Optional[List[str]] = None,
|
|
407
444
|
) -> Dict[str, List[Any]]:
|
|
408
445
|
"""
|
|
409
446
|
Parse list fields from form data by analyzing keys and reconstructing ordered lists.
|
|
@@ -412,10 +449,13 @@ def _parse_list_fields(
|
|
|
412
449
|
form_data: Dictionary containing form field data
|
|
413
450
|
list_field_defs: Dictionary of list field definitions
|
|
414
451
|
base_prefix: Prefix to use when looking up field names in form_data
|
|
452
|
+
exclude_fields: Optional list of field names to exclude from parsing
|
|
415
453
|
|
|
416
454
|
Returns:
|
|
417
455
|
Dictionary with parsed list fields
|
|
418
456
|
"""
|
|
457
|
+
exclude_fields = exclude_fields or []
|
|
458
|
+
|
|
419
459
|
# Skip if no list fields defined
|
|
420
460
|
if not list_field_defs:
|
|
421
461
|
return {}
|
|
@@ -497,20 +537,18 @@ def _parse_list_fields(
|
|
|
497
537
|
if items: # Only add if items were found
|
|
498
538
|
final_lists[field_name] = items
|
|
499
539
|
|
|
500
|
-
#
|
|
540
|
+
# Ensure every rendered list field appears in final_lists
|
|
501
541
|
for field_name, field_def in list_field_defs.items():
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
except Exception:
|
|
513
|
-
pass
|
|
542
|
+
# Skip list fields the UI never showed (those in exclude_fields)
|
|
543
|
+
if field_name in exclude_fields:
|
|
544
|
+
continue
|
|
545
|
+
|
|
546
|
+
# When user supplied ≥1 item we already captured it
|
|
547
|
+
if field_name in final_lists:
|
|
548
|
+
continue
|
|
549
|
+
|
|
550
|
+
# User submitted form with zero items → honour intent with empty list
|
|
551
|
+
final_lists[field_name] = []
|
|
514
552
|
|
|
515
553
|
return final_lists
|
|
516
554
|
|
|
@@ -543,7 +581,9 @@ def _parse_model_list_item(
|
|
|
543
581
|
)
|
|
544
582
|
# 2. Parse inner lists
|
|
545
583
|
result.update(
|
|
546
|
-
_parse_list_fields(
|
|
584
|
+
_parse_list_fields(
|
|
585
|
+
form_data, nested_list_defs, base_prefix=item_prefix, exclude_fields=[]
|
|
586
|
+
)
|
|
547
587
|
)
|
|
548
588
|
return result
|
|
549
589
|
|
|
@@ -17,6 +17,7 @@ import monsterui.all as mui
|
|
|
17
17
|
from fastcore.xml import FT
|
|
18
18
|
from pydantic import BaseModel
|
|
19
19
|
|
|
20
|
+
from fh_pydantic_form.constants import _UNSET
|
|
20
21
|
from fh_pydantic_form.defaults import default_dict_for_model, default_for_annotation
|
|
21
22
|
from fh_pydantic_form.field_renderers import (
|
|
22
23
|
BaseFieldRenderer,
|
|
@@ -30,7 +31,7 @@ from fh_pydantic_form.form_parser import (
|
|
|
30
31
|
)
|
|
31
32
|
from fh_pydantic_form.list_path import walk_path
|
|
32
33
|
from fh_pydantic_form.registry import FieldRendererRegistry
|
|
33
|
-
from fh_pydantic_form.type_helpers import
|
|
34
|
+
from fh_pydantic_form.type_helpers import _is_skip_json_schema_field, get_default
|
|
34
35
|
from fh_pydantic_form.ui_style import (
|
|
35
36
|
SpacingTheme,
|
|
36
37
|
SpacingValue,
|
|
@@ -344,6 +345,11 @@ class PydanticForm(Generic[ModelType]):
|
|
|
344
345
|
logger.debug(f"Skipping excluded field: {field_name}")
|
|
345
346
|
continue
|
|
346
347
|
|
|
348
|
+
# Skip SkipJsonSchema fields (they should not be rendered in the form)
|
|
349
|
+
if _is_skip_json_schema_field(field_info):
|
|
350
|
+
logger.debug(f"Skipping SkipJsonSchema field: {field_name}")
|
|
351
|
+
continue
|
|
352
|
+
|
|
347
353
|
# Only use what was explicitly provided in initial values
|
|
348
354
|
initial_value = (
|
|
349
355
|
self.values_dict.get(field_name) if self.values_dict else None
|
|
@@ -578,35 +584,30 @@ class PydanticForm(Generic[ModelType]):
|
|
|
578
584
|
# Merge list results into the main result
|
|
579
585
|
result.update(list_results)
|
|
580
586
|
|
|
581
|
-
# Inject defaults for
|
|
582
|
-
self.
|
|
587
|
+
# Inject defaults for missing fields before returning
|
|
588
|
+
self._inject_missing_defaults(result)
|
|
583
589
|
|
|
584
590
|
return result
|
|
585
591
|
|
|
586
|
-
def
|
|
587
|
-
self, data: Dict[str, Any]
|
|
588
|
-
) -> Dict[str, Any]:
|
|
592
|
+
def _inject_missing_defaults(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
589
593
|
"""
|
|
590
|
-
Ensures
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
Also ensures all model fields have appropriate defaults if missing.
|
|
594
|
+
Ensures all model fields with defaults are present in data if missing.
|
|
595
|
+
Handles excluded fields, SkipJsonSchema fields, and any other fields
|
|
596
|
+
not rendered in the form.
|
|
594
597
|
|
|
595
598
|
Priority order:
|
|
596
599
|
1. initial_values (if provided during form creation)
|
|
597
600
|
2. model defaults/default_factory
|
|
598
601
|
|
|
599
|
-
Operates top-level only (exclude_fields spec is top-level names).
|
|
600
|
-
|
|
601
602
|
Args:
|
|
602
603
|
data: Dictionary to modify in-place
|
|
603
604
|
|
|
604
605
|
Returns:
|
|
605
606
|
The same dictionary instance for method chaining
|
|
606
607
|
"""
|
|
607
|
-
#
|
|
608
|
-
for field_name in self.
|
|
609
|
-
# Skip if already present
|
|
608
|
+
# Process ALL model fields, not just excluded ones
|
|
609
|
+
for field_name, field_info in self.model_class.model_fields.items():
|
|
610
|
+
# Skip if already present in parsed data
|
|
610
611
|
if field_name in data:
|
|
611
612
|
continue
|
|
612
613
|
|
|
@@ -617,17 +618,10 @@ class PydanticForm(Generic[ModelType]):
|
|
|
617
618
|
if hasattr(initial_val, "model_dump"):
|
|
618
619
|
initial_val = initial_val.model_dump()
|
|
619
620
|
data[field_name] = initial_val
|
|
620
|
-
logger.debug(
|
|
621
|
-
f"Injected initial value for excluded field '{field_name}'"
|
|
622
|
-
)
|
|
621
|
+
logger.debug(f"Injected initial value for missing field '{field_name}'")
|
|
623
622
|
continue
|
|
624
623
|
|
|
625
624
|
# Second priority: use model defaults
|
|
626
|
-
field_info = self.model_class.model_fields.get(field_name)
|
|
627
|
-
if field_info is None:
|
|
628
|
-
logger.warning(f"exclude_fields contains unknown field '{field_name}'")
|
|
629
|
-
continue
|
|
630
|
-
|
|
631
625
|
default_val = get_default(field_info)
|
|
632
626
|
if default_val is not _UNSET:
|
|
633
627
|
# If the default is a BaseModel, convert to dict for consistency
|
|
@@ -635,27 +629,17 @@ class PydanticForm(Generic[ModelType]):
|
|
|
635
629
|
default_val = default_val.model_dump()
|
|
636
630
|
data[field_name] = default_val
|
|
637
631
|
logger.debug(
|
|
638
|
-
f"Injected model default value for
|
|
632
|
+
f"Injected model default value for missing field '{field_name}'"
|
|
639
633
|
)
|
|
640
634
|
else:
|
|
641
|
-
#
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if field_name not in data:
|
|
647
|
-
# Try to inject defaults for missing fields
|
|
648
|
-
if field_name in self.initial_values_dict:
|
|
649
|
-
initial_val = self.initial_values_dict[field_name]
|
|
650
|
-
if hasattr(initial_val, "model_dump"):
|
|
651
|
-
initial_val = initial_val.model_dump()
|
|
652
|
-
data[field_name] = initial_val
|
|
635
|
+
# Check if this is a SkipJsonSchema field
|
|
636
|
+
if _is_skip_json_schema_field(field_info):
|
|
637
|
+
logger.debug(
|
|
638
|
+
f"No default found for SkipJsonSchema field '{field_name}'"
|
|
639
|
+
)
|
|
653
640
|
else:
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if hasattr(default_val, "model_dump"):
|
|
657
|
-
default_val = default_val.model_dump()
|
|
658
|
-
data[field_name] = default_val
|
|
641
|
+
# No default → leave missing; validation will surface error
|
|
642
|
+
logger.debug(f"No default found for field '{field_name}'")
|
|
659
643
|
|
|
660
644
|
return data
|
|
661
645
|
|
fh_pydantic_form/type_helpers.py
CHANGED
|
@@ -1,12 +1,88 @@
|
|
|
1
|
+
# Explicit exports for public API
|
|
2
|
+
__all__ = [
|
|
3
|
+
"_is_optional_type",
|
|
4
|
+
"_get_underlying_type_if_optional",
|
|
5
|
+
"_is_literal_type",
|
|
6
|
+
"_is_enum_type",
|
|
7
|
+
"_is_skip_json_schema_field",
|
|
8
|
+
"default_for_annotation",
|
|
9
|
+
]
|
|
10
|
+
|
|
1
11
|
import logging
|
|
2
12
|
from enum import Enum
|
|
3
13
|
from types import UnionType
|
|
4
|
-
from typing import Any, Literal, Union, get_args, get_origin
|
|
14
|
+
from typing import Annotated, Any, Literal, Union, get_args, get_origin
|
|
15
|
+
|
|
16
|
+
from fh_pydantic_form.constants import _UNSET
|
|
5
17
|
|
|
6
18
|
logger = logging.getLogger(__name__)
|
|
7
19
|
|
|
8
|
-
|
|
9
|
-
|
|
20
|
+
|
|
21
|
+
def _is_skip_json_schema_field(annotation_or_field_info: Any) -> bool:
|
|
22
|
+
"""
|
|
23
|
+
Check if a field annotation or field_info indicates it should be skipped in JSON schema.
|
|
24
|
+
|
|
25
|
+
This handles the pattern where SkipJsonSchema is used with typing.Annotated:
|
|
26
|
+
- Annotated[str, SkipJsonSchema()]
|
|
27
|
+
- SkipJsonSchema[str] (which internally uses Annotated)
|
|
28
|
+
- Field metadata containing SkipJsonSchema (Pydantic 2 behavior)
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
annotation_or_field_info: The field annotation or field_info to check
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
True if the field should be skipped in JSON schema
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
from pydantic.json_schema import SkipJsonSchema
|
|
38
|
+
|
|
39
|
+
skip_json_schema_cls = SkipJsonSchema
|
|
40
|
+
except ImportError: # very old Pydantic
|
|
41
|
+
skip_json_schema_cls = None
|
|
42
|
+
|
|
43
|
+
if skip_json_schema_cls is None:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
# Check if it's a field_info object with metadata
|
|
47
|
+
if hasattr(annotation_or_field_info, "metadata"):
|
|
48
|
+
metadata = getattr(annotation_or_field_info, "metadata", [])
|
|
49
|
+
if metadata:
|
|
50
|
+
for item in metadata:
|
|
51
|
+
if (
|
|
52
|
+
item is skip_json_schema_cls
|
|
53
|
+
or isinstance(item, skip_json_schema_cls)
|
|
54
|
+
or (
|
|
55
|
+
hasattr(item, "__class__")
|
|
56
|
+
and item.__class__.__name__ == "SkipJsonSchema"
|
|
57
|
+
)
|
|
58
|
+
):
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
# Fall back to checking annotation (for backward compatibility)
|
|
62
|
+
annotation = annotation_or_field_info
|
|
63
|
+
if hasattr(annotation_or_field_info, "annotation"):
|
|
64
|
+
annotation = getattr(annotation_or_field_info, "annotation")
|
|
65
|
+
|
|
66
|
+
# 1. Direct or generic alias
|
|
67
|
+
if (
|
|
68
|
+
annotation is skip_json_schema_cls
|
|
69
|
+
or getattr(annotation, "__origin__", None) is skip_json_schema_cls
|
|
70
|
+
):
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
# 2. Something like Annotated[T, SkipJsonSchema()]
|
|
74
|
+
if get_origin(annotation) is Annotated:
|
|
75
|
+
for meta in get_args(annotation)[1:]:
|
|
76
|
+
meta_class = getattr(meta, "__class__", None)
|
|
77
|
+
if (
|
|
78
|
+
meta is skip_json_schema_cls # plain class
|
|
79
|
+
or isinstance(meta, skip_json_schema_cls) # instance
|
|
80
|
+
or (meta_class is not None and meta_class.__name__ == "SkipJsonSchema")
|
|
81
|
+
):
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
# 3. Fallback – cheap but effective
|
|
85
|
+
return "SkipJsonSchema" in repr(annotation)
|
|
10
86
|
|
|
11
87
|
|
|
12
88
|
def _is_optional_type(annotation: Any) -> bool:
|
|
@@ -27,16 +103,6 @@ def _is_optional_type(annotation: Any) -> bool:
|
|
|
27
103
|
return False
|
|
28
104
|
|
|
29
105
|
|
|
30
|
-
# Explicit exports for public API
|
|
31
|
-
__all__ = [
|
|
32
|
-
"_is_optional_type",
|
|
33
|
-
"_get_underlying_type_if_optional",
|
|
34
|
-
"_is_literal_type",
|
|
35
|
-
"_is_enum_type",
|
|
36
|
-
"default_for_annotation",
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
|
|
40
106
|
def _get_underlying_type_if_optional(annotation: Any) -> Any:
|
|
41
107
|
"""
|
|
42
108
|
Extract the type T from Optional[T], otherwise return the original annotation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fh-pydantic-form
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
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
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
fh_pydantic_form/__init__.py,sha256=luxohu6NgZDC0nhSIyw5lJGP2A8JQ51Ge1Ga7DYDkF8,4048
|
|
2
|
+
fh_pydantic_form/constants.py,sha256=-N9wzkibFNn-V6cO8iWTQ7_xBvwSr2hBdq-m3apmW4M,169
|
|
3
|
+
fh_pydantic_form/defaults.py,sha256=Pwv46v7e43cykx4Pt01e4nw-6FBkHmPvTZK36ZTZqgA,6068
|
|
4
|
+
fh_pydantic_form/field_renderers.py,sha256=VYvAmLsLhQttlg97g2KGg-VNlS4ohxrPN1O906EJM6I,54984
|
|
5
|
+
fh_pydantic_form/form_parser.py,sha256=mAsE5pE7A27k7zgHg4UD-P3HHQj9FmZneK_z68jLHbo,26117
|
|
6
|
+
fh_pydantic_form/form_renderer.py,sha256=cPd7NbaPOZC8cTvhEZOsy8sf5fH6FomrsR_r6KAFF54,34573
|
|
7
|
+
fh_pydantic_form/list_path.py,sha256=AA8bmDmaYy4rlGIvQOOZ0fP2tgcimNUB2Re5aVGnYc8,5182
|
|
8
|
+
fh_pydantic_form/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
fh_pydantic_form/registry.py,sha256=sufK-85ST3rc3Vu0XmjjjdTqTAqgHr_ZbMGU0xRgTK8,4996
|
|
10
|
+
fh_pydantic_form/type_helpers.py,sha256=FH4yl5FW1KNKvfHzs8TKQinFTC-MUgqDvRTVfPHs1LM,6815
|
|
11
|
+
fh_pydantic_form/ui_style.py,sha256=aIWDWbPBUAQ73nPC5AHZi5cnqA0SIp9ISWwsxFdXXdE,3776
|
|
12
|
+
fh_pydantic_form-0.2.5.dist-info/METADATA,sha256=0xo0GN6Vj50q9Qb4vQufOaqAs8Fn1w3a3h58elpBbqA,26356
|
|
13
|
+
fh_pydantic_form-0.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
fh_pydantic_form-0.2.5.dist-info/licenses/LICENSE,sha256=AOi2eNK3D2aDycRHfPRiuACZ7WPBsKHTV2tTYNl7cls,577
|
|
15
|
+
fh_pydantic_form-0.2.5.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
fh_pydantic_form/__init__.py,sha256=luxohu6NgZDC0nhSIyw5lJGP2A8JQ51Ge1Ga7DYDkF8,4048
|
|
2
|
-
fh_pydantic_form/defaults.py,sha256=IzBA_soBOdXP_XAUqfFAtniDQaW6N23hiXmWJD2xq0c,5168
|
|
3
|
-
fh_pydantic_form/field_renderers.py,sha256=uJRPoQhsjjRPlT-DGiodumUot0PIpifBrp0C_2OaTMo,54950
|
|
4
|
-
fh_pydantic_form/form_parser.py,sha256=9jSJya4TR5q2LMGV_PK-xiAjoEhq-FYKDN27lFNn5n0,24389
|
|
5
|
-
fh_pydantic_form/form_renderer.py,sha256=epBmdQtHpoazSe3uiG26inyI4XMp6w2I7-JC7F2rwoQ,35264
|
|
6
|
-
fh_pydantic_form/list_path.py,sha256=AA8bmDmaYy4rlGIvQOOZ0fP2tgcimNUB2Re5aVGnYc8,5182
|
|
7
|
-
fh_pydantic_form/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
fh_pydantic_form/registry.py,sha256=sufK-85ST3rc3Vu0XmjjjdTqTAqgHr_ZbMGU0xRgTK8,4996
|
|
9
|
-
fh_pydantic_form/type_helpers.py,sha256=bWHOxu52yh9_79d_x5L3cfMqnZo856OsbL4sTttDoa4,4367
|
|
10
|
-
fh_pydantic_form/ui_style.py,sha256=aIWDWbPBUAQ73nPC5AHZi5cnqA0SIp9ISWwsxFdXXdE,3776
|
|
11
|
-
fh_pydantic_form-0.2.3.dist-info/METADATA,sha256=lqqppd9Y4g48AfSDkqMLaFvqI7KzDVxGVz7RQARZjr0,26356
|
|
12
|
-
fh_pydantic_form-0.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
-
fh_pydantic_form-0.2.3.dist-info/licenses/LICENSE,sha256=AOi2eNK3D2aDycRHfPRiuACZ7WPBsKHTV2tTYNl7cls,577
|
|
14
|
-
fh_pydantic_form-0.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|