Skip to content

Commit

Permalink
More compat
Browse files Browse the repository at this point in the history
  • Loading branch information
Viicos committed Feb 11, 2025
1 parent 31d6c8b commit 70f2a1c
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 31 deletions.
9 changes: 6 additions & 3 deletions src/python-fastui/fastui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ def __get_pydantic_json_schema__(
) -> _t.Any:
# until https://github.com/pydantic/pydantic/issues/8413 is fixed
json_schema = handler(core_schema)
json_schema['required'].append('level')
schema_def = handler.resolve_ref_schema(json_schema)
schema_def['required'].append('level')
return json_schema


Expand Down Expand Up @@ -309,7 +310,8 @@ def __get_pydantic_json_schema__(
) -> _t.Any:
# until https://github.com/pydantic/pydantic/issues/8413 is fixed
json_schema = handler(core_schema)
json_schema.setdefault('required', []).extend(['startLinks', 'endLinks'])
schema_def = handler.resolve_ref_schema(json_schema)
schema_def.setdefault('required', []).extend(['startLinks', 'endLinks'])
return json_schema


Expand Down Expand Up @@ -523,7 +525,8 @@ def __get_pydantic_json_schema__(
) -> _t.Any:
# add `children` to the schema so it can be used in the client
json_schema = handler(core_schema)
json_schema['properties']['children'] = {'tsType': 'ReactNode'}
schema_def = handler.resolve_ref_schema(json_schema)
schema_def['properties']['children'] = {'tsType': 'ReactNode'}
return json_schema


Expand Down
41 changes: 21 additions & 20 deletions src/python-fastui/fastui/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,21 +160,22 @@ def json_schema_obj_to_fields(
def json_schema_any_to_fields(
schema: JsonSchemaAny, loc: SchemeLocation, title: _t.List[str], required: bool, defs: JsonSchemaDefs
) -> _t.Iterable[FormField]:
schema, required = deference_json_schema(schema, defs, required)
title = title + [schema.get('title') or loc_to_title(loc)]

if schema_is_field(schema):
yield json_schema_field_to_field(schema, loc, title, required)
elif schema_is_array(schema):
yield from json_schema_array_to_fields(schema, loc, title, required, defs)
dereferenced, required = deference_json_schema(schema, defs, required)
title = title + [schema.get('title', dereferenced.get('title', loc_to_title(loc)))]
description = schema.get('description', dereferenced.get('description'))

if schema_is_field(dereferenced):
yield json_schema_field_to_field(dereferenced, loc, title, description, required)
elif schema_is_array(dereferenced):
yield from json_schema_array_to_fields(dereferenced, loc, title, description, required, defs)
else:
assert schema_is_object(schema), f'Unexpected schema type {schema}'
assert schema_is_object(dereferenced), f'Unexpected schema type {dereferenced}'

yield from json_schema_obj_to_fields(schema, loc, title, defs)
yield from json_schema_obj_to_fields(dereferenced, loc, title, defs)


def json_schema_field_to_field(
schema: JsonSchemaField, loc: SchemeLocation, title: _t.List[str], required: bool
schema: JsonSchemaField, loc: SchemeLocation, title: _t.List[str], description: _t.Union[str, None], required: bool,
) -> FormField:
name = loc_to_name(loc)
if schema['type'] == 'boolean':
Expand All @@ -183,10 +184,10 @@ def json_schema_field_to_field(
title=title,
required=required,
initial=schema.get('default'),
description=schema.get('description'),
description=description,
mode=schema.get('mode', 'checkbox'),
)
elif field := special_string_field(schema, name, title, required, False):
elif field := special_string_field(schema, name, title, description, required, False):
return field
else:
return FormFieldInput(
Expand All @@ -206,15 +207,15 @@ def loc_to_title(loc: SchemeLocation) -> str:


def json_schema_array_to_fields(
schema: JsonSchemaArray, loc: SchemeLocation, title: _t.List[str], required: bool, defs: JsonSchemaDefs
schema: JsonSchemaArray, loc: SchemeLocation, title: _t.List[str], description: _t.Union[str, None], required: bool, defs: JsonSchemaDefs
) -> _t.Iterable[FormField]:
items_schema = schema.get('items')
if items_schema:
items_schema, required = deference_json_schema(items_schema, defs, required)
for field_name in 'search_url', 'placeholder', 'description':
for field_name in 'search_url', 'placeholder':
if value := schema.get(field_name):
items_schema[field_name] = value # type: ignore
if field := special_string_field(items_schema, loc_to_name(loc), title, required, True):
if field := special_string_field(items_schema, loc_to_name(loc), title, description, required, True):
yield field
return

Expand All @@ -236,7 +237,7 @@ def json_schema_array_to_fields(


def special_string_field(
schema: JsonSchemaConcrete, name: str, title: _t.List[str], required: bool, multiple: bool
schema: JsonSchemaConcrete, name: str, title: _t.List[str], description: _t.Union[str, None], required: bool, multiple: bool
) -> _t.Union[FormField, None]:
if schema['type'] == 'string':
if schema.get('format') == 'binary':
Expand All @@ -246,7 +247,7 @@ def special_string_field(
required=required,
multiple=multiple,
accept=schema.get('accept'),
description=schema.get('description'),
description=description,
)
elif schema.get('format') == 'textarea':
return FormFieldTextarea(
Expand All @@ -257,7 +258,7 @@ def special_string_field(
cols=schema.get('cols'),
placeholder=schema.get('placeholder'),
initial=schema.get('initial'),
description=schema.get('description'),
description=description,
autocomplete=schema.get('autocomplete'),
)
elif enum := schema.get('enum'):
Expand All @@ -270,7 +271,7 @@ def special_string_field(
multiple=multiple,
options=[SelectOption(value=v, label=enum_labels.get(v) or as_title(v)) for v in enum],
initial=schema.get('default'),
description=schema.get('description'),
description=description,
autocomplete=schema.get('autocomplete'),
)
elif search_url := schema.get('search_url'):
Expand All @@ -282,7 +283,7 @@ def special_string_field(
required=required,
multiple=multiple,
initial=schema.get('initial'),
description=schema.get('description'),
description=description,
)


Expand Down
9 changes: 4 additions & 5 deletions src/python-fastui/requirements/pyproject.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile --constraint=src/python-fastui/requirements/lint.txt --extra=fastapi --output-file=src/python-fastui/requirements/pyproject.txt --strip-extras src/python-fastui/pyproject.toml
Expand All @@ -18,20 +18,19 @@ idna==3.6
# via
# anyio
# email-validator
pydantic==2.6.1
pydantic==2.10.6
# via
# fastapi
# fastui (src/python-fastui/pyproject.toml)
# pydantic
pydantic-core==2.16.2
pydantic-core==2.27.2
# via pydantic
python-multipart==0.0.7
# via fastui (src/python-fastui/pyproject.toml)
sniffio==1.3.0
# via anyio
starlette==0.36.3
# via fastapi
typing-extensions==4.9.0
typing-extensions==4.12.2
# via
# fastapi
# pydantic
Expand Down
4 changes: 2 additions & 2 deletions src/python-fastui/tests/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
that's just testing pydantic!
"""
from fastui import FastUI, components
from pydantic_core import Url
from pydantic import HttpUrl


def test_div_text():
Expand Down Expand Up @@ -56,7 +56,7 @@ def test_root_model_single():
def test_iframe():
iframe = components.Iframe(src='https://www.example.com', srcdoc='<p>hello world</p>', sandbox='allow-scripts')
assert iframe.model_dump(by_alias=True, exclude_none=True) == {
'src': Url('https://www.example.com'),
'src': HttpUrl('https://www.example.com'),
'type': 'Iframe',
'srcdoc': '<p>hello world</p>',
'sandbox': 'allow-scripts',
Expand Down
2 changes: 1 addition & 1 deletion src/python-fastui/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,8 @@ class FormSelectMultiple(BaseModel):


def test_form_description_leakage():
m = components.ModelForm(model=FormSelectMultiple, submit_url='/foobar/')

m = components.ModelForm(model=FormSelectMultiple, submit_url='/foobar/')
assert m.model_dump(by_alias=True, exclude_none=True) == {
'formFields': [
{
Expand Down

0 comments on commit 70f2a1c

Please sign in to comment.