From 5b66f6a2044f6b078c229240340601add949ac6b Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Thu, 25 Apr 2024 09:22:00 -0500 Subject: [PATCH] more docstrings --- .pre-commit-config.yaml | 1 + docs/components.md | 45 ++++++- src/npm-fastui/src/models.d.ts | 41 ++++++- .../fastui/components/__init__.py | 12 +- .../fastui/components/display.py | 33 +++-- src/python-fastui/fastui/components/forms.py | 114 +++++++++++++++++- src/python-fastui/fastui/components/tables.py | 28 ++++- src/python-fastui/fastui/types.py | 1 + 8 files changed, 249 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b8d75a4..49bbffc5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,7 @@ repos: types_or: [javascript, jsx, ts, tsx, css, json, markdown] entry: npm run prettier language: system + exclude: '^docs/.*' - id: js-lint name: js-lint types_or: [ts, tsx] diff --git a/docs/components.md b/docs/components.md index 6c431732..6cab7d87 100644 --- a/docs/components.md +++ b/docs/components.md @@ -1,7 +1,44 @@ ::: fastui.components -options: -docstring_options: -ignore_init_summary: false -members: - Text - Paragraph - PageTitle - Div - Page - Heading - Markdown - Code - Json - Button - Link - LinkList - Navbar - Modal - ServerLoad - Image - Iframe - FireEvent - Error - Spinner - Toast - Custom - Table - Pagination - Display - Details - Form - FormField - ModelForm - Footer - AnyComponent - FormFieldBoolean - FormFieldFile - FormFieldInput - FormFieldSelect - FormFieldSelectSearch + options: + inherited_members: true + docstring_options: + ignore_init_summary: false + members: + - Text + - Paragraph + - PageTitle + - Div + - Page + - Heading + - Markdown + - Code + - Json + - Button + - Link + - LinkList + - Navbar + - Modal + - ServerLoad + - Image + - Iframe + - FireEvent + - Error + - Spinner + - Toast + - Custom + - Table + - Pagination + - Display + - Details + - Form + - FormField + - ModelForm + - Footer + - AnyComponent + - FormFieldBoolean + - FormFieldFile + - FormFieldInput + - FormFieldSelect + - FormFieldSelectSearch diff --git a/src/npm-fastui/src/models.d.ts b/src/npm-fastui/src/models.d.ts index 0e2fa75a..f3096397 100644 --- a/src/npm-fastui/src/models.d.ts +++ b/src/npm-fastui/src/models.d.ts @@ -58,6 +58,9 @@ export type JsonData = } export type AnyEvent = PageEvent | GoToEvent | BackEvent | AuthEvent export type NamedStyle = 'primary' | 'secondary' | 'warning' +/** + * Display mode for a value. + */ export type DisplayMode = | 'auto' | 'plain' @@ -72,9 +75,6 @@ export type SelectOptions = SelectOption[] | SelectGroup[] /** * Text component that displays a string. - * - * !!! abstract "Component Demo" - * [Text](https://fastui-demo.onrender.com/components#text) */ export interface Text { text: string @@ -338,6 +338,9 @@ export interface Custom { className?: ClassName type: 'Custom' } +/** + * Table component. + */ export interface Table { data: DataModel[] columns: DisplayLookup[] @@ -358,13 +361,16 @@ export interface DisplayLookup { field: string tableWidthPercent?: number } +/** + * Pagination component to use with tables. + */ export interface Pagination { page: number pageSize: number total: number + pageQueryParam?: string className?: ClassName type: 'Pagination' - pageQueryParam?: string pageCount: number } /** @@ -377,12 +383,18 @@ export interface Display { value: JsonData type: 'Display' } +/** + * Details associated with displaying a data model. + */ export interface Details { data: DataModel fields: DisplayLookup[] className?: ClassName type: 'Details' } +/** + * Form component. + */ export interface Form { submitUrl: string initial?: { @@ -405,6 +417,9 @@ export interface Form { )[] type: 'Form' } +/** + * Form field for basic input. + */ export interface FormFieldInput { name: string title: string[] | string @@ -420,6 +435,9 @@ export interface FormFieldInput { autocomplete?: string type: 'FormFieldInput' } +/** + * Form field for text area input. + */ export interface FormFieldTextarea { name: string title: string[] | string @@ -436,6 +454,9 @@ export interface FormFieldTextarea { autocomplete?: string type: 'FormFieldTextarea' } +/** + * Form field for boolean input. + */ export interface FormFieldBoolean { name: string title: string[] | string @@ -449,6 +470,9 @@ export interface FormFieldBoolean { mode?: 'checkbox' | 'switch' type: 'FormFieldBoolean' } +/** + * Form field for file input. + */ export interface FormFieldFile { name: string title: string[] | string @@ -462,6 +486,9 @@ export interface FormFieldFile { accept?: string type: 'FormFieldFile' } +/** + * Form field for select input. + */ export interface FormFieldSelect { name: string title: string[] | string @@ -487,6 +514,9 @@ export interface SelectGroup { label: string options: SelectOption[] } +/** + * Form field for searchable select input. + */ export interface FormFieldSelectSearch { name: string title: string[] | string @@ -503,6 +533,9 @@ export interface FormFieldSelectSearch { placeholder?: string type: 'FormFieldSelectSearch' } +/** + * Form component generated from a Pydantic model. + */ export interface ModelForm { submitUrl: string initial?: { diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index f2c15dc1..622a02c5 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -70,11 +70,7 @@ class Text(_p.BaseModel, extra='forbid'): - """Text component that displays a string. - - !!! abstract "Component Demo" - [Text](https://fastui-demo.onrender.com/components#text) - """ + """Text component that displays a string.""" text: str """The text to display.""" @@ -160,8 +156,6 @@ def __get_pydantic_json_schema__( return json_schema -# see https://github.com/PrismJS/prism-themes -# and https://cdn.jsdelivr.net/npm/react-syntax-highlighter@15.5.0/dist/esm/styles/prism/index.js CodeStyle = _te.Annotated[_t.Union[str, None], _p.Field(serialization_alias='codeStyle')] """ Code style to apply to a `Code` component. @@ -170,8 +164,8 @@ def __get_pydantic_json_schema__( codeStyle: The code style to apply. If None, no style is applied. See Also: - - https://github.com/PrismJS/prism-themes - - https://cdn.jsdelivr.net/npm/react-syntax-highlighter@15.5.0/dist/esm/styles/prism/index.js + - [PrismJS Themes](https://github.com/PrismJS/prism-themes) + - [PrismJS Theme Index](https://cdn.jsdelivr.net/npm/react-syntax-highlighter@15.5.0/dist/esm/styles/prism/index.js) """ diff --git a/src/python-fastui/fastui/components/display.py b/src/python-fastui/fastui/components/display.py index 6c4ff822..3afd0347 100644 --- a/src/python-fastui/fastui/components/display.py +++ b/src/python-fastui/fastui/components/display.py @@ -15,6 +15,8 @@ class DisplayMode(str, enum.Enum): + """Display mode for a value.""" + auto = 'auto' # default, same as None below plain = 'plain' datetime = 'datetime' @@ -27,37 +29,54 @@ class DisplayMode(str, enum.Enum): class DisplayBase(pydantic.BaseModel, ABC, defer_build=True): + """Base class for display components.""" + mode: _t.Union[DisplayMode, None] = None + """Display mode for the value.""" + 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') + """Event to trigger when the value is clicked.""" class DisplayLookup(DisplayBase, extra='forbid'): - """ - Description of how to display a value looked up from data, either in a table or detail view. - """ + """Description of how to display a value looked up from data, either in a table or detail view.""" field: str - # percentage width - 0 to 100, specific to tables + """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' ) + """Percentage width - 0 to 100, specific to tables.""" class Display(DisplayBase, extra='forbid'): - """ - Description of how to display a value, either in a table or detail view. - """ + """Description of how to display a value, either in a table or detail view.""" value: _types.JsonData + """Value to display.""" + type: _t.Literal['Display'] = 'Display' + """The type of the component. Always 'Display'.""" class Details(pydantic.BaseModel, extra='forbid'): + """Details associated with displaying a data model.""" + data: pydantic.SerializeAsAny[_types.DataModel] + """Data model to display.""" + fields: _t.Union[_t.List[DisplayLookup], None] = None + """Fields to display.""" + class_name: _class_name.ClassNameField = None + """Optional class name to apply to the details component.""" + type: _t.Literal['Details'] = 'Details' + """The type of the component. Always 'Details'.""" @pydantic.model_validator(mode='after') def _fill_fields(self) -> _te.Self: diff --git a/src/python-fastui/fastui/components/forms.py b/src/python-fastui/fastui/components/forms.py index a18a9c4c..d607de12 100644 --- a/src/python-fastui/fastui/components/forms.py +++ b/src/python-fastui/fastui/components/forms.py @@ -15,84 +15,186 @@ class BaseFormField(pydantic.BaseModel, ABC, defer_build=True): + """Base class for form fields.""" + name: str + """Name of the field.""" + title: _t.Union[_t.List[str], str] + """Title of the field to display. Can be a list of strings for multi-line titles.""" + required: bool = False + """Whether the field is required. Defaults to False.""" + error: _t.Union[str, None] = None + """Error message to display if the field is invalid.""" + locked: bool = False + """Whether the field is locked. Defaults to False.""" + 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 for the field.""" + class_name: _class_name.ClassNameField = None + """Optional class name to apply to the field's HTML component.""" class FormFieldInput(BaseFormField): + """Form field for basic input.""" + html_type: InputHtmlType = pydantic.Field(default='text', serialization_alias='htmlType') + """HTML input type for the field.""" + initial: _t.Union[str, float, None] = None + """Initial value for the field.""" + placeholder: _t.Union[str, None] = None + """Placeholder text for the field.""" + autocomplete: _t.Union[str, None] = None + """Autocomplete value for the field.""" + type: _t.Literal['FormFieldInput'] = 'FormFieldInput' + """The type of the component. Always 'FormFieldInput'.""" class FormFieldTextarea(BaseFormField): + """Form field for text area input.""" + rows: _t.Union[int, None] = None + """Number of rows for the text area.""" + cols: _t.Union[int, None] = None + """Number of columns for the text area.""" + initial: _t.Union[str, None] = None + """Initial value for the text area.""" + placeholder: _t.Union[str, None] = None + """Placeholder text for the text area.""" + autocomplete: _t.Union[str, None] = None + """Autocomplete value for the text area.""" + type: _t.Literal['FormFieldTextarea'] = 'FormFieldTextarea' + """The type of the component. Always 'FormFieldTextarea'.""" class FormFieldBoolean(BaseFormField): + """Form field for boolean input.""" + initial: _t.Union[bool, None] = None + """Initial value for the field.""" + mode: _t.Literal['checkbox', 'switch'] = 'checkbox' + """Mode for the boolean field.""" + type: _t.Literal['FormFieldBoolean'] = 'FormFieldBoolean' + """The type of the component. Always 'FormFieldBoolean'.""" class FormFieldFile(BaseFormField): + """Form field for file input.""" + multiple: _t.Union[bool, None] = None + """Whether multiple files can be selected.""" + accept: _t.Union[str, None] = None + """Accepted file types.""" + type: _t.Literal['FormFieldFile'] = 'FormFieldFile' + """The type of the component. Always 'FormFieldFile'.""" class FormFieldSelect(BaseFormField): + """Form field for select input.""" + options: forms.SelectOptions + """Options for the select field.""" + multiple: _t.Union[bool, None] = None + """Whether multiple options can be selected.""" + initial: _t.Union[_t.List[str], str, None] = None + """Initial value for the field.""" + vanilla: _t.Union[bool, None] = None + """Whether to use a vanilla (plain) select element.""" + placeholder: _t.Union[str, None] = None + """Placeholder text for the field.""" + autocomplete: _t.Union[str, None] = None + """Autocomplete value for the field.""" + type: _t.Literal['FormFieldSelect'] = 'FormFieldSelect' + """The type of the component. Always 'FormFieldSelect'.""" class FormFieldSelectSearch(BaseFormField): + """Form field for searchable select input.""" + search_url: str = pydantic.Field(serialization_alias='searchUrl') + """URL to search for options.""" + multiple: _t.Union[bool, None] = None + """Whether multiple options can be selected.""" + initial: _t.Union[forms.SelectOption, None] = None - # time in ms to debounce requests by, defaults to 300ms + """Initial value for the field.""" + debounce: _t.Union[int, None] = None + """Time in milliseconds to debounce requests by. Defaults to 300ms.""" + placeholder: _t.Union[str, None] = None + """Placeholder text for the field.""" + type: _t.Literal['FormFieldSelectSearch'] = 'FormFieldSelectSearch' + """The type of the component. Always 'FormFieldSelectSearch'.""" FormField = _t.Union[ FormFieldInput, FormFieldTextarea, FormFieldBoolean, FormFieldFile, FormFieldSelect, FormFieldSelectSearch ] +"""Union of all form field types.""" class BaseForm(pydantic.BaseModel, ABC, defer_build=True, extra='forbid'): + """Base class for forms.""" + submit_url: str = pydantic.Field(serialization_alias='submitUrl') + """URL to submit the form data to.""" + initial: _t.Union[_t.Dict[str, _types.JsonData], None] = None + """Initial values for the form fields, mapping field names to values.""" + 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 for the form.""" + submit_on_change: _t.Union[bool, None] = pydantic.Field(default=None, serialization_alias='submitOnChange') + """Whether to submit the form on change.""" + submit_trigger: _t.Union[events.PageEvent, None] = pydantic.Field(default=None, serialization_alias='submitTrigger') + """Event to trigger form submission.""" + loading: '_t.Union[_t.List[AnyComponent], None]' = None + """Components to display while the form is submitting.""" + footer: '_t.Union[_t.List[AnyComponent], None]' = None + """Components to display in the form footer.""" + class_name: _class_name.ClassNameField = None + """Optional class name to apply to the form's HTML component.""" @pydantic.model_validator(mode='after') def default_footer(self) -> _te.Self: @@ -102,16 +204,26 @@ def default_footer(self) -> _te.Self: class Form(BaseForm): + """Form component.""" + form_fields: _t.List[FormField] = pydantic.Field(serialization_alias='formFields') + """List of form fields.""" + type: _t.Literal['Form'] = 'Form' + """The type of the component. Always 'Form'.""" FormFieldsModel = _t.TypeVar('FormFieldsModel', bound=pydantic.BaseModel) class ModelForm(BaseForm): + """Form component generated from a Pydantic model.""" + model: _t.Type[pydantic.BaseModel] = pydantic.Field(exclude=True) + """Pydantic model from which to generate the form.""" + type: _t.Literal['ModelForm'] = 'ModelForm' + """The type of the component. Always 'ModelForm'.""" @pydantic.computed_field(alias='formFields') def form_fields(self) -> _t.List[FormField]: diff --git a/src/python-fastui/fastui/components/tables.py b/src/python-fastui/fastui/components/tables.py index 28bfd95f..53d35ce7 100644 --- a/src/python-fastui/fastui/components/tables.py +++ b/src/python-fastui/fastui/components/tables.py @@ -12,12 +12,25 @@ class Table(pydantic.BaseModel, extra='forbid'): + """Table component.""" + data: _t.Sequence[pydantic.SerializeAsAny[_types.DataModel]] + """Sequence of data models to display in the table.""" + 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 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') + """Message to display when there is no data.""" + class_name: _class_name.ClassNameField = None + """Optional class name to apply to the paragraph's HTML component.""" + type: _t.Literal['Table'] = 'Table' + """The type of the component. Always 'Table'.""" @pydantic.model_validator(mode='after') def _fill_columns(self) -> _te.Self: @@ -54,12 +67,25 @@ def __get_pydantic_json_schema__( class Pagination(pydantic.BaseModel): + """Pagination component to use with tables.""" + page: int + """The current page number.""" + page_size: int = pydantic.Field(serialization_alias='pageSize') + """The number of items per page.""" + total: int + """The total number of items.""" + + page_query_param: str = pydantic.Field('page', serialization_alias='pageQueryParam') + """The query parameter to use for the page number.""" + class_name: _class_name.ClassNameField = None + """Optional class name to apply to the pagination's HTML component.""" + type: _t.Literal['Pagination'] = 'Pagination' - page_query_param: str = pydantic.Field('page', serialization_alias='pageQueryParam') + """The type of the component. Always 'Pagination'.""" @pydantic.computed_field(alias='pageCount') def page_count(self) -> int: diff --git a/src/python-fastui/fastui/types.py b/src/python-fastui/fastui/types.py index ea573557..4906ee55 100644 --- a/src/python-fastui/fastui/types.py +++ b/src/python-fastui/fastui/types.py @@ -5,6 +5,7 @@ from pydantic_core import core_schema +# TODO: replace with https://docs.pydantic.dev/dev/api/types/#pydantic.types.JsonValue, maybe? class JsonDataSchema: @staticmethod def __get_pydantic_json_schema__(