fastlifeweb 0.26.1__py3-none-any.whl → 0.27.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.
- CHANGELOG.md +7 -0
- fastlife/__init__.py +1 -2
- fastlife/adapters/jinjax/widgets/base.py +1 -1
- fastlife/adapters/jinjax/widgets/boolean.py +1 -1
- fastlife/adapters/jinjax/widgets/checklist.py +2 -2
- fastlife/adapters/jinjax/widgets/dropdown.py +2 -2
- fastlife/adapters/jinjax/widgets/mfa_code.py +1 -1
- fastlife/adapters/jinjax/widgets/model.py +1 -1
- fastlife/adapters/jinjax/widgets/sequence.py +1 -1
- fastlife/adapters/jinjax/widgets/text.py +12 -6
- fastlife/adapters/jinjax/widgets/union.py +2 -2
- fastlife/components/Form.jinja +1 -1
- fastlife/components/Input.jinja +2 -2
- fastlife/components/Label.jinja +1 -1
- fastlife/config/configurator.py +1 -8
- fastlife/domain/model/request.py +10 -0
- fastlife/domain/model/response.py +40 -0
- fastlife/service/translations.py +34 -22
- fastlife/testing/testclient.py +6 -1
- fastlife/views/pydantic_form.py +5 -0
- {fastlifeweb-0.26.1.dist-info → fastlifeweb-0.27.0.dist-info}/METADATA +1 -1
- {fastlifeweb-0.26.1.dist-info → fastlifeweb-0.27.0.dist-info}/RECORD +25 -24
- {fastlifeweb-0.26.1.dist-info → fastlifeweb-0.27.0.dist-info}/WHEEL +0 -0
- {fastlifeweb-0.26.1.dist-info → fastlifeweb-0.27.0.dist-info}/entry_points.txt +0 -0
- {fastlifeweb-0.26.1.dist-info → fastlifeweb-0.27.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## 0.27.0 - Released on 2025-04-24
|
2
|
+
* Add i18n support on pydantic form.
|
3
|
+
|
4
|
+
## 0.26.2 - Released on 2025-04-20
|
5
|
+
* Add a RedirectResponse class that is htmx friendly and P/R/G pattern friendly.
|
6
|
+
* Fix documentation generation.
|
7
|
+
|
1
8
|
## 0.26.1 - Released on 2025-04-20
|
2
9
|
* Add new helpers for the webtestclient element.
|
3
10
|
|
fastlife/__init__.py
CHANGED
@@ -2,8 +2,6 @@ from importlib import metadata
|
|
2
2
|
|
3
3
|
__version__ = metadata.version("fastlifeweb")
|
4
4
|
|
5
|
-
from fastapi import Response
|
6
|
-
from fastapi.responses import RedirectResponse
|
7
5
|
|
8
6
|
from .adapters.fastapi.form import form_model
|
9
7
|
from .adapters.fastapi.localizer import Localizer
|
@@ -26,6 +24,7 @@ from .config import (
|
|
26
24
|
from .domain.model.asgi import ASGIRequest, ASGIResponse
|
27
25
|
from .domain.model.form import FormModel
|
28
26
|
from .domain.model.request import GenericRequest
|
27
|
+
from .domain.model.response import RedirectResponse, Response
|
29
28
|
from .domain.model.security_policy import (
|
30
29
|
Allowed,
|
31
30
|
Anonymous,
|
@@ -55,7 +55,7 @@ class Widget(JinjaXTemplate, Generic[T]):
|
|
55
55
|
aria_label: str | None = Field(default=None)
|
56
56
|
"Non visible text alternative."
|
57
57
|
token: str = Field(default="")
|
58
|
-
"
|
58
|
+
"Unique token to ensure id are unique in the DOM."
|
59
59
|
removable: bool = Field(default=False)
|
60
60
|
"Indicate that the widget is removable from the dom."
|
61
61
|
|
@@ -16,7 +16,7 @@ class BooleanWidget(Widget[bool]):
|
|
16
16
|
<div class="flex items-center">
|
17
17
|
<Checkbox :name="name" :id="id" :checked="value" value="1" />
|
18
18
|
<Label :for="id" class="ms-2 text-base text-neutral-900 dark:text-white">
|
19
|
-
{{title|safe}}
|
19
|
+
{{ gettext(title)|safe }}
|
20
20
|
</Label>
|
21
21
|
</div>
|
22
22
|
<pydantic_form.Error :text="error" />
|
@@ -40,7 +40,7 @@ class ChecklistWidget(Widget[Sequence[Checkable]]):
|
|
40
40
|
<div class="pt-4">
|
41
41
|
<Details>
|
42
42
|
<Summary :id="id + '-summary'">
|
43
|
-
<H3 :class="H3_SUMMARY_CLASS">{{title}}</H3>
|
43
|
+
<H3 :class="H3_SUMMARY_CLASS">{{ gettext(title) }}</H3>
|
44
44
|
<pydantic_form.Error :text="error" />
|
45
45
|
</Summary>
|
46
46
|
<div>
|
@@ -52,7 +52,7 @@ class ChecklistWidget(Widget[Sequence[Checkable]]):
|
|
52
52
|
:checked="value.checked" />
|
53
53
|
<Label :for="value.id"
|
54
54
|
class="ms-2 text-base text-neutral-900 dark:text-white">
|
55
|
-
{{- value.label -}}
|
55
|
+
{{- gettext(value.label) -}}
|
56
56
|
</Label>
|
57
57
|
<pydantic_form.Error :text="value.error" />
|
58
58
|
</div>
|
@@ -17,12 +17,12 @@ class DropDownWidget(Widget[str]):
|
|
17
17
|
template = """
|
18
18
|
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
19
19
|
<div class="pt-4">
|
20
|
-
<Label :for="id">{{title}}</Label>
|
20
|
+
<Label :for="id">{{ gettext(title) }}</Label>
|
21
21
|
<Select :name="name" :id="id">
|
22
22
|
{%- for opt in options -%}
|
23
23
|
<Option :value="opt.value" id={{id + "-" + opt.value.replace(" ", " -")}}
|
24
24
|
:selected="value==opt.value">
|
25
|
-
{{- opt.text -}}
|
25
|
+
{{- gettext(opt.text) -}}
|
26
26
|
</Option>
|
27
27
|
{%- endfor -%}
|
28
28
|
</Select>
|
@@ -11,7 +11,7 @@ class MFACodeWidget(Widget[str]):
|
|
11
11
|
template = """
|
12
12
|
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
13
13
|
<div class="pt-4">
|
14
|
-
<Label :for="id">{{title}}</Label>
|
14
|
+
<Label :for="id">{{ gettext(title) }}</Label>
|
15
15
|
<pydantic_form.Error :text="error" />
|
16
16
|
<Input :name="name" type="text" :id="id" inputmode="numeric"
|
17
17
|
autocomplete="one-time-code" :autofocus="autofocus"
|
@@ -17,7 +17,7 @@ class ModelWidget(Widget[Sequence[TWidget]]):
|
|
17
17
|
{% if nested %}
|
18
18
|
<Details>
|
19
19
|
<Summary :id="id + '-summary'">
|
20
|
-
<H3 :class="H3_SUMMARY_CLASS">{{title}}</H3>
|
20
|
+
<H3 :class="H3_SUMMARY_CLASS">{{ gettext(title) }}</H3>
|
21
21
|
<pydantic_form.Error :text="error" />
|
22
22
|
</Summary>
|
23
23
|
<div>
|
@@ -14,7 +14,7 @@ class SequenceWidget(Widget[Sequence[TWidget]]):
|
|
14
14
|
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
15
15
|
<Details :id="id">
|
16
16
|
<Summary :id="id + '-summary'">
|
17
|
-
<H3 :class="H3_SUMMARY_CLASS">{{title}}</H3>
|
17
|
+
<H3 :class="H3_SUMMARY_CLASS">{{ gettext(title) }}</H3>
|
18
18
|
<pydantic_form.Error :text="error" />
|
19
19
|
</Summary>
|
20
20
|
<div>
|
@@ -15,7 +15,7 @@ class TextWidget(Widget[Builtins]):
|
|
15
15
|
template = """
|
16
16
|
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
17
17
|
<div class="pt-4">
|
18
|
-
<Label :for="id">{{title}}</Label>
|
18
|
+
<Label :for="id">{{ gettext(title) }}</Label>
|
19
19
|
<pydantic_form.Error :text="error" />
|
20
20
|
<Input :name="name" :value="value" :type="input_type" :id="id"
|
21
21
|
:aria-label="aria_label" :placeholder="placeholder"
|
@@ -26,19 +26,22 @@ class TextWidget(Widget[Builtins]):
|
|
26
26
|
"""
|
27
27
|
|
28
28
|
input_type: str = Field(default="text")
|
29
|
+
"""type attribute for the Input component."""
|
29
30
|
placeholder: str | None = Field(default=None)
|
31
|
+
"""placeholder attribute for the Input component."""
|
30
32
|
autocomplete: str | None = Field(default=None)
|
33
|
+
"""autocomplete attribute for the Input component."""
|
31
34
|
|
32
35
|
|
33
36
|
class PasswordWidget(Widget[SecretStr]):
|
34
37
|
"""
|
35
|
-
Widget for
|
38
|
+
Widget for password fields.
|
36
39
|
"""
|
37
40
|
|
38
41
|
template = """
|
39
42
|
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
40
43
|
<div class="pt-4">
|
41
|
-
<Label :for="id">{{title}}</Label>
|
44
|
+
<Label :for="id">{{ gettext(title) }}</Label>
|
42
45
|
<pydantic_form.Error :text="error" />
|
43
46
|
<Password :name="name" :type="input_type" :id="id"
|
44
47
|
autocomplete={{
|
@@ -51,8 +54,13 @@ class PasswordWidget(Widget[SecretStr]):
|
|
51
54
|
"""
|
52
55
|
|
53
56
|
input_type: str = Field(default="password")
|
57
|
+
"""type attribute for the Input component."""
|
54
58
|
placeholder: str | None = Field(default=None)
|
59
|
+
"""placeholder attribute for the Input component."""
|
55
60
|
new_password: bool = Field(default=False)
|
61
|
+
"""
|
62
|
+
Adapt autocomplete behavior for browsers to hint existing or generate password.
|
63
|
+
"""
|
56
64
|
|
57
65
|
|
58
66
|
class TextareaWidget(Widget[str | Sequence[str]]):
|
@@ -81,7 +89,7 @@ class TextareaWidget(Widget[str | Sequence[str]]):
|
|
81
89
|
template = """
|
82
90
|
<pydantic_form.Widget :widget_id="id" :removable="removable">
|
83
91
|
<div class="pt-4">
|
84
|
-
<Label :for="id">{{title}}</Label>
|
92
|
+
<Label :for="id">{{ gettext(title) }}</Label>
|
85
93
|
<pydantic_form.Error :text="error" />
|
86
94
|
<Textarea :name="name" :id="id" :aria-label="aria_label">
|
87
95
|
{%- if value is string -%}
|
@@ -94,5 +102,3 @@ class TextareaWidget(Widget[str | Sequence[str]]):
|
|
94
102
|
</div>
|
95
103
|
</pydantic_form.Widget>
|
96
104
|
"""
|
97
|
-
|
98
|
-
placeholder: str = Field(default="")
|
@@ -23,7 +23,7 @@ class UnionWidget(Widget[TWidget]):
|
|
23
23
|
<div id="{{id}}">
|
24
24
|
<Details>
|
25
25
|
<Summary :id="id + '-union-summary'">
|
26
|
-
<H3 :class="H3_SUMMARY_CLASS">{{title}}</H3>
|
26
|
+
<H3 :class="H3_SUMMARY_CLASS">{{ gettext(title) }}</H3>
|
27
27
|
<pydantic_form.Error :text="error" />
|
28
28
|
</Summary>
|
29
29
|
<div hx-sync="this" id="{{id}}-child">
|
@@ -37,7 +37,7 @@ class UnionWidget(Widget[TWidget]):
|
|
37
37
|
:hx-vals="typ.params|tojson"
|
38
38
|
:id="typ.id"
|
39
39
|
onclick={{ "document.getElementById('" + id + "-remove-btn').hidden=false" }}
|
40
|
-
:class="SECONDARY_BUTTON_CLASS">{{typ.title}}</Button>
|
40
|
+
:class="SECONDARY_BUTTON_CLASS">{{ gettext(typ.title) }}</Button>
|
41
41
|
{% endfor %}
|
42
42
|
{% endif %}
|
43
43
|
</div>
|
fastlife/components/Form.jinja
CHANGED
fastlife/components/Input.jinja
CHANGED
@@ -42,8 +42,8 @@
|
|
42
42
|
|
43
43
|
<input name="{{name}}" value="{{value|default('')}}" type="{{type}}"
|
44
44
|
{%- if id %} id="{{id}}" {%- endif %}
|
45
|
-
{%- if aria_label %} aria-label="{{aria_label}}" {%- endif %}
|
46
|
-
{%- if placeholder %} placeholder="{{placeholder}}" {%- endif %}
|
45
|
+
{%- if aria_label %} aria-label="{{ gettext(aria_label) }}" {%- endif %}
|
46
|
+
{%- if placeholder %} placeholder="{{ gettext(placeholder) }}" {%- endif %}
|
47
47
|
{%- if inputmode %} inputmode="{{inputmode}}" {%- endif %}
|
48
48
|
{%- if autocomplete %} autocomplete="{{autocomplete}}" {%- endif %}
|
49
49
|
class="{{attrs.class or INPUT_CLASS}}"
|
fastlife/components/Label.jinja
CHANGED
fastlife/config/configurator.py
CHANGED
@@ -493,14 +493,7 @@ class GenericConfigurator(Generic[TRegistry]):
|
|
493
493
|
custom_globals[key] = val
|
494
494
|
return {
|
495
495
|
"request": request,
|
496
|
-
|
497
|
-
"ngettext": lczr.ngettext,
|
498
|
-
"dgettext": lczr.dgettext,
|
499
|
-
"dngettext": lczr.dngettext,
|
500
|
-
"pgettext": lczr.pgettext,
|
501
|
-
"dpgettext": lczr.dpgettext,
|
502
|
-
"npgettext": lczr.npgettext,
|
503
|
-
"dnpgettext": lczr.dnpgettext,
|
496
|
+
**lczr.as_dict(),
|
504
497
|
**custom_globals,
|
505
498
|
**request.renderer_globals,
|
506
499
|
}
|
fastlife/domain/model/request.py
CHANGED
@@ -69,3 +69,13 @@ class GenericRequest(ASGIRequest, Generic[TRegistry, TIdentity, TClaimedIdentity
|
|
69
69
|
)
|
70
70
|
|
71
71
|
return await self.security_policy.has_permission(permission)
|
72
|
+
|
73
|
+
def url_path_for(self, name: str, /, **path_params: Any) -> str:
|
74
|
+
"""
|
75
|
+
Return the url pathinfo for the given route and route parameters.
|
76
|
+
|
77
|
+
:param name: the name of the route
|
78
|
+
:param path_params: parameters for the route.
|
79
|
+
"""
|
80
|
+
url_path_provider: Any = self.scope.get("router") or self.scope.get("app")
|
81
|
+
return url_path_provider.url_path_for(name, **path_params)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from collections.abc import Mapping
|
2
|
+
from urllib.parse import quote
|
3
|
+
|
4
|
+
from starlette.background import BackgroundTask
|
5
|
+
from starlette.datastructures import URL
|
6
|
+
from starlette.responses import Response
|
7
|
+
|
8
|
+
|
9
|
+
class RedirectResponse(Response):
|
10
|
+
"""
|
11
|
+
A redirect response for Post/Redirect/Get pattern.
|
12
|
+
|
13
|
+
The starlette default value status code is 307, which means that it is used
|
14
|
+
as a way to replay the same query which is definitly not the most used case
|
15
|
+
in web applications.
|
16
|
+
|
17
|
+
This is why the redirect response here is using 303 see other which
|
18
|
+
ensure a GET request will be made for the redirection.
|
19
|
+
|
20
|
+
A new parameter hx_redirect exists in order to set the HX-Redirect header
|
21
|
+
to follow a browser redirection from an ajax query.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
url: str | URL,
|
27
|
+
hx_redirect: bool = False,
|
28
|
+
status_code: int = 303,
|
29
|
+
headers: Mapping[str, str] | None = None,
|
30
|
+
background: BackgroundTask | None = None,
|
31
|
+
):
|
32
|
+
super().__init__(
|
33
|
+
content=b"", status_code=status_code, headers=headers, background=background
|
34
|
+
)
|
35
|
+
self.headers["HX-Redirect" if hx_redirect else "location"] = quote(
|
36
|
+
str(url), safe=":/%#?=@[]!$&'()*+,;"
|
37
|
+
)
|
38
|
+
|
39
|
+
|
40
|
+
__all__ = ["Response", "RedirectResponse"]
|
fastlife/service/translations.py
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
import pathlib
|
4
4
|
from collections import defaultdict
|
5
|
-
from collections.abc import Callable, Iterator
|
5
|
+
from collections.abc import Callable, Iterator, Mapping
|
6
6
|
from gettext import GNUTranslations
|
7
7
|
from io import BufferedReader
|
8
|
+
from typing import Any
|
8
9
|
|
9
10
|
from fastlife.shared_utils.resolver import resolve_path
|
10
11
|
|
@@ -100,10 +101,23 @@ class Localizer:
|
|
100
101
|
self.translations[domain].merge(trans)
|
101
102
|
self.global_translations.merge(trans)
|
102
103
|
|
103
|
-
def
|
104
|
-
return
|
105
|
-
|
106
|
-
|
104
|
+
def as_dict(self) -> Mapping[str, Callable[..., str]]:
|
105
|
+
return {
|
106
|
+
"_": self.gettext,
|
107
|
+
"gettext": self.gettext,
|
108
|
+
"ngettext": self.ngettext,
|
109
|
+
"dgettext": self.dgettext,
|
110
|
+
"dngettext": self.dngettext,
|
111
|
+
"pgettext": self.pgettext,
|
112
|
+
"dpgettext": self.dpgettext,
|
113
|
+
"npgettext": self.npgettext,
|
114
|
+
"dnpgettext": self.dnpgettext,
|
115
|
+
}
|
116
|
+
|
117
|
+
def __call__(self, message: str, /, **mapping: Any) -> str:
|
118
|
+
return self.gettext(message, **mapping)
|
119
|
+
|
120
|
+
def gettext(self, message: str, /, **mapping: Any) -> str:
|
107
121
|
if isinstance(message, TranslatableString):
|
108
122
|
ret = self.translations[message.domain].gettext(message) # type: ignore
|
109
123
|
else:
|
@@ -112,16 +126,12 @@ class Localizer:
|
|
112
126
|
ret = ret.format(**mapping)
|
113
127
|
return ret
|
114
128
|
|
115
|
-
def ngettext(
|
116
|
-
self, singular: str, plural: str, n: int, mapping: dict[str, str] | None = None
|
117
|
-
) -> str:
|
129
|
+
def ngettext(self, singular: str, plural: str, n: int, /, **mapping: Any) -> str:
|
118
130
|
ret = self.global_translations.ngettext(singular, plural, n)
|
119
|
-
mapping_num = {"num": n, **
|
131
|
+
mapping_num = {"num": n, **mapping}
|
120
132
|
return ret.format(**mapping_num)
|
121
133
|
|
122
|
-
def dgettext(
|
123
|
-
self, domain: str, message: str, mapping: dict[str, str] | None = None
|
124
|
-
) -> str:
|
134
|
+
def dgettext(self, domain: str, message: str, /, **mapping: Any) -> str:
|
125
135
|
ret = self.translations[domain].gettext(message)
|
126
136
|
if mapping:
|
127
137
|
ret = ret.format(**mapping)
|
@@ -133,15 +143,14 @@ class Localizer:
|
|
133
143
|
singular: str,
|
134
144
|
plural: str,
|
135
145
|
n: int,
|
136
|
-
|
146
|
+
/,
|
147
|
+
**mapping: Any,
|
137
148
|
) -> str:
|
138
149
|
ret = self.translations[domain].ngettext(singular, plural, n)
|
139
|
-
mapping_num = {"num": n, **
|
150
|
+
mapping_num = {"num": n, **mapping}
|
140
151
|
return ret.format(**mapping_num)
|
141
152
|
|
142
|
-
def pgettext(
|
143
|
-
self, context: str, message: str, mapping: dict[str, str] | None = None
|
144
|
-
) -> str:
|
153
|
+
def pgettext(self, context: str, message: str, /, **mapping: Any) -> str:
|
145
154
|
ret = self.global_translations.pgettext(context, message)
|
146
155
|
if mapping:
|
147
156
|
ret = ret.format(**mapping)
|
@@ -152,7 +161,8 @@ class Localizer:
|
|
152
161
|
domain: str,
|
153
162
|
context: str,
|
154
163
|
message: str,
|
155
|
-
|
164
|
+
/,
|
165
|
+
**mapping: Any,
|
156
166
|
) -> str:
|
157
167
|
ret = self.translations[domain].pgettext(context, message)
|
158
168
|
if mapping:
|
@@ -165,10 +175,11 @@ class Localizer:
|
|
165
175
|
singular: str,
|
166
176
|
plural: str,
|
167
177
|
n: int,
|
168
|
-
|
178
|
+
/,
|
179
|
+
**mapping: Any,
|
169
180
|
) -> str:
|
170
181
|
ret = self.global_translations.npgettext(context, singular, plural, n)
|
171
|
-
mapping_num = {"num": n, **
|
182
|
+
mapping_num = {"num": n, **mapping}
|
172
183
|
return ret.format(**mapping_num)
|
173
184
|
|
174
185
|
def dnpgettext(
|
@@ -178,10 +189,11 @@ class Localizer:
|
|
178
189
|
singular: str,
|
179
190
|
plural: str,
|
180
191
|
n: int,
|
181
|
-
|
192
|
+
/,
|
193
|
+
**mapping: Any,
|
182
194
|
) -> str:
|
183
195
|
ret = self.translations[domain].npgettext(context, singular, plural, n)
|
184
|
-
mapping_num = {"num": n, **
|
196
|
+
mapping_num = {"num": n, **mapping}
|
185
197
|
return ret.format(**mapping_num)
|
186
198
|
|
187
199
|
|
fastlife/testing/testclient.py
CHANGED
@@ -158,9 +158,14 @@ class WebTestClient:
|
|
158
158
|
method = "GET"
|
159
159
|
headers = None
|
160
160
|
content = None
|
161
|
+
url = resp.headers.get("location")
|
162
|
+
if "HX-Redirect" in resp.headers:
|
163
|
+
# Redirection requested to the browser from an AJAX request.
|
164
|
+
url = resp.headers["HX-Redirect"]
|
165
|
+
assert url, "Redirect response without a redirection"
|
161
166
|
return self.request(
|
162
167
|
method=method,
|
163
|
-
url=
|
168
|
+
url=url,
|
164
169
|
content=content,
|
165
170
|
headers=headers,
|
166
171
|
max_redirects=max_redirects - 1,
|
fastlife/views/pydantic_form.py
CHANGED
@@ -31,6 +31,11 @@ async def show_widget(
|
|
31
31
|
field = FieldInfo(title=title)
|
32
32
|
# FIXME: .jinja should not be hardcoded
|
33
33
|
renderer = cast(JinjaxRenderer, request.registry.get_renderer(".jinja")(request))
|
34
|
+
lczr = request.registry.localizer(request.locale_name)
|
35
|
+
renderer.globals = {
|
36
|
+
"request": request,
|
37
|
+
**lczr.as_dict(),
|
38
|
+
}
|
34
39
|
data = renderer.pydantic_form_field(
|
35
40
|
model=model_cls, # type: ignore
|
36
41
|
name=name,
|
@@ -1,5 +1,5 @@
|
|
1
|
-
CHANGELOG.md,sha256=
|
2
|
-
fastlife/__init__.py,sha256=
|
1
|
+
CHANGELOG.md,sha256=SaMUOfsgJvjHaEzgvgmtggz0FY0YEqltXogCMACKrvc,8904
|
2
|
+
fastlife/__init__.py,sha256=Fe8JiQyKIN1WGagUGFct-QBW8-Ku5vXhc_7BkFUGcWk,2475
|
3
3
|
fastlife/adapters/__init__.py,sha256=imPD1hImpgrYkvUJRhHA5kVyGAua7VbP2WGkhSWKJT8,93
|
4
4
|
fastlife/adapters/fastapi/__init__.py,sha256=1goV1FGFP04TGyskJBLKZam4Gvt1yoAvLMNs4ekWSSQ,243
|
5
5
|
fastlife/adapters/fastapi/form.py,sha256=csxsDI6RK-g41pMwFhaVQCLDhF7dAZzgUp-VcrC3NFY,823
|
@@ -32,16 +32,16 @@ fastlife/adapters/jinjax/widget_factory/set_builder.py,sha256=kwtVLATkoOFTcKBKHk
|
|
32
32
|
fastlife/adapters/jinjax/widget_factory/simpletype_builder.py,sha256=olP66B9AMY1X8fgEAxhMdozWN_w1TtcIAIW6uPJRSng,1570
|
33
33
|
fastlife/adapters/jinjax/widget_factory/union_builder.py,sha256=qQOK3Y4I0Tg0XOzU_DwseaaKRmqQ7ORMfFyIHd6oysU,2841
|
34
34
|
fastlife/adapters/jinjax/widgets/__init__.py,sha256=HERnX9xiXUbTDz3XtlnHWABTBjhIq_kkBgWs5E6ZIMY,42
|
35
|
-
fastlife/adapters/jinjax/widgets/base.py,sha256=
|
36
|
-
fastlife/adapters/jinjax/widgets/boolean.py,sha256=
|
37
|
-
fastlife/adapters/jinjax/widgets/checklist.py,sha256=
|
38
|
-
fastlife/adapters/jinjax/widgets/dropdown.py,sha256=
|
35
|
+
fastlife/adapters/jinjax/widgets/base.py,sha256=M1Mu4ArU_YCxRrhisaMMzjKMNCw7npX5DRfdZyVKuks,4120
|
36
|
+
fastlife/adapters/jinjax/widgets/boolean.py,sha256=gHErI8tp_6J3kuqjL4-i1dw-UuR65zDsOCcVKcg_s4E,624
|
37
|
+
fastlife/adapters/jinjax/widgets/checklist.py,sha256=lbt2HL8-ie8FxHEXIx9etxfnnDWi-hfM6NXddLlS1fY,1767
|
38
|
+
fastlife/adapters/jinjax/widgets/dropdown.py,sha256=zcm1M7EQLISWr4sbZDinvUR_mgmdezrLHdC7wA0dQsA,1478
|
39
39
|
fastlife/adapters/jinjax/widgets/hidden.py,sha256=IKcVYs6NjN8YjW-UTr3DRBong6Wrc0QLgcp8U9JoQmE,638
|
40
|
-
fastlife/adapters/jinjax/widgets/mfa_code.py,sha256=
|
41
|
-
fastlife/adapters/jinjax/widgets/model.py,sha256=
|
42
|
-
fastlife/adapters/jinjax/widgets/sequence.py,sha256=
|
43
|
-
fastlife/adapters/jinjax/widgets/text.py,sha256=
|
44
|
-
fastlife/adapters/jinjax/widgets/union.py,sha256=
|
40
|
+
fastlife/adapters/jinjax/widgets/mfa_code.py,sha256=dI0CKlx2jCwujfnud_uIjnBCHvlG_gtapL1Iuan5wVg,744
|
41
|
+
fastlife/adapters/jinjax/widgets/model.py,sha256=STojkUMfCP1kyPFzzWS-MIZOoxAEFHTIW_jxFnODPb4,1309
|
42
|
+
fastlife/adapters/jinjax/widgets/sequence.py,sha256=aL93-ytj6nlbT8vumt3br6Jq8D97iiCXU3eQXrGYuG0,2577
|
43
|
+
fastlife/adapters/jinjax/widgets/text.py,sha256=XKpoLoBNsvQhHrezKCEcXlF9iQzuclXGaeFu6uq9_5A,3390
|
44
|
+
fastlife/adapters/jinjax/widgets/union.py,sha256=fNWmAXNEPkDbP14q6HmlD65L7q5_JmspZ_IG_SqB_NM,2624
|
45
45
|
fastlife/assets/dist.css,sha256=d2ez-igscOaeYtJQc2FQuXlN17cYX13sUansFdg_kdA,19753
|
46
46
|
fastlife/assets/source.css,sha256=0KtDcsKHj9LOcqNR1iv9pACwNBaNWkieEDqqjkgNL_s,47
|
47
47
|
fastlife/components/A.jinja,sha256=MDNJ2auIeYbpNeErvJdlGid4nIKfbi85ArmMgChsCJU,1384
|
@@ -49,7 +49,7 @@ fastlife/components/Button.jinja,sha256=itKU-ct45XissU33yfmTekyHsNe00fr4RQL-e9cx
|
|
49
49
|
fastlife/components/Checkbox.jinja,sha256=g62A1LR8TaN60h94pE2e5l9_eMmgnhVVE9HVCQtVVMo,748
|
50
50
|
fastlife/components/CsrfToken.jinja,sha256=mS0q-3_hAevl_waWEPaN0QAYOBzMyzl-W1PSpEHUBA0,215
|
51
51
|
fastlife/components/Details.jinja,sha256=NtQX-V3kcp1CV1GkrMkj5fc-KHPZHshWkrhXAZ8E3ms,736
|
52
|
-
fastlife/components/Form.jinja,sha256=
|
52
|
+
fastlife/components/Form.jinja,sha256=p4HNIpjDLJT0oH_Vbv6_lYV0tuG_2Xu5E2vZ-yJWrT8,2081
|
53
53
|
fastlife/components/H1.jinja,sha256=fWZtTq34qN9gwGgDF91lkypxaXvGN_OTmUY7XUHIpw0,370
|
54
54
|
fastlife/components/H2.jinja,sha256=o7Q-oR_zDtItV5A7QWfEo_LoMw6bR44YNBDQP3ao1bg,370
|
55
55
|
fastlife/components/H3.jinja,sha256=cZHJTVER1LVYWAwP3sR-23Ktbk9WYlLgLnr2Dz_0oqU,370
|
@@ -57,8 +57,8 @@ fastlife/components/H4.jinja,sha256=w-n0bFqR_38oIuju_Bs_8OwYCtLP0gIfSsoi5u3U8GM,
|
|
57
57
|
fastlife/components/H5.jinja,sha256=bpphjO54yrKLKt64voR5wCvxwFpxQRfkZg_OqhCPAcA,370
|
58
58
|
fastlife/components/H6.jinja,sha256=9qzd6LpaLk5oTLdKw3utSVyX-uq7fn837mm22zG23Ko,370
|
59
59
|
fastlife/components/Hidden.jinja,sha256=-D74wZ7qp2n5l_8HKmDhX5v_M2sAJ5l-w_z9m5d5KvA,283
|
60
|
-
fastlife/components/Input.jinja,sha256=
|
61
|
-
fastlife/components/Label.jinja,sha256=
|
60
|
+
fastlife/components/Input.jinja,sha256=Orp_gTo40KfICLZECc8oLWnaBFixYzmzLRVzoAUe8hc,1972
|
61
|
+
fastlife/components/Label.jinja,sha256=5cYezFHNh5Nuytxbgo60fCeCeiiGS-Cs_fVToj7znx0,533
|
62
62
|
fastlife/components/Option.jinja,sha256=x6t7uUQsI1YSRstD4bSVlgK2wm8NJUXnzOWfNWGlk_Y,448
|
63
63
|
fastlife/components/P.jinja,sha256=Jumlwu9Wix8E2K7QwwimgWTrMdrFDAEfdLHlkz_Mp-g,371
|
64
64
|
fastlife/components/Password.jinja,sha256=dSjPzzgBJM1K1hg_9UURPLpvUcwnna8hf6lH0nsYEps,1903
|
@@ -1690,7 +1690,7 @@ fastlife/components/pydantic_form/FatalError.jinja,sha256=ADtQvmo-e-NmDcFM1E6wZV
|
|
1690
1690
|
fastlife/components/pydantic_form/Hint.jinja,sha256=8leBpfMGDmalc_KAjr2paTojr_rwq-luS6m_1BGj7Tw,202
|
1691
1691
|
fastlife/components/pydantic_form/Widget.jinja,sha256=PgguUpvhG6CY9AW6H8qQMjKqjlybjDCAaFFAOHzrzVQ,418
|
1692
1692
|
fastlife/config/__init__.py,sha256=5qpuaVYqi-AS0GgsfggM6rFsSwXgrqrLBo9jH6dVroc,407
|
1693
|
-
fastlife/config/configurator.py,sha256=
|
1693
|
+
fastlife/config/configurator.py,sha256=82YEVRoI6VoyFPIbkWgnqlLIVNSVP9TUkbKl6qksWs8,24898
|
1694
1694
|
fastlife/config/exceptions.py,sha256=9MdBnbfy-Aw-KaIFzju0Kh8Snk41-v9LqK2w48Tdy1s,1169
|
1695
1695
|
fastlife/config/openapiextra.py,sha256=rYoerrn9sni2XwnO3gIWqaz7M0aDZPhVLjzqhDxue0o,514
|
1696
1696
|
fastlife/config/resources.py,sha256=EcPTM25pnHcGFTtXjeZnWn5Mo_-8rhJ72HJ6rxnjPg8,8389
|
@@ -1700,7 +1700,8 @@ fastlife/domain/model/__init__.py,sha256=aoBjaSpDscuFXvtknJHwiNyoJRUpE-v4X54h_wN
|
|
1700
1700
|
fastlife/domain/model/asgi.py,sha256=Cz45TZOtrh2pBVZr37aJ9jpnJH9BeNHrsvk9bq1nBc0,526
|
1701
1701
|
fastlife/domain/model/csrf.py,sha256=BUiWK-S7rVciWHO1qTkM8e_KxzpF6gGC4MMJK1v6iDo,414
|
1702
1702
|
fastlife/domain/model/form.py,sha256=JP6uumlZBYhiPxzcdxOsfsFm5BRfvkDFvlUCD6Vy8dI,3275
|
1703
|
-
fastlife/domain/model/request.py,sha256=
|
1703
|
+
fastlife/domain/model/request.py,sha256=hHtGsfVND3TSG7HQZI_I0n4Gp4KyaAtsjaZEc4lwrv0,3090
|
1704
|
+
fastlife/domain/model/response.py,sha256=Vsd2zYGGhH0D2DlfiKz1CX9OJZ_ZYoEv_-foMZpDFZo,1294
|
1704
1705
|
fastlife/domain/model/security_policy.py,sha256=f9SLi54vvRU-KSPJ5K0unoqYpkxIyzuZjKf2Ylwf5Rg,4796
|
1705
1706
|
fastlife/domain/model/template.py,sha256=z9oxdKme1hMPuvk7mBiKR_tuVY8TqH77aTYqMgvEGl8,876
|
1706
1707
|
fastlife/domain/model/types.py,sha256=64jJKFAi5x0e3vr8naHU1m_as0Qy8MS-s9CG0z6K1qc,381
|
@@ -1720,7 +1721,7 @@ fastlife/service/registry.py,sha256=0r8dVCF44JUugRctL9sDQjnHDV7SepH06OfkV6KE-4s,
|
|
1720
1721
|
fastlife/service/request_factory.py,sha256=9o4B_78qrKPXQAq8A_RDhzAqCHdt6arV96Bq_JByyIM,931
|
1721
1722
|
fastlife/service/security_policy.py,sha256=qYXs4mhfz_u4x59NhUkirqKYKQbFv9YrzyRuXj7mxE0,4688
|
1722
1723
|
fastlife/service/templates.py,sha256=xNMKH-jNkEoCscO04H-QlzTqg-0pYbF_fc65xG-2rzs,2575
|
1723
|
-
fastlife/service/translations.py,sha256=
|
1724
|
+
fastlife/service/translations.py,sha256=UrkITvfdfw68GzWn_uFlCjjhRvwhc0aCJPnEr_Y1rK8,7151
|
1724
1725
|
fastlife/settings.py,sha256=q-rz4CEF2RQGow5-m-yZJOvdh3PPb2c1Q_ZLJGnu4VQ,3647
|
1725
1726
|
fastlife/shared_utils/__init__.py,sha256=i66ytuf-Ezo7jSiNQHIsBMVIcB-tDX0tg28-pUOlhzE,26
|
1726
1727
|
fastlife/shared_utils/infer.py,sha256=0GflLkaWJ-4LZ1Ig3moR-_o55wwJ_p_vJ4xo-yi3lyA,1406
|
@@ -1730,11 +1731,11 @@ fastlife/testing/__init__.py,sha256=VpxkS3Zp3t_hH8dBiLaGFGhsvt511dhBS_8fMoFXdmU,
|
|
1730
1731
|
fastlife/testing/dom.py,sha256=q2GFrHWjwKMMTR0dsP3J-rXSxojZy8rOQ-07h2gfLKA,5869
|
1731
1732
|
fastlife/testing/form.py,sha256=diiGfVMfNt19JTNUxlnbGfcbskR3ZMpk0Y-A57vfShc,7871
|
1732
1733
|
fastlife/testing/session.py,sha256=LEFFbiR67_x_g-ioudkY0C7PycHdbDfaIaoo_G7GXQ8,2226
|
1733
|
-
fastlife/testing/testclient.py,sha256=
|
1734
|
+
fastlife/testing/testclient.py,sha256=gqgHQalhrLLZ8eveN2HeuoG9ne8CwxCm-Ll4b7jo9Xo,7249
|
1734
1735
|
fastlife/views/__init__.py,sha256=zG8gveL8e2zBdYx6_9jtZfpQ6qJT-MFnBY3xXkLwHZI,22
|
1735
|
-
fastlife/views/pydantic_form.py,sha256=
|
1736
|
-
fastlifeweb-0.
|
1737
|
-
fastlifeweb-0.
|
1738
|
-
fastlifeweb-0.
|
1739
|
-
fastlifeweb-0.
|
1740
|
-
fastlifeweb-0.
|
1736
|
+
fastlife/views/pydantic_form.py,sha256=M4uGP-QiDuSyrkYAsvSVJYZzdBUPOmCghQdwtR28K5E,1630
|
1737
|
+
fastlifeweb-0.27.0.dist-info/METADATA,sha256=5cvmsfd1YF7lhRZtH53Ip9Qxd7EwE54AQK4pFV2IChI,3690
|
1738
|
+
fastlifeweb-0.27.0.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
1739
|
+
fastlifeweb-0.27.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
1740
|
+
fastlifeweb-0.27.0.dist-info/licenses/LICENSE,sha256=JFWuiKYRXKKMEAsX0aZp3hBcju-HYflJ2rwJAGwbCJo,1080
|
1741
|
+
fastlifeweb-0.27.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|