From 70f2a1c4819812b370da4001e5ce551c7b3051ab Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Tue, 11 Feb 2025 14:59:56 +0100 Subject: [PATCH] More compat --- .../fastui/components/__init__.py | 9 ++-- src/python-fastui/fastui/json_schema.py | 41 ++++++++++--------- src/python-fastui/requirements/pyproject.txt | 9 ++-- src/python-fastui/tests/test_components.py | 4 +- src/python-fastui/tests/test_forms.py | 2 +- 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index 7ffbb85b..8c2188d6 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -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 @@ -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 @@ -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 diff --git a/src/python-fastui/fastui/json_schema.py b/src/python-fastui/fastui/json_schema.py index c822f180..26904034 100644 --- a/src/python-fastui/fastui/json_schema.py +++ b/src/python-fastui/fastui/json_schema.py @@ -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': @@ -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( @@ -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 @@ -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': @@ -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( @@ -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'): @@ -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'): @@ -282,7 +283,7 @@ def special_string_field( required=required, multiple=multiple, initial=schema.get('initial'), - description=schema.get('description'), + description=description, ) diff --git a/src/python-fastui/requirements/pyproject.txt b/src/python-fastui/requirements/pyproject.txt index 71255820..ad1ed563 100644 --- a/src/python-fastui/requirements/pyproject.txt +++ b/src/python-fastui/requirements/pyproject.txt @@ -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 @@ -18,12 +18,11 @@ 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) @@ -31,7 +30,7 @@ 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 diff --git a/src/python-fastui/tests/test_components.py b/src/python-fastui/tests/test_components.py index 6d3c3d17..053d923b 100644 --- a/src/python-fastui/tests/test_components.py +++ b/src/python-fastui/tests/test_components.py @@ -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(): @@ -56,7 +56,7 @@ def test_root_model_single(): def test_iframe(): iframe = components.Iframe(src='https://www.example.com', srcdoc='
hello world
', 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': 'hello world
', 'sandbox': 'allow-scripts', diff --git a/src/python-fastui/tests/test_forms.py b/src/python-fastui/tests/test_forms.py index ceaa7a5d..0ddf5914 100644 --- a/src/python-fastui/tests/test_forms.py +++ b/src/python-fastui/tests/test_forms.py @@ -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': [ {