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.
Files changed (29) hide show
  1. fastlife/__init__.py +3 -2
  2. fastlife/request/form_data.py +7 -22
  3. fastlife/request/model_result.py +91 -0
  4. fastlife/shared_utils/infer.py +1 -1
  5. fastlife/templates/pydantic_form/Boolean.jinja +7 -2
  6. fastlife/templates/pydantic_form/Checklist.jinja +3 -1
  7. fastlife/templates/pydantic_form/Dropdown.jinja +1 -0
  8. fastlife/templates/pydantic_form/Error.jinja +4 -0
  9. fastlife/templates/pydantic_form/Model.jinja +1 -0
  10. fastlife/templates/pydantic_form/Sequence.jinja +1 -0
  11. fastlife/templates/pydantic_form/Text.jinja +1 -0
  12. fastlife/templates/pydantic_form/Union.jinja +4 -4
  13. fastlife/templating/renderer/abstract.py +16 -2
  14. fastlife/templating/renderer/jinjax.py +25 -4
  15. fastlife/templating/renderer/widgets/base.py +7 -5
  16. fastlife/templating/renderer/widgets/boolean.py +7 -1
  17. fastlife/templating/renderer/widgets/checklist.py +13 -2
  18. fastlife/templating/renderer/widgets/dropdown.py +7 -1
  19. fastlife/templating/renderer/widgets/factory.py +69 -16
  20. fastlife/templating/renderer/widgets/model.py +7 -1
  21. fastlife/templating/renderer/widgets/sequence.py +7 -1
  22. fastlife/templating/renderer/widgets/text.py +3 -1
  23. fastlife/templating/renderer/widgets/union.py +7 -1
  24. fastlife/testing/testclient.py +102 -0
  25. fastlife/views/pydantic_form.py +6 -2
  26. {fastlifeweb-0.5.1.dist-info → fastlifeweb-0.6.1.dist-info}/METADATA +2 -2
  27. {fastlifeweb-0.5.1.dist-info → fastlifeweb-0.6.1.dist-info}/RECORD +29 -27
  28. {fastlifeweb-0.5.1.dist-info → fastlifeweb-0.6.1.dist-info}/LICENSE +0 -0
  29. {fastlifeweb-0.5.1.dist-info → fastlifeweb-0.6.1.dist-info}/WHEEL +0 -0
@@ -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(
@@ -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).pydantic_form(
25
- model_cls, None, name, token, removable, field
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.5.1
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.0,<3.0.0)
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=ewfV5kR3Pq7Bzan_Wnq__LYGzXgk2CJaamxhyMyhaCI,312
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=A1mowwOrsQ0yPvfMwh_m9Mw6yoYTgqayibiXNyyOGLM,4049
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=_hmGzu84VlZAkdw_owkW8eHknULqH3MLDBlXj7LkEsc,466
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=tzEZpgv3t9vEuQr2_NzLvhqyusGm2w7kEkehCRJyG2c,281
39
- fastlife/templates/pydantic_form/Checklist.jinja,sha256=wv224DG6X-XTpiU0hQl5AjuAsb-MMS7TaMdVaYhEkq4,665
40
- fastlife/templates/pydantic_form/Dropdown.jinja,sha256=JBkQp7LYtz6vvW6MyAXyW-6UDZ_xY0Fgd4mpDCQA-wM,500
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=wxEKEO8Kktxkrz-LZ9m_v_NPjKV-C8RFQ9wyEyrRKsI,449
44
- fastlife/templates/pydantic_form/Sequence.jinja,sha256=eA8GYhJC1KokWbU3zqmqCIuG8mR-yhvlEAzt0ds2RTM,1380
45
- fastlife/templates/pydantic_form/Text.jinja,sha256=t8fZTGm7EOrc8JsVlnqRAkAfQfKDDZQk4xXlLAgXCAQ,402
46
- fastlife/templates/pydantic_form/Union.jinja,sha256=Ss4dwNoHqmdAa4O7cvS2vwuxBPspQINBxq4hnkm_GVY,950
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=ksWrz25bmsN4MQJK08OYzoZfiYvAg39Z1DtjxyDDe84,1251
52
- fastlife/templating/renderer/jinjax.py,sha256=bgHgu6VLA4S8fS7Evu68iPaODdev6v0s6Z79TX8c6Mo,3458
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=_MMtrhYvtQgX1EQNku0NSBYwhCDvQ5y8TjnCC2b4CAw,2769
55
- fastlife/templating/renderer/widgets/boolean.py,sha256=UPQOxIpydP2NzDVUQuNau1DewS2FkRqx5WkWSwEu1kk,436
56
- fastlife/templating/renderer/widgets/checklist.py,sha256=lR6A1nui6iYnx9E4cNEaxsgNPsZWtEEofh2l3C9qWos,770
57
- fastlife/templating/renderer/widgets/dropdown.py,sha256=o3BfD9gDiRPsCPB93xCZSYRo50LRAb_tZOmK4x7zkzY,901
58
- fastlife/templating/renderer/widgets/factory.py,sha256=59BfKpNIB8T71vZ0RjjcjNjdP0TfSm-WHgXmelyIziY,12850
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=L7tdmRj0cetM_UusaD2zk5YJtIGBYizCMBKQzFyN_Ko,986
61
- fastlife/templating/renderer/widgets/sequence.py,sha256=x6o1L_hOh3WRlW16ZQaeO6H_qtnIitALuK-w7ck0RAw,1347
62
- fastlife/templating/renderer/widgets/text.py,sha256=mlW8LrRmj-aEMp8QzZOURhns6mfbV7jsOMQrsYZcU3Q,819
63
- fastlife/templating/renderer/widgets/union.py,sha256=GntHssMjcgpD6S5h0JVr9_aWy3pSbMhYZ9QU-ugBH-Y,1689
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=JhWgruKDeIn0QFmEAXKhUXXsZpTpPi_JWIHhWbWIXg0,16404
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=TyZbVfhOGy3en1a7o4wcRp11War7sqVHxyEjOsRMht0,1092
68
- fastlifeweb-0.5.1.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
69
- fastlifeweb-0.5.1.dist-info/METADATA,sha256=kQIE0LHEi25Hg1PXXdYTN-bHE59TB5Www7GwDyRBLF0,1833
70
- fastlifeweb-0.5.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
71
- fastlifeweb-0.5.1.dist-info/RECORD,,
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,,