fastlifeweb 0.5.1__py3-none-any.whl → 0.6.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 +3 -2
- fastlife/request/form_data.py +7 -22
- fastlife/request/model_result.py +91 -0
- fastlife/shared_utils/infer.py +1 -1
- fastlife/templates/pydantic_form/Boolean.jinja +7 -2
- fastlife/templates/pydantic_form/Checklist.jinja +3 -1
- fastlife/templates/pydantic_form/Dropdown.jinja +1 -0
- fastlife/templates/pydantic_form/Error.jinja +4 -0
- fastlife/templates/pydantic_form/Model.jinja +1 -0
- fastlife/templates/pydantic_form/Sequence.jinja +1 -0
- fastlife/templates/pydantic_form/Text.jinja +1 -0
- fastlife/templates/pydantic_form/Union.jinja +4 -4
- fastlife/templating/renderer/abstract.py +16 -2
- fastlife/templating/renderer/jinjax.py +25 -4
- fastlife/templating/renderer/widgets/base.py +7 -5
- fastlife/templating/renderer/widgets/boolean.py +7 -1
- fastlife/templating/renderer/widgets/checklist.py +13 -2
- fastlife/templating/renderer/widgets/dropdown.py +7 -1
- fastlife/templating/renderer/widgets/factory.py +69 -16
- fastlife/templating/renderer/widgets/model.py +7 -1
- fastlife/templating/renderer/widgets/sequence.py +7 -1
- fastlife/templating/renderer/widgets/text.py +3 -1
- fastlife/templating/renderer/widgets/union.py +7 -1
- fastlife/testing/testclient.py +102 -0
- fastlife/views/pydantic_form.py +6 -2
- {fastlifeweb-0.5.1.dist-info → fastlifeweb-0.6.1.dist-info}/METADATA +2 -2
- {fastlifeweb-0.5.1.dist-info → fastlifeweb-0.6.1.dist-info}/RECORD +29 -27
- {fastlifeweb-0.5.1.dist-info → fastlifeweb-0.6.1.dist-info}/LICENSE +0 -0
- {fastlifeweb-0.5.1.dist-info → fastlifeweb-0.6.1.dist-info}/WHEEL +0 -0
fastlife/testing/testclient.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
"""Testing your application."""
|
2
|
+
|
1
3
|
import re
|
2
4
|
import time
|
3
5
|
from collections.abc import MutableMapping
|
@@ -20,41 +22,67 @@ Cookies = httpx._models.Cookies # type: ignore
|
|
20
22
|
|
21
23
|
|
22
24
|
class Element:
|
25
|
+
"""Access to a dom element."""
|
26
|
+
|
23
27
|
def __init__(self, client: "WebTestClient", tag: bs4.Tag):
|
24
28
|
self._client = client
|
25
29
|
self._tag = tag
|
26
30
|
|
27
31
|
def click(self) -> "WebResponse":
|
32
|
+
"""Simulate a client to a a link. No javascript exectuted here."""
|
28
33
|
return self._client.get(self._tag.attrs["href"])
|
29
34
|
|
30
35
|
@property
|
31
36
|
def node_name(self) -> str:
|
37
|
+
"""Get the node name of the dom element."""
|
32
38
|
return self._tag.name
|
33
39
|
|
34
40
|
@property
|
35
41
|
def attrs(self) -> dict[str, str]:
|
42
|
+
"""Attributes of the element."""
|
36
43
|
return self._tag.attrs
|
37
44
|
|
38
45
|
@property
|
39
46
|
def text(self) -> str:
|
47
|
+
"""
|
48
|
+
Return the text of the element, with text of childs element.
|
49
|
+
|
50
|
+
Note that the text is stripped for convenience but inner text may contains
|
51
|
+
many spaces not manipulated here.
|
52
|
+
"""
|
40
53
|
return self._tag.text.strip()
|
41
54
|
|
42
55
|
@property
|
43
56
|
def h1(self) -> "Element":
|
57
|
+
"""
|
58
|
+
Return the h1 child element.
|
59
|
+
|
60
|
+
Should be used on the html body element directly.
|
61
|
+
"""
|
44
62
|
nodes = self.by_node_name("h1")
|
45
63
|
assert len(nodes) == 1, f"Should have 1 <h1>, got {len(nodes)} in {self}"
|
46
64
|
return nodes[0]
|
47
65
|
|
48
66
|
@property
|
49
67
|
def h2(self) -> Sequence["Element"]:
|
68
|
+
"""
|
69
|
+
Return the h2 elements.
|
70
|
+
"""
|
50
71
|
return self.by_node_name("h2")
|
51
72
|
|
52
73
|
@property
|
53
74
|
def form(self) -> "Element | None":
|
75
|
+
"""Get the form element of the web page."""
|
54
76
|
return Element(self._client, self._tag.form) if self._tag.form else None
|
55
77
|
|
56
78
|
@property
|
57
79
|
def hx_target(self) -> Optional[str]:
|
80
|
+
"""
|
81
|
+
Return the hx-target of the element.
|
82
|
+
|
83
|
+
It may be set on a parent. It also resolve special case "this" and return the id
|
84
|
+
of the element.
|
85
|
+
"""
|
58
86
|
el: bs4.Tag | None = self._tag
|
59
87
|
while el:
|
60
88
|
if "hx-target" in el.attrs:
|
@@ -66,12 +94,14 @@ class Element:
|
|
66
94
|
return None
|
67
95
|
|
68
96
|
def by_text(self, text: str, *, node_name: str | None = None) -> "Element | None":
|
97
|
+
"""Find the first element that match the text."""
|
69
98
|
nodes = self.iter_all_by_text(text, node_name=node_name)
|
70
99
|
return next(nodes, None)
|
71
100
|
|
72
101
|
def iter_all_by_text(
|
73
102
|
self, text: str, *, node_name: str | None = None
|
74
103
|
) -> "Iterator[Element]":
|
104
|
+
"""Return an iterator of all elements that match the text."""
|
75
105
|
nodes = self._tag.find_all(string=re.compile(rf"\s*{text}\s*"))
|
76
106
|
for node in nodes:
|
77
107
|
if isinstance(node, bs4.NavigableString):
|
@@ -89,10 +119,12 @@ class Element:
|
|
89
119
|
def get_all_by_text(
|
90
120
|
self, text: str, *, node_name: str | None = None
|
91
121
|
) -> "Sequence[Element]":
|
122
|
+
"""Return the list of all elements that match the text."""
|
92
123
|
nodes = self.iter_all_by_text(text, node_name=node_name)
|
93
124
|
return list(nodes)
|
94
125
|
|
95
126
|
def by_label_text(self, text: str) -> "Element | None":
|
127
|
+
"""Return the element which is the target of the label having the given text."""
|
96
128
|
label = self.by_text(text, node_name="label")
|
97
129
|
assert label is not None
|
98
130
|
assert label.attrs.get("for") is not None
|
@@ -103,6 +135,11 @@ class Element:
|
|
103
135
|
def by_node_name(
|
104
136
|
self, node_name: str, *, attrs: dict[str, str] | None = None
|
105
137
|
) -> list["Element"]:
|
138
|
+
"""
|
139
|
+
Return the list of elements with the given node_name.
|
140
|
+
|
141
|
+
An optional set of attributes may given and must match if passed.
|
142
|
+
"""
|
106
143
|
return [
|
107
144
|
Element(self._client, e) for e in self._tag.find_all(node_name, attrs or {})
|
108
145
|
]
|
@@ -115,6 +152,15 @@ class Element:
|
|
115
152
|
|
116
153
|
|
117
154
|
class WebForm:
|
155
|
+
"""
|
156
|
+
Handle form.
|
157
|
+
|
158
|
+
Form are filled out and submit with methods and try to avoid invalid
|
159
|
+
usage, such as selecting an option that don't exists is not possible here.
|
160
|
+
Again, no javascript is executed here, but htmx attribute `hx-post` and `hx-target`
|
161
|
+
are read while submiting to simulate it.
|
162
|
+
"""
|
163
|
+
|
118
164
|
def __init__(self, client: "WebTestClient", origin: str, form: Element):
|
119
165
|
self._client = client
|
120
166
|
self._form = form
|
@@ -154,6 +200,14 @@ class WebForm:
|
|
154
200
|
# field textearea...
|
155
201
|
|
156
202
|
def set(self, fieldname: str, value: str) -> Any:
|
203
|
+
"""
|
204
|
+
Set a value to an input field.
|
205
|
+
|
206
|
+
It works for checkbox and radio as well.
|
207
|
+
Checkbox may contains many values.
|
208
|
+
Options of select can't be set with this method, the select method must
|
209
|
+
be used instead.
|
210
|
+
"""
|
157
211
|
if fieldname not in self._formfields:
|
158
212
|
raise ValueError(f'"{fieldname}" does not exists')
|
159
213
|
if self._formfields[fieldname].node_name == "select":
|
@@ -175,6 +229,7 @@ class WebForm:
|
|
175
229
|
self._formdata[fieldname] = value
|
176
230
|
|
177
231
|
def unset(self, fieldname: str, value: str) -> Any:
|
232
|
+
"""Unset an element. Only works with checkbox."""
|
178
233
|
if fieldname not in self._formfields:
|
179
234
|
raise ValueError(f'"{fieldname}" does not exists')
|
180
235
|
if self._formfields[fieldname].node_name != "input":
|
@@ -189,6 +244,9 @@ class WebForm:
|
|
189
244
|
self._formdata[fieldname] = val
|
190
245
|
|
191
246
|
def select(self, fieldname: str, value: str) -> Any:
|
247
|
+
"""
|
248
|
+
Select an option, if multiple, value is added, otherwise, value is replaced.
|
249
|
+
"""
|
192
250
|
if fieldname not in self._formfields:
|
193
251
|
raise ValueError(f'"{fieldname}" does not exists')
|
194
252
|
field = self._formfields[fieldname]
|
@@ -206,6 +264,9 @@ class WebForm:
|
|
206
264
|
raise ValueError(f'No option {value} in <select name="{fieldname}">')
|
207
265
|
|
208
266
|
def unselect(self, fieldname: str, value: str) -> Any:
|
267
|
+
"""
|
268
|
+
Unselect an option if multiple, otherwise an exception is raised.
|
269
|
+
"""
|
209
270
|
if fieldname not in self._formfields:
|
210
271
|
raise ValueError(f'"{fieldname}" does not exists')
|
211
272
|
field = self._formfields[fieldname]
|
@@ -231,6 +292,20 @@ class WebForm:
|
|
231
292
|
raise ValueError(f'No option {value} in <select name="{fieldname}">')
|
232
293
|
|
233
294
|
def button(self, text: str, position: int = 0) -> "WebForm":
|
295
|
+
"""
|
296
|
+
Simmulate a click on a button using the text of the button,
|
297
|
+
|
298
|
+
and eventually a position. The button return the form and the submit()
|
299
|
+
should be called directly.
|
300
|
+
|
301
|
+
This is used in order to inject the value of the button in the form, usually
|
302
|
+
done while many actions are available on a form.
|
303
|
+
|
304
|
+
::
|
305
|
+
|
306
|
+
form.button("Go").submit()
|
307
|
+
|
308
|
+
"""
|
234
309
|
buttons = self._form.get_all_by_text(text, node_name="button")
|
235
310
|
if position >= len(buttons):
|
236
311
|
pos = ""
|
@@ -243,6 +318,9 @@ class WebForm:
|
|
243
318
|
return self
|
244
319
|
|
245
320
|
def submit(self, follow_redirects: bool = True) -> "WebResponse":
|
321
|
+
"""
|
322
|
+
Submit the form as it has been previously filled out.
|
323
|
+
"""
|
246
324
|
headers: dict[str, str] = {}
|
247
325
|
target = (
|
248
326
|
self._form.attrs.get("hx-post")
|
@@ -261,10 +339,13 @@ class WebForm:
|
|
261
339
|
)
|
262
340
|
|
263
341
|
def __contains__(self, key: str) -> bool:
|
342
|
+
"""Test if a field exists in the form."""
|
264
343
|
return key in self._formdata
|
265
344
|
|
266
345
|
|
267
346
|
class WebResponse:
|
347
|
+
"""Represent an http response made by the WebTestClient browser."""
|
348
|
+
|
268
349
|
def __init__(self, client: "WebTestClient", origin: str, response: httpx.Response):
|
269
350
|
self._client = client
|
270
351
|
self._response = response
|
@@ -274,38 +355,46 @@ class WebResponse:
|
|
274
355
|
|
275
356
|
@property
|
276
357
|
def status_code(self) -> int:
|
358
|
+
"""Http status code."""
|
277
359
|
return self._response.status_code
|
278
360
|
|
279
361
|
@property
|
280
362
|
def is_redirect(self) -> bool:
|
363
|
+
"""True for any kind of http redirect status."""
|
281
364
|
return 300 <= self._response.status_code < 400
|
282
365
|
|
283
366
|
@property
|
284
367
|
def content_type(self) -> str:
|
368
|
+
"""Get the content type of the response, from the header."""
|
285
369
|
return self._response.headers.get("content-type", "").split(";").pop(0)
|
286
370
|
|
287
371
|
@property
|
288
372
|
def headers(self) -> httpx.Headers:
|
373
|
+
"""All http headers of the response."""
|
289
374
|
return self._response.headers
|
290
375
|
|
291
376
|
@property
|
292
377
|
def text(self) -> str:
|
378
|
+
"""Http response body."""
|
293
379
|
return self._response.text
|
294
380
|
|
295
381
|
@property
|
296
382
|
def html(self) -> Element:
|
383
|
+
"""Http response body as an Element."""
|
297
384
|
if self._html is None:
|
298
385
|
self._html = bs4.BeautifulSoup(self._response.text, "html.parser")
|
299
386
|
return Element(self._client, self._html)
|
300
387
|
|
301
388
|
@property
|
302
389
|
def html_body(self) -> Element:
|
390
|
+
"""The body element of the html response."""
|
303
391
|
body = self.html.by_node_name("body")
|
304
392
|
assert len(body) == 1
|
305
393
|
return body[0]
|
306
394
|
|
307
395
|
@property
|
308
396
|
def form(self) -> WebForm:
|
397
|
+
"""The form element of the html response."""
|
309
398
|
if self._form is None:
|
310
399
|
form = self.html.form
|
311
400
|
assert form is not None
|
@@ -313,18 +402,23 @@ class WebResponse:
|
|
313
402
|
return self._form
|
314
403
|
|
315
404
|
def by_text(self, text: str, *, node_name: str | None = None) -> Element | None:
|
405
|
+
"""Search a dom element by its text."""
|
316
406
|
return self.html.by_text(text, node_name=node_name)
|
317
407
|
|
318
408
|
def by_label_text(self, text: str) -> Element | None:
|
409
|
+
"""Search a dom element by its associated label text."""
|
319
410
|
return self.html.by_label_text(text)
|
320
411
|
|
321
412
|
def by_node_name(
|
322
413
|
self, node_name: str, *, attrs: dict[str, str] | None = None
|
323
414
|
) -> list[Element]:
|
415
|
+
"""List dom element having the given node name, and eventually attributes."""
|
324
416
|
return self.html.by_node_name(node_name, attrs=attrs)
|
325
417
|
|
326
418
|
|
327
419
|
class Session(dict[str, Any]):
|
420
|
+
"""Manipulate the session of the WebTestClient browser."""
|
421
|
+
|
328
422
|
def __init__(self, client: "WebTestClient"):
|
329
423
|
self.client = client
|
330
424
|
self.srlz = client.session_serializer
|
@@ -343,6 +437,7 @@ class Session(dict[str, Any]):
|
|
343
437
|
super().__init__(data)
|
344
438
|
|
345
439
|
def __setitem__(self, __key: Any, __value: Any) -> None:
|
440
|
+
"""Initialize a value in the session of the client in order to test."""
|
346
441
|
super().__setitem__(__key, __value)
|
347
442
|
settings = self.settings
|
348
443
|
data = self.serialize()
|
@@ -380,6 +475,8 @@ class Session(dict[str, Any]):
|
|
380
475
|
|
381
476
|
|
382
477
|
class WebTestClient:
|
478
|
+
"""The fake browser used for testing purpose."""
|
479
|
+
|
383
480
|
def __init__(
|
384
481
|
self,
|
385
482
|
app: ASGIApp,
|
@@ -404,10 +501,12 @@ class WebTestClient:
|
|
404
501
|
|
405
502
|
@property
|
406
503
|
def cookies(self) -> Cookies:
|
504
|
+
"""HTTP Cookies"""
|
407
505
|
return self.testclient.cookies
|
408
506
|
|
409
507
|
@property
|
410
508
|
def session(self) -> MutableMapping[str, Any]:
|
509
|
+
"""Session shared between the server and the client."""
|
411
510
|
return Session(self)
|
412
511
|
|
413
512
|
def request(
|
@@ -419,6 +518,7 @@ class WebTestClient:
|
|
419
518
|
headers: Mapping[str, str] | None = None,
|
420
519
|
max_redirects: int = 0,
|
421
520
|
) -> WebResponse:
|
521
|
+
"""Perform http requests."""
|
422
522
|
rawresp = self.testclient.request(
|
423
523
|
method=method,
|
424
524
|
url=url,
|
@@ -455,6 +555,7 @@ class WebTestClient:
|
|
455
555
|
return resp
|
456
556
|
|
457
557
|
def get(self, url: str, follow_redirects: bool = True) -> WebResponse:
|
558
|
+
"""Perform http GET request."""
|
458
559
|
return self.request(
|
459
560
|
"GET",
|
460
561
|
url,
|
@@ -469,6 +570,7 @@ class WebTestClient:
|
|
469
570
|
headers: Mapping[str, Any] | None = None,
|
470
571
|
follow_redirects: bool = True,
|
471
572
|
) -> WebResponse:
|
573
|
+
"""Perform http POST request in "application/x-www-form-urlencoded" format."""
|
472
574
|
if headers is None:
|
473
575
|
headers = {}
|
474
576
|
return self.request(
|
fastlife/views/pydantic_form.py
CHANGED
@@ -21,8 +21,12 @@ async def show_widget(
|
|
21
21
|
field = None
|
22
22
|
if title:
|
23
23
|
field = FieldInfo(title=title)
|
24
|
-
data = reg.renderer(request).
|
25
|
-
model_cls,
|
24
|
+
data = reg.renderer(request).pydantic_form_field(
|
25
|
+
model=model_cls,
|
26
|
+
name=name,
|
27
|
+
token=token,
|
28
|
+
removable=removable,
|
29
|
+
field=field,
|
26
30
|
)
|
27
31
|
return Response(data, headers={"Content-Type": "text/html"})
|
28
32
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fastlifeweb
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.1
|
4
4
|
Summary: High-level web framework
|
5
5
|
Home-page: https://github.com/mardiros/fastlife
|
6
6
|
License: BSD-derived
|
@@ -24,7 +24,7 @@ Requires-Dist: itsdangerous (>=2.1.2,<3.0.0)
|
|
24
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
|
-
Requires-Dist: pydantic (>=2.3
|
27
|
+
Requires-Dist: pydantic (>=2.5.3,<3.0.0)
|
28
28
|
Requires-Dist: pydantic-settings (>=2.0.3,<3.0.0)
|
29
29
|
Requires-Dist: python-multipart (>=0.0.6,<0.0.7)
|
30
30
|
Requires-Dist: venusian (>=3.0.0,<4.0.0)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
fastlife/__init__.py,sha256=
|
1
|
+
fastlife/__init__.py,sha256=nAeweHnTvWHgDZYuPIWawZvT4juuhvMp4hzF2B-yCJU,317
|
2
2
|
fastlife/configurator/__init__.py,sha256=2EPjM1o5iHJIViPwgJjaPQS3pMhE-9dik_mm53eX2DY,91
|
3
3
|
fastlife/configurator/base.py,sha256=2ahvTudLmD99YQjnIeGN5JDPCSl3k-mauu7bsSEB5RE,216
|
4
4
|
fastlife/configurator/configurator.py,sha256=6BaB7SR24Q4Qvs8NrCpatRkkZiPXf9mKLID6RxOKxDg,5740
|
@@ -6,7 +6,8 @@ fastlife/configurator/registry.py,sha256=1sOicKvwIvLbrzRk9z8yb65YUXxxagJK9AK-2gG
|
|
6
6
|
fastlife/configurator/settings.py,sha256=ftv5MkNXeyBrvcqxnt2WKtLuzo7ge2_BNx1gX4CcSOE,1489
|
7
7
|
fastlife/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
fastlife/request/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
fastlife/request/form_data.py,sha256=
|
9
|
+
fastlife/request/form_data.py,sha256=mP8ilwRUY2WbktIkRgaJJ2EUjwUMPbSPg29GzwZgT18,3713
|
10
|
+
fastlife/request/model_result.py,sha256=TRaVkyIE50IzVprncoWUUZd15-y4D3ywyZdx7eh6nFE,3237
|
10
11
|
fastlife/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
12
|
fastlife/security/csrf.py,sha256=47epJVJtr5X6j-Hog54WCGOoiRLQQHvgBU71iqR1N0A,1025
|
12
13
|
fastlife/security/policy.py,sha256=5jV5nypy5O8XPFFRJ_bzG8Ltk5xcPWclkz23qiG1_I8,509
|
@@ -14,7 +15,7 @@ fastlife/session/__init__.py,sha256=OnzRCYRzc1lw9JB0UdKi-aRLPNT2n8mM8kwY1P4w7uU,
|
|
14
15
|
fastlife/session/middleware.py,sha256=JgXdBlxlm9zIEgXcidbBrMAp5wJVPsZWtvCLVDk5h2s,3049
|
15
16
|
fastlife/session/serializer.py,sha256=qpVnHQjYTxw3aOnoEOKIjOFJg2z45KjiX5sipWk2gws,1458
|
16
17
|
fastlife/shared_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
-
fastlife/shared_utils/infer.py,sha256=
|
18
|
+
fastlife/shared_utils/infer.py,sha256=0jNPY5vqKvDlNCmVPnRAXbTcQnmbuOIOIGAeGcxDPok,472
|
18
19
|
fastlife/shared_utils/resolver.py,sha256=wXQQTB4jf86m4qENhMOkHkWpLJj_T4-_eND_ItTLnTE,1410
|
19
20
|
fastlife/templates/A.jinja,sha256=q71nu4Rq874LG6SykCKv8W-EZeX13NMF0AsLc9FFAP0,677
|
20
21
|
fastlife/templates/Button.jinja,sha256=535UIK4Prunj4f0YZXBmCI0rfOiTr5GJTQkM0XDmtNA,1137
|
@@ -35,37 +36,38 @@ fastlife/templates/P.jinja,sha256=xEcHIv9dJRpELu_SdqQcftvKrU8z1i_BHTEVO5Mu5dU,16
|
|
35
36
|
fastlife/templates/Radio.jinja,sha256=51I5n1yjWQ_uLZfzuUUf3C8ngo-KT4dPw29-pjP9uSU,723
|
36
37
|
fastlife/templates/Select.jinja,sha256=GoK2oh4Jl5dAfL2wN718RCwtHaQIslzDCN_nwy6a9oY,480
|
37
38
|
fastlife/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
|
-
fastlife/templates/pydantic_form/Boolean.jinja,sha256=
|
39
|
-
fastlife/templates/pydantic_form/Checklist.jinja,sha256=
|
40
|
-
fastlife/templates/pydantic_form/Dropdown.jinja,sha256=
|
39
|
+
fastlife/templates/pydantic_form/Boolean.jinja,sha256=gf8j1Wyy8C95tYsjDjxKL_ivSJPsUHBoRaK-4AiBiBA,467
|
40
|
+
fastlife/templates/pydantic_form/Checklist.jinja,sha256=s55FqHJGNryTT3h6VWFx6mEgR8lIVBoqo7NGYUTM_rE,775
|
41
|
+
fastlife/templates/pydantic_form/Dropdown.jinja,sha256=_dBSf7-Ba1Yr4BPXu08yJBSZjUW4Jo8t-s3R6TgN-oc,548
|
42
|
+
fastlife/templates/pydantic_form/Error.jinja,sha256=Wb5NnVRc4U7ZGKmYV7s4eGenWEug8WK9li48iTlX4cQ,121
|
41
43
|
fastlife/templates/pydantic_form/Hidden.jinja,sha256=n6CbTSwZr2E_oY8TO2WPbnrLHBaWfe_CXVCYOYmCfts,83
|
42
44
|
fastlife/templates/pydantic_form/Hint.jinja,sha256=O0ZsAQnATcG0a_qLQfrwM6VZHmAw3k1W33WYlEBUas8,123
|
43
|
-
fastlife/templates/pydantic_form/Model.jinja,sha256=
|
44
|
-
fastlife/templates/pydantic_form/Sequence.jinja,sha256=
|
45
|
-
fastlife/templates/pydantic_form/Text.jinja,sha256=
|
46
|
-
fastlife/templates/pydantic_form/Union.jinja,sha256=
|
45
|
+
fastlife/templates/pydantic_form/Model.jinja,sha256=1t3eOxYjT_Kl85En_w27gmRuZ3xrET1x87UQfDw0Os4,501
|
46
|
+
fastlife/templates/pydantic_form/Sequence.jinja,sha256=RU19X6FwNhoFhQ0fBpTrfRsS_gMPkhpDhzyhFShwoHQ,1430
|
47
|
+
fastlife/templates/pydantic_form/Text.jinja,sha256=DCZNAly_YjYQcGdz-EiCbX5DThHHTDq66KySpiJe3Ik,450
|
48
|
+
fastlife/templates/pydantic_form/Union.jinja,sha256=aH9Cj9IycCkMkL8j_yb5Ux5ysVZLVET9_AKTndQQX-Q,992
|
47
49
|
fastlife/templates/pydantic_form/Widget.jinja,sha256=8raoMjtO4ARBfbz8EG-HKT112KkrWG82BbUfbXpAmZs,287
|
48
50
|
fastlife/templating/__init__.py,sha256=QdrTmxt-K7UcJ_o9LY1n-QZAvk5foct5MNFt02zXr2w,234
|
49
51
|
fastlife/templating/binding.py,sha256=n-MDg98Bne49BmWWWCopZsI6ef7h6PIdcszM_pRutYA,1378
|
50
52
|
fastlife/templating/renderer/__init__.py,sha256=UJUX3T9VYjPQUhS5Enz3P6OtwftimKoGGpoQEpewVFA,181
|
51
|
-
fastlife/templating/renderer/abstract.py,sha256=
|
52
|
-
fastlife/templating/renderer/jinjax.py,sha256=
|
53
|
+
fastlife/templating/renderer/abstract.py,sha256=Ljzll05kI9KIMRmBeZ9fIVs4PcIi6FE1DB_YmMiBGVY,1552
|
54
|
+
fastlife/templating/renderer/jinjax.py,sha256=pCaBLqboFUuIKZcSuksNxJngzw8_zFLqLgBYuIEMytE,3977
|
53
55
|
fastlife/templating/renderer/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
|
-
fastlife/templating/renderer/widgets/base.py,sha256=
|
55
|
-
fastlife/templating/renderer/widgets/boolean.py,sha256=
|
56
|
-
fastlife/templating/renderer/widgets/checklist.py,sha256=
|
57
|
-
fastlife/templating/renderer/widgets/dropdown.py,sha256=
|
58
|
-
fastlife/templating/renderer/widgets/factory.py,sha256=
|
56
|
+
fastlife/templating/renderer/widgets/base.py,sha256=XtD-NRacHMn9Xt_dSfWb1Emk3XEXz5jExglx23Rzzpw,2808
|
57
|
+
fastlife/templating/renderer/widgets/boolean.py,sha256=1Cnrs6Sqf7CJURrCRkDL2NDQZWO9dvac350w0PcZtDg,544
|
58
|
+
fastlife/templating/renderer/widgets/checklist.py,sha256=Qit-k4RnW9Y3xOyE9BevBRuFZ8XN5jH3x_sI5fWt43Y,1009
|
59
|
+
fastlife/templating/renderer/widgets/dropdown.py,sha256=FyPZzrTprLff6YRqZ031J8-KZpBgidTw0BsKY1Qt7Ts,1009
|
60
|
+
fastlife/templating/renderer/widgets/factory.py,sha256=BVz2iNb5GMbjGlTaWndcUIitQHBb88ZSp39ays2-Os0,14779
|
59
61
|
fastlife/templating/renderer/widgets/hidden.py,sha256=2fsbTQKsACV0JVYpCjXaQAV7VnQTIBPCi4lJPdWCRHc,308
|
60
|
-
fastlife/templating/renderer/widgets/model.py,sha256=
|
61
|
-
fastlife/templating/renderer/widgets/sequence.py,sha256=
|
62
|
-
fastlife/templating/renderer/widgets/text.py,sha256=
|
63
|
-
fastlife/templating/renderer/widgets/union.py,sha256=
|
62
|
+
fastlife/templating/renderer/widgets/model.py,sha256=BNM6dPPN9jzM26LreeOw7wiCsZun1uSMMFXMNcO2hII,1094
|
63
|
+
fastlife/templating/renderer/widgets/sequence.py,sha256=rYXsUZokV3wnKa-26BmgAu7sVCtFf8FdBmhvrnqR-gM,1455
|
64
|
+
fastlife/templating/renderer/widgets/text.py,sha256=OWFFA0muZCznrlUrBRRUKVj60TdWtsdgw0bURdOA3lE,879
|
65
|
+
fastlife/templating/renderer/widgets/union.py,sha256=xNDctq0SRXfRyMHXL8FgRKyUOUreR1xENnt6onkZJ9I,1797
|
64
66
|
fastlife/testing/__init__.py,sha256=KgTlRI0g8z7HRpL7mD5QgI__LT9Y4QDSzKMlxJG3wNk,67
|
65
|
-
fastlife/testing/testclient.py,sha256=
|
67
|
+
fastlife/testing/testclient.py,sha256=o7Yc-_At0S23lmV8-vbGGPm-s0xRTAV7OhBri2rqqrU,20220
|
66
68
|
fastlife/views/__init__.py,sha256=nn4B_8YTbTmhGPvSd20yyKK_9Dh1Pfh_Iq7z6iK8-CE,154
|
67
|
-
fastlife/views/pydantic_form.py,sha256=
|
68
|
-
fastlifeweb-0.
|
69
|
-
fastlifeweb-0.
|
70
|
-
fastlifeweb-0.
|
71
|
-
fastlifeweb-0.
|
69
|
+
fastlife/views/pydantic_form.py,sha256=uUSw9Wrpx2XAiep26L6fViXv9p6KYi-DFQTYymrbBMk,1158
|
70
|
+
fastlifeweb-0.6.1.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
|
71
|
+
fastlifeweb-0.6.1.dist-info/METADATA,sha256=a76c4GR6xUogf6ORnzywvcl2miKzGQFawFOhLCXeb8k,1833
|
72
|
+
fastlifeweb-0.6.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
73
|
+
fastlifeweb-0.6.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|