Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fixing selects
Browse files Browse the repository at this point in the history
samuelcolvin committed Nov 17, 2023

Verified

This commit was signed with the committer’s verified signature.
samuelcolvin Samuel Colvin
1 parent 44def84 commit 8fd4dce
Showing 4 changed files with 48 additions and 12 deletions.
4 changes: 2 additions & 2 deletions demo/server.py
Original file line number Diff line number Diff line change
@@ -80,8 +80,8 @@ class ToolEnum(StrEnum):

class MyFormModel(BaseModel):
name: str = Field(default='foobar', title='Name')
tool: ToolEnum
task: Literal['build', 'destroy']
tool: ToolEnum = ToolEnum.saw
task: Literal['build', 'destroy'] | None = None
# dob: date = Field(title='Date of Birth', description='Your date of birth')
# weight: typing.Annotated[int, annotated_types.Gt(0)]
# size: PositiveInt = None
8 changes: 7 additions & 1 deletion python/fastui/form_extract.py
Original file line number Diff line number Diff line change
@@ -49,10 +49,16 @@ class FormResponse(pydantic.BaseModel):

def unflatten(form_data: datastructures.FormData) -> NestedDict:
"""
Unflatten a form data dict into a nested dict.
Unflatten a `FormData` dict into a nested dict.
Also omit empty strings, this might be a bit controversial, but it helps in many scenarios, e.g. a select
which hasn't been updated. It also avoids empty values for string inputs that haven't been fill in.
"""
result_dict: NestedDict = {}
for key, value in form_data.items():
if value == '':
continue

d: dict[str | int, typing.Any] = result_dict

*path, last_key = name_to_loc(key)
47 changes: 38 additions & 9 deletions python/fastui/json_schema.py
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ def model_json_schema_to_fields(model: type[BaseModel]) -> list[FormField]:
JsonSchemaInput: TypeAlias = 'JsonSchemaString | JsonSchemaInt | JsonSchemaNumber'
JsonSchemaField: TypeAlias = 'JsonSchemaInput | JsonSchemaBool'
JsonSchemaConcrete: TypeAlias = 'JsonSchemaField | JsonSchemaArray | JsonSchemaObject'
JsonSchemaAny: TypeAlias = 'JsonSchemaConcrete | JsonSchemaRef'
JsonSchemaAny: TypeAlias = 'JsonSchemaConcrete | JsonSchemaAnyOf | JsonSchemaAllOf | JsonSchemaRef'


class JsonSchemaBase(TypedDict, total=False):
@@ -69,10 +69,7 @@ class JsonSchemaArray(JsonSchemaBase, total=False):
items: JsonSchemaAny


JsonSchemaRef = TypedDict('JsonSchemaRef', {'$ref': str})

JsonSchemaDefs = dict[str, JsonSchemaConcrete]

JsonSchemaObject = TypedDict(
'JsonSchemaObject',
{
@@ -86,6 +83,21 @@ class JsonSchemaArray(JsonSchemaBase, total=False):
total=False,
)


class JsonSchemaNull(JsonSchemaBase):
type: Literal['null']


class JsonSchemaAnyOf(JsonSchemaBase):
anyOf: list[JsonSchemaAny]


class JsonSchemaAllOf(JsonSchemaBase):
allOf: list[JsonSchemaAny]


JsonSchemaRef = TypedDict('JsonSchemaRef', {'$ref': str})

SchemeLocation = list[str | int]


@@ -101,7 +113,7 @@ def json_schema_obj_to_fields(
def json_schema_any_to_fields(
schema: JsonSchemaAny, loc: SchemeLocation, title: list[str], required: bool, defs: JsonSchemaDefs
) -> Iterable[FormField]:
schema = deference_json_schema(schema, defs)
schema, required = deference_json_schema(schema, defs, required)
if schema_is_field(schema):
yield json_schema_field_to_field(schema, loc, title, required)
return
@@ -174,19 +186,36 @@ def loc_to_name(loc: SchemeLocation) -> str:
return '.'.join(str(v) for v in loc)


def deference_json_schema(schema: JsonSchemaAny, defs: JsonSchemaDefs) -> JsonSchemaConcrete:
def deference_json_schema(
schema: JsonSchemaAny, defs: JsonSchemaDefs, required: bool
) -> tuple[JsonSchemaConcrete, bool]:
"""
Convert a schema which might be a reference to a concrete schema.
Convert a schema which might be a reference or union to a concrete schema.
"""
if ref := schema.get('$ref'):
defs = defs or {}
def_schema = defs[ref.rsplit('/')[-1]]
if def_schema is None:
raise ValueError(f'Invalid $ref "{ref}", not found in {defs}')
else:
return def_schema
return def_schema, required
elif any_of := schema.get('anyOf'):
if len(any_of) == 2 and sum(s.get('type') == 'null' for s in any_of) == 1:
# If anyOf is a single type and null, then it is optional
not_null_schema = next(s for s in any_of if s.get('type') != 'null')
return deference_json_schema(not_null_schema, defs, False)
else:
raise NotImplementedError('`anyOf` schemas which are not simply `X | None` are not yet supported')
elif all_of := schema.get('allOf'):
all_of = cast(list[JsonSchemaAny], all_of)
if len(all_of) == 1:
new_schema, required = deference_json_schema(all_of[0], defs, required)
new_schema.update({k: v for k, v in schema.items() if k != 'allOf'}) # type: ignore
return new_schema, required
else:
raise NotImplementedError('`allOf` schemas with more than 1 choice are not yet supported')
else:
return cast(JsonSchemaConcrete, schema)
return cast(JsonSchemaConcrete, schema), required


def as_title(s: typing.Any) -> str:
1 change: 1 addition & 0 deletions react/fastui/components/FormField.tsx
Original file line number Diff line number Diff line change
@@ -82,6 +82,7 @@ export const FormFieldSelectComp: FC<FormFieldSelectProps> = (props) => {
required={required}
disabled={locked}
>
<option></option>
{choices.map(([value, label]) => (
<option key={value} value={value}>
{label}

0 comments on commit 8fd4dce

Please sign in to comment.