fastlifeweb 0.26.2__py3-none-any.whl → 0.27.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.
CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.27.1 - Released on 2025-07-10
2
+ * Add response_model to @resource_view in order to document views that use StreamResponse.
3
+
4
+ ## 0.27.0 - Released on 2025-04-24
5
+ * Add i18n support on pydantic form.
6
+
1
7
  ## 0.26.2 - Released on 2025-04-20
2
8
  * Add a RedirectResponse class that is htmx friendly and P/R/G pattern friendly.
3
9
  * Fix documentation generation.
@@ -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
- "unique token to ensure id are unique in the DOM."
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 text like field (email, ...).
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/assets/dist.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! tailwindcss v4.1.4 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */
2
2
  @layer properties;
3
3
  @layer theme, base, components, utilities;
4
4
  @layer theme {
@@ -288,6 +288,9 @@
288
288
  .cursor-pointer {
289
289
  cursor: pointer;
290
290
  }
291
+ .appearance-none {
292
+ appearance: none;
293
+ }
291
294
  .items-center {
292
295
  align-items: center;
293
296
  }
@@ -20,13 +20,17 @@
20
20
  "specify how the response will be swapped in relative to the target of an AJAX request."
21
21
  ] = "innerHTML show:body:top",
22
22
  hx_push_url: Annotated[bool, "replace the browser url with the link."] = True,
23
+ hx_get: Annotated[
24
+ str | None,
25
+ "Override the target link only for htmx request for component rendering. href will be used if None."
26
+ ] = None,
23
27
  disable_htmx: Annotated[bool, "do not add any `hx-*` attibute to the link."] = False
24
28
  #}
25
29
 
26
30
  <a href="{{href}}"
27
31
  {%- if id %} id="{{ id }}" {%- endif %}
28
32
  {%- if not disable_htmx %}
29
- hx-get="{{ href }}"
33
+ hx-get="{{ hx_get or href }}"
30
34
  hx-target="{{ hx_target }}"
31
35
  hx-swap="{{ hx_swap }}"
32
36
  {%- if hx_push_url %} hx-push-url="true" {%- endif %}
@@ -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}}"
@@ -14,5 +14,5 @@
14
14
  {%- if attrs.for %} for="{{attrs.for}}" {%- endif %}
15
15
  class="{{attrs.class or LABEL_CLASS}}"
16
16
  {%- if id %} id="{{id}}" {%- endif %}>
17
- {{-content -}}
17
+ {{- content -}}
18
18
  </label>
@@ -368,7 +368,7 @@ class GenericConfigurator(Generic[TRegistry]):
368
368
  deprecated: bool | None = None,
369
369
  methods: list[str] | None = None,
370
370
  operation_id: str | None = None,
371
- # response_model: Any = Default(None),
371
+ response_model: Any = None,
372
372
  response_model_include: IncEx | None = None,
373
373
  response_model_exclude: IncEx | None = None,
374
374
  response_model_by_alias: bool = True,
@@ -433,7 +433,7 @@ class GenericConfigurator(Generic[TRegistry]):
433
433
  self._current_router.add_api_route(
434
434
  path,
435
435
  endpoint,
436
- # response_model=response_model,
436
+ response_model=response_model,
437
437
  status_code=status_code,
438
438
  tags=tags,
439
439
  dependencies=dependencies,
@@ -493,14 +493,7 @@ class GenericConfigurator(Generic[TRegistry]):
493
493
  custom_globals[key] = val
494
494
  return {
495
495
  "request": request,
496
- "gettext": lczr.gettext,
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
  }
@@ -99,6 +99,7 @@ def resource(
99
99
  response_description=endpoint.response_description,
100
100
  deprecated=endpoint.deprecated,
101
101
  operation_id=endpoint.operation_id,
102
+ response_model=endpoint.response_model,
102
103
  response_model_include=endpoint.response_model_include,
103
104
  response_model_exclude=endpoint.response_model_exclude,
104
105
  response_model_by_alias=endpoint.response_model_by_alias,
@@ -146,6 +147,7 @@ def resource_view(
146
147
  deprecated: bool | None = None,
147
148
  methods: list[str] | None = None,
148
149
  operation_id: str | None = None,
150
+ response_model: type[Any] | None = None,
149
151
  response_model_include: IncEx | None = None,
150
152
  response_model_exclude: IncEx | None = None,
151
153
  response_model_by_alias: bool = True,
@@ -155,7 +157,7 @@ def resource_view(
155
157
  include_in_schema: bool = True,
156
158
  openapi_extra: dict[str, Any] | None = None,
157
159
  ) -> Callable[..., Any]:
158
- """ "
160
+ """
159
161
  Decorator to use on a method of a class decorated with {func}`resource` in order
160
162
  to add OpenAPI information.
161
163
 
@@ -177,8 +179,11 @@ def resource_view(
177
179
  :param include_in_schema: Expose or not the route in the OpenAPI schema and
178
180
  documentation.
179
181
 
180
- :param response_model_include: customize fields list to include in repsonse.
181
- :param response_model_exclude: customize fields list to exclude in repsonse.
182
+ :param response_model: class used for the api documentation for streaming response.
183
+ It may also be used to validate data in case the view return dict.
184
+ See [FastAPI doc](https://fastapi.tiangolo.com/tutorial/response-model/).
185
+ :param response_model_include: customize fields list to include in response.
186
+ :param response_model_exclude: customize fields list to exclude in response.
182
187
  :param response_model_by_alias: serialize fields by alias or by name if False.
183
188
  :param response_model_exclude_unset: exclude fields that are not explicitly
184
189
  set in response.
@@ -197,6 +202,7 @@ def resource_view(
197
202
  fn.deprecated = deprecated
198
203
  fn.methods = methods
199
204
  fn.operation_id = operation_id
205
+ fn.response_model = response_model
200
206
  fn.response_model_include = response_model_include
201
207
  fn.response_model_exclude = response_model_exclude
202
208
  fn.response_model_by_alias = response_model_by_alias
@@ -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)
@@ -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 __call__(self, message: str, mapping: dict[str, str] | None = None) -> str:
104
- return self.gettext(message, mapping)
105
-
106
- def gettext(self, message: str, mapping: dict[str, str] | None = None) -> str:
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, **(mapping or {})}
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
- mapping: dict[str, str] | None = None,
146
+ /,
147
+ **mapping: Any,
137
148
  ) -> str:
138
149
  ret = self.translations[domain].ngettext(singular, plural, n)
139
- mapping_num = {"num": n, **(mapping or {})}
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
- mapping: dict[str, str] | None = None,
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
- mapping: dict[str, str] | None = None,
178
+ /,
179
+ **mapping: Any,
169
180
  ) -> str:
170
181
  ret = self.global_translations.npgettext(context, singular, plural, n)
171
- mapping_num = {"num": n, **(mapping or {})}
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
- mapping: dict[str, str] | None = None,
192
+ /,
193
+ **mapping: Any,
182
194
  ) -> str:
183
195
  ret = self.translations[domain].npgettext(context, singular, plural, n)
184
- mapping_num = {"num": n, **(mapping or {})}
196
+ mapping_num = {"num": n, **mapping}
185
197
  return ret.format(**mapping_num)
186
198
 
187
199
 
@@ -29,6 +29,7 @@ class Globals(BaseModel):
29
29
  """Default css class for {jinjax:component}`A`."""
30
30
 
31
31
  BUTTON_CLASS: str = space_join(
32
+ "appearance-none",
32
33
  "bg-primary-600",
33
34
  "px-5",
34
35
  "py-2.5",
@@ -52,6 +53,7 @@ class Globals(BaseModel):
52
53
  """Default css class for {jinjax:component}`Details`."""
53
54
 
54
55
  SECONDARY_BUTTON_CLASS: str = space_join(
56
+ "appearance-none",
55
57
  "bg-neutral-300",
56
58
  "px-5",
57
59
  "py-2.5",
@@ -79,6 +81,7 @@ class Globals(BaseModel):
79
81
  """
80
82
 
81
83
  ICON_BUTTON_CLASS: str = space_join(
84
+ "appearance-none",
82
85
  "bg-white",
83
86
  "p-1",
84
87
  "rounded-xs",
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastlifeweb
3
- Version: 0.26.2
3
+ Version: 0.27.1
4
4
  Summary: High-level web framework
5
5
  Author-Email: Guillaume Gauvrit <guillaume@gauvr.it>
6
6
  License: MIT
@@ -77,7 +77,7 @@ to maintain and scale your project.
77
77
 
78
78
  ## Tests
79
79
 
80
- Fastlife come with [a test client](https://mardiros.github.io/fastlife/develop/fastlife/fastlife.testing.testclient.html) that can interact with html inside unit tests.
80
+ Fastlife comes with [a test client](https://mardiros.github.io/fastlife/develop/fastlife/fastlife.testing.testclient.html) that can interact with html inside unit tests.
81
81
 
82
82
 
83
83
  ## Try it
@@ -1,4 +1,4 @@
1
- CHANGELOG.md,sha256=NqlUm-TificrXs2dKAL5-1_6i3Eo74MRksoaa0RBS1k,8830
1
+ CHANGELOG.md,sha256=EdwLpgFV0D5skxaZ0DbAtJPkk2QwLDt8sGNVqSGQJ28,9032
2
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
@@ -32,19 +32,19 @@ 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=jU-oklWuM6MPlNzBvYO0HtxUrUteb-Dh96OudPLFJx4,4120
36
- fastlife/adapters/jinjax/widgets/boolean.py,sha256=GFvyO2Lz-vTJ8lpX2UrkJk7ymu0vU-xnBBEmyBKamJg,613
37
- fastlife/adapters/jinjax/widgets/checklist.py,sha256=xsr_edMu1nTcNnMEs1AAS3U-ugK95pEqn5NV5hjTeI0,1747
38
- fastlife/adapters/jinjax/widgets/dropdown.py,sha256=ou8U48txShr8IXdwF4_pBvTn1VcR9SazZKzbLcsN3_0,1458
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=8HHaEHyUEQT6xzJ0Y4O0eA1SU3wSfzXljx8XY73o-Rc,733
41
- fastlife/adapters/jinjax/widgets/model.py,sha256=YBIEWa_6mnmrBnesXjLTrpJ4drUS2CIorNmhK24cz7Q,1298
42
- fastlife/adapters/jinjax/widgets/sequence.py,sha256=dVoHQmHloaRuU1Sd82b2jnO8WDfdwM2FaZlLCJCps1o,2566
43
- fastlife/adapters/jinjax/widgets/text.py,sha256=TfmlJU233aZWIl-4cmm-f-pFxp6ycHWHnbiluOvRDgM,3040
44
- fastlife/adapters/jinjax/widgets/union.py,sha256=roCoFA82dLjF1XFW6UYaV7SCQWdFsSAT8Ux7KEB6_Us,2602
45
- fastlife/assets/dist.css,sha256=d2ez-igscOaeYtJQc2FQuXlN17cYX13sUansFdg_kdA,19753
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
+ fastlife/assets/dist.css,sha256=xuD2jjSO5GMCjyr_rmhcazJQ5wQpcYUvFxKm0RcC1XY,19801
46
46
  fastlife/assets/source.css,sha256=0KtDcsKHj9LOcqNR1iv9pACwNBaNWkieEDqqjkgNL_s,47
47
- fastlife/components/A.jinja,sha256=MDNJ2auIeYbpNeErvJdlGid4nIKfbi85ArmMgChsCJU,1384
47
+ fastlife/components/A.jinja,sha256=hTZeYWXKLgnqPucSrF2IY7TDP4d_xNfU2Rjl-bXnpGc,1548
48
48
  fastlife/components/Button.jinja,sha256=itKU-ct45XissU33yfmTekyHsNe00fr4RQL-e9cxbgU,2305
49
49
  fastlife/components/Checkbox.jinja,sha256=g62A1LR8TaN60h94pE2e5l9_eMmgnhVVE9HVCQtVVMo,748
50
50
  fastlife/components/CsrfToken.jinja,sha256=mS0q-3_hAevl_waWEPaN0QAYOBzMyzl-W1PSpEHUBA0,215
@@ -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=bg2YCVGQsXwNF5zPauNg0VYbhf3qzEW12t6Xz7bWYVI,1950
61
- fastlife/components/Label.jinja,sha256=t50MUGyNSFB4LK-J0D0fM2xMT_vCsG3fnO0OapvzPJQ,532
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,17 +1690,17 @@ 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=2LkPXoarMTk40S7AVlxODPAckDllE0G8u27jtPmvGM8,25188
1693
+ fastlife/config/configurator.py,sha256=WVV6UIqVBQDNh_YuRIpRTo6O_-_Ut-ZqW8SNeMAujYI,24885
1694
1694
  fastlife/config/exceptions.py,sha256=9MdBnbfy-Aw-KaIFzju0Kh8Snk41-v9LqK2w48Tdy1s,1169
1695
1695
  fastlife/config/openapiextra.py,sha256=rYoerrn9sni2XwnO3gIWqaz7M0aDZPhVLjzqhDxue0o,514
1696
- fastlife/config/resources.py,sha256=EcPTM25pnHcGFTtXjeZnWn5Mo_-8rhJ72HJ6rxnjPg8,8389
1696
+ fastlife/config/resources.py,sha256=stKCuZQGgiDW9xTrTNxKMb_JzkgkM1Ubum1b7OK4Lm4,8780
1697
1697
  fastlife/config/views.py,sha256=9CZ0qNi8vKvQuGo1GgM6cwNK8WwHOxwIHqtikAOaOHY,2399
1698
1698
  fastlife/domain/__init__.py,sha256=3zDDos5InVX0el9OO0lgSDGzdUNYIhlA6w4uhBh2pF8,29
1699
1699
  fastlife/domain/model/__init__.py,sha256=aoBjaSpDscuFXvtknJHwiNyoJRUpE-v4X54h_wNuo2Y,27
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=HgUSnUu3q18e07y57PadN3pPQwYrIZS1YEhYkBZ_Zfg,2674
1703
+ fastlife/domain/model/request.py,sha256=hHtGsfVND3TSG7HQZI_I0n4Gp4KyaAtsjaZEc4lwrv0,3090
1704
1704
  fastlife/domain/model/response.py,sha256=Vsd2zYGGhH0D2DlfiKz1CX9OJZ_ZYoEv_-foMZpDFZo,1294
1705
1705
  fastlife/domain/model/security_policy.py,sha256=f9SLi54vvRU-KSPJ5K0unoqYpkxIyzuZjKf2Ylwf5Rg,4796
1706
1706
  fastlife/domain/model/template.py,sha256=z9oxdKme1hMPuvk7mBiKR_tuVY8TqH77aTYqMgvEGl8,876
@@ -1721,21 +1721,21 @@ fastlife/service/registry.py,sha256=0r8dVCF44JUugRctL9sDQjnHDV7SepH06OfkV6KE-4s,
1721
1721
  fastlife/service/request_factory.py,sha256=9o4B_78qrKPXQAq8A_RDhzAqCHdt6arV96Bq_JByyIM,931
1722
1722
  fastlife/service/security_policy.py,sha256=qYXs4mhfz_u4x59NhUkirqKYKQbFv9YrzyRuXj7mxE0,4688
1723
1723
  fastlife/service/templates.py,sha256=xNMKH-jNkEoCscO04H-QlzTqg-0pYbF_fc65xG-2rzs,2575
1724
- fastlife/service/translations.py,sha256=cAfvUlLM3KcgQjlD9PtEpZpTMctXKM_CUAmUeKw9n4M,6901
1724
+ fastlife/service/translations.py,sha256=UrkITvfdfw68GzWn_uFlCjjhRvwhc0aCJPnEr_Y1rK8,7151
1725
1725
  fastlife/settings.py,sha256=q-rz4CEF2RQGow5-m-yZJOvdh3PPb2c1Q_ZLJGnu4VQ,3647
1726
1726
  fastlife/shared_utils/__init__.py,sha256=i66ytuf-Ezo7jSiNQHIsBMVIcB-tDX0tg28-pUOlhzE,26
1727
1727
  fastlife/shared_utils/infer.py,sha256=0GflLkaWJ-4LZ1Ig3moR-_o55wwJ_p_vJ4xo-yi3lyA,1406
1728
1728
  fastlife/shared_utils/resolver.py,sha256=Wb9cO2MWavpti63hju15xmwFMgaD5DsQaxikRpB39E8,3713
1729
- fastlife/template_globals.py,sha256=bKcj6kSnQlzuOeoILA5oRxxzy6CsrBFMZv9S0w5XlTQ,9021
1729
+ fastlife/template_globals.py,sha256=pn2fWRE5QwYii4K0XJtfCt1hUmN8VvXXwAOWSwRFe94,9102
1730
1730
  fastlife/testing/__init__.py,sha256=VpxkS3Zp3t_hH8dBiLaGFGhsvt511dhBS_8fMoFXdmU,99
1731
1731
  fastlife/testing/dom.py,sha256=q2GFrHWjwKMMTR0dsP3J-rXSxojZy8rOQ-07h2gfLKA,5869
1732
1732
  fastlife/testing/form.py,sha256=diiGfVMfNt19JTNUxlnbGfcbskR3ZMpk0Y-A57vfShc,7871
1733
1733
  fastlife/testing/session.py,sha256=LEFFbiR67_x_g-ioudkY0C7PycHdbDfaIaoo_G7GXQ8,2226
1734
1734
  fastlife/testing/testclient.py,sha256=gqgHQalhrLLZ8eveN2HeuoG9ne8CwxCm-Ll4b7jo9Xo,7249
1735
1735
  fastlife/views/__init__.py,sha256=zG8gveL8e2zBdYx6_9jtZfpQ6qJT-MFnBY3xXkLwHZI,22
1736
- fastlife/views/pydantic_form.py,sha256=o7EUItciAGL1OSaGNHo-3BTrYAk34GuWE7zGikjiAGY,1486
1737
- fastlifeweb-0.26.2.dist-info/METADATA,sha256=btOasL9jb9IiIrj9yPnrSW60e3T8Xpxy2Abu0hBBcb8,3690
1738
- fastlifeweb-0.26.2.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
1739
- fastlifeweb-0.26.2.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
1740
- fastlifeweb-0.26.2.dist-info/licenses/LICENSE,sha256=JFWuiKYRXKKMEAsX0aZp3hBcju-HYflJ2rwJAGwbCJo,1080
1741
- fastlifeweb-0.26.2.dist-info/RECORD,,
1736
+ fastlife/views/pydantic_form.py,sha256=M4uGP-QiDuSyrkYAsvSVJYZzdBUPOmCghQdwtR28K5E,1630
1737
+ fastlifeweb-0.27.1.dist-info/METADATA,sha256=Ztrqc7ngoilivkBACoQ2P94LAbPHQhuEORjLkiO7weQ,3691
1738
+ fastlifeweb-0.27.1.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
1739
+ fastlifeweb-0.27.1.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
1740
+ fastlifeweb-0.27.1.dist-info/licenses/LICENSE,sha256=JFWuiKYRXKKMEAsX0aZp3hBcju-HYflJ2rwJAGwbCJo,1080
1741
+ fastlifeweb-0.27.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.4.4)
2
+ Generator: pdm-backend (2.4.5)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any