fastlifeweb 0.23.0__py3-none-any.whl → 0.24.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CHANGELOG.md +7 -0
- fastlife/__init__.py +2 -0
- fastlife/adapters/fastapi/localizer.py +1 -1
- fastlife/config/configurator.py +2 -1
- fastlife/config/resources.py +1 -7
- fastlife/middlewares/session/middleware.py +1 -1
- fastlife/service/registry.py +20 -4
- fastlife/service/translatablestring.py +1 -0
- fastlife/service/translations.py +45 -8
- fastlife/testing/form.py +1 -1
- fastlife/testing/testclient.py +9 -2
- {fastlifeweb-0.23.0.dist-info → fastlifeweb-0.24.0.dist-info}/METADATA +1 -1
- {fastlifeweb-0.23.0.dist-info → fastlifeweb-0.24.0.dist-info}/RECORD +16 -15
- {fastlifeweb-0.23.0.dist-info → fastlifeweb-0.24.0.dist-info}/WHEEL +0 -0
- {fastlifeweb-0.23.0.dist-info → fastlifeweb-0.24.0.dist-info}/entry_points.txt +0 -0
- {fastlifeweb-0.23.0.dist-info → fastlifeweb-0.24.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## 0.24.0 - Released on 2025-01-17
|
2
|
+
* Add a way to hook the lifespan to the app
|
3
|
+
|
4
|
+
## 0.23.1 - Released on 2025-01-14
|
5
|
+
* Fix typing issue
|
6
|
+
* Update docs
|
7
|
+
|
1
8
|
## 0.23.0 - Released on 2024-12-04
|
2
9
|
* Update Request type.
|
3
10
|
* Breaking changes: Request[TUser, TRegistry] -> Request[TRegistry, TIdentity, TClaimedIdentity].
|
fastlife/__init__.py
CHANGED
@@ -48,6 +48,7 @@ from .service.security_policy import (
|
|
48
48
|
AbstractSecurityPolicy,
|
49
49
|
InsecurePolicy,
|
50
50
|
)
|
51
|
+
from .service.translations import TranslatableStringFactory
|
51
52
|
from .settings import Settings
|
52
53
|
|
53
54
|
__all__ = [
|
@@ -97,4 +98,5 @@ __all__ = [
|
|
97
98
|
"JinjaXTemplate",
|
98
99
|
# i18n
|
99
100
|
"Localizer",
|
101
|
+
"TranslatableStringFactory",
|
100
102
|
]
|
@@ -8,7 +8,7 @@ from fastlife.service.translations import Localizer as RequestLocalizer
|
|
8
8
|
|
9
9
|
def get_localizer(request: Request) -> RequestLocalizer:
|
10
10
|
"""Return the localizer for the given request."""
|
11
|
-
return request.registry.localizer(request)
|
11
|
+
return request.registry.localizer(request.locale_name)
|
12
12
|
|
13
13
|
|
14
14
|
Localizer = Annotated[RequestLocalizer, Depends(get_localizer)]
|
fastlife/config/configurator.py
CHANGED
@@ -191,6 +191,7 @@ class GenericConfigurator(Generic[TRegistry]):
|
|
191
191
|
dependencies=[Depends(check_csrf())],
|
192
192
|
docs_url=self.api_swagger_ui_url,
|
193
193
|
redoc_url=self.api_redoc_url,
|
194
|
+
lifespan=self.registry.lifespan,
|
194
195
|
openapi_tags=[tag.model_dump(by_alias=True) for tag in self.tags.values()]
|
195
196
|
if self.tags
|
196
197
|
else None,
|
@@ -473,7 +474,7 @@ class GenericConfigurator(Generic[TRegistry]):
|
|
473
474
|
* `gettext`, `ngettext`, `dgettext`, `dngettext`, `pgettext`, `dpgettext`,
|
474
475
|
`npgettext`, `dnpgettext` methods are installed for i18n purpose.
|
475
476
|
"""
|
476
|
-
lczr = request.registry.localizer(request)
|
477
|
+
lczr = request.registry.localizer(request.locale_name)
|
477
478
|
custom_globals = {}
|
478
479
|
for key, (val, evaluate) in self._renderer_globals.items():
|
479
480
|
if evaluate and callable(val):
|
fastlife/config/resources.py
CHANGED
@@ -124,13 +124,7 @@ def resource(
|
|
124
124
|
config, method, collection_path, getattr(api, method)
|
125
125
|
)
|
126
126
|
case (
|
127
|
-
"get"
|
128
|
-
| "post"
|
129
|
-
| "put"
|
130
|
-
| "patch"
|
131
|
-
| "delete"
|
132
|
-
| "head"
|
133
|
-
| "options"
|
127
|
+
"get" | "post" | "put" | "patch" | "delete" | "head" | "options"
|
134
128
|
):
|
135
129
|
bind_config(config, method, path, getattr(api, method))
|
136
130
|
case _:
|
@@ -72,7 +72,7 @@ class SessionMiddleware(AbstractMiddleware):
|
|
72
72
|
headers = MutableHeaders(scope=message)
|
73
73
|
expires = "expires=Thu, 01 Jan 1970 00:00:00 GMT; "
|
74
74
|
header_value = (
|
75
|
-
f"{self.cookie_name}=;
|
75
|
+
f"{self.cookie_name}=; {expires}{self.security_flags}"
|
76
76
|
)
|
77
77
|
headers.append("set-cookie", header_value)
|
78
78
|
await send(message)
|
fastlife/service/registry.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
from collections.abc import Mapping
|
2
|
-
from
|
1
|
+
from collections.abc import AsyncIterator, Mapping
|
2
|
+
from contextlib import asynccontextmanager
|
3
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
4
|
+
|
5
|
+
from fastapi import FastAPI
|
3
6
|
|
4
7
|
if TYPE_CHECKING:
|
5
8
|
from fastlife.service.locale_negociator import LocaleNegociator # coverage: ignore
|
@@ -22,7 +25,7 @@ class GenericRegistry(Generic[TSettings]):
|
|
22
25
|
It is initialized by the configurator and accessed by the `fastlife.Registry`.
|
23
26
|
"""
|
24
27
|
|
25
|
-
settings:
|
28
|
+
settings: TSettings
|
26
29
|
"""Application settings."""
|
27
30
|
renderers: Mapping[str, "AbstractTemplateRendererFactory"]
|
28
31
|
"""Registered template engine."""
|
@@ -31,7 +34,7 @@ class GenericRegistry(Generic[TSettings]):
|
|
31
34
|
localizer: "LocalizerFactory"
|
32
35
|
"""Used to localized message."""
|
33
36
|
|
34
|
-
def __init__(self, settings:
|
37
|
+
def __init__(self, settings: TSettings) -> None:
|
35
38
|
from fastlife.service.locale_negociator import default_negociator
|
36
39
|
from fastlife.service.translations import LocalizerFactory
|
37
40
|
|
@@ -46,6 +49,19 @@ class GenericRegistry(Generic[TSettings]):
|
|
46
49
|
return val
|
47
50
|
raise RuntimeError(f"No renderer registered for template {template}")
|
48
51
|
|
52
|
+
@asynccontextmanager
|
53
|
+
async def lifespan(self, app: FastAPI) -> AsyncIterator[Any]:
|
54
|
+
"""
|
55
|
+
hook to override the lifespan of the starlette app.
|
56
|
+
|
57
|
+
The [lifespan](https://asgi.readthedocs.io/en/latest/specs/lifespan.html)
|
58
|
+
is used to initialized and dispose the application state.
|
59
|
+
|
60
|
+
In fastlife the application state is the registry, it has to be overriden
|
61
|
+
to add an implementation.
|
62
|
+
"""
|
63
|
+
yield
|
64
|
+
|
49
65
|
|
50
66
|
DefaultRegistry = GenericRegistry[Settings]
|
51
67
|
"""
|
@@ -0,0 +1 @@
|
|
1
|
+
|
fastlife/service/translations.py
CHANGED
@@ -3,18 +3,52 @@ from collections import defaultdict
|
|
3
3
|
from collections.abc import Callable, Iterator
|
4
4
|
from gettext import GNUTranslations
|
5
5
|
from io import BufferedReader
|
6
|
-
from typing import TYPE_CHECKING
|
7
6
|
|
8
7
|
from fastlife.shared_utils.resolver import resolve_path
|
9
8
|
|
10
|
-
if TYPE_CHECKING:
|
11
|
-
from fastlife import Request # coverage: ignore
|
12
|
-
|
13
9
|
LocaleName = str
|
14
10
|
Domain = str
|
15
11
|
CONTEXT_ENCODING = "%s\x04%s"
|
16
12
|
|
17
13
|
|
14
|
+
class TranslatableString(str):
|
15
|
+
"""
|
16
|
+
Create a string made for translation associated to a domain.
|
17
|
+
This class is instanciated by the
|
18
|
+
:class:`fastlife.service.translations.TranslatableStringFactory` class.
|
19
|
+
"""
|
20
|
+
|
21
|
+
__slots__ = ("domain",)
|
22
|
+
|
23
|
+
def __new__(cls, msgid: str, domain: str) -> "TranslatableString":
|
24
|
+
self = str.__new__(cls, msgid)
|
25
|
+
self.domain = domain # type: ignore
|
26
|
+
return self
|
27
|
+
|
28
|
+
|
29
|
+
class TranslatableStringFactory:
|
30
|
+
"""Create a catalog of string associated to a domain."""
|
31
|
+
|
32
|
+
def __init__(self, domain: str):
|
33
|
+
self.domain = domain
|
34
|
+
|
35
|
+
def __call__(self, msgid: str) -> str:
|
36
|
+
"""
|
37
|
+
Use to generate the translatable string.
|
38
|
+
|
39
|
+
usually:
|
40
|
+
|
41
|
+
```python
|
42
|
+
_ = TranslatableStringFactory("mydomain")
|
43
|
+
mymessage = _("translatable")
|
44
|
+
```
|
45
|
+
|
46
|
+
Note that the string is associated to mydomain, so the babel extraction has
|
47
|
+
to be initialized with that particular domain.
|
48
|
+
"""
|
49
|
+
return TranslatableString(msgid, self.domain)
|
50
|
+
|
51
|
+
|
18
52
|
def find_mo_files(root_path: str) -> Iterator[tuple[LocaleName, Domain, pathlib.Path]]:
|
19
53
|
"""
|
20
54
|
Find .mo files in a locales directory.
|
@@ -68,7 +102,10 @@ class Localizer:
|
|
68
102
|
return self.gettext(message, mapping)
|
69
103
|
|
70
104
|
def gettext(self, message: str, mapping: dict[str, str] | None = None) -> str:
|
71
|
-
|
105
|
+
if isinstance(message, TranslatableString):
|
106
|
+
ret = self.translations[message.domain].gettext(message) # type: ignore
|
107
|
+
else:
|
108
|
+
ret = self.global_translations.gettext(message)
|
72
109
|
if mapping:
|
73
110
|
ret = ret.format(**mapping)
|
74
111
|
return ret
|
@@ -177,8 +214,8 @@ class LocalizerFactory:
|
|
177
214
|
root_path = resolve_path(path)
|
178
215
|
self._translations.load(root_path)
|
179
216
|
|
180
|
-
def __call__(self,
|
217
|
+
def __call__(self, locale_name: LocaleName) -> Localizer:
|
181
218
|
"""Create the translation context for the given request."""
|
182
|
-
if
|
219
|
+
if locale_name not in self._translations:
|
183
220
|
return self.null_localizer
|
184
|
-
return self._translations.get(
|
221
|
+
return self._translations.get(locale_name)
|
fastlife/testing/form.py
CHANGED
@@ -109,7 +109,7 @@ class WebForm:
|
|
109
109
|
raise ValueError(f'"{fieldname}" does not exists')
|
110
110
|
field = self._formfields[fieldname]
|
111
111
|
if field.node_name != "select":
|
112
|
-
raise ValueError(f"{fieldname} is a {field!r},
|
112
|
+
raise ValueError(f"{fieldname} is a {field!r}, use set() instead")
|
113
113
|
|
114
114
|
for option in field.by_node_name("option"):
|
115
115
|
if option.text == value.strip():
|
fastlife/testing/testclient.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""Testing your application."""
|
2
2
|
|
3
3
|
from collections.abc import Mapping, MutableMapping
|
4
|
-
from typing import Any, Literal
|
4
|
+
from typing import Any, Literal, Self
|
5
5
|
from urllib.parse import urlencode
|
6
6
|
|
7
7
|
import bs4
|
@@ -126,7 +126,7 @@ class WebTestClient:
|
|
126
126
|
|
127
127
|
@property
|
128
128
|
def session(self) -> MutableMapping[str, Any]:
|
129
|
-
"""
|
129
|
+
"""Server session stored in a cookies."""
|
130
130
|
return Session(self)
|
131
131
|
|
132
132
|
def request(
|
@@ -208,3 +208,10 @@ class WebTestClient:
|
|
208
208
|
headers={"Content-Type": "application/x-www-form-urlencoded", **headers},
|
209
209
|
max_redirects=int(follow_redirects) * 10,
|
210
210
|
)
|
211
|
+
|
212
|
+
def __enter__(self) -> Self:
|
213
|
+
self.testclient.__enter__()
|
214
|
+
return self
|
215
|
+
|
216
|
+
def __exit__(self, *args: Any, **kwargs: Any) -> None:
|
217
|
+
self.testclient.__exit__()
|
@@ -1,10 +1,10 @@
|
|
1
|
-
CHANGELOG.md,sha256=
|
2
|
-
fastlife/__init__.py,sha256=
|
1
|
+
CHANGELOG.md,sha256=1gHbqLrx79ltwysUaPw25Sp6Aqz5nbyZK_qcgJxc6Og,7952
|
2
|
+
fastlife/__init__.py,sha256=nXWE4AbhkhG_yBjPJU-XnKDMTsU9ebv7Vj4eIciWQI0,2219
|
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
|
6
6
|
fastlife/adapters/fastapi/form_data.py,sha256=2DQ0o-RvY6iROUKQjS-UJdNYEVSsNPd-AjpergI3w54,4473
|
7
|
-
fastlife/adapters/fastapi/localizer.py,sha256=
|
7
|
+
fastlife/adapters/fastapi/localizer.py,sha256=Efn6rrf-SnSfM4TqqE_5chacrxaPpupxbvIqXipXEEw,448
|
8
8
|
fastlife/adapters/fastapi/request.py,sha256=COOoSMZAm4VhyJgM7dlqJ7YdGjeGI7qs93PtBsriEPc,1115
|
9
9
|
fastlife/adapters/fastapi/routing/__init__.py,sha256=8EMnQE5n8oA4J9_c3nxzwKDVt3tefZ6fGH0d2owE8mo,195
|
10
10
|
fastlife/adapters/fastapi/routing/route.py,sha256=XnDPvd5V0Zl7Ke6bBErEtUCjmNQPcV2U_w1dWpx6qM4,1476
|
@@ -1686,10 +1686,10 @@ fastlife/components/pydantic_form/FatalError.jinja,sha256=lFVlNrXzBR6ExMahq77h0t
|
|
1686
1686
|
fastlife/components/pydantic_form/Hint.jinja,sha256=8leBpfMGDmalc_KAjr2paTojr_rwq-luS6m_1BGj7Tw,202
|
1687
1687
|
fastlife/components/pydantic_form/Widget.jinja,sha256=PgguUpvhG6CY9AW6H8qQMjKqjlybjDCAaFFAOHzrzVQ,418
|
1688
1688
|
fastlife/config/__init__.py,sha256=5qpuaVYqi-AS0GgsfggM6rFsSwXgrqrLBo9jH6dVroc,407
|
1689
|
-
fastlife/config/configurator.py,sha256=
|
1689
|
+
fastlife/config/configurator.py,sha256=wlgSfbKoeBzk5kmCnftmrgVV8CWQhxoioBZeuRTPZ64,24779
|
1690
1690
|
fastlife/config/exceptions.py,sha256=9MdBnbfy-Aw-KaIFzju0Kh8Snk41-v9LqK2w48Tdy1s,1169
|
1691
1691
|
fastlife/config/openapiextra.py,sha256=rYoerrn9sni2XwnO3gIWqaz7M0aDZPhVLjzqhDxue0o,514
|
1692
|
-
fastlife/config/resources.py,sha256=
|
1692
|
+
fastlife/config/resources.py,sha256=EcPTM25pnHcGFTtXjeZnWn5Mo_-8rhJ72HJ6rxnjPg8,8389
|
1693
1693
|
fastlife/config/views.py,sha256=9CZ0qNi8vKvQuGo1GgM6cwNK8WwHOxwIHqtikAOaOHY,2399
|
1694
1694
|
fastlife/domain/__init__.py,sha256=3zDDos5InVX0el9OO0lgSDGzdUNYIhlA6w4uhBh2pF8,29
|
1695
1695
|
fastlife/domain/model/__init__.py,sha256=aoBjaSpDscuFXvtknJHwiNyoJRUpE-v4X54h_wNuo2Y,27
|
@@ -1705,17 +1705,18 @@ fastlife/middlewares/base.py,sha256=7FZE_1YU7wNew2u1qdYXjamosk4CXJmg1mJWGp6Xhc0,
|
|
1705
1705
|
fastlife/middlewares/reverse_proxy/__init__.py,sha256=g1SoVDmenKzpAAPYHTEsWgdBByOxtLg9fGx6RV3i0ok,846
|
1706
1706
|
fastlife/middlewares/reverse_proxy/x_forwarded.py,sha256=PPDjcfwik5eoYaolSY1Y4x5QMKpDV0XrOP_i4Am0y30,1724
|
1707
1707
|
fastlife/middlewares/session/__init__.py,sha256=ZhXWXs53A__F9wJKBJ87rW8Qyt5Mn866vhzKDxVZ4t0,1348
|
1708
|
-
fastlife/middlewares/session/middleware.py,sha256=
|
1708
|
+
fastlife/middlewares/session/middleware.py,sha256=MyZ0MobhlnGiqacYq0pPYBrlqCjTkWjpNXNeFyPD1fI,3133
|
1709
1709
|
fastlife/middlewares/session/serializer.py,sha256=nbJGiCJ_ryZxkW1I28kmK6hD3U98D4ZlUQA7B8_tngQ,635
|
1710
1710
|
fastlife/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1711
1711
|
fastlife/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1712
1712
|
fastlife/service/check_permission.py,sha256=-TsI58YZJtWIw5bsm0fVpfuaCMUx4cmoLTKGXeyQPDk,1809
|
1713
1713
|
fastlife/service/csrf.py,sha256=wC1PaKOmZ3il0FF_kevxnlg9PxDqruRdLrNnOA3ZHrU,1886
|
1714
1714
|
fastlife/service/locale_negociator.py,sha256=JUqzTukxDqTJVOR-CNI7Vqo6kvdvwxYvZQe8P3V9S2U,796
|
1715
|
-
fastlife/service/registry.py,sha256=
|
1715
|
+
fastlife/service/registry.py,sha256=3lm7aUD7FdlV1lQUS73OuBlL55-O1DulPJdE0n6Epks,2648
|
1716
1716
|
fastlife/service/security_policy.py,sha256=qYXs4mhfz_u4x59NhUkirqKYKQbFv9YrzyRuXj7mxE0,4688
|
1717
1717
|
fastlife/service/templates.py,sha256=QPAIUbbZiekazz_jV3q4JCwQd6Q4KA6a4RDek2RWuhE,2548
|
1718
|
-
fastlife/service/
|
1718
|
+
fastlife/service/translatablestring.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
1719
|
+
fastlife/service/translations.py,sha256=s6qFZSXR-1vYxSr7RRH-mS-VjNaa8OTxR7-k6Ib7h0E,6878
|
1719
1720
|
fastlife/settings.py,sha256=q-rz4CEF2RQGow5-m-yZJOvdh3PPb2c1Q_ZLJGnu4VQ,3647
|
1720
1721
|
fastlife/shared_utils/__init__.py,sha256=i66ytuf-Ezo7jSiNQHIsBMVIcB-tDX0tg28-pUOlhzE,26
|
1721
1722
|
fastlife/shared_utils/infer.py,sha256=0GflLkaWJ-4LZ1Ig3moR-_o55wwJ_p_vJ4xo-yi3lyA,1406
|
@@ -1723,14 +1724,14 @@ fastlife/shared_utils/resolver.py,sha256=Wb9cO2MWavpti63hju15xmwFMgaD5DsQaxikRpB
|
|
1723
1724
|
fastlife/template_globals.py,sha256=rJ7XB9DlPubejs_GDlEyO9V98QZTJnmdTznlguYOyoc,8386
|
1724
1725
|
fastlife/testing/__init__.py,sha256=VpxkS3Zp3t_hH8dBiLaGFGhsvt511dhBS_8fMoFXdmU,99
|
1725
1726
|
fastlife/testing/dom.py,sha256=dVzDoZokn-ii681UaEwAr-khM5KE-CHgXSSLSo24oH0,4489
|
1726
|
-
fastlife/testing/form.py,sha256=
|
1727
|
+
fastlife/testing/form.py,sha256=diiGfVMfNt19JTNUxlnbGfcbskR3ZMpk0Y-A57vfShc,7871
|
1727
1728
|
fastlife/testing/session.py,sha256=LEFFbiR67_x_g-ioudkY0C7PycHdbDfaIaoo_G7GXQ8,2226
|
1728
|
-
fastlife/testing/testclient.py,sha256=
|
1729
|
+
fastlife/testing/testclient.py,sha256=Id1tlA1ZapyW-8kUh2_U3lLteL64m3ERqOO7NAN7HEY,6922
|
1729
1730
|
fastlife/views/__init__.py,sha256=zG8gveL8e2zBdYx6_9jtZfpQ6qJT-MFnBY3xXkLwHZI,22
|
1730
1731
|
fastlife/views/pydantic_form.py,sha256=o7EUItciAGL1OSaGNHo-3BTrYAk34GuWE7zGikjiAGY,1486
|
1731
|
-
fastlifeweb-0.
|
1732
|
-
fastlifeweb-0.
|
1733
|
-
fastlifeweb-0.
|
1734
|
-
fastlifeweb-0.
|
1732
|
+
fastlifeweb-0.24.0.dist-info/METADATA,sha256=a4Pp2mA7yCIvBnvF7Jrrt0jzQRX7ousymKDpb1slGfY,3663
|
1733
|
+
fastlifeweb-0.24.0.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
|
1734
|
+
fastlifeweb-0.24.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
1735
|
+
fastlifeweb-0.24.0.dist-info/licenses/LICENSE,sha256=NlRX9Z-dcv8X1VFW9odlIQBbgNN9pcO94XzvKp2R16o,1075
|
1735
1736
|
tailwind.config.js,sha256=EN3EahBDmQBbmJvkw3SdGWNOkfkzw0cg-QvBikOhkrw,1348
|
1736
|
-
fastlifeweb-0.
|
1737
|
+
fastlifeweb-0.24.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|