From 7d07f037e8fd0f7adbace4da168f410c1d57210e Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Wed, 1 May 2024 10:26:30 -0500 Subject: [PATCH 1/4] use alias generator --- .../fastui/components/__init__.py | 87 +++++++++---------- .../fastui/components/display.py | 11 ++- src/python-fastui/fastui/components/forms.py | 29 +++---- .../fastui/components/shared_base.py | 7 ++ src/python-fastui/fastui/components/tables.py | 13 +-- 5 files changed, 75 insertions(+), 72 deletions(-) create mode 100644 src/python-fastui/fastui/components/shared_base.py diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index 622a02c5..561ea25b 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -23,6 +23,7 @@ FormFieldSelectSearch, ModelForm, ) +from .shared_base import BaseModel from .tables import Pagination, Table __all__ = ( @@ -69,7 +70,7 @@ ) -class Text(_p.BaseModel, extra='forbid'): +class Text(BaseModel, extra='forbid'): """Text component that displays a string.""" text: str @@ -79,7 +80,7 @@ class Text(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Text'.""" -class Paragraph(_p.BaseModel, extra='forbid'): +class Paragraph(BaseModel, extra='forbid'): """Paragraph component that displays a string as a paragraph.""" text: str @@ -92,7 +93,7 @@ class Paragraph(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Paragraph'.""" -class PageTitle(_p.BaseModel, extra='forbid'): +class PageTitle(BaseModel, extra='forbid'): """Sets the title of the HTML page via the `document.title` property.""" text: str @@ -102,7 +103,7 @@ class PageTitle(_p.BaseModel, extra='forbid'): """The type of the component. Always 'PageTitle'.""" -class Div(_p.BaseModel, extra='forbid'): +class Div(BaseModel, extra='forbid'): """A generic container component.""" components: '_t.List[AnyComponent]' @@ -115,7 +116,7 @@ class Div(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Div'.""" -class Page(_p.BaseModel, extra='forbid'): +class Page(BaseModel, extra='forbid'): """Similar to `container` in many UI frameworks, this acts as a root component for most pages.""" components: '_t.List[AnyComponent]' @@ -128,7 +129,7 @@ class Page(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Page'.""" -class Heading(_p.BaseModel, extra='forbid'): +class Heading(BaseModel, extra='forbid'): """Heading component.""" text: str @@ -137,7 +138,7 @@ class Heading(_p.BaseModel, extra='forbid'): level: _t.Literal[1, 2, 3, 4, 5, 6] = 1 """The level of the heading. 1 is the largest, 6 is the smallest.""" - html_id: _t.Union[str, None] = _p.Field(default=None, serialization_alias='htmlId') + html_id: _t.Union[str, None] = _p.Field(default=None) """Optional HTML ID to apply to the heading's HTML component.""" class_name: _class_name.ClassNameField = None @@ -169,7 +170,7 @@ def __get_pydantic_json_schema__( """ -class Markdown(_p.BaseModel, extra='forbid'): +class Markdown(BaseModel, extra='forbid'): """Markdown component that renders markdown text.""" text: str @@ -185,7 +186,7 @@ class Markdown(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Markdown'.""" -class Code(_p.BaseModel, extra='forbid'): +class Code(BaseModel, extra='forbid'): """Code component that renders code with syntax highlighting.""" text: str @@ -204,7 +205,7 @@ class Code(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Code'.""" -class Json(_p.BaseModel, extra='forbid'): +class Json(BaseModel, extra='forbid'): """JSON component that renders JSON data.""" value: _types.JsonData @@ -217,18 +218,16 @@ class Json(_p.BaseModel, extra='forbid'): """The type of the component. Always 'JSON'.""" -class Button(_p.BaseModel, extra='forbid'): +class Button(BaseModel, extra='forbid'): """Button component.""" text: str """The text to display on the button.""" - on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None, serialization_alias='onClick') + on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None) """Optional event to trigger when the button is clicked.""" - html_type: _t.Union[_t.Literal['button', 'reset', 'submit'], None] = _p.Field( - default=None, serialization_alias='htmlType' - ) + html_type: _t.Union[_t.Literal['button', 'reset', 'submit'], None] = _p.Field(default=None) """Optional HTML type of the button. If None, defaults to 'button'.""" named_style: _class_name.NamedStyleField = None @@ -241,13 +240,13 @@ class Button(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Button'.""" -class Link(_p.BaseModel, extra='forbid'): +class Link(BaseModel, extra='forbid'): """Link component.""" components: '_t.List[AnyComponent]' """List of components to render attached to the link.""" - on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None, serialization_alias='onClick') + on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None) """Optional event to trigger when the link is clicked.""" mode: _t.Union[_t.Literal['navbar', 'footer', 'tabs', 'vertical', 'pagination'], None] = None @@ -266,7 +265,7 @@ class Link(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Link'.""" -class LinkList(_p.BaseModel, extra='forbid'): +class LinkList(BaseModel, extra='forbid'): """List of Link components.""" links: _t.List[Link] @@ -282,19 +281,19 @@ class LinkList(_p.BaseModel, extra='forbid'): """The type of the component. Always 'LinkList'.""" -class Navbar(_p.BaseModel, extra='forbid'): +class Navbar(BaseModel, extra='forbid'): """Navbar component used for moving between pages.""" title: _t.Union[str, None] = None """Optional title to display in the navbar.""" - title_event: _t.Union[events.AnyEvent, None] = _p.Field(default=None, serialization_alias='titleEvent') + title_event: _t.Union[events.AnyEvent, None] = _p.Field(default=None) """Optional event to trigger when the title is clicked. Often used to navigate to the home page.""" - start_links: _t.List[Link] = _p.Field(default=[], serialization_alias='startLinks') + start_links: _t.List[Link] = _p.Field(default=[]) """List of links to render at the start of the navbar.""" - end_links: _t.List[Link] = _p.Field(default=[], serialization_alias='endLinks') + end_links: _t.List[Link] = _p.Field(default=[]) """List of links to render at the end of the navbar.""" class_name: _class_name.ClassNameField = None @@ -313,13 +312,13 @@ def __get_pydantic_json_schema__( return json_schema -class Footer(_p.BaseModel, extra='forbid'): +class Footer(BaseModel, extra='forbid'): """Footer component.""" links: _t.List[Link] """List of links to render in the footer.""" - extra_text: _t.Union[str, None] = _p.Field(default=None, serialization_alias='extraText') + extra_text: _t.Union[str, None] = _p.Field(default=None) """Optional extra text to display in the footer.""" class_name: _class_name.ClassNameField = None @@ -329,7 +328,7 @@ class Footer(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Footer'.""" -class Modal(_p.BaseModel, extra='forbid'): +class Modal(BaseModel, extra='forbid'): """Modal component that displays a modal dialog.""" title: str @@ -341,10 +340,10 @@ class Modal(_p.BaseModel, extra='forbid'): footer: '_t.Union[_t.List[AnyComponent], None]' = None """Optional list of components to render in the modal footer.""" - open_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None, serialization_alias='openTrigger') + open_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None) """Optional event to trigger when the modal is opened.""" - open_context: _t.Union[events.ContextType, None] = _p.Field(default=None, serialization_alias='openContext') + open_context: _t.Union[events.ContextType, None] = _p.Field(default=None) """Optional context to pass to the open trigger event.""" class_name: _class_name.ClassNameField = None @@ -354,13 +353,13 @@ class Modal(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Modal'.""" -class ServerLoad(_p.BaseModel, extra='forbid'): +class ServerLoad(BaseModel, extra='forbid'): """A component that will be replaced by the server with the component returned by the given URL.""" path: str """The URL to load the component from.""" - load_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None, serialization_alias='loadTrigger') + load_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None) """Optional event to trigger when the component is loaded.""" components: '_t.Union[_t.List[AnyComponent], None]' = None @@ -369,7 +368,7 @@ class ServerLoad(_p.BaseModel, extra='forbid'): sse: _t.Union[bool, None] = None """Optional flag to enable server-sent events (SSE) for the server load.""" - sse_retry: _t.Union[int, None] = _p.Field(default=None, serialization_alias='sseRetry') + sse_retry: _t.Union[int, None] = _p.Field(default=None) """Optional time in milliseconds to retry the SSE connection.""" method: _t.Union[_t.Literal['GET', 'POST', 'PATCH', 'PUT', 'DELETE'], None] = None @@ -379,7 +378,7 @@ class ServerLoad(_p.BaseModel, extra='forbid'): """The type of the component. Always 'ServerLoad'.""" -class Image(_p.BaseModel, extra='forbid'): +class Image(BaseModel, extra='forbid'): """Image container component.""" src: str @@ -406,7 +405,7 @@ class Image(_p.BaseModel, extra='forbid'): 'unsafe-url', ], None, - ] = _p.Field(None, serialization_alias='referrerPolicy') + ] = _p.Field(None) """Optional referrer policy for the image. Specifies what information to send when fetching the image. For more info, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy.""" @@ -414,7 +413,7 @@ class Image(_p.BaseModel, extra='forbid'): loading: _t.Union[_t.Literal['eager', 'lazy'], None] = None """Optional loading strategy for the image.""" - on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None, serialization_alias='onClick') + on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None) """Optional event to trigger when the image is clicked.""" class_name: _class_name.ClassNameField = None @@ -424,7 +423,7 @@ class Image(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Image'.""" -class Iframe(_p.BaseModel, extra='forbid'): +class Iframe(BaseModel, extra='forbid'): """Iframe component that displays content from a URL.""" src: _p.HttpUrl @@ -452,7 +451,7 @@ class Iframe(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Iframe'.""" -class Video(_p.BaseModel, extra='forbid'): +class Video(BaseModel, extra='forbid'): """Video component that displays a video or multiple videos.""" sources: _t.List[_p.AnyUrl] @@ -486,7 +485,7 @@ class Video(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Video'.""" -class FireEvent(_p.BaseModel, extra='forbid'): +class FireEvent(BaseModel, extra='forbid'): """Fire an event.""" event: events.AnyEvent @@ -499,7 +498,7 @@ class FireEvent(_p.BaseModel, extra='forbid'): """The type of the component. Always 'FireEvent'.""" -class Error(_p.BaseModel, extra='forbid'): +class Error(BaseModel, extra='forbid'): """Utility component used to display an error.""" title: str @@ -508,7 +507,7 @@ class Error(_p.BaseModel, extra='forbid'): description: str """The description of the error.""" - status_code: _t.Union[int, None] = _p.Field(None, serialization_alias='statusCode') + status_code: _t.Union[int, None] = _p.Field(None) """Optional status code of the error.""" class_name: _class_name.ClassNameField = None @@ -527,7 +526,7 @@ def __get_pydantic_json_schema__( return json_schema -class Spinner(_p.BaseModel, extra='forbid'): +class Spinner(BaseModel, extra='forbid'): """Spinner component that displays a loading spinner.""" text: _t.Union[str, None] = None @@ -540,7 +539,7 @@ class Spinner(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Spinner'.""" -class Toast(_p.BaseModel, extra='forbid'): +class Toast(BaseModel, extra='forbid'): """Toast component that displays a toast message (small temporary message).""" title: str @@ -566,10 +565,10 @@ class Toast(_p.BaseModel, extra='forbid'): ] = None """Optional position of the toast.""" - open_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None, serialization_alias='openTrigger') + open_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None) """Optional event to trigger when the toast is opened.""" - open_context: _t.Union[events.ContextType, None] = _p.Field(default=None, serialization_alias='openContext') + open_context: _t.Union[events.ContextType, None] = _p.Field(default=None) """Optional context to pass to the open trigger event.""" class_name: _class_name.ClassNameField = None @@ -579,13 +578,13 @@ class Toast(_p.BaseModel, extra='forbid'): """The type of the component. Always 'Toast'.""" -class Custom(_p.BaseModel, extra='forbid'): +class Custom(BaseModel, extra='forbid'): """Custom component that allows for special data to be rendered.""" data: _types.JsonData """The data to render in the custom component.""" - sub_type: str = _p.Field(serialization_alias='subType') + sub_type: str """The sub-type of the custom component.""" library: _t.Union[str, None] = None diff --git a/src/python-fastui/fastui/components/display.py b/src/python-fastui/fastui/components/display.py index 3afd0347..bd066868 100644 --- a/src/python-fastui/fastui/components/display.py +++ b/src/python-fastui/fastui/components/display.py @@ -10,6 +10,7 @@ from .. import class_name as _class_name from .. import events from .. import types as _types +from .shared_base import BaseModel __all__ = 'DisplayMode', 'DisplayLookup', 'Display', 'Details' @@ -28,7 +29,7 @@ class DisplayMode(str, enum.Enum): inline_code = 'inline_code' -class DisplayBase(pydantic.BaseModel, ABC, defer_build=True): +class DisplayBase(BaseModel, ABC, defer_build=True): """Base class for display components.""" mode: _t.Union[DisplayMode, None] = None @@ -37,7 +38,7 @@ class DisplayBase(pydantic.BaseModel, ABC, defer_build=True): title: _t.Union[str, None] = None """Title to display for the value.""" - on_click: _t.Union[events.AnyEvent, None] = pydantic.Field(default=None, serialization_alias='onClick') + on_click: _t.Union[events.AnyEvent, None] = pydantic.Field(default=None) """Event to trigger when the value is clicked.""" @@ -47,9 +48,7 @@ class DisplayLookup(DisplayBase, extra='forbid'): field: str """Field to display.""" - table_width_percent: _t.Union[_te.Annotated[int, _at.Interval(ge=0, le=100)], None] = pydantic.Field( - default=None, serialization_alias='tableWidthPercent' - ) + table_width_percent: _t.Union[_te.Annotated[int, _at.Interval(ge=0, le=100)], None] = pydantic.Field(default=None) """Percentage width - 0 to 100, specific to tables.""" @@ -63,7 +62,7 @@ class Display(DisplayBase, extra='forbid'): """The type of the component. Always 'Display'.""" -class Details(pydantic.BaseModel, extra='forbid'): +class Details(BaseModel, extra='forbid'): """Details associated with displaying a data model.""" data: pydantic.SerializeAsAny[_types.DataModel] diff --git a/src/python-fastui/fastui/components/forms.py b/src/python-fastui/fastui/components/forms.py index d607de12..3725a73b 100644 --- a/src/python-fastui/fastui/components/forms.py +++ b/src/python-fastui/fastui/components/forms.py @@ -7,6 +7,7 @@ from .. import class_name as _class_name from .. import events, forms from .. import types as _types +from .shared_base import BaseModel if _t.TYPE_CHECKING: from . import AnyComponent @@ -14,7 +15,7 @@ InputHtmlType = _t.Literal['text', 'date', 'datetime-local', 'time', 'email', 'url', 'number', 'password', 'hidden'] -class BaseFormField(pydantic.BaseModel, ABC, defer_build=True): +class BaseFormField(BaseModel, ABC, defer_build=True): """Base class for form fields.""" name: str @@ -35,9 +36,7 @@ class BaseFormField(pydantic.BaseModel, ABC, defer_build=True): description: _t.Union[str, None] = None """Description of the field.""" - display_mode: _t.Union[_t.Literal['default', 'inline'], None] = pydantic.Field( - default=None, serialization_alias='displayMode' - ) + display_mode: _t.Union[_t.Literal['default', 'inline'], None] = pydantic.Field(default=None) """Display mode for the field.""" class_name: _class_name.ClassNameField = None @@ -47,7 +46,7 @@ class BaseFormField(pydantic.BaseModel, ABC, defer_build=True): class FormFieldInput(BaseFormField): """Form field for basic input.""" - html_type: InputHtmlType = pydantic.Field(default='text', serialization_alias='htmlType') + html_type: InputHtmlType = pydantic.Field(default='text') """HTML input type for the field.""" initial: _t.Union[str, float, None] = None @@ -139,7 +138,7 @@ class FormFieldSelect(BaseFormField): class FormFieldSelectSearch(BaseFormField): """Form field for searchable select input.""" - search_url: str = pydantic.Field(serialization_alias='searchUrl') + search_url: str """URL to search for options.""" multiple: _t.Union[bool, None] = None @@ -164,10 +163,10 @@ class FormFieldSelectSearch(BaseFormField): """Union of all form field types.""" -class BaseForm(pydantic.BaseModel, ABC, defer_build=True, extra='forbid'): +class BaseForm(BaseModel, ABC, defer_build=True, extra='forbid'): """Base class for forms.""" - submit_url: str = pydantic.Field(serialization_alias='submitUrl') + submit_url: str """URL to submit the form data to.""" initial: _t.Union[_t.Dict[str, _types.JsonData], None] = None @@ -176,15 +175,13 @@ class BaseForm(pydantic.BaseModel, ABC, defer_build=True, extra='forbid'): method: _t.Literal['POST', 'GOTO', 'GET'] = 'POST' """HTTP method to use for the form submission.""" - display_mode: _t.Union[_t.Literal['default', 'page', 'inline'], None] = pydantic.Field( - default=None, serialization_alias='displayMode' - ) + display_mode: _t.Union[_t.Literal['default', 'page', 'inline'], None] = pydantic.Field(default=None) """Display mode for the form.""" - submit_on_change: _t.Union[bool, None] = pydantic.Field(default=None, serialization_alias='submitOnChange') + submit_on_change: _t.Union[bool, None] = pydantic.Field(default=None) """Whether to submit the form on change.""" - submit_trigger: _t.Union[events.PageEvent, None] = pydantic.Field(default=None, serialization_alias='submitTrigger') + submit_trigger: _t.Union[events.PageEvent, None] = pydantic.Field(default=None) """Event to trigger form submission.""" loading: '_t.Union[_t.List[AnyComponent], None]' = None @@ -206,20 +203,20 @@ def default_footer(self) -> _te.Self: class Form(BaseForm): """Form component.""" - form_fields: _t.List[FormField] = pydantic.Field(serialization_alias='formFields') + form_fields: _t.List[FormField] """List of form fields.""" type: _t.Literal['Form'] = 'Form' """The type of the component. Always 'Form'.""" -FormFieldsModel = _t.TypeVar('FormFieldsModel', bound=pydantic.BaseModel) +FormFieldsModel = _t.TypeVar('FormFieldsModel', bound=BaseModel) class ModelForm(BaseForm): """Form component generated from a Pydantic model.""" - model: _t.Type[pydantic.BaseModel] = pydantic.Field(exclude=True) + model: _t.Type[BaseModel] = pydantic.Field(exclude=True) """Pydantic model from which to generate the form.""" type: _t.Literal['ModelForm'] = 'ModelForm' diff --git a/src/python-fastui/fastui/components/shared_base.py b/src/python-fastui/fastui/components/shared_base.py new file mode 100644 index 00000000..a0eb9c35 --- /dev/null +++ b/src/python-fastui/fastui/components/shared_base.py @@ -0,0 +1,7 @@ +from pydantic import AliasGenerator, ConfigDict +from pydantic import BaseModel as _BaseModel +from pydantic.alias_generators import to_camel + + +class BaseModel(_BaseModel): + model_config = ConfigDict(alias_generator=AliasGenerator(serialization_alias=to_camel)) diff --git a/src/python-fastui/fastui/components/tables.py b/src/python-fastui/fastui/components/tables.py index 53d35ce7..8b0b9acb 100644 --- a/src/python-fastui/fastui/components/tables.py +++ b/src/python-fastui/fastui/components/tables.py @@ -7,11 +7,12 @@ from .. import class_name as _class_name from .. import types as _types from . import display +from .shared_base import BaseModel # TODO allow dataclasses and typed dicts here too -class Table(pydantic.BaseModel, extra='forbid'): +class Table(BaseModel, extra='forbid'): """Table component.""" data: _t.Sequence[pydantic.SerializeAsAny[_types.DataModel]] @@ -20,10 +21,10 @@ class Table(pydantic.BaseModel, extra='forbid'): columns: _t.Union[_t.List[display.DisplayLookup], None] = None """List of columns to display in the table. If not provided, columns will be inferred from the data model.""" - data_model: _t.Union[_t.Type[pydantic.BaseModel], None] = pydantic.Field(default=None, exclude=True) + data_model: _t.Union[_t.Type[BaseModel], None] = pydantic.Field(default=None, exclude=True) """Data model to use for the table. If not provided, the model will be inferred from the first data item.""" - no_data_message: _t.Union[str, None] = pydantic.Field(default=None, serialization_alias='noDataMessage') + no_data_message: _t.Union[str, None] = None """Message to display when there is no data.""" class_name: _class_name.ClassNameField = None @@ -66,19 +67,19 @@ def __get_pydantic_json_schema__( return json_schema -class Pagination(pydantic.BaseModel): +class Pagination(BaseModel): """Pagination component to use with tables.""" page: int """The current page number.""" - page_size: int = pydantic.Field(serialization_alias='pageSize') + page_size: int """The number of items per page.""" total: int """The total number of items.""" - page_query_param: str = pydantic.Field('page', serialization_alias='pageQueryParam') + page_query_param: str = 'page' """The query parameter to use for the page number.""" class_name: _class_name.ClassNameField = None From 10e8173e842a9dafcb30e45c76e7a99c7f3ea8ae Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Wed, 1 May 2024 10:29:17 -0500 Subject: [PATCH 2/4] move to outer base --- src/python-fastui/fastui/{components/shared_base.py => base.py} | 0 src/python-fastui/fastui/components/__init__.py | 2 +- src/python-fastui/fastui/components/display.py | 2 +- src/python-fastui/fastui/components/forms.py | 2 +- src/python-fastui/fastui/components/tables.py | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/python-fastui/fastui/{components/shared_base.py => base.py} (100%) diff --git a/src/python-fastui/fastui/components/shared_base.py b/src/python-fastui/fastui/base.py similarity index 100% rename from src/python-fastui/fastui/components/shared_base.py rename to src/python-fastui/fastui/base.py diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index 561ea25b..59896152 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -12,6 +12,7 @@ from .. import class_name as _class_name from .. import events from .. import types as _types +from ..base import BaseModel from .display import Details, Display from .forms import ( Form, @@ -23,7 +24,6 @@ FormFieldSelectSearch, ModelForm, ) -from .shared_base import BaseModel from .tables import Pagination, Table __all__ = ( diff --git a/src/python-fastui/fastui/components/display.py b/src/python-fastui/fastui/components/display.py index bd066868..3a7039e9 100644 --- a/src/python-fastui/fastui/components/display.py +++ b/src/python-fastui/fastui/components/display.py @@ -10,7 +10,7 @@ from .. import class_name as _class_name from .. import events from .. import types as _types -from .shared_base import BaseModel +from ..base import BaseModel __all__ = 'DisplayMode', 'DisplayLookup', 'Display', 'Details' diff --git a/src/python-fastui/fastui/components/forms.py b/src/python-fastui/fastui/components/forms.py index 3725a73b..d8aedf1c 100644 --- a/src/python-fastui/fastui/components/forms.py +++ b/src/python-fastui/fastui/components/forms.py @@ -7,7 +7,7 @@ from .. import class_name as _class_name from .. import events, forms from .. import types as _types -from .shared_base import BaseModel +from ..base import BaseModel if _t.TYPE_CHECKING: from . import AnyComponent diff --git a/src/python-fastui/fastui/components/tables.py b/src/python-fastui/fastui/components/tables.py index 8b0b9acb..bf09ffc8 100644 --- a/src/python-fastui/fastui/components/tables.py +++ b/src/python-fastui/fastui/components/tables.py @@ -6,8 +6,8 @@ from .. import class_name as _class_name from .. import types as _types +from ..base import BaseModel from . import display -from .shared_base import BaseModel # TODO allow dataclasses and typed dicts here too From dfc532b09a8523283ef05e4d56051b13e911707f Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Wed, 1 May 2024 10:32:56 -0500 Subject: [PATCH 3/4] using higher level base model --- .../fastui/components/__init__.py | 34 +++++++++---------- .../fastui/components/display.py | 4 +-- src/python-fastui/fastui/components/forms.py | 10 +++--- src/python-fastui/fastui/events.py | 8 +++-- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index 59896152..f74fafc2 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -138,7 +138,7 @@ class Heading(BaseModel, extra='forbid'): level: _t.Literal[1, 2, 3, 4, 5, 6] = 1 """The level of the heading. 1 is the largest, 6 is the smallest.""" - html_id: _t.Union[str, None] = _p.Field(default=None) + html_id: _t.Union[str, None] = None """Optional HTML ID to apply to the heading's HTML component.""" class_name: _class_name.ClassNameField = None @@ -224,10 +224,10 @@ class Button(BaseModel, extra='forbid'): text: str """The text to display on the button.""" - on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None) + on_click: _t.Union[events.AnyEvent, None] = None """Optional event to trigger when the button is clicked.""" - html_type: _t.Union[_t.Literal['button', 'reset', 'submit'], None] = _p.Field(default=None) + html_type: _t.Union[_t.Literal['button', 'reset', 'submit'], None] = None """Optional HTML type of the button. If None, defaults to 'button'.""" named_style: _class_name.NamedStyleField = None @@ -246,7 +246,7 @@ class Link(BaseModel, extra='forbid'): components: '_t.List[AnyComponent]' """List of components to render attached to the link.""" - on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None) + on_click: _t.Union[events.AnyEvent, None] = None """Optional event to trigger when the link is clicked.""" mode: _t.Union[_t.Literal['navbar', 'footer', 'tabs', 'vertical', 'pagination'], None] = None @@ -287,13 +287,13 @@ class Navbar(BaseModel, extra='forbid'): title: _t.Union[str, None] = None """Optional title to display in the navbar.""" - title_event: _t.Union[events.AnyEvent, None] = _p.Field(default=None) + title_event: _t.Union[events.AnyEvent, None] = None """Optional event to trigger when the title is clicked. Often used to navigate to the home page.""" - start_links: _t.List[Link] = _p.Field(default=[]) + start_links: _t.List[Link] = [] """List of links to render at the start of the navbar.""" - end_links: _t.List[Link] = _p.Field(default=[]) + end_links: _t.List[Link] = [] """List of links to render at the end of the navbar.""" class_name: _class_name.ClassNameField = None @@ -318,7 +318,7 @@ class Footer(BaseModel, extra='forbid'): links: _t.List[Link] """List of links to render in the footer.""" - extra_text: _t.Union[str, None] = _p.Field(default=None) + extra_text: _t.Union[str, None] = None """Optional extra text to display in the footer.""" class_name: _class_name.ClassNameField = None @@ -340,10 +340,10 @@ class Modal(BaseModel, extra='forbid'): footer: '_t.Union[_t.List[AnyComponent], None]' = None """Optional list of components to render in the modal footer.""" - open_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None) + open_trigger: _t.Union[events.PageEvent, None] = None """Optional event to trigger when the modal is opened.""" - open_context: _t.Union[events.ContextType, None] = _p.Field(default=None) + open_context: _t.Union[events.ContextType, None] = None """Optional context to pass to the open trigger event.""" class_name: _class_name.ClassNameField = None @@ -359,7 +359,7 @@ class ServerLoad(BaseModel, extra='forbid'): path: str """The URL to load the component from.""" - load_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None) + load_trigger: _t.Union[events.PageEvent, None] = None """Optional event to trigger when the component is loaded.""" components: '_t.Union[_t.List[AnyComponent], None]' = None @@ -368,7 +368,7 @@ class ServerLoad(BaseModel, extra='forbid'): sse: _t.Union[bool, None] = None """Optional flag to enable server-sent events (SSE) for the server load.""" - sse_retry: _t.Union[int, None] = _p.Field(default=None) + sse_retry: _t.Union[int, None] = None """Optional time in milliseconds to retry the SSE connection.""" method: _t.Union[_t.Literal['GET', 'POST', 'PATCH', 'PUT', 'DELETE'], None] = None @@ -405,7 +405,7 @@ class Image(BaseModel, extra='forbid'): 'unsafe-url', ], None, - ] = _p.Field(None) + ] = None """Optional referrer policy for the image. Specifies what information to send when fetching the image. For more info, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy.""" @@ -413,7 +413,7 @@ class Image(BaseModel, extra='forbid'): loading: _t.Union[_t.Literal['eager', 'lazy'], None] = None """Optional loading strategy for the image.""" - on_click: _t.Union[events.AnyEvent, None] = _p.Field(default=None) + on_click: _t.Union[events.AnyEvent, None] = None """Optional event to trigger when the image is clicked.""" class_name: _class_name.ClassNameField = None @@ -507,7 +507,7 @@ class Error(BaseModel, extra='forbid'): description: str """The description of the error.""" - status_code: _t.Union[int, None] = _p.Field(None) + status_code: _t.Union[int, None] = None """Optional status code of the error.""" class_name: _class_name.ClassNameField = None @@ -565,10 +565,10 @@ class Toast(BaseModel, extra='forbid'): ] = None """Optional position of the toast.""" - open_trigger: _t.Union[events.PageEvent, None] = _p.Field(default=None) + open_trigger: _t.Union[events.PageEvent, None] = None """Optional event to trigger when the toast is opened.""" - open_context: _t.Union[events.ContextType, None] = _p.Field(default=None) + open_context: _t.Union[events.ContextType, None] = None """Optional context to pass to the open trigger event.""" class_name: _class_name.ClassNameField = None diff --git a/src/python-fastui/fastui/components/display.py b/src/python-fastui/fastui/components/display.py index 3a7039e9..5f147def 100644 --- a/src/python-fastui/fastui/components/display.py +++ b/src/python-fastui/fastui/components/display.py @@ -38,7 +38,7 @@ class DisplayBase(BaseModel, ABC, defer_build=True): title: _t.Union[str, None] = None """Title to display for the value.""" - on_click: _t.Union[events.AnyEvent, None] = pydantic.Field(default=None) + on_click: _t.Union[events.AnyEvent, None] = None """Event to trigger when the value is clicked.""" @@ -48,7 +48,7 @@ class DisplayLookup(DisplayBase, extra='forbid'): field: str """Field to display.""" - table_width_percent: _t.Union[_te.Annotated[int, _at.Interval(ge=0, le=100)], None] = pydantic.Field(default=None) + table_width_percent: _t.Union[_te.Annotated[int, _at.Interval(ge=0, le=100)], None] = None """Percentage width - 0 to 100, specific to tables.""" diff --git a/src/python-fastui/fastui/components/forms.py b/src/python-fastui/fastui/components/forms.py index d8aedf1c..2747b2bc 100644 --- a/src/python-fastui/fastui/components/forms.py +++ b/src/python-fastui/fastui/components/forms.py @@ -36,7 +36,7 @@ class BaseFormField(BaseModel, ABC, defer_build=True): description: _t.Union[str, None] = None """Description of the field.""" - display_mode: _t.Union[_t.Literal['default', 'inline'], None] = pydantic.Field(default=None) + display_mode: _t.Union[_t.Literal['default', 'inline'], None] = None """Display mode for the field.""" class_name: _class_name.ClassNameField = None @@ -46,7 +46,7 @@ class BaseFormField(BaseModel, ABC, defer_build=True): class FormFieldInput(BaseFormField): """Form field for basic input.""" - html_type: InputHtmlType = pydantic.Field(default='text') + html_type: InputHtmlType = 'text' """HTML input type for the field.""" initial: _t.Union[str, float, None] = None @@ -175,13 +175,13 @@ class BaseForm(BaseModel, ABC, defer_build=True, extra='forbid'): method: _t.Literal['POST', 'GOTO', 'GET'] = 'POST' """HTTP method to use for the form submission.""" - display_mode: _t.Union[_t.Literal['default', 'page', 'inline'], None] = pydantic.Field(default=None) + display_mode: _t.Union[_t.Literal['default', 'page', 'inline'], None] = None """Display mode for the form.""" - submit_on_change: _t.Union[bool, None] = pydantic.Field(default=None) + submit_on_change: _t.Union[bool, None] = None """Whether to submit the form on change.""" - submit_trigger: _t.Union[events.PageEvent, None] = pydantic.Field(default=None) + submit_trigger: _t.Union[events.PageEvent, None] = None """Event to trigger form submission.""" loading: '_t.Union[_t.List[AnyComponent], None]' = None diff --git a/src/python-fastui/fastui/events.py b/src/python-fastui/fastui/events.py index 1c5c491d..9b5970dd 100644 --- a/src/python-fastui/fastui/events.py +++ b/src/python-fastui/fastui/events.py @@ -1,17 +1,19 @@ from typing import Dict, Literal, Union -from pydantic import BaseModel, Field +from pydantic import Field from typing_extensions import Annotated, TypeAliasType +from .base import BaseModel + ContextType = TypeAliasType('ContextType', Dict[str, Union[str, int]]) class PageEvent(BaseModel): name: str - push_path: Union[str, None] = Field(default=None, serialization_alias='pushPath') + push_path: Union[str, None] = None context: Union[ContextType, None] = None clear: Union[bool, None] = None - next_event: 'Union[AnyEvent, None]' = Field(default=None, serialization_alias='nextEvent') + next_event: 'Union[AnyEvent, None]' = None type: Literal['page'] = 'page' From e1cd5b4db50153b78a17a06ba96a6dda3d2f6fb0 Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Wed, 1 May 2024 11:00:36 -0500 Subject: [PATCH 4/4] fix all the tests I broke :) --- src/python-fastui/fastui/components/forms.py | 4 ++-- src/python-fastui/fastui/components/tables.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/python-fastui/fastui/components/forms.py b/src/python-fastui/fastui/components/forms.py index 2747b2bc..b4f2f2d9 100644 --- a/src/python-fastui/fastui/components/forms.py +++ b/src/python-fastui/fastui/components/forms.py @@ -210,13 +210,13 @@ class Form(BaseForm): """The type of the component. Always 'Form'.""" -FormFieldsModel = _t.TypeVar('FormFieldsModel', bound=BaseModel) +FormFieldsModel = _t.TypeVar('FormFieldsModel', bound=pydantic.BaseModel) class ModelForm(BaseForm): """Form component generated from a Pydantic model.""" - model: _t.Type[BaseModel] = pydantic.Field(exclude=True) + model: _t.Type[pydantic.BaseModel] = pydantic.Field(exclude=True) """Pydantic model from which to generate the form.""" type: _t.Literal['ModelForm'] = 'ModelForm' diff --git a/src/python-fastui/fastui/components/tables.py b/src/python-fastui/fastui/components/tables.py index bf09ffc8..9044ef3d 100644 --- a/src/python-fastui/fastui/components/tables.py +++ b/src/python-fastui/fastui/components/tables.py @@ -21,7 +21,7 @@ class Table(BaseModel, extra='forbid'): columns: _t.Union[_t.List[display.DisplayLookup], None] = None """List of columns to display in the table. If not provided, columns will be inferred from the data model.""" - data_model: _t.Union[_t.Type[BaseModel], None] = pydantic.Field(default=None, exclude=True) + data_model: _t.Union[_t.Type[pydantic.BaseModel], None] = pydantic.Field(default=None, exclude=True) """Data model to use for the table. If not provided, the model will be inferred from the first data item.""" no_data_message: _t.Union[str, None] = None