fastlifeweb 0.15.0__py3-none-any.whl → 0.16.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.
- fastlife/__init__.py +10 -7
- fastlife/adapters/jinjax/widgets/__init__.py +1 -0
- fastlife/adapters/jinjax/widgets/base.py +1 -0
- fastlife/components/Tbody.jinja +1 -1
- fastlife/components/Tfoot.jinja +1 -1
- fastlife/components/Thead.jinja +1 -1
- fastlife/components/Tr.jinja +1 -1
- fastlife/config/__init__.py +4 -4
- fastlife/config/configurator.py +36 -15
- fastlife/config/registry.py +5 -22
- fastlife/config/settings.py +1 -7
- fastlife/middlewares/reverse_proxy/__init__.py +1 -0
- fastlife/request/__init__.py +2 -2
- fastlife/request/form.py +1 -1
- fastlife/request/form_data.py +9 -12
- fastlife/request/request.py +30 -5
- fastlife/routing/route.py +2 -2
- fastlife/security/policy.py +13 -6
- fastlife/services/locale_negociator.py +25 -0
- fastlife/views/pydantic_form.py +1 -3
- {fastlifeweb-0.15.0.dist-info → fastlifeweb-0.16.0.dist-info}/METADATA +6 -5
- {fastlifeweb-0.15.0.dist-info → fastlifeweb-0.16.0.dist-info}/RECORD +24 -23
- {fastlifeweb-0.15.0.dist-info → fastlifeweb-0.16.0.dist-info}/LICENSE +0 -0
- {fastlifeweb-0.15.0.dist-info → fastlifeweb-0.16.0.dist-info}/WHEEL +0 -0
fastlife/__init__.py
CHANGED
@@ -2,32 +2,35 @@ from fastapi import Response
|
|
2
2
|
|
3
3
|
from .config import (
|
4
4
|
Configurator,
|
5
|
-
|
5
|
+
DefaultRegistry,
|
6
|
+
GenericConfigurator,
|
6
7
|
Settings,
|
7
8
|
configure,
|
8
9
|
resource,
|
9
10
|
resource_view,
|
10
11
|
view_config,
|
11
12
|
)
|
12
|
-
from .request import Request
|
13
|
+
from .request import GenericRequest, Registry, Request, get_request
|
13
14
|
|
14
15
|
# from .request.form_data import model
|
15
|
-
from .templates import
|
16
|
+
from .services.templates import TemplateParams
|
16
17
|
|
17
18
|
__all__ = [
|
18
19
|
# Config
|
19
20
|
"configure",
|
21
|
+
"GenericConfigurator",
|
20
22
|
"Configurator",
|
21
|
-
"
|
22
|
-
"
|
23
|
-
"Registry",
|
23
|
+
"DefaultRegistry",
|
24
|
+
"TemplateParams",
|
24
25
|
"Settings",
|
25
26
|
"view_config",
|
26
27
|
"resource",
|
27
28
|
"resource_view",
|
28
29
|
# Model
|
29
30
|
# "model",
|
30
|
-
# Fast API reexport
|
31
31
|
"Request",
|
32
|
+
"GenericRequest",
|
33
|
+
"get_request",
|
34
|
+
"Registry",
|
32
35
|
"Response",
|
33
36
|
]
|
@@ -0,0 +1 @@
|
|
1
|
+
"""HTML Form generation using widgets."""
|
fastlife/components/Tbody.jinja
CHANGED
fastlife/components/Tfoot.jinja
CHANGED
fastlife/components/Thead.jinja
CHANGED
fastlife/components/Tr.jinja
CHANGED
fastlife/config/__init__.py
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
"""Configure fastlife app for dependency injection."""
|
2
2
|
|
3
|
-
from .configurator import Configurator, configure
|
4
|
-
from .registry import
|
3
|
+
from .configurator import Configurator, GenericConfigurator, configure
|
4
|
+
from .registry import DefaultRegistry
|
5
5
|
from .resources import resource, resource_view
|
6
6
|
from .settings import Settings
|
7
7
|
from .views import view_config
|
8
8
|
|
9
9
|
__all__ = [
|
10
10
|
"Configurator",
|
11
|
+
"GenericConfigurator",
|
11
12
|
"configure",
|
12
13
|
"view_config",
|
13
14
|
"resource",
|
14
15
|
"resource_view",
|
15
|
-
"
|
16
|
-
"AppRegistry",
|
16
|
+
"DefaultRegistry",
|
17
17
|
"Settings",
|
18
18
|
]
|
fastlife/config/configurator.py
CHANGED
@@ -19,7 +19,7 @@ from collections.abc import Mapping, Sequence
|
|
19
19
|
from enum import Enum
|
20
20
|
from pathlib import Path
|
21
21
|
from types import ModuleType
|
22
|
-
from typing import TYPE_CHECKING, Annotated, Any, Callable, Self, Tuple, Type
|
22
|
+
from typing import TYPE_CHECKING, Annotated, Any, Callable, Generic, Self, Tuple, Type
|
23
23
|
|
24
24
|
import venusian
|
25
25
|
from fastapi import Depends, FastAPI
|
@@ -38,6 +38,7 @@ from fastlife.security.csrf import check_csrf
|
|
38
38
|
from fastlife.services.policy import check_permission
|
39
39
|
from fastlife.shared_utils.resolver import resolve
|
40
40
|
|
41
|
+
from .registry import DefaultRegistry, TRegistry
|
41
42
|
from .settings import Settings
|
42
43
|
|
43
44
|
if TYPE_CHECKING:
|
@@ -46,7 +47,7 @@ if TYPE_CHECKING:
|
|
46
47
|
AbstractTemplateRendererFactory, # coverage: ignore
|
47
48
|
)
|
48
49
|
|
49
|
-
|
50
|
+
from fastlife.services.locale_negociator import LocaleNegociator
|
50
51
|
|
51
52
|
log = logging.getLogger(__name__)
|
52
53
|
VENUSIAN_CATEGORY = "fastlife"
|
@@ -109,14 +110,14 @@ def rebuild_router(router: Router) -> Router:
|
|
109
110
|
return _router
|
110
111
|
|
111
112
|
|
112
|
-
class
|
113
|
+
class GenericConfigurator(Generic[TRegistry]):
|
113
114
|
"""
|
114
115
|
Configure and build an application.
|
115
116
|
|
116
117
|
Initialize the app from the settings.
|
117
118
|
"""
|
118
119
|
|
119
|
-
registry:
|
120
|
+
registry: TRegistry
|
120
121
|
|
121
122
|
def __init__(self, settings: Settings) -> None:
|
122
123
|
"""
|
@@ -135,10 +136,14 @@ class Configurator:
|
|
135
136
|
self.api_version = "v1"
|
136
137
|
self.api_description: str = ""
|
137
138
|
self.api_summary: str | None = None
|
139
|
+
self.api_swagger_ui_url: str | None = None
|
140
|
+
self.api_redoc_url: str | None = None
|
138
141
|
|
139
142
|
self._route_prefix: str = ""
|
140
143
|
self._routers: dict[str, Router] = defaultdict(Router)
|
141
|
-
self._security_policies: dict[
|
144
|
+
self._security_policies: dict[
|
145
|
+
str, "type[AbstractSecurityPolicy[Any, TRegistry]]"
|
146
|
+
] = {}
|
142
147
|
|
143
148
|
self.scanner = venusian.Scanner(fastlife=self)
|
144
149
|
self.include("fastlife.views")
|
@@ -170,8 +175,8 @@ class Configurator:
|
|
170
175
|
description=self.api_description,
|
171
176
|
summary=self.api_summary,
|
172
177
|
dependencies=[Depends(check_csrf())],
|
173
|
-
docs_url=self.
|
174
|
-
redoc_url=self.
|
178
|
+
docs_url=self.api_swagger_ui_url,
|
179
|
+
redoc_url=self.api_redoc_url,
|
175
180
|
openapi_tags=[tag.model_dump(by_alias=True) for tag in self.tags.values()]
|
176
181
|
if self.tags
|
177
182
|
else None,
|
@@ -241,7 +246,7 @@ class Configurator:
|
|
241
246
|
self._route_prefix = old
|
242
247
|
return self
|
243
248
|
|
244
|
-
def set_locale_negociator(self, locale_negociator:
|
249
|
+
def set_locale_negociator(self, locale_negociator: LocaleNegociator) -> Self:
|
245
250
|
"""Install a locale negociator for the app."""
|
246
251
|
self.registry.locale_negociator = locale_negociator
|
247
252
|
return self
|
@@ -263,20 +268,28 @@ class Configurator:
|
|
263
268
|
title: str,
|
264
269
|
version: str,
|
265
270
|
description: str,
|
271
|
+
*,
|
266
272
|
summary: str | None = None,
|
273
|
+
swagger_ui_url: str | None = None,
|
274
|
+
redoc_url: str | None = None,
|
267
275
|
) -> Self:
|
268
276
|
"""
|
269
277
|
Set your api documentation title for application that expose an API.
|
270
278
|
|
271
279
|
:param title: OpenAPI documentation title
|
272
280
|
:param version: OpenAPI api version
|
273
|
-
:param description: OpenAPI documentation description
|
274
|
-
:param summary: OpenAPI documentation summary
|
281
|
+
:param description: OpenAPI documentation description. Use markdown here.
|
282
|
+
:param summary: OpenAPI documentation summary.
|
283
|
+
A short description: text only.
|
284
|
+
:param swagger_ui_url: Endpoint for {term}`Swagger UI` served by FastAPI
|
285
|
+
:param redoc_url: Endpoint for {term}`Redoc` served by FastAPI
|
275
286
|
"""
|
276
287
|
self.api_title = title
|
277
288
|
self.api_version = version
|
278
289
|
self.api_description = description
|
279
290
|
self.api_summary = summary
|
291
|
+
self.api_swagger_ui_url = swagger_ui_url
|
292
|
+
self.api_redoc_url = redoc_url
|
280
293
|
return self
|
281
294
|
|
282
295
|
def add_open_tag(self, tag: OpenApiTag) -> Self:
|
@@ -296,7 +309,7 @@ class Configurator:
|
|
296
309
|
return self
|
297
310
|
|
298
311
|
def set_security_policy(
|
299
|
-
self, security_policy: "type[AbstractSecurityPolicy[Any]]"
|
312
|
+
self, security_policy: "type[AbstractSecurityPolicy[Any, TRegistry]]"
|
300
313
|
) -> Self:
|
301
314
|
"""
|
302
315
|
Set a security policy for the application.
|
@@ -367,7 +380,7 @@ class Configurator:
|
|
367
380
|
:param endpoint: the function that will reveive the request.
|
368
381
|
:param permission: a permission to validate by the security policy.
|
369
382
|
:param methods: restrict route to a list of http methods.
|
370
|
-
:param description:OpenAPI description for the route.
|
383
|
+
:param description:{term}`OpenAPI` description for the route.
|
371
384
|
:param summary: OpenAPI summary for the route.
|
372
385
|
:param response_description: OpenAPI description for the response.
|
373
386
|
:param operation_id: OpenAPI optional unique string used to identify an
|
@@ -385,7 +398,7 @@ class Configurator:
|
|
385
398
|
:param response_model_exclude_none: exclude fields instead of serialize to
|
386
399
|
null value.
|
387
400
|
:param include_in_schema: expose or not the route in the doc.
|
388
|
-
:param openapi_extra:
|
401
|
+
:param openapi_extra: OpenAPI documentation extra fields.
|
389
402
|
|
390
403
|
:return: the configurator.
|
391
404
|
"""
|
@@ -549,7 +562,7 @@ class Configurator:
|
|
549
562
|
self.registry.renderers[f".{file_ext.lstrip('.')}"] = renderer # type: ignore
|
550
563
|
return self
|
551
564
|
|
552
|
-
def add_template_search_path(self, path: str) -> Self:
|
565
|
+
def add_template_search_path(self, path: str | Path) -> Self:
|
553
566
|
"""
|
554
567
|
Add a template search path directly from the code.
|
555
568
|
|
@@ -561,9 +574,17 @@ class Configurator:
|
|
561
574
|
return self
|
562
575
|
|
563
576
|
|
577
|
+
class Configurator(GenericConfigurator[DefaultRegistry]):
|
578
|
+
"""
|
579
|
+
Configure and build an application.
|
580
|
+
|
581
|
+
Initialize the app from the settings.
|
582
|
+
"""
|
583
|
+
|
584
|
+
|
564
585
|
def configure(
|
565
586
|
wrapped: Callable[[Configurator], None],
|
566
|
-
) -> Callable[[
|
587
|
+
) -> Callable[[Any], None]:
|
567
588
|
"""
|
568
589
|
Decorator used to attach route in a submodule while using the configurator.include.
|
569
590
|
"""
|
fastlife/config/registry.py
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
from collections.abc import Mapping
|
2
|
-
from typing import TYPE_CHECKING,
|
2
|
+
from typing import TYPE_CHECKING, TypeVar
|
3
3
|
|
4
|
-
from
|
5
|
-
from fastapi import Request as FastAPIRequest
|
6
|
-
|
7
|
-
from fastlife.request.request import Request
|
4
|
+
from fastlife.services.locale_negociator import LocaleNegociator, default_negociator
|
8
5
|
from fastlife.services.translations import LocalizerFactory
|
9
6
|
|
10
7
|
if TYPE_CHECKING:
|
@@ -14,17 +11,8 @@ if TYPE_CHECKING:
|
|
14
11
|
|
15
12
|
from .settings import Settings
|
16
13
|
|
17
|
-
LocaleNegociator = Callable[[Request], str]
|
18
|
-
|
19
|
-
|
20
|
-
def _default_negociator(settings: Settings) -> LocaleNegociator:
|
21
|
-
def locale_negociator(request: FastAPIRequest) -> str:
|
22
|
-
return settings.default_locale
|
23
|
-
|
24
|
-
return locale_negociator
|
25
14
|
|
26
|
-
|
27
|
-
class AppRegistry:
|
15
|
+
class DefaultRegistry:
|
28
16
|
"""
|
29
17
|
The application registry got fastlife dependency injection.
|
30
18
|
It is initialized by the configurator and accessed by the `fastlife.Registry`.
|
@@ -37,7 +25,7 @@ class AppRegistry:
|
|
37
25
|
|
38
26
|
def __init__(self, settings: Settings) -> None:
|
39
27
|
self.settings = settings
|
40
|
-
self.locale_negociator =
|
28
|
+
self.locale_negociator = default_negociator(self.settings)
|
41
29
|
self.renderers = {}
|
42
30
|
self.localizer = LocalizerFactory()
|
43
31
|
|
@@ -48,9 +36,4 @@ class AppRegistry:
|
|
48
36
|
raise RuntimeError(f"No renderer registered for template {template}")
|
49
37
|
|
50
38
|
|
51
|
-
|
52
|
-
return request.registry
|
53
|
-
|
54
|
-
|
55
|
-
Registry = Annotated[AppRegistry, Depends(get_registry)]
|
56
|
-
"""FastAPI dependency to access to the registry."""
|
39
|
+
TRegistry = TypeVar("TRegistry", bound=DefaultRegistry, covariant=True)
|
fastlife/config/settings.py
CHANGED
@@ -29,7 +29,7 @@ class Settings(BaseSettings):
|
|
29
29
|
a python module name. for instance `fastlife:components` is the directory components
|
30
30
|
found in the fastlife package.
|
31
31
|
"""
|
32
|
-
registry_class: str = Field(default="fastlife.config.registry:
|
32
|
+
registry_class: str = Field(default="fastlife.config.registry:DefaultRegistry")
|
33
33
|
"""Implementation class for the application regitry."""
|
34
34
|
template_renderer_class: str = Field(
|
35
35
|
default="fastlife.templates.renderer:JinjaxTemplateRenderer"
|
@@ -108,9 +108,3 @@ class Settings(BaseSettings):
|
|
108
108
|
|
109
109
|
decode_reverse_proxy_headers: bool = Field(default=True)
|
110
110
|
"""Ensure that the request object has information based on http proxy headers."""
|
111
|
-
|
112
|
-
api_swagger_ui_url: str | None = Field(default=None)
|
113
|
-
"""Path to the automatic API documentation using Swagger UI."""
|
114
|
-
|
115
|
-
api_redocs_url: str | None = Field(default=None)
|
116
|
-
"""Path to the automatic API documentation using ReDoc."""
|
fastlife/request/__init__.py
CHANGED
fastlife/request/form.py
CHANGED
@@ -5,7 +5,7 @@ from typing import Any, Callable, Generic, Mapping, Type, TypeVar, get_origin
|
|
5
5
|
from fastapi import Depends
|
6
6
|
from pydantic import BaseModel, ValidationError
|
7
7
|
|
8
|
-
from fastlife
|
8
|
+
from fastlife import Registry
|
9
9
|
from fastlife.request.form_data import MappingFormData
|
10
10
|
from fastlife.shared_utils.infer import is_union
|
11
11
|
|
fastlife/request/form_data.py
CHANGED
@@ -12,9 +12,9 @@ from typing import (
|
|
12
12
|
Sequence,
|
13
13
|
)
|
14
14
|
|
15
|
-
from fastapi import Depends
|
15
|
+
from fastapi import Depends
|
16
16
|
|
17
|
-
from fastlife
|
17
|
+
from fastlife import Request
|
18
18
|
|
19
19
|
|
20
20
|
def unflatten_struct(
|
@@ -80,13 +80,11 @@ def unflatten_struct(
|
|
80
80
|
return unflattened_output
|
81
81
|
|
82
82
|
|
83
|
-
async def unflatten_mapping_form_data(
|
84
|
-
request: Request, registry: Registry
|
85
|
-
) -> Mapping[str, Any]:
|
83
|
+
async def unflatten_mapping_form_data(request: Request) -> Mapping[str, Any]:
|
86
84
|
"""
|
87
85
|
Parse the {meth}`fastlife.request.request.form` and build a nested structure.
|
88
86
|
"""
|
89
|
-
|
87
|
+
registry = request.registry
|
90
88
|
form_data = await request.form()
|
91
89
|
form_data_decode_list: MutableMapping[str, Any] = {}
|
92
90
|
for key, val in form_data.multi_items():
|
@@ -109,17 +107,16 @@ async def unflatten_mapping_form_data(
|
|
109
107
|
return ret # type: ignore
|
110
108
|
|
111
109
|
|
112
|
-
async def unflatten_sequence_form_data(
|
113
|
-
request: Request, reg: Registry
|
114
|
-
) -> Sequence[str]:
|
110
|
+
async def unflatten_sequence_form_data(request: Request) -> Sequence[str]:
|
115
111
|
"""
|
116
112
|
Parse the {meth}`fastlife.request.request.form` and build a list of structure.
|
117
113
|
"""
|
114
|
+
registry = request.registry
|
118
115
|
form_data = await request.form()
|
119
116
|
# Could raise a value error !
|
120
|
-
return unflatten_struct(
|
121
|
-
form_data, [], csrf_token_name=
|
122
|
-
)
|
117
|
+
return unflatten_struct( # type: ignore
|
118
|
+
form_data, [], csrf_token_name=registry.settings.csrf_token_name
|
119
|
+
)
|
123
120
|
|
124
121
|
|
125
122
|
MappingFormData = Annotated[Mapping[str, Any], Depends(unflatten_mapping_form_data)]
|
fastlife/request/request.py
CHANGED
@@ -3,27 +3,30 @@
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
4
4
|
|
5
5
|
from fastapi import Request as FastAPIRequest
|
6
|
+
from fastapi.params import Depends
|
7
|
+
from typing_extensions import Annotated, Generic
|
8
|
+
|
9
|
+
from fastlife.config.registry import DefaultRegistry, TRegistry
|
6
10
|
|
7
11
|
if TYPE_CHECKING:
|
8
|
-
from fastlife.config.registry import AppRegistry # coverage: ignore
|
9
12
|
from fastlife.security.policy import ( # coverage: ignore
|
10
13
|
AbstractSecurityPolicy,
|
11
14
|
HasPermission,
|
12
15
|
)
|
13
16
|
|
14
17
|
|
15
|
-
class
|
18
|
+
class GenericRequest(FastAPIRequest, Generic[TRegistry]):
|
16
19
|
"""HTTP Request representation."""
|
17
20
|
|
18
|
-
registry:
|
21
|
+
registry: TRegistry
|
19
22
|
"""Direct access to the application registry."""
|
20
23
|
locale_name: str
|
21
24
|
"""Request locale used for the i18n of the response."""
|
22
25
|
|
23
|
-
security_policy: "AbstractSecurityPolicy[Any] | None"
|
26
|
+
security_policy: "AbstractSecurityPolicy[Any, TRegistry] | None"
|
24
27
|
"""Request locale used for the i18n of the response."""
|
25
28
|
|
26
|
-
def __init__(self, registry:
|
29
|
+
def __init__(self, registry: TRegistry, request: FastAPIRequest) -> None:
|
27
30
|
super().__init__(request.scope, request.receive)
|
28
31
|
self.registry = registry
|
29
32
|
self.locale_name = registry.locale_negociator(self)
|
@@ -47,3 +50,25 @@ class Request(FastAPIRequest):
|
|
47
50
|
)
|
48
51
|
|
49
52
|
return await self.security_policy.has_permission(permission)
|
53
|
+
|
54
|
+
|
55
|
+
def get_request(request: FastAPIRequest) -> GenericRequest[Any]:
|
56
|
+
return request # type: ignore
|
57
|
+
|
58
|
+
|
59
|
+
Request = Annotated[GenericRequest[DefaultRegistry], Depends(get_request)]
|
60
|
+
"""A request that is associated to the default registry."""
|
61
|
+
# FastAPI handle its Request objects using a lenient_issubclass,
|
62
|
+
# basically a issubclass(Request), doe to the Generic[T], it does not work.
|
63
|
+
|
64
|
+
|
65
|
+
AnyRequest = Annotated[GenericRequest[Any], Depends(get_request)]
|
66
|
+
"""A request version that is associated to the any registry."""
|
67
|
+
|
68
|
+
|
69
|
+
def get_registry(request: Request) -> DefaultRegistry:
|
70
|
+
return request.registry
|
71
|
+
|
72
|
+
|
73
|
+
Registry = Annotated[DefaultRegistry, Depends(get_registry)]
|
74
|
+
"""FastAPI dependency to access to the registry."""
|
fastlife/routing/route.py
CHANGED
@@ -8,7 +8,7 @@ from starlette.responses import Response
|
|
8
8
|
from fastlife.request.request import Request
|
9
9
|
|
10
10
|
if TYPE_CHECKING:
|
11
|
-
from fastlife.config.registry import
|
11
|
+
from fastlife.config.registry import DefaultRegistry # coverage: ignore
|
12
12
|
|
13
13
|
|
14
14
|
class Route(APIRoute):
|
@@ -19,7 +19,7 @@ class Route(APIRoute):
|
|
19
19
|
have the registry property available in every received request.
|
20
20
|
"""
|
21
21
|
|
22
|
-
_registry: "
|
22
|
+
_registry: "DefaultRegistry"
|
23
23
|
"""
|
24
24
|
The application registry.
|
25
25
|
|
fastlife/security/policy.py
CHANGED
@@ -2,14 +2,16 @@
|
|
2
2
|
|
3
3
|
import abc
|
4
4
|
import logging
|
5
|
-
from typing import Any, Callable, Coroutine, Literal, TypeVar
|
5
|
+
from typing import Annotated, Any, Callable, Coroutine, Literal, TypeVar
|
6
6
|
from uuid import UUID
|
7
7
|
|
8
|
-
from fastapi import HTTPException
|
8
|
+
from fastapi import Depends, HTTPException
|
9
9
|
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
10
10
|
from typing_extensions import Generic
|
11
11
|
|
12
|
-
from fastlife import
|
12
|
+
from fastlife import get_request
|
13
|
+
from fastlife.config.registry import TRegistry
|
14
|
+
from tests.fastlife_app.config import GenericRequest
|
13
15
|
|
14
16
|
CheckPermissionHook = Callable[..., Coroutine[Any, Any, None]] | Callable[..., None]
|
15
17
|
CheckPermission = Callable[[str], CheckPermissionHook]
|
@@ -93,7 +95,7 @@ class Denied(HasPermission):
|
|
93
95
|
reason = "Access denied to this resource"
|
94
96
|
|
95
97
|
|
96
|
-
class AbstractSecurityPolicy(abc.ABC, Generic[TUser]):
|
98
|
+
class AbstractSecurityPolicy(abc.ABC, Generic[TUser, TRegistry]):
|
97
99
|
"""Security policy base classe."""
|
98
100
|
|
99
101
|
Forbidden = Forbidden
|
@@ -101,7 +103,12 @@ class AbstractSecurityPolicy(abc.ABC, Generic[TUser]):
|
|
101
103
|
Unauthorized = Unauthorized
|
102
104
|
"""The exception raised if no user has been identified."""
|
103
105
|
|
104
|
-
|
106
|
+
request: GenericRequest[TRegistry]
|
107
|
+
"""Request where the security policy is applied."""
|
108
|
+
|
109
|
+
def __init__(
|
110
|
+
self, request: Annotated[GenericRequest[TRegistry], Depends(get_request)]
|
111
|
+
):
|
105
112
|
"""
|
106
113
|
Build the security policy.
|
107
114
|
|
@@ -141,7 +148,7 @@ class AbstractSecurityPolicy(abc.ABC, Generic[TUser]):
|
|
141
148
|
"""Destroy the request session."""
|
142
149
|
|
143
150
|
|
144
|
-
class InsecurePolicy(AbstractSecurityPolicy[None]):
|
151
|
+
class InsecurePolicy(AbstractSecurityPolicy[None, Any]):
|
145
152
|
"""
|
146
153
|
An implementation of the security policy made for explicit unsecured access.
|
147
154
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
"""Find the localization gor the given request."""
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
4
|
+
|
5
|
+
from fastlife.config.settings import Settings
|
6
|
+
|
7
|
+
LocaleName = str
|
8
|
+
"""The LocaleName is a locale such as en, fr that will be consume for translations."""
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from fastlife.request.request import GenericRequest # coverage: ignore
|
12
|
+
|
13
|
+
LocaleNegociator = Callable[[GenericRequest[Any]], LocaleName] # coverage: ignore
|
14
|
+
"""Interface to implement to negociate a locale""" # coverage: ignore
|
15
|
+
else:
|
16
|
+
LocaleNegociator = Any
|
17
|
+
|
18
|
+
|
19
|
+
def default_negociator(settings: Settings) -> LocaleNegociator:
|
20
|
+
"""The default local negociator return the locale set in the conf."""
|
21
|
+
|
22
|
+
def locale_negociator(request: "GenericRequest[Any]") -> str:
|
23
|
+
return settings.default_locale
|
24
|
+
|
25
|
+
return locale_negociator
|
fastlife/views/pydantic_form.py
CHANGED
@@ -5,13 +5,11 @@ from pydantic.fields import FieldInfo
|
|
5
5
|
|
6
6
|
from fastlife import Configurator, Request, Response, configure
|
7
7
|
from fastlife.adapters.jinjax.renderer import JinjaxRenderer
|
8
|
-
from fastlife.config.registry import Registry
|
9
8
|
from fastlife.shared_utils.resolver import resolve_extended
|
10
9
|
|
11
10
|
|
12
11
|
async def show_widget(
|
13
12
|
typ: str,
|
14
|
-
reg: Registry,
|
15
13
|
request: Request,
|
16
14
|
title: Optional[str] = Query(None),
|
17
15
|
name: Optional[str] = Query(None),
|
@@ -26,7 +24,7 @@ async def show_widget(
|
|
26
24
|
if title:
|
27
25
|
field = FieldInfo(title=title)
|
28
26
|
# FIXME: .jinja should not be hardcoded
|
29
|
-
renderer = cast(JinjaxRenderer,
|
27
|
+
renderer = cast(JinjaxRenderer, request.registry.get_renderer(".jinja")(request))
|
30
28
|
data = renderer.pydantic_form_field(
|
31
29
|
model=model_cls, # type: ignore
|
32
30
|
name=name,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fastlifeweb
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.16.0
|
4
4
|
Summary: High-level web framework
|
5
5
|
Home-page: https://github.com/mardiros/fastlife
|
6
6
|
License: BSD-derived
|
@@ -17,8 +17,9 @@ Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
18
18
|
Classifier: Topic :: Internet :: WWW/HTTP
|
19
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
20
|
+
Provides-Extra: testing
|
20
21
|
Requires-Dist: babel (>=2.16.0,<3.0.0)
|
21
|
-
Requires-Dist: beautifulsoup4
|
22
|
+
Requires-Dist: beautifulsoup4 (>=4.12.2,<5.0.0) ; extra == "testing"
|
22
23
|
Requires-Dist: fastapi[standard] (>=0.115.0,<0.116.0)
|
23
24
|
Requires-Dist: itsdangerous (>=2.1.2,<3.0.0)
|
24
25
|
Requires-Dist: jinjax (>=0.44,<0.45)
|
@@ -42,12 +43,12 @@ Description-Content-Type: text/markdown
|
|
42
43
|
> Please note that this project is still in active development. Features and APIs may change frequently.
|
43
44
|
> Even the name is not definitive.
|
44
45
|
|
45
|
-
An opinionated
|
46
|
+
An opinionated Python web framework (based on FastAPI).
|
46
47
|
|
47
48
|
## Purpose
|
48
49
|
|
49
|
-
Fastlife helps at building Web Application with
|
50
|
-
using customizable widget.
|
50
|
+
Fastlife helps at building Web Application with session, security, html test client,
|
51
|
+
and html form generated from pydantic schema using customizable widget.
|
51
52
|
|
52
53
|
Templates are made using [JinjaX](https://jinjax.scaletti.dev/) and an extensible [set of
|
53
54
|
component](https://mardiros.github.io/fastlife/components/index.html) is available
|
@@ -1,9 +1,9 @@
|
|
1
|
-
fastlife/__init__.py,sha256=
|
1
|
+
fastlife/__init__.py,sha256=fokakuhI0fdAjHP5w6GWi-YfCx7iTnrVzjSyZ11Cdgg,676
|
2
2
|
fastlife/adapters/__init__.py,sha256=WYjEN8gp4r7LCHqmIO5VzzvsT8QGRE3w4G47UwYDtAo,94
|
3
3
|
fastlife/adapters/jinjax/__init__.py,sha256=jy88zyqk7nFlaY-0lmgAoe0HyO5r_NKckQb3faQiUv4,137
|
4
4
|
fastlife/adapters/jinjax/renderer.py,sha256=tKX_C0fGnooMcMXBz4_8Ym2VLCKEkRk9_AXq6HrN11I,13040
|
5
|
-
fastlife/adapters/jinjax/widgets/__init__.py,sha256=
|
6
|
-
fastlife/adapters/jinjax/widgets/base.py,sha256=
|
5
|
+
fastlife/adapters/jinjax/widgets/__init__.py,sha256=HERnX9xiXUbTDz3XtlnHWABTBjhIq_kkBgWs5E6ZIMY,42
|
6
|
+
fastlife/adapters/jinjax/widgets/base.py,sha256=JoB1bLI97ZW2ycfBcHgrPxiOszE9t_SFFK5L1bR-cSo,4015
|
7
7
|
fastlife/adapters/jinjax/widgets/boolean.py,sha256=w4hZMo_8xDoThStlIUR4eVfLm8JwUp0-TaGCjGSyCbA,1145
|
8
8
|
fastlife/adapters/jinjax/widgets/checklist.py,sha256=VIIfJ8JB7ZAISwFFiGZ7jfGuNJ9sjkhRSVFqDTr-RR4,1655
|
9
9
|
fastlife/adapters/jinjax/widgets/dropdown.py,sha256=x2Y9BOfHfSuzWD_HNrvCkiJtKDxl8Vs05Uk8QKvRxyY,1622
|
@@ -34,13 +34,13 @@ fastlife/components/Radio.jinja,sha256=ohucEGNofDsC4-Hp6Ovexi5XW812-kfAzsFCcOyVF
|
|
34
34
|
fastlife/components/Select.jinja,sha256=aPqdPrCh6ooxj3XkeptefwvzFqC5XC4FLtW8XLj3CpM,556
|
35
35
|
fastlife/components/Summary.jinja,sha256=EFVG2d7JJu7j5RRLNqKHxXcWAEJmv3Wif1aI5HXq1Zc,853
|
36
36
|
fastlife/components/Table.jinja,sha256=X87pJkqp-fl4Uno52Ijj1Lw4MFYsqr4Jc6Oi4KGQVhM,390
|
37
|
-
fastlife/components/Tbody.jinja,sha256=
|
37
|
+
fastlife/components/Tbody.jinja,sha256=FqldzLFhV6dfG0UsB55kDtALixpnECdIISXGWXiXxEM,331
|
38
38
|
fastlife/components/Td.jinja,sha256=NTV86bdtm8t_2_2Pj1X8vsCqm4NSE4H5NPUoSt1i8cA,374
|
39
39
|
fastlife/components/Textarea.jinja,sha256=0WsTi-yxVwYXndmJJN93WblQVqM0h0NCSMUDwoC3kdc,636
|
40
|
-
fastlife/components/Tfoot.jinja,sha256=
|
40
|
+
fastlife/components/Tfoot.jinja,sha256=Hw_eFhmbHcOx0dCHrNkyi4q1lrKye5RpihHBwjaT3zY,331
|
41
41
|
fastlife/components/Th.jinja,sha256=tuIlLlwtvZiBHhQD_J7p8V-An-EeA4mzm_nRxUoGDzQ,375
|
42
|
-
fastlife/components/Thead.jinja,sha256=
|
43
|
-
fastlife/components/Tr.jinja,sha256=
|
42
|
+
fastlife/components/Thead.jinja,sha256=TpnnbtbgYe97e0QDjeJulbh0_-Cbu3z_Qh9dsraQNb4,330
|
43
|
+
fastlife/components/Tr.jinja,sha256=kWy-jx4ShT2dv9vuHqJtMnc3O2pZ3y8KCWEAifMpjrU,322
|
44
44
|
fastlife/components/icons/AcademicCap.jinja,sha256=0yJiwNF09SXGmssEHFkEE_2GPb2LFN1BIR4DChDRBls,528
|
45
45
|
fastlife/components/icons/AdjustmentsHorizontal.jinja,sha256=eaAOfrTqfmkJmNaFeCiBSzSCohu6_kqNSuFIB1Vz1RE,568
|
46
46
|
fastlife/components/icons/AdjustmentsVertical.jinja,sha256=n7jBi400_q6KnhMABtUwKBBM_7n1IXFtdtaNwamrfxs,560
|
@@ -1665,34 +1665,35 @@ fastlife/components/pydantic_form/Text.jinja,sha256=2f_3Q32GySHTLFt-YO8gEJNCY-3X
|
|
1665
1665
|
fastlife/components/pydantic_form/Textarea.jinja,sha256=NzfCi5agRUSVcb5RXw0QamM8P1lZ-CdNI6P30zb2948,1155
|
1666
1666
|
fastlife/components/pydantic_form/Union.jinja,sha256=czTska54z9KCZKu-FaycLmOvtH6y6CGUFQ8DHnkjrJk,1461
|
1667
1667
|
fastlife/components/pydantic_form/Widget.jinja,sha256=EXskDqt22D5grpGVwlZA3ndve2Wr_6yQH4qVE9c31Og,397
|
1668
|
-
fastlife/config/__init__.py,sha256=
|
1669
|
-
fastlife/config/configurator.py,sha256=
|
1668
|
+
fastlife/config/__init__.py,sha256=ThosRIPZ_fpD0exZu-kUC_f8ZNa5KyDlleWMmEHkjEo,448
|
1669
|
+
fastlife/config/configurator.py,sha256=5In_ciAxqORR47gKIbZKTd1IOkkMO0a0iB4q5x7ltaI,21955
|
1670
1670
|
fastlife/config/exceptions.py,sha256=2MS2MFgb3mDbtdHEwnC-QYubob3Rl0v8O8y615LY0ds,1180
|
1671
1671
|
fastlife/config/openapiextra.py,sha256=_9rBYeTqB7nVuzvUHMwZU387bTfYFHYLlP05NP0vEDs,513
|
1672
|
-
fastlife/config/registry.py,sha256=
|
1672
|
+
fastlife/config/registry.py,sha256=dGcNm7E6WY0x5HZNzo1gBFvGFCWeJj6JFXsJtLax5NU,1347
|
1673
1673
|
fastlife/config/resources.py,sha256=pM0j5VKVbVak4Z5mFRHBjAjUqORP4TAtCnZM3res5o8,8776
|
1674
|
-
fastlife/config/settings.py,sha256=
|
1674
|
+
fastlife/config/settings.py,sha256=7oggPOucyJwQYI97q8vs3kPXjFIVpQu1q6BK25h-uFs,3789
|
1675
1675
|
fastlife/config/views.py,sha256=Dxi6lO4gFs6GriAW7Rh5GDvebwbrpS2HzYhf30pXJiE,2058
|
1676
1676
|
fastlife/middlewares/__init__.py,sha256=C3DUOzR5EhlAv5Zq7h-Abyvkd7bUsJohTRSB2wpRYQE,220
|
1677
1677
|
fastlife/middlewares/base.py,sha256=9OYqByRuVoIrLt353NOedPQTLdr7LSmxhb2BZcp20qk,638
|
1678
|
-
fastlife/middlewares/reverse_proxy/__init__.py,sha256=
|
1678
|
+
fastlife/middlewares/reverse_proxy/__init__.py,sha256=g1SoVDmenKzpAAPYHTEsWgdBByOxtLg9fGx6RV3i0ok,846
|
1679
1679
|
fastlife/middlewares/reverse_proxy/x_forwarded.py,sha256=WC4xV3i6_Ogqsf_Zgt1ESml8zfnPbJJJkPlC2gTEqW8,1095
|
1680
1680
|
fastlife/middlewares/session/__init__.py,sha256=3XgXcIO6yQls5G7x8K2T8b7a_enA_7rQptWZcp3j2Ak,1400
|
1681
1681
|
fastlife/middlewares/session/middleware.py,sha256=AlRIFXfn3JesKJzMAFUHDOo22mfuwDHkyecDHo9jCdA,3172
|
1682
1682
|
fastlife/middlewares/session/serializer.py,sha256=fTdZCop0y4VkCMyOIo6GEbo5fVWrwsBXaSWfConPL8E,2144
|
1683
1683
|
fastlife/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1684
|
-
fastlife/request/__init__.py,sha256
|
1685
|
-
fastlife/request/form.py,sha256=
|
1686
|
-
fastlife/request/form_data.py,sha256=
|
1684
|
+
fastlife/request/__init__.py,sha256=-wrh12uWM7ysmIUE6FszBit2H6iI5LWVEnHxQJ_23ZE,157
|
1685
|
+
fastlife/request/form.py,sha256=Ln9TySvAccYhFY9zc49P6YGE9cQSi_o3mByxvcuCIKY,3516
|
1686
|
+
fastlife/request/form_data.py,sha256=DNKpXUxeDMYb43zSBnSPTBjAB8OhsYlNjERSsLgFdvI,4454
|
1687
1687
|
fastlife/request/localizer.py,sha256=9MXAcsod-Po5qeg4lttD3dyumiI0y5vGHCwSSmt9or8,349
|
1688
|
-
fastlife/request/request.py,sha256=
|
1688
|
+
fastlife/request/request.py,sha256=bJydiRLkhRaA2Y5OCYN6p1rIlhSRsRicCFNVeM-RdgI,2597
|
1689
1689
|
fastlife/routing/__init__.py,sha256=8EMnQE5n8oA4J9_c3nxzwKDVt3tefZ6fGH0d2owE8mo,195
|
1690
|
-
fastlife/routing/route.py,sha256=
|
1690
|
+
fastlife/routing/route.py,sha256=O0gwPtP7ur2EHRf76kBASgoLMQciGHXcrJkW8zEPFJA,1413
|
1691
1691
|
fastlife/routing/router.py,sha256=bLZ4k2aDs4L423znwGnw-X2LnM36O8fjhDWc8q1WewI,481
|
1692
1692
|
fastlife/security/__init__.py,sha256=QYDcJ3oXQzqXQxoDD_6biGAtercFrtePttoifiL1j34,25
|
1693
1693
|
fastlife/security/csrf.py,sha256=PvC9Fqdb6c0IzzsnaMx2quQdjjKrb-nOPoAHfcwoAe8,2141
|
1694
|
-
fastlife/security/policy.py,sha256=
|
1694
|
+
fastlife/security/policy.py,sha256=BYGXmtEFlJgQSydFd88huHWJNLEScbmnjO--eVNBMok,5181
|
1695
1695
|
fastlife/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1696
|
+
fastlife/services/locale_negociator.py,sha256=b1Fsx-r_zIZe0GD8WH1gmslMb0mNrvxuacxD4LNX63o,819
|
1696
1697
|
fastlife/services/policy.py,sha256=ZK4K3fVGT1fUeBdo5sSWnEcJg49CZuaI3z2gyg3KguQ,1653
|
1697
1698
|
fastlife/services/templates.py,sha256=7gPJxGWD-XqputbZpy_Icsz3WHKJaWg2JgkVOeKrjfA,3840
|
1698
1699
|
fastlife/services/translations.py,sha256=Bo5CIjdbQ3g_ihbv4Bz60hzd8VOtqEEPOyhJEbGcvP4,4363
|
@@ -1705,8 +1706,8 @@ fastlife/templates/constants.py,sha256=MGdUjkF9hsPMN8rOS49eWbAApcb8FL-FAeFvJU8k9
|
|
1705
1706
|
fastlife/testing/__init__.py,sha256=vuqwoNUd3BuIp3fm7nkvmYkIGjIimf5zUGhDkeWrg2s,98
|
1706
1707
|
fastlife/testing/testclient.py,sha256=BC7lLQ_jc59UmknAKzgRtW9a3cpX_V_QLp9Mg2ScLA8,20546
|
1707
1708
|
fastlife/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1708
|
-
fastlife/views/pydantic_form.py,sha256=
|
1709
|
-
fastlifeweb-0.
|
1710
|
-
fastlifeweb-0.
|
1711
|
-
fastlifeweb-0.
|
1712
|
-
fastlifeweb-0.
|
1709
|
+
fastlife/views/pydantic_form.py,sha256=ZYOXKudmSqtRvFn5ZY75DOXZVunGXJBKpjh9FJcqu6k,1386
|
1710
|
+
fastlifeweb-0.16.0.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
|
1711
|
+
fastlifeweb-0.16.0.dist-info/METADATA,sha256=Q875Q1ELgr_qbBTGf5huMPCQwpwqovev2_OU0CNX4yU,3345
|
1712
|
+
fastlifeweb-0.16.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1713
|
+
fastlifeweb-0.16.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|