fastlifeweb 0.4.1__tar.gz → 0.5.1__tar.gz

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.
Files changed (71) hide show
  1. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/PKG-INFO +2 -2
  2. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/pyproject.toml +2 -2
  3. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/request/form_data.py +12 -3
  4. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Checkbox.jinja +4 -2
  5. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Boolean.jinja +1 -1
  6. fastlifeweb-0.5.1/src/fastlife/templates/pydantic_form/Checklist.jinja +20 -0
  7. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/boolean.py +1 -3
  8. fastlifeweb-0.5.1/src/fastlife/templating/renderer/widgets/checklist.py +36 -0
  9. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/factory.py +75 -0
  10. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/LICENSE +0 -0
  11. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/README.md +0 -0
  12. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/__init__.py +0 -0
  13. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/configurator/__init__.py +0 -0
  14. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/configurator/base.py +0 -0
  15. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/configurator/configurator.py +0 -0
  16. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/configurator/registry.py +0 -0
  17. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/configurator/settings.py +0 -0
  18. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/py.typed +0 -0
  19. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/request/__init__.py +0 -0
  20. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/security/__init__.py +0 -0
  21. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/security/csrf.py +0 -0
  22. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/security/policy.py +0 -0
  23. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/session/__init__.py +0 -0
  24. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/session/middleware.py +0 -0
  25. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/session/serializer.py +0 -0
  26. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/shared_utils/__init__.py +0 -0
  27. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/shared_utils/infer.py +0 -0
  28. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/shared_utils/resolver.py +0 -0
  29. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/A.jinja +0 -0
  30. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Button.jinja +0 -0
  31. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/CsrfToken.jinja +0 -0
  32. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Form.jinja +0 -0
  33. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/H1.jinja +0 -0
  34. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/H2.jinja +0 -0
  35. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/H3.jinja +0 -0
  36. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/H4.jinja +0 -0
  37. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/H5.jinja +0 -0
  38. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/H6.jinja +0 -0
  39. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Hidden.jinja +0 -0
  40. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Input.jinja +0 -0
  41. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Label.jinja +0 -0
  42. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Option.jinja +0 -0
  43. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/P.jinja +0 -0
  44. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Radio.jinja +0 -0
  45. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/Select.jinja +0 -0
  46. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/__init__.py +0 -0
  47. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Dropdown.jinja +0 -0
  48. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Hidden.jinja +0 -0
  49. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Hint.jinja +0 -0
  50. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Model.jinja +0 -0
  51. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Sequence.jinja +0 -0
  52. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Text.jinja +0 -0
  53. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Union.jinja +0 -0
  54. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templates/pydantic_form/Widget.jinja +0 -0
  55. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/__init__.py +0 -0
  56. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/binding.py +0 -0
  57. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/__init__.py +0 -0
  58. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/abstract.py +0 -0
  59. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/jinjax.py +0 -0
  60. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/__init__.py +0 -0
  61. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/base.py +0 -0
  62. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/dropdown.py +0 -0
  63. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/hidden.py +0 -0
  64. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/model.py +0 -0
  65. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/sequence.py +0 -0
  66. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/text.py +0 -0
  67. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/templating/renderer/widgets/union.py +0 -0
  68. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/testing/__init__.py +0 -0
  69. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/testing/testclient.py +0 -0
  70. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/views/__init__.py +0 -0
  71. {fastlifeweb-0.4.1 → fastlifeweb-0.5.1}/src/fastlife/views/pydantic_form.py +0 -0
@@ -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)
@@ -15,13 +15,13 @@ classifiers = [
15
15
  "Topic :: Software Development :: Libraries :: Python Modules",
16
16
  "Topic :: Internet :: WWW/HTTP",
17
17
  ]
18
- version = "0.4.1"
18
+ version = "0.5.1"
19
19
 
20
20
  [tool.poetry.dependencies]
21
21
  python = "^3.11"
22
22
  beautifulsoup4 = {version = "^4.12.2", extras = ["testing"]}
23
23
  fastapi = "^0.110.0"
24
- jinjax = "^0.32"
24
+ jinjax = "^0.34"
25
25
  pydantic = "^2.3.0"
26
26
  pydantic-settings = "^2.0.3"
27
27
  python-multipart = "^0.0.6"
@@ -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,
File without changes
File without changes