fastlifeweb 0.2.2__py3-none-any.whl → 0.3.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.
Files changed (63) hide show
  1. fastlife/__init__.py +5 -0
  2. fastlife/configurator/configurator.py +8 -2
  3. fastlife/configurator/registry.py +7 -3
  4. fastlife/configurator/settings.py +1 -1
  5. fastlife/request/form_data.py +2 -7
  6. fastlife/security/csrf.py +1 -1
  7. fastlife/session/middleware.py +0 -1
  8. fastlife/shared_utils/__init__.py +0 -0
  9. fastlife/shared_utils/resolver.py +5 -3
  10. fastlife/templates/A.jinja +5 -0
  11. fastlife/templates/Button.jinja +27 -0
  12. fastlife/templates/Checkbox.jinja +2 -0
  13. fastlife/templates/CsrfToken.jinja +2 -0
  14. fastlife/templates/Form.jinja +5 -0
  15. fastlife/templates/H1.jinja +4 -0
  16. fastlife/templates/H2.jinja +4 -0
  17. fastlife/templates/Hidden.jinja +6 -0
  18. fastlife/templates/Input.jinja +8 -0
  19. fastlife/templates/Label.jinja +3 -0
  20. fastlife/templates/Option.jinja +2 -0
  21. fastlife/templates/Radio.jinja +8 -0
  22. fastlife/templates/Select.jinja +8 -0
  23. fastlife/templates/__init__.py +0 -0
  24. fastlife/templates/pydantic_form/Boolean.jinja +7 -0
  25. fastlife/templates/pydantic_form/Dropdown.jinja +17 -0
  26. fastlife/templates/pydantic_form/Hidden.jinja +2 -0
  27. fastlife/templates/pydantic_form/Model.jinja +16 -0
  28. fastlife/templates/pydantic_form/Sequence.jinja +37 -0
  29. fastlife/templates/pydantic_form/Text.jinja +12 -0
  30. fastlife/templates/pydantic_form/Union.jinja +26 -0
  31. fastlife/templates/pydantic_form/Widget.jinja +10 -0
  32. fastlife/templating/__init__.py +2 -2
  33. fastlife/templating/binding.py +6 -7
  34. fastlife/templating/renderer/__init__.py +2 -2
  35. fastlife/templating/renderer/abstract.py +9 -7
  36. fastlife/templating/renderer/jinjax.py +73 -0
  37. fastlife/templating/renderer/widgets/base.py +14 -13
  38. fastlife/templating/renderer/widgets/boolean.py +1 -1
  39. fastlife/templating/renderer/widgets/dropdown.py +7 -6
  40. fastlife/templating/renderer/widgets/factory.py +14 -7
  41. fastlife/templating/renderer/widgets/hidden.py +1 -1
  42. fastlife/templating/renderer/widgets/model.py +8 -10
  43. fastlife/templating/renderer/widgets/sequence.py +4 -4
  44. fastlife/templating/renderer/widgets/text.py +1 -1
  45. fastlife/templating/renderer/widgets/union.py +5 -4
  46. fastlife/testing/testclient.py +115 -10
  47. fastlife/views/pydantic_form.py +8 -3
  48. {fastlifeweb-0.2.2.dist-info → fastlifeweb-0.3.0.dist-info}/METADATA +2 -2
  49. fastlifeweb-0.3.0.dist-info/RECORD +63 -0
  50. {fastlifeweb-0.2.2.dist-info → fastlifeweb-0.3.0.dist-info}/WHEEL +1 -1
  51. fastlife/templates/base.jinja2 +0 -2
  52. fastlife/templates/globals.jinja2 +0 -83
  53. fastlife/templates/pydantic_form/boolean.jinja2 +0 -8
  54. fastlife/templates/pydantic_form/dropdown.jinja2 +0 -18
  55. fastlife/templates/pydantic_form/hidden.jinja2 +0 -1
  56. fastlife/templates/pydantic_form/model.jinja2 +0 -16
  57. fastlife/templates/pydantic_form/sequence.jinja2 +0 -41
  58. fastlife/templates/pydantic_form/text.jinja2 +0 -16
  59. fastlife/templates/pydantic_form/union.jinja2 +0 -40
  60. fastlife/templates/pydantic_form/widget.jinja2 +0 -10
  61. fastlife/templating/renderer/jinja2.py +0 -110
  62. fastlifeweb-0.2.2.dist-info/RECORD +0 -50
  63. {fastlifeweb-0.2.2.dist-info → fastlifeweb-0.3.0.dist-info}/LICENSE +0 -0
@@ -1,41 +0,0 @@
1
- {% from "globals.jinja2" import button %}
2
- {% from "pydantic_form/widget.jinja2" import show_widget %}
3
- {% call show_widget(widget) %}
4
- <details id="{{widget.id}}" open>
5
- <summary class="justify-between items-center font-medium cursor-pointer">
6
- <h4 class="inline font-sans text-3xl font-bold">{{widget.title}}</h4>
7
- </summary>
8
- <div>
9
- <script>
10
- function getName() {
11
- const el = document.getElementById("{{widget.id}}-content");
12
- const len = el.dataset.length;
13
- el.dataset.length = parseInt(len) + 1;
14
- return "{{type.name}}." + len;
15
- }
16
- </script>
17
-
18
- <div id="{{widget.id}}-content" class="m-4" data-length="{{children_widgets|length|string}}">
19
- {% for child in children_widgets %}
20
- {% set container_id = widget.id + "-container" %}
21
- <div id="{{container_id}}">
22
- {{ child }}
23
- </div>
24
- {% endfor%}
25
- </div>
26
- <div>
27
- {{
28
- button(
29
- "Add",
30
- type="button",
31
- target="#" + widget.id + "-content",
32
- swap="beforeend",
33
- id=widget.id + "-add",
34
- vals='js:{"name": getName(), "token": "' + type.token + '", "removable": true}',
35
- get=type.url,
36
- )
37
- }}
38
- </div>
39
- </div>
40
- </details>
41
- {% endcall %}
@@ -1,16 +0,0 @@
1
- {% from "globals.jinja2" import button %}
2
- {% from "pydantic_form/widget.jinja2" import show_widget %}
3
- {% call show_widget(widget) %}
4
- <div class="pt-4">
5
- <label for="{{widget.id}}" class="block mb-2 text-base font-bold text-neutral-900 dark:text-white">
6
- {{widget.title}}
7
- </label>
8
- <input name="{{widget.name}}" value="{{widget.value}}" type="{{widget.input_type}}" id="{{widget.id}}" {% if
9
- widget.aria_label %}aria-label="{{widget.aria_label}}" {% endif %} {% if widget.placeholder
10
- %}placeholder="{{widget.placeholder}}" {% endif %}
11
- class="bg-neutral-50 border border-neutral-300 text-neutral-900 text-base rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-neutral-700 dark:border-neutral-600 dark:placeholder-neutral-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
12
- {% if widget.help_text %}
13
- <span class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">{{widget.help_text}}</span>
14
- {% endif %}
15
- </div>
16
- {% endcall %}
@@ -1,40 +0,0 @@
1
- {% from "globals.jinja2" import button %}
2
- {% from "pydantic_form/widget.jinja2" import show_widget %}
3
- {% call show_widget(widget) %}
4
- <div id="{{widget.id}}">
5
- <details open>
6
- <summary class="justify-between items-center font-medium cursor-pointer">
7
- <h4 class="inline font-sans text-3xl font-bold">{{widget.title}}</h4>
8
- </summary>
9
- <div hx-sync="this" id="{{widget.id}}-child">
10
- {% if child %}
11
- {{ child }}
12
- {% else %}
13
- {% for typ in types %}
14
- {{
15
- button(
16
- typ.title,
17
- type="button",
18
- target="closest div",
19
- get=typ.url,
20
- vals=typ.params|tojson,
21
- onclick="document.getElementById('"+ widget.id +"-remove-btn').hidden = false",
22
- )
23
- }}
24
- {% endfor %}
25
- {% endif %}
26
- </div>
27
- {{
28
- button(
29
- "remove",
30
- type="button",
31
- id=widget.id + "-remove-btn",
32
- target="#" + widget.id,
33
- vals=parent_type.params|tojson,
34
- get=parent_type.url,
35
- hidden=not child,
36
- )
37
- }}
38
- </details>
39
- </div>
40
- {% endcall %}
@@ -1,10 +0,0 @@
1
- {%- from "globals.jinja2" import button -%}
2
- {% macro show_widget(widget) %}
3
- {% set container_id = widget.name + "-" + widget.token + "-container" %}
4
- <div id="{{container_id}}">
5
- {{ caller() }}
6
- {% if widget.removable %}
7
- {{ button("Remove", type="button", onclick="document.getElementById('"+ container_id +"').remove()" )}}
8
- {% endif %}
9
- </div>
10
- {% endmacro %}
@@ -1,110 +0,0 @@
1
- from typing import Any, AsyncGenerator, Callable, Mapping, Optional, Sequence, Type
2
-
3
- from fastapi import Request
4
- from jinja2 import Environment, FileSystemLoader, Template
5
- from markupsafe import Markup
6
-
7
- from fastlife.configurator.settings import Settings
8
- from fastlife.shared_utils.resolver import resolve_path
9
-
10
- from .abstract import AbstractTemplateRenderer
11
- from .widgets.factory import WidgetFactory
12
-
13
-
14
- def build_searchpath(template_search_path: str) -> Sequence[str]:
15
- searchpath: list[str] = []
16
- paths = template_search_path.split(",")
17
-
18
- for path in paths:
19
- if ":" in path:
20
- searchpath.append(resolve_path(path))
21
- else:
22
- searchpath.append(path)
23
- return searchpath
24
-
25
-
26
- class Jinja2TemplateRenderer(AbstractTemplateRenderer):
27
- def __init__(self, settings: Settings) -> None:
28
- super().__init__()
29
- self.route_prefix = settings.fastlife_route_prefix
30
- self.env = Environment(
31
- loader=FileSystemLoader(build_searchpath(settings.template_search_path)),
32
- enable_async=True,
33
- )
34
- self.form_data_model_prefix = settings.form_data_model_prefix
35
- self.csrf_token_name = settings.csrf_token_name
36
-
37
- def _get_template(self, template: str, **kwargs: Any) -> Template:
38
- return self.env.get_template(
39
- template,
40
- globals={**kwargs},
41
- )
42
-
43
- def get_csrf_token(self, request: Request) -> Callable[..., str]:
44
- def get_csrf_token() -> str:
45
- return request.scope.get(self.csrf_token_name, "")
46
-
47
- return get_csrf_token
48
-
49
- async def _render_block(self, gen_blocks: AsyncGenerator[str, None]) -> str:
50
- # the typing of Jinja2 async is wrong. It says there is Iterator[str]
51
- # insead of Asyngenerator[str, None]
52
- blocks: list[str] = []
53
- async for value in gen_blocks:
54
- blocks.append(value)
55
- return self.env.concat(blocks) # type: ignore
56
-
57
- async def render_page(self, request: Request, template: str, **kwargs: Any) -> str:
58
- """
59
- Render the the template to build a full page or only a block, in case of
60
- htmx request containing a HX-Target.
61
- """
62
- tpl = self._get_template(
63
- template,
64
- request=request,
65
- get_csrf_token_name=lambda: self.csrf_token_name,
66
- get_csrf_token=self.get_csrf_token(request),
67
- pydantic_form=self.pydantic_form,
68
- )
69
- if "HX-Target" in request.headers:
70
- block_name = request.headers["HX-Target"]
71
- # We render the hx-target as a Jinja2 block of the page,
72
- # we use low lever functions here to build only whats we need.
73
- # Fist, we need to push the macros in the context has it is done in the
74
- # base.jinja2 for compatibility.
75
- macros = self._get_template("globals.jinja2", request=request)
76
- ctx = tpl.new_context(kwargs)
77
- render_macros = macros.root_render_func(ctx)
78
- # the typing of Jinja2 async is wrong here
79
- gen_blocks = await self._render_block(render_macros) # type: ignore
80
- # Now we build the block without the layout.
81
- render_block = tpl.blocks[block_name]
82
- gen_blocks = render_block(ctx) # type: ignore
83
- return await self._render_block(gen_blocks) # type: ignore
84
- else:
85
- # we render the full page
86
- ret = await tpl.render_async(**kwargs)
87
- return ret
88
-
89
- async def render_template(self, template: str, **kwargs: Any) -> str:
90
- tpl = self._get_template(
91
- template,
92
- pydantic_form=self.pydantic_form,
93
- )
94
- ret = await tpl.render_async(**kwargs)
95
- return ret
96
-
97
- async def pydantic_form(
98
- self,
99
- model: Type[Any],
100
- form_data: Optional[Mapping[str, Any]] = None,
101
- name: Optional[str] = None,
102
- token: Optional[str] = None,
103
- removable: bool = False,
104
- ) -> Markup:
105
- return await WidgetFactory(self, token).get_markup(
106
- model,
107
- form_data or {},
108
- prefix=(name or self.form_data_model_prefix),
109
- removable=removable,
110
- )
@@ -1,50 +0,0 @@
1
- fastlife/__init__.py,sha256=__RsTYXTkhcxwHRvT1xWQ5XfygdwBtotbrff71lu-kk,190
2
- fastlife/configurator/__init__.py,sha256=2EPjM1o5iHJIViPwgJjaPQS3pMhE-9dik_mm53eX2DY,91
3
- fastlife/configurator/base.py,sha256=2ahvTudLmD99YQjnIeGN5JDPCSl3k-mauu7bsSEB5RE,216
4
- fastlife/configurator/configurator.py,sha256=SfzHlS6ou2JrUhzm0Q--G8R_XGo4nARQO-Mwzy-Mfn4,5474
5
- fastlife/configurator/registry.py,sha256=fSwlfeqrVzLJmaR2-bAu41rJP8uRMZd-8vZnfsfN2xg,1332
6
- fastlife/configurator/settings.py,sha256=ui40ldu3f6v9ARDVijhNcD2AxF1fWFbfOkkZ7z_2gH0,1394
7
- fastlife/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- fastlife/request/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- fastlife/request/form_data.py,sha256=7gV2hLeFyhPRrNYL_UlRAqM-oqxSu2-VvxKHMv5F900,3849
10
- fastlife/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- fastlife/security/csrf.py,sha256=-2XVfkSpmU1HJB2-_tZiOmqs9KLtib0tb_l1QbIFj34,1005
12
- fastlife/security/policy.py,sha256=5jV5nypy5O8XPFFRJ_bzG8Ltk5xcPWclkz23qiG1_I8,509
13
- fastlife/session/__init__.py,sha256=OnzRCYRzc1lw9JB0UdKi-aRLPNT2n8mM8kwY1P4w7uU,838
14
- fastlife/session/middleware.py,sha256=tz5ANgTKXaIqXGaqNM9czfSRdkfwRChODmKAqWGzP60,3050
15
- fastlife/session/serializer.py,sha256=qpVnHQjYTxw3aOnoEOKIjOFJg2z45KjiX5sipWk2gws,1458
16
- fastlife/shared_utils/infer.py,sha256=_hmGzu84VlZAkdw_owkW8eHknULqH3MLDBlXj7LkEsc,466
17
- fastlife/shared_utils/resolver.py,sha256=cZYcaV27sIC5vLc_xo-yj0S3nTimeY5KRZTanHY6e_Y,1295
18
- fastlife/templates/base.jinja2,sha256=JOHL2bexmNfaRwStdOnd_oLW1YhKnhQbNQfVItMVBkM,112
19
- fastlife/templates/globals.jinja2,sha256=XHHU9Btvhxxmz2AG9vKJjGtaPXP2VHwPabxEZkFfY1k,3799
20
- fastlife/templates/pydantic_form/boolean.jinja2,sha256=ICxi1RC4961pIFtcDum3YEBtL5LeuhCpAy67OZwgWpM,322
21
- fastlife/templates/pydantic_form/dropdown.jinja2,sha256=ZjhwJnCHIXAIelpJmsxoC3z98kOF4pHn-ibI2YOQgxY,985
22
- fastlife/templates/pydantic_form/hidden.jinja2,sha256=8ahIUOxzeynxC9LgKzoVbBvtmi_O7hU8pF24LKuiZlE,91
23
- fastlife/templates/pydantic_form/model.jinja2,sha256=qPPDntf7En5QM6v89s7UHi4jdo3eezl2IV84l52TJZ4,489
24
- fastlife/templates/pydantic_form/sequence.jinja2,sha256=k0AplFXFeDYdD3aG6jQZ4F19LakrLAR3sAlmf-yTuWU,1219
25
- fastlife/templates/pydantic_form/text.jinja2,sha256=cQ-p53JtgXZU7mkLFyL_t-VSih8aa2fYr520zTjKXfk,1014
26
- fastlife/templates/pydantic_form/union.jinja2,sha256=PSQaMwZvGyZUb3XRQtE2BDULIN53Ni-CKG55qdiwMjw,1012
27
- fastlife/templates/pydantic_form/widget.jinja2,sha256=5jAAwn5v86ajUt4uBJSJ14sx8YyFawyApJcBB1nCuPE,364
28
- fastlife/templating/__init__.py,sha256=isex4Dx5mc2rKoV7hDDM9b6oud4f4pTZJIAdV_fichI,220
29
- fastlife/templating/binding.py,sha256=92YBhlnYY9fiZStSgRIChp1I-Ob5b5iWReB6szlitTk,2009
30
- fastlife/templating/renderer/__init__.py,sha256=F0q9jIbCwgORFUHWkvmLQwxIo-1tXHgzBgVNHBXPrPA,156
31
- fastlife/templating/renderer/abstract.py,sha256=221eO314VuECSZtypnuAUUUcamGbuxrlei2RkRrgEng,760
32
- fastlife/templating/renderer/jinja2.py,sha256=UuzNNs-11DHaqmsbZ9euJxoTUc2JoTpYEfOYKgsEyqE,4184
33
- fastlife/templating/renderer/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- fastlife/templating/renderer/widgets/base.py,sha256=iyox5sKc4ZiAiYIyMMt_L_iga_XzMj0xuqkkOVJCPtM,2462
35
- fastlife/templating/renderer/widgets/boolean.py,sha256=8UkYYHrelL4qrGatFQBfECLbv1ztRbouramFy62PMu8,461
36
- fastlife/templating/renderer/widgets/dropdown.py,sha256=yQ2zbPFdt7NjRo9Cbsq0malexaxkOvLIOpyN4ncGHLA,790
37
- fastlife/templating/renderer/widgets/factory.py,sha256=UlWhi1ZlA5pgjLEh5b1ulUy3WbPE_rAA-k6BNlCqGq4,9460
38
- fastlife/templating/renderer/widgets/hidden.py,sha256=875m4bRZV4DP7FmT2PdDErOKgz4d25veiOy-vCQwCCY,324
39
- fastlife/templating/renderer/widgets/model.py,sha256=CjAf8OEHbHc92JkgBxNdX5MndgMOUEL1fgx8H50scTs,973
40
- fastlife/templating/renderer/widgets/sequence.py,sha256=b2e7YOU4BCASJ46peYtSaaoqeXhrR_yca77P_fApzrQ,1380
41
- fastlife/templating/renderer/widgets/text.py,sha256=6sQ9tlmWVn8-bogSbb8m2gAL-1Lrkb026W5ekez4Jlc,789
42
- fastlife/templating/renderer/widgets/union.py,sha256=pT_Mcrb-_ZTZV3ZPkyQYdEW2AE3PglojXdYaMfrgZ0k,1645
43
- fastlife/testing/__init__.py,sha256=KgTlRI0g8z7HRpL7mD5QgI__LT9Y4QDSzKMlxJG3wNk,67
44
- fastlife/testing/testclient.py,sha256=GBSL9xECrLf4jkFiZCT2h0NajFNBSVuFY4zm5MyolBw,10095
45
- fastlife/views/__init__.py,sha256=nn4B_8YTbTmhGPvSd20yyKK_9Dh1Pfh_Iq7z6iK8-CE,154
46
- fastlife/views/pydantic_form.py,sha256=5fBmw94wYLKuEN-YwqTnCLDDCq-TEzeJNmzWdsB2I3M,887
47
- fastlifeweb-0.2.2.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
48
- fastlifeweb-0.2.2.dist-info/METADATA,sha256=AU7ZtKIfvQIFIaqSJhU5D4Fk5jzo4M-L2Kf5VGpTJBc,1750
49
- fastlifeweb-0.2.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
50
- fastlifeweb-0.2.2.dist-info/RECORD,,