fastlifeweb 0.9.7__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,7 @@ from typing import (
21
21
  Union,
22
22
  )
23
23
 
24
- import venusian # type: ignore
24
+ import venusian
25
25
  from fastapi import Depends, FastAPI, Request, Response
26
26
  from fastapi.params import Depends as DependsType
27
27
  from fastapi.staticfiles import StaticFiles
@@ -11,7 +11,7 @@ T = TypeVar("T", bound=BaseModel)
11
11
  """Template type for form serialized model"""
12
12
 
13
13
 
14
- class ModelResult(Generic[T]):
14
+ class FormModel(Generic[T]):
15
15
  prefix: str
16
16
  model: T
17
17
  errors: Mapping[str, str]
@@ -26,9 +26,19 @@ class ModelResult(Generic[T]):
26
26
  self.is_valid = is_valid
27
27
 
28
28
  @classmethod
29
- def default(cls, prefix: str, pydantic_type: Type[T]) -> "ModelResult[T]":
29
+ def default(cls, prefix: str, pydantic_type: Type[T]) -> "FormModel[T]":
30
30
  return cls(prefix, pydantic_type.model_construct(), {})
31
31
 
32
+ def edit(self, pydantic_type: T) -> None:
33
+ """
34
+ Load the form with the given model and consider it as valid for the user.
35
+
36
+ No error will be reported.
37
+ """
38
+ self.model = pydantic_type
39
+ self.errors = {}
40
+ self.is_valid = True
41
+
32
42
  @property
33
43
  def form_data(self) -> Mapping[str, Any]:
34
44
  return {self.prefix: self.model.model_dump()}
@@ -36,7 +46,7 @@ class ModelResult(Generic[T]):
36
46
  @classmethod
37
47
  def from_payload(
38
48
  cls, prefix: str, pydantic_type: Type[T], data: Mapping[str, Any]
39
- ) -> "ModelResult[T]":
49
+ ) -> "FormModel[T]":
40
50
  try:
41
51
  return cls(prefix, pydantic_type(**data.get(prefix, {})), {}, True)
42
52
  except ValidationError as exc:
@@ -75,17 +85,17 @@ class ModelResult(Generic[T]):
75
85
  return cls(prefix, model, errors)
76
86
 
77
87
 
78
- def model(
88
+ def form_model(
79
89
  cls: Type[T], name: str | None = None
80
- ) -> Callable[[Mapping[str, Any]], ModelResult[T]]:
90
+ ) -> Callable[[Mapping[str, Any]], FormModel[T]]:
81
91
  """
82
92
  Build a model, a class of type T based on Pydandic Base Model from a form payload.
83
93
  """
84
94
 
85
- def to_model(data: MappingFormData, registry: Registry) -> ModelResult[T]:
95
+ def to_model(data: MappingFormData, registry: Registry) -> FormModel[T]:
86
96
  prefix = name or registry.settings.form_data_model_prefix
87
97
  if not data:
88
- return ModelResult[T].default(prefix, cls)
89
- return ModelResult[T].from_payload(prefix, cls, data)
98
+ return FormModel[T].default(prefix, cls)
99
+ return FormModel[T].from_payload(prefix, cls, data)
90
100
 
91
101
  return Depends(to_model)
@@ -2,9 +2,10 @@
2
2
  method="",
3
3
  action=None,
4
4
  hx_post=None,
5
+ hx_disable=None,
5
6
  #}
6
- <form class="{{attrs.class or FORM_CLASS}}" {% if hx_post is not none
7
- %}hx-post="{% if hx_post is not true %}{{hx_post}}{% endif%}" {% endif %}{% if action is not none
7
+ <form class="{{attrs.class or FORM_CLASS}}" {% if hx_disable %}hx-disable {% else %}{% if hx_post is not none
8
+ %}hx-post="{% if hx_post is not true %}{{hx_post}}{% endif%}" {% endif %}{% endif %}{% if action is not none
8
9
  %}action="{{action}}" {% endif %}{% if method %}method="{{method}}" {% endif %}>
9
10
  <CsrfToken />
10
11
  {{ content }}
@@ -0,0 +1,11 @@
1
+ {# def
2
+ name,
3
+ value="",
4
+ id="",
5
+ aria_label="",
6
+ placeholder="",
7
+ #}
8
+
9
+ <textarea name="{{name}}" {% if id %}id="{{id}}" {% endif %}{% if aria_label
10
+ %}aria-label="{{aria_label}}" {% endif %} {% if placeholder %}placeholder="{{placeholder}}" {% endif %}
11
+ class="{{attrs.class or INPUT_CLASS}}">{{content}}</textarea>
@@ -1,7 +1,8 @@
1
1
  {# def widget, children_widget #}
2
2
 
3
3
  <pydantic_form.Widget :widget="widget" :removable="widget.removable">
4
- <div id="{{widget.id}}" class="m-4">
4
+ <div id="{{widget.id}}"{% if widget.nested %} class="m-4"{%endif%}>
5
+ {% if widget.nested %}
5
6
  <Details>
6
7
  <Summary :id="widget.id + '-summary'">
7
8
  <H3 :class="H3_SUMMARY_CLASS">{{widget.title}}</H3>
@@ -13,5 +14,10 @@
13
14
  {% endfor %}
14
15
  </div>
15
16
  </Details>
17
+ {% else %}
18
+ {% for child in children_widget %}
19
+ {{ child }}
20
+ {% endfor %}
21
+ {% endif %}
16
22
  </div>
17
23
  </pydantic_form.Widget>
@@ -18,15 +18,16 @@
18
18
  </script>
19
19
 
20
20
  <div id="{{widget.id}}-content" class="m-4" data-length="{{children_widgets|length|string}}">
21
- {% for child in children_widgets %}
22
- {% set container_id = widget.id + "-container" %}
21
+ {% set container_id = widget.id + "-children-container" %}
23
22
  <div id="{{container_id}}">
23
+ {% for child in children_widgets %}
24
24
  {{ child }}
25
+ {% endfor%}
25
26
  </div>
26
- {% endfor%}
27
27
  </div>
28
+
28
29
  <div>
29
- {% set container_id = "#" + widget.id + "-container" %}
30
+ {% set container_id = "#" + widget.id + "-children-container" %}
30
31
  {% set add_id = widget.id + "-add" %}
31
32
  {% set vals = 'js:{"name": '+ fnGetName + '(), "token": "' + type.token + '", "removable": true}' %}
32
33
  <Button type="button" :hx-target="container_id" hx-swap="beforeend" :id="add_id" :hx-vals="vals" :hx-get="type.url">
@@ -0,0 +1,10 @@
1
+ {# def widget #}
2
+ <pydantic_form.Widget :widget="widget" :removable="widget.removable">
3
+ <div class="pt-4">
4
+ <Label :for="widget.id">{{widget.title}}</Label>
5
+ <pydantic_form.Error :text="widget.error" />
6
+ <Textarea :name="widget.name" :id="widget.id" :aria-label="widget.aria_label">{% for v in widget.value%}{{v}}
7
+ {% endfor %}</Textarea>
8
+ <pydantic_form.Hint :text="widget.hint" />
9
+ </div>
10
+ </pydantic_form.Widget>
@@ -3,7 +3,7 @@
3
3
  <div id="{{container_id}}">
4
4
  {{ content }}
5
5
  {% if removable %}
6
- <Button type="button" :onclick={{"document.getElementById('" + container_id + "').remove()"}}>
6
+ <Button type="button" :onclick={{"document.getElementById('" + container_id + "').remove()" }}>
7
7
  Remove
8
8
  </Button>
9
9
  {% endif %}
@@ -5,7 +5,7 @@ from fastapi import Request
5
5
  from markupsafe import Markup
6
6
  from pydantic.fields import FieldInfo
7
7
 
8
- from fastlife.request.model_result import ModelResult
8
+ from fastlife.request.form import FormModel
9
9
 
10
10
 
11
11
  class AbstractTemplateRenderer(abc.ABC):
@@ -46,7 +46,7 @@ class AbstractTemplateRenderer(abc.ABC):
46
46
 
47
47
  @abc.abstractmethod
48
48
  def pydantic_form(
49
- self, model: ModelResult[Any], *, token: Optional[str] = None
49
+ self, model: FormModel[Any], *, token: Optional[str] = None
50
50
  ) -> Markup:
51
51
  """
52
52
  Render an http form from a given model.
@@ -5,7 +5,7 @@ from jinjax.catalog import Catalog
5
5
  from markupsafe import Markup
6
6
  from pydantic.fields import FieldInfo
7
7
 
8
- from fastlife.request.model_result import ModelResult
8
+ from fastlife.request.form import FormModel
9
9
  from fastlife.templating.renderer.widgets.factory import WidgetFactory
10
10
 
11
11
  if TYPE_CHECKING:
@@ -69,7 +69,7 @@ class JinjaxRenderer(AbstractTemplateRenderer):
69
69
  )
70
70
 
71
71
  def pydantic_form(
72
- self, model: ModelResult[Any], *, token: Optional[str] = None
72
+ self, model: FormModel[Any], *, token: Optional[str] = None
73
73
  ) -> Markup:
74
74
  return WidgetFactory(self, token).get_markup(model)
75
75
 
@@ -23,6 +23,8 @@ class Widget(abc.ABC, Generic[T]):
23
23
  "variable name, nested variables have dots"
24
24
  title: str
25
25
  "Human title for the widget"
26
+ hint: str
27
+ "A help message for the the widget"
26
28
  aria_label: str
27
29
  "Non visible text alternative"
28
30
  token: str
@@ -37,6 +39,7 @@ class Widget(abc.ABC, Generic[T]):
37
39
  value: T | None = None,
38
40
  error: str | None = None,
39
41
  title: str | None = None,
42
+ hint: str | None = None,
40
43
  token: str | None = None,
41
44
  aria_label: str | None = None,
42
45
  removable: bool = False,
@@ -45,6 +48,7 @@ class Widget(abc.ABC, Generic[T]):
45
48
  self.value = value
46
49
  self.error = error
47
50
  self.title = title or name.split(".")[-1]
51
+ self.hint = hint or ""
48
52
  self.aria_label = aria_label or ""
49
53
  self.token = token or secrets.token_urlsafe(4).replace("_", "-")
50
54
  self.removable = removable
@@ -7,6 +7,8 @@ class BooleanWidget(Widget[bool]):
7
7
  name: str,
8
8
  *,
9
9
  title: str | None,
10
+ hint: str | None = None,
11
+ aria_label: str | None = None,
10
12
  value: bool = False,
11
13
  error: str | None = None,
12
14
  removable: bool = False,
@@ -15,6 +17,8 @@ class BooleanWidget(Widget[bool]):
15
17
  super().__init__(
16
18
  name,
17
19
  title=title,
20
+ hint=hint,
21
+ aria_label=aria_label,
18
22
  value=value,
19
23
  error=error,
20
24
  removable=removable,
@@ -29,6 +29,8 @@ class ChecklistWidget(Widget[Sequence[Checkable]]):
29
29
  name: str,
30
30
  *,
31
31
  title: str | None,
32
+ hint: str | None = None,
33
+ aria_label: str | None = None,
32
34
  value: Sequence[Checkable],
33
35
  error: str | None = None,
34
36
  token: str,
@@ -40,6 +42,8 @@ class ChecklistWidget(Widget[Sequence[Checkable]]):
40
42
  error=error,
41
43
  token=token,
42
44
  title=title,
45
+ hint=hint,
46
+ aria_label=aria_label,
43
47
  removable=removable,
44
48
  )
45
49
 
@@ -9,12 +9,13 @@ class DropDownWidget(Widget[str]):
9
9
  name: str,
10
10
  *,
11
11
  title: Optional[str],
12
+ hint: Optional[str] = None,
13
+ aria_label: Optional[str] = None,
12
14
  value: Optional[str] = None,
13
15
  error: str | None = None,
14
16
  options: Sequence[Tuple[str, str]] | Sequence[str],
15
17
  removable: bool = False,
16
18
  token: Optional[str] = None,
17
- hint: Optional[str] = None,
18
19
  ) -> None:
19
20
  super().__init__(
20
21
  name,
@@ -23,6 +24,8 @@ class DropDownWidget(Widget[str]):
23
24
  title=title,
24
25
  token=token,
25
26
  removable=removable,
27
+ hint=hint,
28
+ aria_label=aria_label,
26
29
  )
27
30
  self.options: list[dict[str, str]] = []
28
31
  for opt in options:
@@ -30,7 +33,6 @@ class DropDownWidget(Widget[str]):
30
33
  self.options.append({"value": opt[0], "text": opt[1]})
31
34
  else:
32
35
  self.options.append({"value": opt, "text": opt})
33
- self.hint = hint
34
36
 
35
37
  def get_template(self) -> str:
36
38
  return "pydantic_form.Dropdown"
@@ -11,7 +11,7 @@ from markupsafe import Markup
11
11
  from pydantic import BaseModel, EmailStr, SecretStr, ValidationError
12
12
  from pydantic.fields import FieldInfo
13
13
 
14
- from fastlife.request.model_result import ModelResult
14
+ from fastlife.request.form import FormModel
15
15
  from fastlife.shared_utils.infer import is_complex_type, is_union
16
16
  from fastlife.templating.renderer.abstract import AbstractTemplateRenderer
17
17
  from fastlife.templating.renderer.widgets.boolean import BooleanWidget
@@ -20,7 +20,7 @@ from fastlife.templating.renderer.widgets.dropdown import DropDownWidget
20
20
  from fastlife.templating.renderer.widgets.hidden import HiddenWidget
21
21
  from fastlife.templating.renderer.widgets.sequence import SequenceWidget
22
22
 
23
- from .base import Widget, get_title
23
+ from .base import Widget
24
24
  from .model import ModelWidget
25
25
  from .text import TextWidget
26
26
  from .union import UnionWidget
@@ -33,7 +33,7 @@ class WidgetFactory:
33
33
 
34
34
  def get_markup(
35
35
  self,
36
- model: ModelResult[Any],
36
+ model: FormModel[Any],
37
37
  *,
38
38
  removable: bool = False,
39
39
  field: FieldInfo | None = None,
@@ -86,7 +86,12 @@ class WidgetFactory:
86
86
  value=value,
87
87
  removable=removable,
88
88
  title=field.title if field else "",
89
- aria_label=field.description if field else None,
89
+ hint=field.description if field else None,
90
+ aria_label=(
91
+ field.json_schema_extra.get("aria_label") # type:ignore
92
+ if field and field.json_schema_extra
93
+ else None
94
+ ),
90
95
  token=self.token,
91
96
  error=form_errors.get(name),
92
97
  ),
@@ -154,18 +159,18 @@ class WidgetFactory:
154
159
  removable: bool,
155
160
  ) -> Widget[Any]:
156
161
  ret: dict[str, Any] = {}
157
- for key, field in typ.model_fields.items():
162
+ for key, child_field in typ.model_fields.items():
158
163
  child_key = f"{field_name}.{key}" if field_name else key
159
- if field.exclude:
164
+ if child_field.exclude:
160
165
  continue
161
- if field.annotation is None:
166
+ if child_field.annotation is None:
162
167
  raise ValueError( # coverage: ignore
163
- f"Missing annotation for {field} in {child_key}"
168
+ f"Missing annotation for {child_field} in {child_key}"
164
169
  )
165
170
  ret[key] = self.build(
166
- field.annotation,
171
+ child_field.annotation,
167
172
  name=child_key,
168
- field=field,
173
+ field=child_field,
169
174
  value=value.get(key),
170
175
  form_errors=form_errors,
171
176
  removable=False,
@@ -174,9 +179,16 @@ class WidgetFactory:
174
179
  field_name,
175
180
  value=list(ret.values()),
176
181
  removable=removable,
177
- title=get_title(typ),
182
+ title=field.title if field and field.title else "",
183
+ hint=field.description if field else None,
184
+ aria_label=(
185
+ field.json_schema_extra.get("aria_label") # type:ignore
186
+ if field and field.json_schema_extra
187
+ else None
188
+ ),
178
189
  token=self.token,
179
190
  error=form_errors.get(field_name),
191
+ nested=field is not None,
180
192
  )
181
193
 
182
194
  def build_union(
@@ -233,6 +245,12 @@ class WidgetFactory:
233
245
  value=child,
234
246
  children_types=types, # type: ignore
235
247
  title=field.title if field else "",
248
+ hint=field.description if field else None,
249
+ aria_label=(
250
+ field.json_schema_extra.get("aria_label") # type:ignore
251
+ if field and field.json_schema_extra
252
+ else None
253
+ ),
236
254
  token=self.token,
237
255
  removable=removable,
238
256
  error=form_errors.get(field_name),
@@ -264,8 +282,13 @@ class WidgetFactory:
264
282
  ]
265
283
  return SequenceWidget(
266
284
  field_name,
267
- hint=field.description if field else "",
268
285
  title=field.title if field else "",
286
+ hint=field.description if field else None,
287
+ aria_label=(
288
+ field.json_schema_extra.get("aria_label") # type:ignore
289
+ if field and field.json_schema_extra
290
+ else None
291
+ ),
269
292
  value=items,
270
293
  item_type=typ, # type: ignore
271
294
  token=self.token,
@@ -320,6 +343,12 @@ class WidgetFactory:
320
343
  return ChecklistWidget(
321
344
  field_name,
322
345
  title=field.title if field else "",
346
+ hint=field.description if field else None,
347
+ aria_label=(
348
+ field.json_schema_extra.get("aria_label") # type:ignore
349
+ if field and field.json_schema_extra
350
+ else None
351
+ ),
323
352
  token=self.token,
324
353
  value=choices,
325
354
  removable=removable,
@@ -339,6 +368,12 @@ class WidgetFactory:
339
368
  field_name,
340
369
  removable=removable,
341
370
  title=field.title if field else "",
371
+ hint=field.description if field else None,
372
+ aria_label=(
373
+ field.json_schema_extra.get("aria_label") # type:ignore
374
+ if field and field.json_schema_extra
375
+ else None
376
+ ),
342
377
  token=self.token,
343
378
  value=value,
344
379
  error=form_errors.get(field_name),
@@ -355,11 +390,16 @@ class WidgetFactory:
355
390
  ) -> Widget[Any]:
356
391
  return TextWidget(
357
392
  field_name,
358
- hint=field.description if field else "",
359
393
  input_type="email",
360
394
  placeholder=str(field.examples[0]) if field and field.examples else None,
361
395
  removable=removable,
362
396
  title=field.title if field else "",
397
+ hint=field.description if field else None,
398
+ aria_label=(
399
+ field.json_schema_extra.get("aria_label") # type:ignore
400
+ if field and field.json_schema_extra
401
+ else None
402
+ ),
363
403
  token=self.token,
364
404
  value=str(value),
365
405
  error=form_errors.get(field_name),
@@ -376,11 +416,16 @@ class WidgetFactory:
376
416
  ) -> Widget[Any]:
377
417
  return TextWidget(
378
418
  field_name,
379
- hint=field.description if field else "",
380
419
  input_type="password",
381
420
  placeholder=str(field.examples[0]) if field and field.examples else None,
382
421
  removable=removable,
383
422
  title=field.title if field else "",
423
+ hint=field.description if field else None,
424
+ aria_label=(
425
+ field.json_schema_extra.get("aria_label") # type:ignore
426
+ if field and field.json_schema_extra
427
+ else None
428
+ ),
384
429
  token=self.token,
385
430
  value=value.get_secret_value() if isinstance(value, SecretStr) else value,
386
431
  error=form_errors.get(field_name),
@@ -407,6 +452,12 @@ class WidgetFactory:
407
452
  options=choices,
408
453
  removable=removable,
409
454
  title=field.title if field else "",
455
+ hint=field.description if field else None,
456
+ aria_label=(
457
+ field.json_schema_extra.get("aria_label") # type:ignore
458
+ if field and field.json_schema_extra
459
+ else None
460
+ ),
410
461
  token=self.token,
411
462
  value=str(value),
412
463
  error=form_errors.get(field_name),
@@ -427,6 +478,12 @@ class WidgetFactory:
427
478
  options=options, # type: ignore
428
479
  removable=removable,
429
480
  title=field.title if field else "",
481
+ hint=field.description if field else None,
482
+ aria_label=(
483
+ field.json_schema_extra.get("aria_label") # type:ignore
484
+ if field and field.json_schema_extra
485
+ else None
486
+ ),
430
487
  token=self.token,
431
488
  value=str(value),
432
489
  error=form_errors.get(field_name),
@@ -443,11 +500,15 @@ class WidgetFactory:
443
500
  ) -> Widget[Any]:
444
501
  return TextWidget(
445
502
  field_name,
446
- hint=field.description if field else None,
447
503
  placeholder=str(field.examples[0]) if field and field.examples else None,
448
- aria_label=field.description if field else None,
449
- removable=removable,
450
504
  title=field.title if field else "",
505
+ hint=field.description if field else None,
506
+ aria_label=(
507
+ field.json_schema_extra.get("aria_label") # type:ignore
508
+ if field and field.json_schema_extra
509
+ else None
510
+ ),
511
+ removable=removable,
451
512
  token=self.token,
452
513
  value=str(value),
453
514
  error=form_errors.get(field_name),
@@ -1,3 +1,4 @@
1
+ from typing import Any
1
2
  from .base import Widget
2
3
 
3
4
 
@@ -8,6 +9,7 @@ class HiddenWidget(Widget[str]):
8
9
  *,
9
10
  value: str,
10
11
  token: str,
12
+ **kwargs: Any,
11
13
  ) -> None:
12
14
  super().__init__(name, value=value, token=token)
13
15
 
@@ -16,16 +16,22 @@ class ModelWidget(Widget[Sequence[Widget[Any]]]):
16
16
  error: str | None = None,
17
17
  removable: bool,
18
18
  title: str,
19
+ hint: str | None = None,
20
+ aria_label: str | None = None,
19
21
  token: str,
22
+ nested: bool,
20
23
  ):
21
24
  super().__init__(
22
25
  name,
23
26
  title=title,
27
+ hint=hint,
28
+ aria_label=aria_label,
24
29
  value=value,
25
30
  error=error,
26
31
  removable=removable,
27
32
  token=token,
28
33
  )
34
+ self.nested = nested
29
35
 
30
36
  def get_template(self) -> str:
31
37
  return "pydantic_form.Model"
@@ -1,4 +1,4 @@
1
- from typing import Any, Optional, Sequence, Type
1
+ from typing import Any, Sequence, Type
2
2
 
3
3
  from markupsafe import Markup
4
4
 
@@ -12,9 +12,10 @@ class SequenceWidget(Widget[Sequence[Widget[Any]]]):
12
12
  self,
13
13
  name: str,
14
14
  *,
15
- title: Optional[str],
16
- hint: Optional[str],
17
- value: Optional[Sequence[Widget[Any]]],
15
+ title: str | None,
16
+ hint: str | None = None,
17
+ aria_label: str | None = None,
18
+ value: Sequence[Widget[Any]] | None,
18
19
  error: str | None = None,
19
20
  item_type: Type[Any],
20
21
  token: str,
@@ -25,11 +26,12 @@ class SequenceWidget(Widget[Sequence[Widget[Any]]]):
25
26
  value=value,
26
27
  error=error,
27
28
  title=title,
29
+ hint=hint,
30
+ aria_label=aria_label,
28
31
  token=token,
29
32
  removable=removable,
30
33
  )
31
34
  self.item_type = item_type
32
- self.hint = hint
33
35
 
34
36
  def get_template(self) -> str:
35
37
  return "pydantic_form/Sequence"
@@ -9,27 +9,57 @@ class TextWidget(Widget[str]):
9
9
  name: str,
10
10
  *,
11
11
  title: Optional[str],
12
+ hint: Optional[str] = None,
12
13
  aria_label: Optional[str] = None,
13
14
  placeholder: Optional[str] = None,
14
15
  error: str | None = None,
15
16
  removable: bool = False,
16
17
  value: str = "",
17
18
  token: Optional[str] = None,
18
- hint: Optional[str] = None,
19
19
  input_type: str = "text",
20
20
  ) -> None:
21
21
  super().__init__(
22
22
  name,
23
23
  value=value,
24
24
  title=title,
25
+ hint=hint,
25
26
  aria_label=aria_label,
26
27
  token=token,
27
28
  error=error,
28
29
  removable=removable,
29
30
  )
30
31
  self.placeholder = placeholder or ""
31
- self.hint = hint
32
32
  self.input_type = input_type
33
33
 
34
34
  def get_template(self) -> str:
35
35
  return "pydantic_form.Text"
36
+
37
+
38
+ class TextareaWidget(Widget[str]):
39
+ def __init__(
40
+ self,
41
+ name: str,
42
+ *,
43
+ title: Optional[str],
44
+ hint: Optional[str] = None,
45
+ aria_label: Optional[str] = None,
46
+ placeholder: Optional[str] = None,
47
+ error: str | None = None,
48
+ removable: bool = False,
49
+ value: str = "",
50
+ token: Optional[str] = None,
51
+ ) -> None:
52
+ super().__init__(
53
+ name,
54
+ value=value,
55
+ title=title,
56
+ hint=hint,
57
+ aria_label=aria_label,
58
+ token=token,
59
+ error=error,
60
+ removable=removable,
61
+ )
62
+ self.placeholder = placeholder or ""
63
+
64
+ def get_template(self) -> str:
65
+ return "pydantic_form.Textarea"
@@ -14,6 +14,8 @@ class UnionWidget(Widget[Widget[Any]]):
14
14
  name: str,
15
15
  *,
16
16
  title: Optional[str],
17
+ hint: Optional[str] = None,
18
+ aria_label: Optional[str] = None,
17
19
  value: Optional[Widget[Any]],
18
20
  error: str | None = None,
19
21
  children_types: Sequence[Type[BaseModel]],
@@ -25,6 +27,8 @@ class UnionWidget(Widget[Widget[Any]]):
25
27
  value=value,
26
28
  error=error,
27
29
  title=title,
30
+ hint=hint,
31
+ aria_label=aria_label,
28
32
  token=token,
29
33
  removable=removable,
30
34
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastlifeweb
3
- Version: 0.9.7
3
+ Version: 0.10.0
4
4
  Summary: High-level web framework
5
5
  Home-page: https://github.com/mardiros/fastlife
6
6
  License: BSD-derived
@@ -1,7 +1,7 @@
1
1
  fastlife/__init__.py,sha256=nAeweHnTvWHgDZYuPIWawZvT4juuhvMp4hzF2B-yCJU,317
2
2
  fastlife/configurator/__init__.py,sha256=vMV25HEfuXjCL7DhZukhlB2JSfxwzX2lGbErcZ5b7N0,146
3
3
  fastlife/configurator/base.py,sha256=2ahvTudLmD99YQjnIeGN5JDPCSl3k-mauu7bsSEB5RE,216
4
- fastlife/configurator/configurator.py,sha256=1RzDS-BEQ5MjNv29QKnmRO-j50xJ4Noax-I8tcClRyQ,7238
4
+ fastlife/configurator/configurator.py,sha256=DbeOfecoplix35AvTBsO-WLFXb07c1kvL-1r4hLIy6U,7222
5
5
  fastlife/configurator/registry.py,sha256=FKXCxWlMMGNN1-l5H6QLTCoqP_tWENN84RjkConejWI,1580
6
6
  fastlife/configurator/route_handler.py,sha256=TRsiGM8xQxJvRFbdKZphLK7l37DSfKvvmkEbg-h5EoU,977
7
7
  fastlife/configurator/settings.py,sha256=Go1y7dGfxG5tfdAfysSl6TruBUfD_IGMq0Ks_eFBakE,3718
@@ -13,8 +13,8 @@ fastlife/middlewares/session/middleware.py,sha256=JgXdBlxlm9zIEgXcidbBrMAp5wJVPs
13
13
  fastlife/middlewares/session/serializer.py,sha256=qpVnHQjYTxw3aOnoEOKIjOFJg2z45KjiX5sipWk2gws,1458
14
14
  fastlife/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  fastlife/request/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ fastlife/request/form.py,sha256=aFFdIPZUrs3i9QAwgJw6ajUWMq_A9q_gA3oMxVaXrSg,3506
16
17
  fastlife/request/form_data.py,sha256=mP8ilwRUY2WbktIkRgaJJ2EUjwUMPbSPg29GzwZgT18,3713
17
- fastlife/request/model_result.py,sha256=TRaVkyIE50IzVprncoWUUZd15-y4D3ywyZdx7eh6nFE,3237
18
18
  fastlife/routing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  fastlife/routing/router.py,sha256=hHK4NumgTTXI330ZxQeZBiEfGtBRn4_k_fIh_xaaVtg,338
20
20
  fastlife/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,7 +28,7 @@ fastlife/templates/Button.jinja,sha256=uSDCdK2uYEsLnTUDjKo8qaUKHjddIoDvTMR6QrADO
28
28
  fastlife/templates/Checkbox.jinja,sha256=wljKcufMxSWMCiTpqI5Ugfxn4GF224tJaw0AtM1syPo,236
29
29
  fastlife/templates/CsrfToken.jinja,sha256=7qbPvgiAgR6fgaCnDTLozJgOPSWWUJ_W4VLPGra4ahA,61
30
30
  fastlife/templates/Details.jinja,sha256=JP_otsatCu4w68iIqoMiCwh1S0ymUd_yFqoztdifOVU,166
31
- fastlife/templates/Form.jinja,sha256=mimb2oq1jHt-5wtN-JD2gbcZEN5UiaLFwzuqxl6X5IU,341
31
+ fastlife/templates/Form.jinja,sha256=WUS68Z4BH3kpv2qntPALZ8X259P-FxwjNfABQmSXFgM,409
32
32
  fastlife/templates/H1.jinja,sha256=bx8MnoZKN2C2GbfmX4lgi1P6XRh_g67qb_hWmrodGhs,59
33
33
  fastlife/templates/H2.jinja,sha256=aY2Q_DdHHViqA2nUCPjAqlUgcAqadYnUJnxFnCONydY,59
34
34
  fastlife/templates/H3.jinja,sha256=pkt2APIelNnjTE67XBgr8Futs5mAY7QzmF_isMINim8,59
@@ -46,6 +46,7 @@ fastlife/templates/Summary.jinja,sha256=eATV30yqsz-wASiCNaQzhNNuVfRXNoMgeyjKv4J8
46
46
  fastlife/templates/Table.jinja,sha256=goC3qNsbt1NZgbhhuDRuoJuMKHNRCt_bmK96SY_KF1Y,74
47
47
  fastlife/templates/Tbody.jinja,sha256=K8wwTYcYi8-NYdP4g7MBokvYqJp9QW7DmChADuCa-Gs,35
48
48
  fastlife/templates/Td.jinja,sha256=xF2h-Vie0OnjShxzcLwjrGHgi9hvLdlq4H7Ost7aCAA,65
49
+ fastlife/templates/Textarea.jinja,sha256=q-xb58Gr-vSvs9nQVDb-vDU-5FrTNR9QDAyTw8jK5ag,312
49
50
  fastlife/templates/Tfoot.jinja,sha256=iSHDcwhi7VNql5Yl3jvkrx2WVquT5Zm8to9P3WBlKYQ,35
50
51
  fastlife/templates/Th.jinja,sha256=dhWdbwSWIMpIxWFOIXIX6FhRkeUwwWZr5R7PoTcJZ9w,65
51
52
  fastlife/templates/Thead.jinja,sha256=gp3T4XiEk5zgih0gmmLcVke-Z8AgTol7AQrRtVev480,35
@@ -1669,33 +1670,34 @@ fastlife/templates/pydantic_form/Dropdown.jinja,sha256=B3OlOOIY6trxfqEJZRUutWPbA
1669
1670
  fastlife/templates/pydantic_form/Error.jinja,sha256=Wb5NnVRc4U7ZGKmYV7s4eGenWEug8WK9li48iTlX4cQ,121
1670
1671
  fastlife/templates/pydantic_form/Hidden.jinja,sha256=NF8Od_mSSnRJAxWqsgeD1C5YzqJso-HCeDjGbpHjmlE,86
1671
1672
  fastlife/templates/pydantic_form/Hint.jinja,sha256=O0ZsAQnATcG0a_qLQfrwM6VZHmAw3k1W33WYlEBUas8,123
1672
- fastlife/templates/pydantic_form/Model.jinja,sha256=IRCU9PpqGLv6l7uVpVfAucWsVZbZQBCtfF_lpVMMOfc,490
1673
- fastlife/templates/pydantic_form/Sequence.jinja,sha256=oZMdpKdIvp7h-HPyK3wBFSH0ruHICDsPXsM4WOFxaJc,1420
1673
+ fastlife/templates/pydantic_form/Model.jinja,sha256=mZ7dAaDAdrvmEgL-Xq_GWgsd6rPDGhQvbu2XuoiS3K4,663
1674
+ fastlife/templates/pydantic_form/Sequence.jinja,sha256=0plVNnJ-E_lSv0izjJABqu-4ayP_XLBVRtqE_WVTISY,1443
1674
1675
  fastlife/templates/pydantic_form/Text.jinja,sha256=7OJt4rC11-XCKFKDP8lIsFy6M33pRW9ceD9K-irhiJg,461
1676
+ fastlife/templates/pydantic_form/Textarea.jinja,sha256=1OP2NX8hv1scIQDypz2TcP0AL0CCbGE2uM5zYlty3n8,428
1675
1677
  fastlife/templates/pydantic_form/Union.jinja,sha256=ibSgK04qE_GND8Fm0T5pBsP2LUaVe9lZQgSlVU6Cblk,1080
1676
- fastlife/templates/pydantic_form/Widget.jinja,sha256=xXjesiIfaS2CWv9T11UDVHwBz6JT1FSPB6EWtp98_hA,290
1678
+ fastlife/templates/pydantic_form/Widget.jinja,sha256=S8RE4J0N5sNThJnnuuZPfy1YB0z2boSHfuEizyd8kKo,291
1677
1679
  fastlife/templating/__init__.py,sha256=UY_hSTlJKDZnkSIK-BzRD4_AXOWgHbRuvJsKAS9ljgE,307
1678
1680
  fastlife/templating/binding.py,sha256=noY9QrArJGqB1X80Ny-0zk1Dg6T9mMXahjEcIiHvioo,1648
1679
1681
  fastlife/templating/renderer/__init__.py,sha256=6z7MTmj3-TgP_-cKtjhUypJcXvMOmaWaPHuoROyhobE,231
1680
- fastlife/templating/renderer/abstract.py,sha256=9eQgshUMiVTaMcICr-RtP3Zqpu7Bji_sLif2InrxMek,3519
1682
+ fastlife/templating/renderer/abstract.py,sha256=9-y_1Ci4vyAggO9JUVlfN5dI9j7Uq1P4Mbp9bYFmTS8,3507
1681
1683
  fastlife/templating/renderer/constants.py,sha256=A1DY6D5f32j7exAIgnuiLHbQHaq8BOcFt9xZHQHUBuU,6131
1682
- fastlife/templating/renderer/jinjax.py,sha256=oYMsmrY7ksuDNCRNtuy4W1_meObiNFDNHFteB-cteY0,4042
1684
+ fastlife/templating/renderer/jinjax.py,sha256=hG-TOv92cB83OvyI9PI0PU2RZ3EkbMYE-ip7y9bMEB4,4030
1683
1685
  fastlife/templating/renderer/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1684
- fastlife/templating/renderer/widgets/base.py,sha256=XtD-NRacHMn9Xt_dSfWb1Emk3XEXz5jExglx23Rzzpw,2808
1685
- fastlife/templating/renderer/widgets/boolean.py,sha256=1Cnrs6Sqf7CJURrCRkDL2NDQZWO9dvac350w0PcZtDg,544
1686
- fastlife/templating/renderer/widgets/checklist.py,sha256=Qit-k4RnW9Y3xOyE9BevBRuFZ8XN5jH3x_sI5fWt43Y,1009
1687
- fastlife/templating/renderer/widgets/dropdown.py,sha256=FyPZzrTprLff6YRqZ031J8-KZpBgidTw0BsKY1Qt7Ts,1009
1688
- fastlife/templating/renderer/widgets/factory.py,sha256=iOBuxWlY1d9WfCqcm5f5Ck5NwoK9uDkioCSPyu5HsQo,14787
1689
- fastlife/templating/renderer/widgets/hidden.py,sha256=2fsbTQKsACV0JVYpCjXaQAV7VnQTIBPCi4lJPdWCRHc,308
1690
- fastlife/templating/renderer/widgets/model.py,sha256=BNM6dPPN9jzM26LreeOw7wiCsZun1uSMMFXMNcO2hII,1094
1691
- fastlife/templating/renderer/widgets/sequence.py,sha256=rYXsUZokV3wnKa-26BmgAu7sVCtFf8FdBmhvrnqR-gM,1455
1692
- fastlife/templating/renderer/widgets/text.py,sha256=OWFFA0muZCznrlUrBRRUKVj60TdWtsdgw0bURdOA3lE,879
1693
- fastlife/templating/renderer/widgets/union.py,sha256=xNDctq0SRXfRyMHXL8FgRKyUOUreR1xENnt6onkZJ9I,1797
1686
+ fastlife/templating/renderer/widgets/base.py,sha256=O7fKA35WvkxOofLgSyoz8y2Dc8TBqIHPXKwL52-vui8,2926
1687
+ fastlife/templating/renderer/widgets/boolean.py,sha256=-pRzTjxGfdU1enewP0mm5Ppq979y0BTXtvhCecYQEmA,674
1688
+ fastlife/templating/renderer/widgets/checklist.py,sha256=1TsMX2PI4-ANjt0vFC1r-2GwWGpt78AC4UdOsVbbho4,1139
1689
+ fastlife/templating/renderer/widgets/dropdown.py,sha256=WO_YWI6A1eH6_ZMZfrrPumF5vRYD59h_dA6OTh0BzEg,1084
1690
+ fastlife/templating/renderer/widgets/factory.py,sha256=AOjqV82JzT4xOddHvCqPV5MeXE6ZuPlT6x-f9VyVmNc,17329
1691
+ fastlife/templating/renderer/widgets/hidden.py,sha256=0MgTIBPc7u3jrx9-I1PLaCQKfM2WTftVY7NbXVsBfeY,354
1692
+ fastlife/templating/renderer/widgets/model.py,sha256=ju3CCu_vtK29ffdiG-ODnA-T6mjdb9m6WRfdSqxtBTA,1275
1693
+ fastlife/templating/renderer/widgets/sequence.py,sha256=oEQ7KPMLeCzyteM1D_8U4kDjK_tEquvCpwygsaA4jj4,1515
1694
+ fastlife/templating/renderer/widgets/text.py,sha256=goM7tvkc1yzgalqzNNfsFjXv5Zlr9KzABXC0HFlO5kk,1637
1695
+ fastlife/templating/renderer/widgets/union.py,sha256=fj2HzVQsPpU9WaRrKDYsagFoEB7-KOnLn28q4GAZeuM,1933
1694
1696
  fastlife/testing/__init__.py,sha256=vuqwoNUd3BuIp3fm7nkvmYkIGjIimf5zUGhDkeWrg2s,98
1695
1697
  fastlife/testing/testclient.py,sha256=izNTkFgArIUrdSemNl3iiEDdsiUfnb2TtfKnZi3Jwv8,20546
1696
1698
  fastlife/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1697
1699
  fastlife/views/pydantic_form.py,sha256=KJtH_DK8em0czGPsv0XpzGUFhtycyXdeRldwiU7d_j4,1257
1698
- fastlifeweb-0.9.7.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
1699
- fastlifeweb-0.9.7.dist-info/METADATA,sha256=Wm-IDhXKykc8zqCMA_lTRezj8WN86oXz7MlarEX7Wu0,1885
1700
- fastlifeweb-0.9.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1701
- fastlifeweb-0.9.7.dist-info/RECORD,,
1700
+ fastlifeweb-0.10.0.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
1701
+ fastlifeweb-0.10.0.dist-info/METADATA,sha256=dfGVXkrcbzFCqCtE541S16toxUAn29MSho9nC7Kk1Ls,1886
1702
+ fastlifeweb-0.10.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1703
+ fastlifeweb-0.10.0.dist-info/RECORD,,