fastlifeweb 0.4.1__py3-none-any.whl → 0.5.1__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.
@@ -25,8 +25,8 @@ def unflatten_struct(
25
25
  csrf_token_name: Optional[str] = None,
26
26
  ) -> Mapping[str, Any] | Sequence[Any]:
27
27
  # we sort to ensure that list index are ordered
28
- formkeys = sorted(flatten_input.keys())
29
- for key in formkeys:
28
+ # formkeys = sorted(flatten_input.keys())
29
+ for key in flatten_input:
30
30
  if csrf_token_name is not None and key == csrf_token_name:
31
31
  continue
32
32
  lkey, sep, rest = key.partition(".")
@@ -78,8 +78,17 @@ async def unflatten_mapping_form_data(
78
78
  request: Request, reg: Registry
79
79
  ) -> Mapping[str, Any]:
80
80
  form_data = await request.form()
81
+ form_data_decode_list: MutableMapping[str, Any] = {}
82
+ for key, val in form_data.multi_items():
83
+ if key in form_data_decode_list:
84
+ if not isinstance(form_data_decode_list, list):
85
+ form_data_decode_list[key] = [form_data_decode_list[key]]
86
+ form_data_decode_list[key].append(val)
87
+ else:
88
+ form_data_decode_list[key] = val
89
+
81
90
  ret = unflatten_struct(
82
- form_data, {}, csrf_token_name=reg.settings.csrf_token_name
91
+ form_data_decode_list, {}, csrf_token_name=reg.settings.csrf_token_name
83
92
  ) # type: ignore
84
93
  return ret # type: ignore
85
94
 
@@ -1,7 +1,8 @@
1
1
  {# def
2
2
  name,
3
- value,
4
3
  id,
4
+ value=None,
5
+ checked=False,
5
6
  class_="""
6
7
  bg-neutral-100
7
8
  border-neutral-300
@@ -17,4 +18,5 @@ focus:ring-2
17
18
  focus:ring-primary-500
18
19
  """
19
20
  #}
20
- <input name="{{name}}" {% if value%}checked{% endif %} type="checkbox" id="{{id}}" class="{{attrs.class or class_}}" />
21
+ <input name="{{name}}" {% if value is not none %}value="{{value}}" {% endif %} {% if checked %}checked{% endif %}
22
+ type="checkbox" id="{{id}}" class="{{attrs.class or class_}}" />
@@ -2,6 +2,6 @@
2
2
  <pydantic_form.Widget widget={widget} removable={widget.removable}>
3
3
  <div class="pt-4">
4
4
  <Label for={widget.id}>{{widget.title}}</Label>
5
- <Checkbox name={widget.name} type="checkbox" id={widget.id} {% if widget.value %}checked{% endif %} />
5
+ <Checkbox name={widget.name} type="checkbox" id={widget.id} checked={widget.value} />
6
6
  </div>
7
7
  </pydantic_form.Widget>
@@ -0,0 +1,20 @@
1
+ {# def
2
+ widget,
3
+ #}
4
+
5
+ <pydantic_form.Widget widget={widget} removable={widget.removable}>
6
+ <div class="pt-4">
7
+ <details open>
8
+ <summary class="justify-between items-center font-medium cursor-pointer">
9
+ <H3>{{widget.title}}</H3>
10
+ </summary>
11
+ <div>
12
+ {% for value in widget.value %}
13
+ <div class="flex items-center mb-4">
14
+ <Checkbox name={widget.name} type="checkbox" id={value.id} value={value.value} checked={value.checked} />
15
+ <Label for={value.id} class="ms-2 text-base text-neutral-900 dark:text-white">{{value.label}}</Label>
16
+ </div>
17
+ {% endfor %}
18
+ </div>
19
+ </div>
20
+ </pydantic_form.Widget>
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from .base import Widget
4
2
 
5
3
 
@@ -8,7 +6,7 @@ class BooleanWidget(Widget[bool]):
8
6
  self,
9
7
  name: str,
10
8
  *,
11
- title: Optional[str],
9
+ title: str | None,
12
10
  value: bool = False,
13
11
  removable: bool = False,
14
12
  token: str,
@@ -0,0 +1,36 @@
1
+ from typing import Sequence
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from .base import Widget
6
+
7
+
8
+ class Checkable(BaseModel):
9
+ label: str
10
+ name: str
11
+ value: str
12
+ token: str
13
+ checked: bool
14
+
15
+ @property
16
+ def id(self) -> str:
17
+ id = f"{self.name}-{self.value}-{self.token}"
18
+ return id.replace(".", "-").replace("_", "-")
19
+
20
+
21
+ class ChecklistWidget(Widget[Sequence[Checkable]]):
22
+ def __init__(
23
+ self,
24
+ name: str,
25
+ *,
26
+ title: str | None,
27
+ value: Sequence[Checkable],
28
+ token: str,
29
+ removable: bool,
30
+ ) -> None:
31
+ super().__init__(
32
+ name, value=value, token=token, title=title, removable=removable
33
+ )
34
+
35
+ def get_template(self) -> str:
36
+ return "pydantic_form.Checklist"
@@ -1,6 +1,7 @@
1
1
  import secrets
2
2
  from collections.abc import MutableSequence, Sequence
3
3
  from decimal import Decimal
4
+ from enum import Enum
4
5
  from types import NoneType
5
6
  from typing import Any, Literal, Mapping, Optional, Type, cast, get_origin
6
7
  from uuid import UUID
@@ -12,6 +13,7 @@ from pydantic.fields import FieldInfo
12
13
  from fastlife.shared_utils.infer import is_complex_type, is_union
13
14
  from fastlife.templating.renderer.abstract import AbstractTemplateRenderer
14
15
  from fastlife.templating.renderer.widgets.boolean import BooleanWidget
16
+ from fastlife.templating.renderer.widgets.checklist import Checkable, ChecklistWidget
15
17
  from fastlife.templating.renderer.widgets.dropdown import DropDownWidget
16
18
  from fastlife.templating.renderer.widgets.hidden import HiddenWidget
17
19
  from fastlife.templating.renderer.widgets.sequence import SequenceWidget
@@ -96,6 +98,12 @@ class WidgetFactory:
96
98
  if type_origin is Literal:
97
99
  return self.build_literal(name, typ, field, value, removable)
98
100
 
101
+ if type_origin is set:
102
+ return self.build_set(name, typ, field, value, removable)
103
+
104
+ if issubclass(typ, Enum): # if it raises here, the type_origin is unknown
105
+ return self.build_enum(name, typ, field, value, removable)
106
+
99
107
  if issubclass(typ, BaseModel): # if it raises here, the type_origin is unknown
100
108
  return self.build_model(name, typ, field, value or {}, removable)
101
109
 
@@ -228,6 +236,55 @@ class WidgetFactory:
228
236
  removable=removable,
229
237
  )
230
238
 
239
+ def build_set(
240
+ self,
241
+ field_name: str,
242
+ field_type: Type[Any],
243
+ field: Optional[FieldInfo],
244
+ value: Optional[Sequence[Any]],
245
+ removable: bool,
246
+ ) -> Widget[Any]:
247
+ choice_wrapper = field_type.__args__[0]
248
+ choices = []
249
+ choice_wrapper_origin = get_origin(choice_wrapper)
250
+ if choice_wrapper_origin:
251
+ if choice_wrapper_origin is Literal:
252
+ litchoice: list[str] = choice_wrapper.__args__ # type: ignore
253
+ choices = [
254
+ Checkable(
255
+ label=c,
256
+ value=c,
257
+ checked=c in value if value else False, # type: ignore
258
+ name=field_name,
259
+ token=self.token,
260
+ )
261
+ for c in litchoice
262
+ ]
263
+
264
+ else:
265
+ raise NotImplementedError
266
+ elif issubclass(choice_wrapper, Enum):
267
+ choices = [
268
+ Checkable(
269
+ label=e.value,
270
+ value=e.name,
271
+ checked=e.name in value if value else False, # type: ignore
272
+ name=field_name,
273
+ token=self.token,
274
+ )
275
+ for e in choice_wrapper
276
+ ]
277
+ else:
278
+ raise NotImplementedError
279
+
280
+ return ChecklistWidget(
281
+ field_name,
282
+ title=field.title if field else "",
283
+ token=self.token,
284
+ value=choices,
285
+ removable=removable,
286
+ )
287
+
231
288
  def build_boolean(
232
289
  self,
233
290
  field_name: str,
@@ -306,6 +363,24 @@ class WidgetFactory:
306
363
  value=str(value),
307
364
  )
308
365
 
366
+ def build_enum(
367
+ self,
368
+ field_name: str,
369
+ field_type: Type[Any], # an enum subclass
370
+ field: FieldInfo | None,
371
+ value: str | int | float,
372
+ removable: bool,
373
+ ) -> Widget[Any]:
374
+ options = [(item.name, item.value) for item in field_type] # type: ignore
375
+ return DropDownWidget(
376
+ field_name,
377
+ options=options, # type: ignore
378
+ removable=removable,
379
+ title=field.title if field else "",
380
+ token=self.token,
381
+ value=str(value),
382
+ )
383
+
309
384
  def build_simpletype(
310
385
  self,
311
386
  field_name: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastlifeweb
3
- Version: 0.4.1
3
+ Version: 0.5.1
4
4
  Summary: High-level web framework
5
5
  Home-page: https://github.com/mardiros/fastlife
6
6
  License: BSD-derived
@@ -21,7 +21,7 @@ Requires-Dist: beautifulsoup4[testing] (>=4.12.2,<5.0.0)
21
21
  Requires-Dist: behave (>=1.2.6,<2.0.0)
22
22
  Requires-Dist: fastapi (>=0.110.0,<0.111.0)
23
23
  Requires-Dist: itsdangerous (>=2.1.2,<3.0.0)
24
- Requires-Dist: jinjax (>=0.32,<0.33)
24
+ Requires-Dist: jinjax (>=0.34,<0.35)
25
25
  Requires-Dist: markupsafe (>=2.1.3,<3.0.0)
26
26
  Requires-Dist: multidict (>=6.0.5,<7.0.0)
27
27
  Requires-Dist: pydantic (>=2.3.0,<3.0.0)
@@ -6,7 +6,7 @@ fastlife/configurator/registry.py,sha256=1sOicKvwIvLbrzRk9z8yb65YUXxxagJK9AK-2gG
6
6
  fastlife/configurator/settings.py,sha256=ftv5MkNXeyBrvcqxnt2WKtLuzo7ge2_BNx1gX4CcSOE,1489
7
7
  fastlife/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  fastlife/request/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- fastlife/request/form_data.py,sha256=gKXo92ZwsMX6UpMkf4BlNYDiOfwxyzDzItc_lpplAzY,3642
9
+ fastlife/request/form_data.py,sha256=A1mowwOrsQ0yPvfMwh_m9Mw6yoYTgqayibiXNyyOGLM,4049
10
10
  fastlife/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  fastlife/security/csrf.py,sha256=47epJVJtr5X6j-Hog54WCGOoiRLQQHvgBU71iqR1N0A,1025
12
12
  fastlife/security/policy.py,sha256=5jV5nypy5O8XPFFRJ_bzG8Ltk5xcPWclkz23qiG1_I8,509
@@ -18,7 +18,7 @@ fastlife/shared_utils/infer.py,sha256=_hmGzu84VlZAkdw_owkW8eHknULqH3MLDBlXj7LkEs
18
18
  fastlife/shared_utils/resolver.py,sha256=wXQQTB4jf86m4qENhMOkHkWpLJj_T4-_eND_ItTLnTE,1410
19
19
  fastlife/templates/A.jinja,sha256=q71nu4Rq874LG6SykCKv8W-EZeX13NMF0AsLc9FFAP0,677
20
20
  fastlife/templates/Button.jinja,sha256=535UIK4Prunj4f0YZXBmCI0rfOiTr5GJTQkM0XDmtNA,1137
21
- fastlife/templates/Checkbox.jinja,sha256=EbKJ9KmfexegcocxeMtoG1kt-x4bP8Tq3odh8prbJoQ,366
21
+ fastlife/templates/Checkbox.jinja,sha256=XdgtI7GTVjPkxTje9KMILzHqjMFRmTpubtKM8nL7K1o,447
22
22
  fastlife/templates/CsrfToken.jinja,sha256=X2-H0VmukyUV448blh9M8rhr_gN82ar51TnRAwfV3OY,59
23
23
  fastlife/templates/Form.jinja,sha256=h8Y2yrlrTRuLnwqCTDv8eeFgaej2eQiXDvFOx5eEcGg,367
24
24
  fastlife/templates/H1.jinja,sha256=fetjIhZvgw5gm6xJhl_-cnHvrPwuLYE4S1rVYgbqNl8,196
@@ -35,7 +35,8 @@ fastlife/templates/P.jinja,sha256=xEcHIv9dJRpELu_SdqQcftvKrU8z1i_BHTEVO5Mu5dU,16
35
35
  fastlife/templates/Radio.jinja,sha256=51I5n1yjWQ_uLZfzuUUf3C8ngo-KT4dPw29-pjP9uSU,723
36
36
  fastlife/templates/Select.jinja,sha256=GoK2oh4Jl5dAfL2wN718RCwtHaQIslzDCN_nwy6a9oY,480
37
37
  fastlife/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- fastlife/templates/pydantic_form/Boolean.jinja,sha256=XE3NM4vhwQwRUIbjv10t9dI70MzWhicyQ_l6FAOs8-k,298
38
+ fastlife/templates/pydantic_form/Boolean.jinja,sha256=tzEZpgv3t9vEuQr2_NzLvhqyusGm2w7kEkehCRJyG2c,281
39
+ fastlife/templates/pydantic_form/Checklist.jinja,sha256=wv224DG6X-XTpiU0hQl5AjuAsb-MMS7TaMdVaYhEkq4,665
39
40
  fastlife/templates/pydantic_form/Dropdown.jinja,sha256=JBkQp7LYtz6vvW6MyAXyW-6UDZ_xY0Fgd4mpDCQA-wM,500
40
41
  fastlife/templates/pydantic_form/Hidden.jinja,sha256=n6CbTSwZr2E_oY8TO2WPbnrLHBaWfe_CXVCYOYmCfts,83
41
42
  fastlife/templates/pydantic_form/Hint.jinja,sha256=O0ZsAQnATcG0a_qLQfrwM6VZHmAw3k1W33WYlEBUas8,123
@@ -51,9 +52,10 @@ fastlife/templating/renderer/abstract.py,sha256=ksWrz25bmsN4MQJK08OYzoZfiYvAg39Z
51
52
  fastlife/templating/renderer/jinjax.py,sha256=bgHgu6VLA4S8fS7Evu68iPaODdev6v0s6Z79TX8c6Mo,3458
52
53
  fastlife/templating/renderer/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
54
  fastlife/templating/renderer/widgets/base.py,sha256=_MMtrhYvtQgX1EQNku0NSBYwhCDvQ5y8TjnCC2b4CAw,2769
54
- fastlife/templating/renderer/widgets/boolean.py,sha256=bk9bExUDp73is6tvbpC28ploIVjyMecFsxPN8NxnOd8,468
55
+ fastlife/templating/renderer/widgets/boolean.py,sha256=UPQOxIpydP2NzDVUQuNau1DewS2FkRqx5WkWSwEu1kk,436
56
+ fastlife/templating/renderer/widgets/checklist.py,sha256=lR6A1nui6iYnx9E4cNEaxsgNPsZWtEEofh2l3C9qWos,770
55
57
  fastlife/templating/renderer/widgets/dropdown.py,sha256=o3BfD9gDiRPsCPB93xCZSYRo50LRAb_tZOmK4x7zkzY,901
56
- fastlife/templating/renderer/widgets/factory.py,sha256=uEYaxHYygLQ-5eWWjsOlvPM6GmhHW7Nu_w-1l2HRihA,10335
58
+ fastlife/templating/renderer/widgets/factory.py,sha256=59BfKpNIB8T71vZ0RjjcjNjdP0TfSm-WHgXmelyIziY,12850
57
59
  fastlife/templating/renderer/widgets/hidden.py,sha256=2fsbTQKsACV0JVYpCjXaQAV7VnQTIBPCi4lJPdWCRHc,308
58
60
  fastlife/templating/renderer/widgets/model.py,sha256=L7tdmRj0cetM_UusaD2zk5YJtIGBYizCMBKQzFyN_Ko,986
59
61
  fastlife/templating/renderer/widgets/sequence.py,sha256=x6o1L_hOh3WRlW16ZQaeO6H_qtnIitALuK-w7ck0RAw,1347
@@ -63,7 +65,7 @@ fastlife/testing/__init__.py,sha256=KgTlRI0g8z7HRpL7mD5QgI__LT9Y4QDSzKMlxJG3wNk,
63
65
  fastlife/testing/testclient.py,sha256=JhWgruKDeIn0QFmEAXKhUXXsZpTpPi_JWIHhWbWIXg0,16404
64
66
  fastlife/views/__init__.py,sha256=nn4B_8YTbTmhGPvSd20yyKK_9Dh1Pfh_Iq7z6iK8-CE,154
65
67
  fastlife/views/pydantic_form.py,sha256=TyZbVfhOGy3en1a7o4wcRp11War7sqVHxyEjOsRMht0,1092
66
- fastlifeweb-0.4.1.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
67
- fastlifeweb-0.4.1.dist-info/METADATA,sha256=yGbsMj4GuWSO4lRUXOfE01wTMS2KYfMh04qBpu7iL1s,1833
68
- fastlifeweb-0.4.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
69
- fastlifeweb-0.4.1.dist-info/RECORD,,
68
+ fastlifeweb-0.5.1.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
69
+ fastlifeweb-0.5.1.dist-info/METADATA,sha256=kQIE0LHEi25Hg1PXXdYTN-bHE59TB5Www7GwDyRBLF0,1833
70
+ fastlifeweb-0.5.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
71
+ fastlifeweb-0.5.1.dist-info/RECORD,,