fastlifeweb 0.26.0__py3-none-any.whl → 0.26.2__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/assets/dist.css +98 -54
- fastlife/components/Form.jinja +1 -1
- fastlife/config/configurator.py +1 -1
- fastlife/domain/model/response.py +40 -0
- fastlife/testing/dom.py +58 -6
- fastlife/testing/testclient.py +10 -3
- {fastlifeweb-0.26.0.dist-info → fastlifeweb-0.26.2.dist-info}/METADATA +1 -1
- {fastlifeweb-0.26.0.dist-info → fastlifeweb-0.26.2.dist-info}/RECORD +13 -12
- {fastlifeweb-0.26.0.dist-info → fastlifeweb-0.26.2.dist-info}/WHEEL +1 -1
- {fastlifeweb-0.26.0.dist-info → fastlifeweb-0.26.2.dist-info}/entry_points.txt +0 -0
- {fastlifeweb-0.26.0.dist-info → fastlifeweb-0.26.2.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## 0.26.2 - Released on 2025-04-20
|
2
|
+
* Add a RedirectResponse class that is htmx friendly and P/R/G pattern friendly.
|
3
|
+
* Fix documentation generation.
|
4
|
+
|
5
|
+
## 0.26.1 - Released on 2025-04-20
|
6
|
+
* Add new helpers for the webtestclient element.
|
7
|
+
|
1
8
|
## 0.26.0 - Released on 2025-03-08
|
2
9
|
* Add a new method to the configuration to customize the request class.
|
3
10
|
See [Configurator.set_request_factory](#fastlife.config.configurator.GenericConfigurator.set_request_factory)
|
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,
|
fastlife/assets/dist.css
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
/*! tailwindcss v4.
|
1
|
+
/*! tailwindcss v4.1.4 | MIT License | https://tailwindcss.com */
|
2
|
+
@layer properties;
|
2
3
|
@layer theme, base, components, utilities;
|
3
4
|
@layer theme {
|
4
5
|
:root, :host {
|
@@ -6,20 +7,20 @@
|
|
6
7
|
'Noto Color Emoji';
|
7
8
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
8
9
|
monospace;
|
9
|
-
--color-red-50: oklch(
|
10
|
-
--color-red-400: oklch(
|
11
|
-
--color-red-700: oklch(
|
12
|
-
--color-orange-500: oklch(
|
13
|
-
--color-neutral-50: oklch(
|
14
|
-
--color-neutral-100: oklch(
|
15
|
-
--color-neutral-200: oklch(
|
16
|
-
--color-neutral-300: oklch(
|
17
|
-
--color-neutral-400: oklch(
|
18
|
-
--color-neutral-500: oklch(
|
19
|
-
--color-neutral-600: oklch(
|
20
|
-
--color-neutral-700: oklch(
|
21
|
-
--color-neutral-800: oklch(
|
22
|
-
--color-neutral-900: oklch(
|
10
|
+
--color-red-50: oklch(97.1% 0.013 17.38);
|
11
|
+
--color-red-400: oklch(70.4% 0.191 22.216);
|
12
|
+
--color-red-700: oklch(50.5% 0.213 27.518);
|
13
|
+
--color-orange-500: oklch(70.5% 0.213 47.604);
|
14
|
+
--color-neutral-50: oklch(98.5% 0 0);
|
15
|
+
--color-neutral-100: oklch(97% 0 0);
|
16
|
+
--color-neutral-200: oklch(92.2% 0 0);
|
17
|
+
--color-neutral-300: oklch(87% 0 0);
|
18
|
+
--color-neutral-400: oklch(70.8% 0 0);
|
19
|
+
--color-neutral-500: oklch(55.6% 0 0);
|
20
|
+
--color-neutral-600: oklch(43.9% 0 0);
|
21
|
+
--color-neutral-700: oklch(37.1% 0 0);
|
22
|
+
--color-neutral-800: oklch(26.9% 0 0);
|
23
|
+
--color-neutral-900: oklch(20.5% 0 0);
|
23
24
|
--color-white: #fff;
|
24
25
|
--spacing: 0.25rem;
|
25
26
|
--text-sm: 0.875rem;
|
@@ -48,11 +49,7 @@
|
|
48
49
|
--default-transition-duration: 150ms;
|
49
50
|
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
50
51
|
--default-font-family: var(--font-sans);
|
51
|
-
--default-font-feature-settings: var(--font-sans--font-feature-settings);
|
52
|
-
--default-font-variation-settings: var(--font-sans--font-variation-settings);
|
53
52
|
--default-mono-font-family: var(--font-mono);
|
54
|
-
--default-mono-font-feature-settings: var(--font-mono--font-feature-settings);
|
55
|
-
--default-mono-font-variation-settings: var(--font-mono--font-variation-settings);
|
56
53
|
}
|
57
54
|
}
|
58
55
|
@layer base {
|
@@ -66,14 +63,11 @@
|
|
66
63
|
line-height: 1.5;
|
67
64
|
-webkit-text-size-adjust: 100%;
|
68
65
|
tab-size: 4;
|
69
|
-
font-family: var(
|
66
|
+
font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji');
|
70
67
|
font-feature-settings: var(--default-font-feature-settings, normal);
|
71
68
|
font-variation-settings: var(--default-font-variation-settings, normal);
|
72
69
|
-webkit-tap-highlight-color: transparent;
|
73
70
|
}
|
74
|
-
body {
|
75
|
-
line-height: inherit;
|
76
|
-
}
|
77
71
|
hr {
|
78
72
|
height: 0;
|
79
73
|
color: inherit;
|
@@ -96,7 +90,7 @@
|
|
96
90
|
font-weight: bolder;
|
97
91
|
}
|
98
92
|
code, kbd, samp, pre {
|
99
|
-
font-family: var(
|
93
|
+
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);
|
100
94
|
font-feature-settings: var(--default-mono-font-feature-settings, normal);
|
101
95
|
font-variation-settings: var(--default-mono-font-variation-settings, normal);
|
102
96
|
font-size: 1em;
|
@@ -162,7 +156,14 @@
|
|
162
156
|
}
|
163
157
|
::placeholder {
|
164
158
|
opacity: 1;
|
165
|
-
|
159
|
+
}
|
160
|
+
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
|
161
|
+
::placeholder {
|
162
|
+
color: currentcolor;
|
163
|
+
@supports (color: color-mix(in lab, red, red)) {
|
164
|
+
color: color-mix(in oklab, currentcolor 50%, transparent);
|
165
|
+
}
|
166
|
+
}
|
166
167
|
}
|
167
168
|
textarea {
|
168
169
|
resize: vertical;
|
@@ -215,24 +216,6 @@
|
|
215
216
|
.static {
|
216
217
|
position: static;
|
217
218
|
}
|
218
|
-
.container {
|
219
|
-
width: 100%;
|
220
|
-
@media (width >= 40rem) {
|
221
|
-
max-width: 40rem;
|
222
|
-
}
|
223
|
-
@media (width >= 48rem) {
|
224
|
-
max-width: 48rem;
|
225
|
-
}
|
226
|
-
@media (width >= 64rem) {
|
227
|
-
max-width: 64rem;
|
228
|
-
}
|
229
|
-
@media (width >= 80rem) {
|
230
|
-
max-width: 80rem;
|
231
|
-
}
|
232
|
-
@media (width >= 96rem) {
|
233
|
-
max-width: 96rem;
|
234
|
-
}
|
235
|
-
}
|
236
219
|
.m-3 {
|
237
220
|
margin: calc(var(--spacing) * 3);
|
238
221
|
}
|
@@ -263,9 +246,6 @@
|
|
263
246
|
.inline {
|
264
247
|
display: inline;
|
265
248
|
}
|
266
|
-
.table {
|
267
|
-
display: table;
|
268
|
-
}
|
269
249
|
.h-4 {
|
270
250
|
height: calc(var(--spacing) * 4);
|
271
251
|
}
|
@@ -303,7 +283,7 @@
|
|
303
283
|
rotate: 90deg;
|
304
284
|
}
|
305
285
|
.transform {
|
306
|
-
transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y);
|
286
|
+
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
307
287
|
}
|
308
288
|
.cursor-pointer {
|
309
289
|
cursor: pointer;
|
@@ -494,18 +474,19 @@
|
|
494
474
|
}
|
495
475
|
.focus\:ring-2 {
|
496
476
|
&:focus {
|
497
|
-
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,
|
477
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
498
478
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
499
479
|
}
|
500
480
|
}
|
501
481
|
.focus\:ring-4 {
|
502
482
|
&:focus {
|
503
|
-
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,
|
483
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
504
484
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
505
485
|
}
|
506
486
|
}
|
507
487
|
.focus\:outline-hidden {
|
508
488
|
&:focus {
|
489
|
+
--tw-outline-style: none;
|
509
490
|
outline-style: none;
|
510
491
|
@media (forced-colors: active) {
|
511
492
|
outline: 2px solid transparent;
|
@@ -618,27 +599,22 @@
|
|
618
599
|
@property --tw-rotate-x {
|
619
600
|
syntax: "*";
|
620
601
|
inherits: false;
|
621
|
-
initial-value: rotateX(0);
|
622
602
|
}
|
623
603
|
@property --tw-rotate-y {
|
624
604
|
syntax: "*";
|
625
605
|
inherits: false;
|
626
|
-
initial-value: rotateY(0);
|
627
606
|
}
|
628
607
|
@property --tw-rotate-z {
|
629
608
|
syntax: "*";
|
630
609
|
inherits: false;
|
631
|
-
initial-value: rotateZ(0);
|
632
610
|
}
|
633
611
|
@property --tw-skew-x {
|
634
612
|
syntax: "*";
|
635
613
|
inherits: false;
|
636
|
-
initial-value: skewX(0);
|
637
614
|
}
|
638
615
|
@property --tw-skew-y {
|
639
616
|
syntax: "*";
|
640
617
|
inherits: false;
|
641
|
-
initial-value: skewY(0);
|
642
618
|
}
|
643
619
|
@property --tw-space-y-reverse {
|
644
620
|
syntax: "*";
|
@@ -707,6 +683,19 @@
|
|
707
683
|
syntax: "*";
|
708
684
|
inherits: false;
|
709
685
|
}
|
686
|
+
@property --tw-drop-shadow-color {
|
687
|
+
syntax: "*";
|
688
|
+
inherits: false;
|
689
|
+
}
|
690
|
+
@property --tw-drop-shadow-alpha {
|
691
|
+
syntax: "<percentage>";
|
692
|
+
inherits: false;
|
693
|
+
initial-value: 100%;
|
694
|
+
}
|
695
|
+
@property --tw-drop-shadow-size {
|
696
|
+
syntax: "*";
|
697
|
+
inherits: false;
|
698
|
+
}
|
710
699
|
@property --tw-duration {
|
711
700
|
syntax: "*";
|
712
701
|
inherits: false;
|
@@ -720,6 +709,11 @@
|
|
720
709
|
syntax: "*";
|
721
710
|
inherits: false;
|
722
711
|
}
|
712
|
+
@property --tw-shadow-alpha {
|
713
|
+
syntax: "<percentage>";
|
714
|
+
inherits: false;
|
715
|
+
initial-value: 100%;
|
716
|
+
}
|
723
717
|
@property --tw-inset-shadow {
|
724
718
|
syntax: "*";
|
725
719
|
inherits: false;
|
@@ -729,6 +723,11 @@
|
|
729
723
|
syntax: "*";
|
730
724
|
inherits: false;
|
731
725
|
}
|
726
|
+
@property --tw-inset-shadow-alpha {
|
727
|
+
syntax: "<percentage>";
|
728
|
+
inherits: false;
|
729
|
+
initial-value: 100%;
|
730
|
+
}
|
732
731
|
@property --tw-ring-color {
|
733
732
|
syntax: "*";
|
734
733
|
inherits: false;
|
@@ -766,3 +765,48 @@
|
|
766
765
|
inherits: false;
|
767
766
|
initial-value: 0 0 #0000;
|
768
767
|
}
|
768
|
+
@layer properties {
|
769
|
+
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
770
|
+
*, ::before, ::after, ::backdrop {
|
771
|
+
--tw-rotate-x: initial;
|
772
|
+
--tw-rotate-y: initial;
|
773
|
+
--tw-rotate-z: initial;
|
774
|
+
--tw-skew-x: initial;
|
775
|
+
--tw-skew-y: initial;
|
776
|
+
--tw-space-y-reverse: 0;
|
777
|
+
--tw-border-style: solid;
|
778
|
+
--tw-leading: initial;
|
779
|
+
--tw-font-weight: initial;
|
780
|
+
--tw-tracking: initial;
|
781
|
+
--tw-outline-style: solid;
|
782
|
+
--tw-blur: initial;
|
783
|
+
--tw-brightness: initial;
|
784
|
+
--tw-contrast: initial;
|
785
|
+
--tw-grayscale: initial;
|
786
|
+
--tw-hue-rotate: initial;
|
787
|
+
--tw-invert: initial;
|
788
|
+
--tw-opacity: initial;
|
789
|
+
--tw-saturate: initial;
|
790
|
+
--tw-sepia: initial;
|
791
|
+
--tw-drop-shadow: initial;
|
792
|
+
--tw-drop-shadow-color: initial;
|
793
|
+
--tw-drop-shadow-alpha: 100%;
|
794
|
+
--tw-drop-shadow-size: initial;
|
795
|
+
--tw-duration: initial;
|
796
|
+
--tw-shadow: 0 0 #0000;
|
797
|
+
--tw-shadow-color: initial;
|
798
|
+
--tw-shadow-alpha: 100%;
|
799
|
+
--tw-inset-shadow: 0 0 #0000;
|
800
|
+
--tw-inset-shadow-color: initial;
|
801
|
+
--tw-inset-shadow-alpha: 100%;
|
802
|
+
--tw-ring-color: initial;
|
803
|
+
--tw-ring-shadow: 0 0 #0000;
|
804
|
+
--tw-inset-ring-color: initial;
|
805
|
+
--tw-inset-ring-shadow: 0 0 #0000;
|
806
|
+
--tw-ring-inset: initial;
|
807
|
+
--tw-ring-offset-width: 0px;
|
808
|
+
--tw-ring-offset-color: #fff;
|
809
|
+
--tw-ring-offset-shadow: 0 0 #0000;
|
810
|
+
}
|
811
|
+
}
|
812
|
+
}
|
fastlife/components/Form.jinja
CHANGED
fastlife/config/configurator.py
CHANGED
@@ -177,7 +177,7 @@ class GenericConfigurator(Generic[TRegistry]):
|
|
177
177
|
"""
|
178
178
|
|
179
179
|
# register our main template renderer at then end, to ensure that
|
180
|
-
# if settings have been manipulated,
|
180
|
+
# if settings have been manipulated, everything is taken into account.
|
181
181
|
self.add_renderer(
|
182
182
|
self.registry.settings.jinjax_file_ext,
|
183
183
|
resolve("fastlife.adapters.jinjax.renderer:JinjaxEngine")(
|
@@ -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/testing/dom.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
import re
|
4
4
|
from collections.abc import Iterator, Sequence
|
5
|
-
from typing import TYPE_CHECKING
|
5
|
+
from typing import TYPE_CHECKING, Literal, overload
|
6
6
|
|
7
7
|
import bs4
|
8
8
|
|
@@ -82,10 +82,19 @@ class Element:
|
|
82
82
|
el = el.parent
|
83
83
|
return None
|
84
84
|
|
85
|
-
def by_text(
|
85
|
+
def by_text(
|
86
|
+
self, text: str, *, node_name: str | None = None, position: int | None = None
|
87
|
+
) -> "Element | None":
|
86
88
|
"""Find the first element that match the text."""
|
87
89
|
nodes = self.iter_all_by_text(text, node_name=node_name)
|
88
|
-
|
90
|
+
ret = list(nodes)
|
91
|
+
if not ret:
|
92
|
+
return None
|
93
|
+
if position is None:
|
94
|
+
assert len(ret) == 1, f"Should have 1 element, got {len(ret)} in {self}"
|
95
|
+
else:
|
96
|
+
assert len(ret) > position, "Not enough element found"
|
97
|
+
return ret[position or 0]
|
89
98
|
|
90
99
|
def iter_all_by_text(
|
91
100
|
self, text: str, *, node_name: str | None = None
|
@@ -121,17 +130,60 @@ class Element:
|
|
121
130
|
assert not isinstance(resp, bs4.NavigableString)
|
122
131
|
return Element(self._client, resp) if resp else None
|
123
132
|
|
133
|
+
def by_id(self, id: str) -> "Element | None":
|
134
|
+
"""Find the element having the given id."""
|
135
|
+
resp = self._tag.find_all(id=id)
|
136
|
+
assert not isinstance(resp, bs4.NavigableString)
|
137
|
+
if not resp:
|
138
|
+
return None
|
139
|
+
assert len(resp) == 1
|
140
|
+
return Element(self._client, resp[0]) if resp else None
|
141
|
+
|
142
|
+
@overload
|
143
|
+
def by_node_name(
|
144
|
+
self,
|
145
|
+
node_name: str,
|
146
|
+
*,
|
147
|
+
attrs: dict[str, str] | None = None,
|
148
|
+
multiple: Literal[False],
|
149
|
+
) -> "Element": ...
|
150
|
+
|
151
|
+
@overload
|
152
|
+
def by_node_name(
|
153
|
+
self,
|
154
|
+
node_name: str,
|
155
|
+
*,
|
156
|
+
attrs: dict[str, str] | None = None,
|
157
|
+
multiple: Literal[True],
|
158
|
+
) -> "list[Element]": ...
|
159
|
+
|
160
|
+
@overload
|
161
|
+
def by_node_name(
|
162
|
+
self,
|
163
|
+
node_name: str,
|
164
|
+
*,
|
165
|
+
attrs: dict[str, str] | None = None,
|
166
|
+
) -> "list[Element]": ...
|
167
|
+
|
124
168
|
def by_node_name(
|
125
|
-
self,
|
126
|
-
|
169
|
+
self,
|
170
|
+
node_name: str,
|
171
|
+
*,
|
172
|
+
attrs: dict[str, str] | None = None,
|
173
|
+
multiple: bool = True,
|
174
|
+
) -> "list[Element] | Element":
|
127
175
|
"""
|
128
176
|
Return the list of elements with the given node_name.
|
129
177
|
|
130
178
|
An optional set of attributes may given and must match if passed.
|
131
179
|
"""
|
132
|
-
|
180
|
+
ret = [
|
133
181
|
Element(self._client, e) for e in self._tag.find_all(node_name, attrs or {})
|
134
182
|
]
|
183
|
+
if not multiple:
|
184
|
+
assert len(ret) == 1
|
185
|
+
return ret[0]
|
186
|
+
return ret
|
135
187
|
|
136
188
|
def __repr__(self) -> str:
|
137
189
|
return f"<{self.node_name}>"
|
fastlife/testing/testclient.py
CHANGED
@@ -79,9 +79,11 @@ class WebResponse:
|
|
79
79
|
self._form = WebForm(self._client, self._origin, form)
|
80
80
|
return self._form
|
81
81
|
|
82
|
-
def by_text(
|
82
|
+
def by_text(
|
83
|
+
self, text: str, *, node_name: str | None = None, position: int | None = None
|
84
|
+
) -> Element | None:
|
83
85
|
"""Search a dom element by its text."""
|
84
|
-
return self.html.by_text(text, node_name=node_name)
|
86
|
+
return self.html.by_text(text, node_name=node_name, position=position)
|
85
87
|
|
86
88
|
def by_label_text(self, text: str) -> Element | None:
|
87
89
|
"""Search a dom element by its associated label text."""
|
@@ -156,9 +158,14 @@ class WebTestClient:
|
|
156
158
|
method = "GET"
|
157
159
|
headers = None
|
158
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"
|
159
166
|
return self.request(
|
160
167
|
method=method,
|
161
|
-
url=
|
168
|
+
url=url,
|
162
169
|
content=content,
|
163
170
|
headers=headers,
|
164
171
|
max_redirects=max_redirects - 1,
|
@@ -1,5 +1,5 @@
|
|
1
|
-
CHANGELOG.md,sha256=
|
2
|
-
fastlife/__init__.py,sha256=
|
1
|
+
CHANGELOG.md,sha256=NqlUm-TificrXs2dKAL5-1_6i3Eo74MRksoaa0RBS1k,8830
|
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
|
@@ -42,14 +42,14 @@ fastlife/adapters/jinjax/widgets/model.py,sha256=YBIEWa_6mnmrBnesXjLTrpJ4drUS2CI
|
|
42
42
|
fastlife/adapters/jinjax/widgets/sequence.py,sha256=dVoHQmHloaRuU1Sd82b2jnO8WDfdwM2FaZlLCJCps1o,2566
|
43
43
|
fastlife/adapters/jinjax/widgets/text.py,sha256=TfmlJU233aZWIl-4cmm-f-pFxp6ycHWHnbiluOvRDgM,3040
|
44
44
|
fastlife/adapters/jinjax/widgets/union.py,sha256=roCoFA82dLjF1XFW6UYaV7SCQWdFsSAT8Ux7KEB6_Us,2602
|
45
|
-
fastlife/assets/dist.css,sha256=
|
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
|
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
|
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
|
@@ -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=2LkPXoarMTk40S7AVlxODPAckDllE0G8u27jtPmvGM8,25188
|
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
|
@@ -1701,6 +1701,7 @@ fastlife/domain/model/asgi.py,sha256=Cz45TZOtrh2pBVZr37aJ9jpnJH9BeNHrsvk9bq1nBc0
|
|
1701
1701
|
fastlife/domain/model/csrf.py,sha256=BUiWK-S7rVciWHO1qTkM8e_KxzpF6gGC4MMJK1v6iDo,414
|
1702
1702
|
fastlife/domain/model/form.py,sha256=JP6uumlZBYhiPxzcdxOsfsFm5BRfvkDFvlUCD6Vy8dI,3275
|
1703
1703
|
fastlife/domain/model/request.py,sha256=HgUSnUu3q18e07y57PadN3pPQwYrIZS1YEhYkBZ_Zfg,2674
|
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
|
@@ -1727,14 +1728,14 @@ fastlife/shared_utils/infer.py,sha256=0GflLkaWJ-4LZ1Ig3moR-_o55wwJ_p_vJ4xo-yi3ly
|
|
1727
1728
|
fastlife/shared_utils/resolver.py,sha256=Wb9cO2MWavpti63hju15xmwFMgaD5DsQaxikRpB39E8,3713
|
1728
1729
|
fastlife/template_globals.py,sha256=bKcj6kSnQlzuOeoILA5oRxxzy6CsrBFMZv9S0w5XlTQ,9021
|
1729
1730
|
fastlife/testing/__init__.py,sha256=VpxkS3Zp3t_hH8dBiLaGFGhsvt511dhBS_8fMoFXdmU,99
|
1730
|
-
fastlife/testing/dom.py,sha256=
|
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
1736
|
fastlife/views/pydantic_form.py,sha256=o7EUItciAGL1OSaGNHo-3BTrYAk34GuWE7zGikjiAGY,1486
|
1736
|
-
fastlifeweb-0.26.
|
1737
|
-
fastlifeweb-0.26.
|
1738
|
-
fastlifeweb-0.26.
|
1739
|
-
fastlifeweb-0.26.
|
1740
|
-
fastlifeweb-0.26.
|
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,,
|
File without changes
|
File without changes
|