Skip to content

Commit a19d7bf

Browse files
authored
Fix description for array-like fields (#184)
1 parent 1f944be commit a19d7bf

File tree

2 files changed

+56
-3
lines changed

2 files changed

+56
-3
lines changed

src/python-fastui/fastui/json_schema.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def json_schema_array_to_fields(
210210
items_schema = schema.get('items')
211211
if items_schema:
212212
items_schema, required = deference_json_schema(items_schema, defs, required)
213-
for field_name in 'search_url', 'placeholder':
213+
for field_name in 'search_url', 'placeholder', 'description':
214214
if value := schema.get(field_name):
215215
items_schema[field_name] = value # type: ignore
216216
if field := special_string_field(items_schema, loc_to_name(loc), title, required, True):
@@ -312,7 +312,7 @@ def deference_json_schema(
312312
if def_schema is None:
313313
raise ValueError(f'Invalid $ref "{ref}", not found in {defs}')
314314
else:
315-
return def_schema, required
315+
return def_schema.copy(), required # clone dict to avoid attribute leakage via shared schema.
316316
elif any_of := schema.get('anyOf'):
317317
if len(any_of) == 2 and sum(s.get('type') == 'null' for s in any_of) == 1:
318318
# If anyOf is a single type and null, then it is optional

src/python-fastui/tests/test_forms.py

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import enum
12
from contextlib import asynccontextmanager
23
from io import BytesIO
34
from typing import List, Tuple, Union
@@ -6,7 +7,7 @@
67
from fastapi import HTTPException
78
from fastui import components
89
from fastui.forms import FormFile, Textarea, fastui_form
9-
from pydantic import BaseModel
10+
from pydantic import BaseModel, Field
1011
from starlette.datastructures import FormData, Headers, UploadFile
1112
from typing_extensions import Annotated
1213

@@ -469,3 +470,55 @@ def test_form_textarea_form_fields():
469470
}
470471
],
471472
}
473+
474+
475+
class SelectEnum(str, enum.Enum):
476+
one = 'one'
477+
two = 'two'
478+
479+
480+
class FormSelectMultiple(BaseModel):
481+
select_single: SelectEnum = Field(title='Select Single', description='first field')
482+
select_single_2: SelectEnum = Field(title='Select Single') # unset description to test leakage from prev. field
483+
select_multiple: List[SelectEnum] = Field(title='Select Multiple', description='third field')
484+
485+
486+
def test_form_description_leakage():
487+
m = components.ModelForm(model=FormSelectMultiple, submit_url='/foobar/')
488+
489+
assert m.model_dump(by_alias=True, exclude_none=True) == {
490+
'formFields': [
491+
{
492+
'description': 'first field',
493+
'locked': False,
494+
'multiple': False,
495+
'name': 'select_single',
496+
'options': [{'label': 'One', 'value': 'one'}, {'label': 'Two', 'value': 'two'}],
497+
'required': True,
498+
'title': ['Select Single'],
499+
'type': 'FormFieldSelect',
500+
},
501+
{
502+
'locked': False,
503+
'multiple': False,
504+
'name': 'select_single_2',
505+
'options': [{'label': 'One', 'value': 'one'}, {'label': 'Two', 'value': 'two'}],
506+
'required': True,
507+
'title': ['Select Single'],
508+
'type': 'FormFieldSelect',
509+
},
510+
{
511+
'description': 'third field',
512+
'locked': False,
513+
'multiple': True,
514+
'name': 'select_multiple',
515+
'options': [{'label': 'One', 'value': 'one'}, {'label': 'Two', 'value': 'two'}],
516+
'required': True,
517+
'title': ['Select Multiple'],
518+
'type': 'FormFieldSelect',
519+
},
520+
],
521+
'method': 'POST',
522+
'submitUrl': '/foobar/',
523+
'type': 'ModelForm',
524+
}

0 commit comments

Comments
 (0)