fastlifeweb 0.20.1__py3-none-any.whl → 0.22.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 +18 -1
- fastlife/__init__.py +45 -13
- fastlife/adapters/__init__.py +1 -1
- fastlife/adapters/fastapi/__init__.py +9 -0
- fastlife/adapters/fastapi/form.py +26 -0
- fastlife/{request → adapters/fastapi}/form_data.py +1 -1
- fastlife/{request → adapters/fastapi}/localizer.py +4 -2
- fastlife/adapters/fastapi/request.py +33 -0
- fastlife/{routing → adapters/fastapi/routing}/route.py +3 -3
- fastlife/{routing → adapters/fastapi/routing}/router.py +1 -1
- fastlife/adapters/itsdangerous/__init__.py +3 -0
- fastlife/adapters/itsdangerous/session.py +50 -0
- fastlife/adapters/jinjax/jinjax_ext/inspectable_component.py +7 -7
- fastlife/adapters/jinjax/jinjax_ext/jinjax_doc.py +1 -1
- fastlife/adapters/jinjax/renderer.py +9 -57
- fastlife/adapters/jinjax/widget_factory/bool_builder.py +2 -2
- fastlife/adapters/jinjax/widget_factory/emailstr_builder.py +5 -4
- fastlife/adapters/jinjax/widget_factory/enum_builder.py +2 -2
- fastlife/adapters/jinjax/widget_factory/factory.py +32 -23
- fastlife/adapters/jinjax/widget_factory/literal_builder.py +7 -6
- fastlife/adapters/jinjax/widget_factory/model_builder.py +3 -3
- fastlife/adapters/jinjax/widget_factory/secretstr_builder.py +2 -2
- fastlife/adapters/jinjax/widget_factory/sequence_builder.py +3 -3
- fastlife/adapters/jinjax/widget_factory/set_builder.py +2 -2
- fastlife/adapters/jinjax/widget_factory/simpletype_builder.py +7 -8
- fastlife/adapters/jinjax/widget_factory/union_builder.py +3 -3
- fastlife/adapters/jinjax/widgets/base.py +36 -36
- fastlife/adapters/jinjax/widgets/boolean.py +13 -34
- fastlife/adapters/jinjax/widgets/checklist.py +36 -42
- fastlife/adapters/jinjax/widgets/dropdown.py +32 -38
- fastlife/adapters/jinjax/widgets/hidden.py +7 -15
- fastlife/adapters/jinjax/widgets/model.py +36 -43
- fastlife/adapters/jinjax/widgets/sequence.py +63 -42
- fastlife/adapters/jinjax/widgets/text.py +39 -78
- fastlife/adapters/jinjax/widgets/union.py +51 -58
- fastlife/components/CsrfToken.jinja +1 -1
- fastlife/components/Form.jinja +1 -1
- fastlife/components/pydantic_form/FatalError.jinja +8 -0
- fastlife/components/pydantic_form/Widget.jinja +4 -3
- fastlife/config/__init__.py +3 -6
- fastlife/config/configurator.py +80 -32
- fastlife/config/exceptions.py +0 -2
- fastlife/config/resources.py +1 -2
- fastlife/config/views.py +2 -4
- fastlife/domain/__init__.py +1 -0
- fastlife/domain/model/__init__.py +1 -0
- fastlife/domain/model/asgi.py +3 -0
- fastlife/domain/model/csrf.py +19 -0
- fastlife/{request → domain/model}/form.py +13 -22
- fastlife/{request → domain/model}/request.py +26 -30
- fastlife/domain/model/security_policy.py +105 -0
- fastlife/{templates/inline.py → domain/model/template.py} +8 -0
- fastlife/domain/model/types.py +17 -0
- fastlife/middlewares/base.py +1 -1
- fastlife/middlewares/reverse_proxy/x_forwarded.py +1 -2
- fastlife/middlewares/session/__init__.py +2 -2
- fastlife/middlewares/session/middleware.py +4 -3
- fastlife/middlewares/session/serializer.py +0 -44
- fastlife/{services/policy.py → service/check_permission.py} +1 -1
- fastlife/{security → service}/csrf.py +5 -15
- fastlife/{services → service}/locale_negociator.py +5 -8
- fastlife/{config → service}/registry.py +13 -7
- fastlife/service/security_policy.py +100 -0
- fastlife/{services → service}/templates.py +10 -48
- fastlife/{services → service}/translations.py +15 -0
- fastlife/{config/settings.py → settings.py} +6 -12
- fastlife/shared_utils/infer.py +24 -1
- fastlife/{templates/constants.py → template_globals.py} +2 -2
- fastlife/testing/testclient.py +2 -2
- fastlife/views/__init__.py +1 -0
- fastlife/views/pydantic_form.py +6 -0
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.22.0.dist-info}/METADATA +1 -1
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.22.0.dist-info}/RECORD +79 -80
- tailwind.config.js +1 -1
- fastlife/components/pydantic_form/Boolean.jinja +0 -13
- fastlife/components/pydantic_form/Checklist.jinja +0 -21
- fastlife/components/pydantic_form/Dropdown.jinja +0 -18
- fastlife/components/pydantic_form/Hidden.jinja +0 -3
- fastlife/components/pydantic_form/Model.jinja +0 -30
- fastlife/components/pydantic_form/Sequence.jinja +0 -47
- fastlife/components/pydantic_form/Text.jinja +0 -11
- fastlife/components/pydantic_form/Textarea.jinja +0 -38
- fastlife/components/pydantic_form/Union.jinja +0 -34
- fastlife/request/__init__.py +0 -5
- fastlife/security/__init__.py +0 -1
- fastlife/security/policy.py +0 -188
- fastlife/templates/__init__.py +0 -12
- fastlife/templates/binding.py +0 -52
- /fastlife/{routing → adapters/fastapi/routing}/__init__.py +0 -0
- /fastlife/{services → service}/__init__.py +0 -0
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.22.0.dist-info}/WHEEL +0 -0
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.22.0.dist-info}/entry_points.txt +0 -0
- {fastlifeweb-0.20.1.dist-info → fastlifeweb-0.22.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,30 +0,0 @@
|
|
1
|
-
{# doc Widget for pydantic BaseModel subclasses. #}
|
2
|
-
{# def
|
3
|
-
widget: Annotated[fastlife.adapters.jinjax.widgets.model.ModelWidget, "widget to display."],
|
4
|
-
children_widget : Annotated[
|
5
|
-
Sequence[fastlife.adapters.jinjax.widgets.base.Widget],
|
6
|
-
"child widgets for every fields of the model."
|
7
|
-
],
|
8
|
-
#}
|
9
|
-
|
10
|
-
<pydantic_form.Widget :widget="widget">
|
11
|
-
<div id="{{widget.id}}"{% if widget.nested %} class="m-4"{%endif%}>
|
12
|
-
{% if widget.nested %}
|
13
|
-
<Details>
|
14
|
-
<Summary :id="widget.id + '-summary'">
|
15
|
-
<H3 :class="H3_SUMMARY_CLASS">{{widget.title}}</H3>
|
16
|
-
<pydantic_form.Error :text="widget.error" />
|
17
|
-
</Summary>
|
18
|
-
<div>
|
19
|
-
{% for child in children_widget %}
|
20
|
-
{{ child }}
|
21
|
-
{% endfor %}
|
22
|
-
</div>
|
23
|
-
</Details>
|
24
|
-
{% else %}
|
25
|
-
{% for child in children_widget %}
|
26
|
-
{{ child }}
|
27
|
-
{% endfor %}
|
28
|
-
{% endif %}
|
29
|
-
</div>
|
30
|
-
</pydantic_form.Widget>
|
@@ -1,47 +0,0 @@
|
|
1
|
-
{# doc Widget for pydantic BaseModel subclasses. #}
|
2
|
-
{# def
|
3
|
-
widget: Annotated[fastlife.adapters.jinjax.widgets.sequence.SequenceWidget, "widget to display."],
|
4
|
-
children_widgets : Annotated[
|
5
|
-
Sequence[fastlife.adapters.jinjax.widgets.base.Widget],
|
6
|
-
"child widgets for every fields of the model."
|
7
|
-
],
|
8
|
-
type: Annotated[fastlife.adapters.jinjax.widgets.base.TypeWrapper, "child type wrapped."]
|
9
|
-
#}
|
10
|
-
|
11
|
-
<pydantic_form.Widget :widget="widget">
|
12
|
-
<Details :id="widget.id">
|
13
|
-
<Summary :id="widget.id + '-summary'">
|
14
|
-
<H3 :class="H3_SUMMARY_CLASS">{{widget.title}}</H3>
|
15
|
-
<pydantic_form.Error :text="widget.error" />
|
16
|
-
</Summary>
|
17
|
-
<div>
|
18
|
-
{% set fnGetName = "get" + widget.id.replace("-", "_") %}
|
19
|
-
<script>
|
20
|
-
function {{ fnGetName }} () {
|
21
|
-
const el = document.getElementById("{{widget.id}}-content");
|
22
|
-
const len = el.dataset.length;
|
23
|
-
el.dataset.length = parseInt(len) + 1;
|
24
|
-
return "{{type.name}}." + len;
|
25
|
-
}
|
26
|
-
</script>
|
27
|
-
|
28
|
-
<div id="{{widget.id}}-content" class="m-4" data-length="{{children_widgets|length|string}}">
|
29
|
-
{% set container_id = widget.id + "-children-container" %}
|
30
|
-
<div id="{{container_id}}">
|
31
|
-
{% for child in children_widgets %}
|
32
|
-
{{ child }}
|
33
|
-
{% endfor%}
|
34
|
-
</div>
|
35
|
-
</div>
|
36
|
-
|
37
|
-
<div>
|
38
|
-
{% set container_id = "#" + widget.id + "-children-container" %}
|
39
|
-
{% set add_id = widget.id + "-add" %}
|
40
|
-
{% set vals = 'js:{"name": '+ fnGetName + '(), "token": "' + type.token + '", "removable": true}' %}
|
41
|
-
<Button type="button" :hx-target="container_id" hx-swap="beforeend" :id="add_id" :hx-vals="vals" :hx-get="type.url">
|
42
|
-
Add
|
43
|
-
</Button>
|
44
|
-
</div>
|
45
|
-
</div>
|
46
|
-
</Details>
|
47
|
-
</pydantic_form.Widget>
|
@@ -1,11 +0,0 @@
|
|
1
|
-
{# def widget: Annotated[fastlife.adapters.jinjax.widgets.text.TextWidget, "widget to display."] #}
|
2
|
-
|
3
|
-
<pydantic_form.Widget :widget="widget">
|
4
|
-
<div class="pt-4">
|
5
|
-
<Label :for="widget.id">{{widget.title}}</Label>
|
6
|
-
<pydantic_form.Error :text="widget.error" />
|
7
|
-
<Input :name="widget.name" :value="widget.value" :type="widget.input_type" :id="widget.id"
|
8
|
-
:aria-label="widget.aria_label" :placeholder="widget.placeholder" />
|
9
|
-
<pydantic_form.Hint :text="widget.hint" />
|
10
|
-
</div>
|
11
|
-
</pydantic_form.Widget>
|
@@ -1,38 +0,0 @@
|
|
1
|
-
{# doc
|
2
|
-
Render textarea widget for field of type text of event sequence.
|
3
|
-
|
4
|
-
::
|
5
|
-
|
6
|
-
from fastlife.adapters.jinjax.widgets.text import TextareaWidget
|
7
|
-
from pydantic import BaseModel, Field, field_validator
|
8
|
-
|
9
|
-
class TagsForm(BaseModel):
|
10
|
-
|
11
|
-
tags: Annotated[Sequence[str], TextareaWidget] = Field(
|
12
|
-
default_factory=list,
|
13
|
-
title="Tags",
|
14
|
-
description="One tag per line",
|
15
|
-
)
|
16
|
-
|
17
|
-
@field_validator("tags", mode="before")
|
18
|
-
def split(cls, s: Any) -> Sequence[str]:
|
19
|
-
return s.split() if s else []
|
20
|
-
|
21
|
-
#}
|
22
|
-
{# def widget: Annotated[fastlife.adapters.jinjax.widgets.text.TextareaWidget, "widget to display."] #}
|
23
|
-
|
24
|
-
<pydantic_form.Widget :widget="widget">
|
25
|
-
<div class="pt-4">
|
26
|
-
<Label :for="widget.id">{{widget.title}}</Label>
|
27
|
-
<pydantic_form.Error :text="widget.error" />
|
28
|
-
<Textarea :name="widget.name" :id="widget.id" :aria-label="widget.aria_label">
|
29
|
-
{%- if v is string -%}
|
30
|
-
{{- v -}}}
|
31
|
-
{%- else -%}
|
32
|
-
{%- for v in widget.value %}{{v}}
|
33
|
-
{% endfor -%}
|
34
|
-
{% endif %}
|
35
|
-
</Textarea>
|
36
|
-
<pydantic_form.Hint :text="widget.hint" />
|
37
|
-
</div>
|
38
|
-
</pydantic_form.Widget>
|
@@ -1,34 +0,0 @@
|
|
1
|
-
{# doc display widget for union type field #}
|
2
|
-
{# def
|
3
|
-
widget: Annotated[fastlife.adapters.jinjax.widgets.base.Widget, "widget to display."],
|
4
|
-
child: Annotated[fastlife.adapters.jinjax.widgets.base.Widget, "current widget if any"],
|
5
|
-
types: Annotated[Sequence[fastlife.adapters.jinjax.widgets.base.TypeWrapper], "Child types to choose"],
|
6
|
-
parent_type: Annotated[fastlife.adapters.jinjax.widgets.base.TypeWrapper, "parent type"]
|
7
|
-
#}
|
8
|
-
|
9
|
-
<pydantic_form.Widget :widget="widget">
|
10
|
-
<div id="{{widget.id}}">
|
11
|
-
<Details>
|
12
|
-
<Summary :id="widget.id + '-union-summary'">
|
13
|
-
<H3 :class="H3_SUMMARY_CLASS">{{widget.title}}</H3>
|
14
|
-
<pydantic_form.Error :text="widget.error" />
|
15
|
-
</Summary>
|
16
|
-
<div hx-sync="this" id="{{widget.id}}-child">
|
17
|
-
{% if child %}
|
18
|
-
{{ child }}
|
19
|
-
{% else %}
|
20
|
-
{% for typ in types %}
|
21
|
-
<Button type="button" hx-target="closest div" :hx-get="typ.url" :hx-vals="typ.params|tojson" :id="typ.id"
|
22
|
-
onclick={{ "document.getElementById('" + widget.id + "-remove-btn').hidden=false" }}
|
23
|
-
:class="SECONDARY_BUTTON_CLASS">{{typ.title}}</Button>
|
24
|
-
{% endfor %}
|
25
|
-
{% endif %}
|
26
|
-
</div>
|
27
|
-
<Button type="button" :id="widget.id + '-remove-btn'" :hx-target="'#' + widget.id"
|
28
|
-
:hx-vals="parent_type.params|tojson" :hx-get="parent_type.url" :hidden="not child"
|
29
|
-
:class="SECONDARY_BUTTON_CLASS">
|
30
|
-
Remove
|
31
|
-
</Button>
|
32
|
-
</Details>
|
33
|
-
</div>
|
34
|
-
</pydantic_form.Widget>
|
fastlife/request/__init__.py
DELETED
fastlife/security/__init__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
"""Security features."""
|
fastlife/security/policy.py
DELETED
@@ -1,188 +0,0 @@
|
|
1
|
-
"""Security policy."""
|
2
|
-
|
3
|
-
import abc
|
4
|
-
import logging
|
5
|
-
from collections.abc import Callable, Coroutine
|
6
|
-
from typing import Annotated, Any, Generic, Literal, TypeVar
|
7
|
-
from uuid import UUID
|
8
|
-
|
9
|
-
from fastapi import Depends, HTTPException
|
10
|
-
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
11
|
-
|
12
|
-
from fastlife import GenericRequest, get_request
|
13
|
-
from fastlife.config.registry import TRegistry
|
14
|
-
|
15
|
-
CheckPermissionHook = Callable[..., Coroutine[Any, Any, None]] | Callable[..., None]
|
16
|
-
CheckPermission = Callable[[str], CheckPermissionHook]
|
17
|
-
|
18
|
-
TUser = TypeVar("TUser")
|
19
|
-
|
20
|
-
log = logging.getLogger(__name__)
|
21
|
-
|
22
|
-
|
23
|
-
class Unauthorized(HTTPException):
|
24
|
-
"""An exception raised to stop a request exectution and return an HTTP Error."""
|
25
|
-
|
26
|
-
def __init__(
|
27
|
-
self,
|
28
|
-
status_code: int = HTTP_401_UNAUTHORIZED,
|
29
|
-
detail: str = "Unauthorized",
|
30
|
-
headers: dict[str, str] | None = None,
|
31
|
-
) -> None:
|
32
|
-
super().__init__(status_code, detail, headers)
|
33
|
-
|
34
|
-
|
35
|
-
class Forbidden(HTTPException):
|
36
|
-
"""An exception raised to stop a request exectution and return an HTTP Error."""
|
37
|
-
|
38
|
-
def __init__(
|
39
|
-
self,
|
40
|
-
status_code: int = HTTP_403_FORBIDDEN,
|
41
|
-
detail: str = "Forbidden",
|
42
|
-
headers: dict[str, str] | None = None,
|
43
|
-
) -> None:
|
44
|
-
super().__init__(status_code, detail, headers)
|
45
|
-
|
46
|
-
|
47
|
-
class BoolMeta(type):
|
48
|
-
def __bool__(cls) -> bool:
|
49
|
-
return cls.kind == "allowed" # type: ignore
|
50
|
-
|
51
|
-
def __repr__(cls) -> str:
|
52
|
-
return cls.reason # type: ignore
|
53
|
-
|
54
|
-
|
55
|
-
class HasPermission(int, metaclass=BoolMeta):
|
56
|
-
"""
|
57
|
-
A type used to know if a permission is allowed or not.
|
58
|
-
|
59
|
-
It behave has a boolean, but 3 possibilities exists defind has 3 sub-types
|
60
|
-
{class}`Allowed` {class}`Unauthenticated` or {class}`Denied`.
|
61
|
-
|
62
|
-
In many cases Unauthenticated call may redirect to a login page,
|
63
|
-
where authenticated user are not redirected. they have an error message,
|
64
|
-
or the frontend may use the information to adapt its interface.
|
65
|
-
"""
|
66
|
-
|
67
|
-
kind: Literal["allowed", "unauthenticated", "denied"]
|
68
|
-
reason: str
|
69
|
-
|
70
|
-
def __new__(cls, reason: str) -> "HasPermission":
|
71
|
-
instance = super().__new__(cls)
|
72
|
-
instance.reason = reason
|
73
|
-
return instance
|
74
|
-
|
75
|
-
def __repr__(self) -> str:
|
76
|
-
return self.reason
|
77
|
-
|
78
|
-
def __bool__(self) -> bool:
|
79
|
-
return self.kind == "allowed"
|
80
|
-
|
81
|
-
|
82
|
-
class Allowed(HasPermission):
|
83
|
-
"""Represent a permission check result that is allowed."""
|
84
|
-
|
85
|
-
kind = "allowed"
|
86
|
-
reason = "Allowed"
|
87
|
-
|
88
|
-
|
89
|
-
class Unauthenticated(HasPermission):
|
90
|
-
"""
|
91
|
-
Represent a permission check result that is not allowed due to
|
92
|
-
missing authentication mechanism.
|
93
|
-
"""
|
94
|
-
|
95
|
-
kind = "unauthenticated"
|
96
|
-
reason = "Authentication required"
|
97
|
-
|
98
|
-
|
99
|
-
class Denied(HasPermission):
|
100
|
-
"""
|
101
|
-
Represent a permission check result that is not allowed due to lack of permission.
|
102
|
-
"""
|
103
|
-
|
104
|
-
kind = "denied"
|
105
|
-
reason = "Access denied to this resource"
|
106
|
-
|
107
|
-
|
108
|
-
class AbstractSecurityPolicy(abc.ABC, Generic[TUser, TRegistry]):
|
109
|
-
"""Security policy base classe."""
|
110
|
-
|
111
|
-
Forbidden = Forbidden
|
112
|
-
"""The exception raised if the user identified is not granted."""
|
113
|
-
Unauthorized = Unauthorized
|
114
|
-
"""The exception raised if no user has been identified."""
|
115
|
-
|
116
|
-
request: GenericRequest[TRegistry]
|
117
|
-
"""Request where the security policy is applied."""
|
118
|
-
|
119
|
-
def __init__(
|
120
|
-
self, request: Annotated[GenericRequest[TRegistry], Depends(get_request)]
|
121
|
-
):
|
122
|
-
"""
|
123
|
-
Build the security policy.
|
124
|
-
|
125
|
-
When implementing a security policy, multiple parameters can be added
|
126
|
-
to the constructor as FastAPI dependencies, using the `Depends` FastAPI
|
127
|
-
annotation.
|
128
|
-
The security policy is installed has a depenency of the router that hold
|
129
|
-
a route prefix of the application.
|
130
|
-
"""
|
131
|
-
self.request = request
|
132
|
-
self.request.security_policy = self # we do backref to implement has_permission
|
133
|
-
|
134
|
-
@abc.abstractmethod
|
135
|
-
async def identity(self) -> TUser | None:
|
136
|
-
"""
|
137
|
-
Return app-specific user object or raise an HTTPException.
|
138
|
-
"""
|
139
|
-
|
140
|
-
@abc.abstractmethod
|
141
|
-
async def authenticated_userid(self) -> str | UUID | None:
|
142
|
-
"""
|
143
|
-
Return app-specific user object or raise an HTTPException.
|
144
|
-
"""
|
145
|
-
|
146
|
-
@abc.abstractmethod
|
147
|
-
async def has_permission(
|
148
|
-
self, permission: str
|
149
|
-
) -> HasPermission | type[HasPermission]:
|
150
|
-
"""Allow access to everything if signed in."""
|
151
|
-
|
152
|
-
@abc.abstractmethod
|
153
|
-
async def remember(self, user: TUser) -> None:
|
154
|
-
"""Save the user identity in the request session."""
|
155
|
-
|
156
|
-
@abc.abstractmethod
|
157
|
-
async def forget(self) -> None:
|
158
|
-
"""Destroy the request session."""
|
159
|
-
|
160
|
-
|
161
|
-
class InsecurePolicy(AbstractSecurityPolicy[None, Any]):
|
162
|
-
"""
|
163
|
-
An implementation of the security policy made for explicit unsecured access.
|
164
|
-
|
165
|
-
Setting a permission on a view require a security policy, if not set, accessing
|
166
|
-
to a view will raise a RuntimeError. To bypass this error for testing purpose
|
167
|
-
or your own reason, the InsecurePolicy has to be set to the configurator.
|
168
|
-
"""
|
169
|
-
|
170
|
-
async def identity(self) -> None:
|
171
|
-
"""Nobodies is identified."""
|
172
|
-
return None
|
173
|
-
|
174
|
-
async def authenticated_userid(self) -> str | UUID:
|
175
|
-
"""An uuid mades of 0."""
|
176
|
-
return UUID(int=0)
|
177
|
-
|
178
|
-
async def has_permission(
|
179
|
-
self, permission: str
|
180
|
-
) -> HasPermission | type[HasPermission]:
|
181
|
-
"""Access is allways granted."""
|
182
|
-
return Allowed
|
183
|
-
|
184
|
-
async def remember(self, user: None) -> None:
|
185
|
-
"""Do nothing."""
|
186
|
-
|
187
|
-
async def forget(self) -> None:
|
188
|
-
"""Do nothing."""
|
fastlife/templates/__init__.py
DELETED
fastlife/templates/binding.py
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Bind template to the view in order to build an html response.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from collections.abc import Callable
|
6
|
-
from typing import Any
|
7
|
-
|
8
|
-
from fastapi import Depends, Response
|
9
|
-
|
10
|
-
from fastlife.request import Request
|
11
|
-
from fastlife.security.csrf import create_csrf_token
|
12
|
-
|
13
|
-
Template = Callable[..., Response]
|
14
|
-
"""Type to annotate a FastAPI depency injection."""
|
15
|
-
|
16
|
-
TemplateEngine = Callable[[Request], Template]
|
17
|
-
|
18
|
-
|
19
|
-
def get_template(template: str, *, content_type: str = "text/html") -> TemplateEngine:
|
20
|
-
"""
|
21
|
-
Return a closure to render the given template.
|
22
|
-
|
23
|
-
:param template: path to template to render.
|
24
|
-
:param content_type: response ``Content-Type`` header.
|
25
|
-
"""
|
26
|
-
|
27
|
-
def render_template(
|
28
|
-
request: Request,
|
29
|
-
*,
|
30
|
-
_create_csrf_token: Callable[..., str] = create_csrf_token,
|
31
|
-
) -> Template:
|
32
|
-
reg = request.registry
|
33
|
-
|
34
|
-
def parametrizer(**kwargs: Any) -> Response:
|
35
|
-
return reg.get_renderer(template)(request).render(
|
36
|
-
template, content_type=content_type, params=kwargs
|
37
|
-
)
|
38
|
-
|
39
|
-
return parametrizer
|
40
|
-
|
41
|
-
return render_template
|
42
|
-
|
43
|
-
|
44
|
-
def template(template_path: str) -> Template:
|
45
|
-
"""
|
46
|
-
Return a FastAPI dependency template engine ready to render the template.
|
47
|
-
|
48
|
-
|
49
|
-
:param template_path: path to template to render by the engine setup in the regitry.
|
50
|
-
:return: A callable accepting kwargs to pass as the context, returning a string.
|
51
|
-
"""
|
52
|
-
return Depends(get_template(template_path))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|