fastlifeweb 0.2.3__py3-none-any.whl → 0.3.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.
- fastlife/__init__.py +5 -0
- fastlife/configurator/configurator.py +2 -2
- fastlife/configurator/registry.py +7 -3
- fastlife/configurator/settings.py +1 -1
- fastlife/request/form_data.py +2 -7
- fastlife/security/csrf.py +1 -1
- fastlife/session/middleware.py +0 -1
- fastlife/shared_utils/__init__.py +0 -0
- fastlife/shared_utils/resolver.py +5 -3
- fastlife/templates/A.jinja +5 -0
- fastlife/templates/Button.jinja +27 -0
- fastlife/templates/Checkbox.jinja +2 -0
- fastlife/templates/CsrfToken.jinja +2 -0
- fastlife/templates/Form.jinja +5 -0
- fastlife/templates/H1.jinja +4 -0
- fastlife/templates/H2.jinja +4 -0
- fastlife/templates/Hidden.jinja +6 -0
- fastlife/templates/Input.jinja +8 -0
- fastlife/templates/Label.jinja +3 -0
- fastlife/templates/Option.jinja +2 -0
- fastlife/templates/Radio.jinja +8 -0
- fastlife/templates/Select.jinja +8 -0
- fastlife/templates/__init__.py +0 -0
- fastlife/templates/pydantic_form/Boolean.jinja +7 -0
- fastlife/templates/pydantic_form/Dropdown.jinja +17 -0
- fastlife/templates/pydantic_form/Hidden.jinja +2 -0
- fastlife/templates/pydantic_form/Model.jinja +16 -0
- fastlife/templates/pydantic_form/Sequence.jinja +37 -0
- fastlife/templates/pydantic_form/Text.jinja +12 -0
- fastlife/templates/pydantic_form/Union.jinja +26 -0
- fastlife/templates/pydantic_form/Widget.jinja +10 -0
- fastlife/templating/__init__.py +2 -2
- fastlife/templating/binding.py +6 -7
- fastlife/templating/renderer/__init__.py +2 -2
- fastlife/templating/renderer/abstract.py +9 -7
- fastlife/templating/renderer/jinjax.py +73 -0
- fastlife/templating/renderer/widgets/base.py +14 -13
- fastlife/templating/renderer/widgets/boolean.py +1 -1
- fastlife/templating/renderer/widgets/dropdown.py +7 -6
- fastlife/templating/renderer/widgets/factory.py +14 -7
- fastlife/templating/renderer/widgets/hidden.py +1 -1
- fastlife/templating/renderer/widgets/model.py +8 -10
- fastlife/templating/renderer/widgets/sequence.py +4 -4
- fastlife/templating/renderer/widgets/text.py +1 -1
- fastlife/templating/renderer/widgets/union.py +5 -4
- fastlife/testing/testclient.py +115 -10
- fastlife/views/pydantic_form.py +8 -3
- {fastlifeweb-0.2.3.dist-info → fastlifeweb-0.3.1.dist-info}/METADATA +3 -3
- fastlifeweb-0.3.1.dist-info/RECORD +63 -0
- {fastlifeweb-0.2.3.dist-info → fastlifeweb-0.3.1.dist-info}/WHEEL +1 -1
- fastlife/templates/base.jinja2 +0 -2
- fastlife/templates/globals.jinja2 +0 -83
- fastlife/templates/pydantic_form/boolean.jinja2 +0 -8
- fastlife/templates/pydantic_form/dropdown.jinja2 +0 -18
- fastlife/templates/pydantic_form/hidden.jinja2 +0 -1
- fastlife/templates/pydantic_form/model.jinja2 +0 -16
- fastlife/templates/pydantic_form/sequence.jinja2 +0 -41
- fastlife/templates/pydantic_form/text.jinja2 +0 -16
- fastlife/templates/pydantic_form/union.jinja2 +0 -40
- fastlife/templates/pydantic_form/widget.jinja2 +0 -10
- fastlife/templating/renderer/jinja2.py +0 -110
- fastlifeweb-0.2.3.dist-info/RECORD +0 -50
- {fastlifeweb-0.2.3.dist-info → fastlifeweb-0.3.1.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=Xi89B4L_2jc8Dg5_xfbJ-Z_dtvvE43ynGUdMszpQwtc,5720
|
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.3.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
|
48
|
-
fastlifeweb-0.2.3.dist-info/METADATA,sha256=J6UDPApMl02CtbN2xeVIOep4T0u6qIASw69kUsYbITU,1750
|
49
|
-
fastlifeweb-0.2.3.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
50
|
-
fastlifeweb-0.2.3.dist-info/RECORD,,
|
File without changes
|