fastlifeweb 0.18.0__py3-none-any.whl → 0.20.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 +206 -0
- fastlife/__init__.py +6 -0
- fastlife/adapters/jinjax/renderer.py +24 -2
- fastlife/config/__init__.py +2 -1
- fastlife/config/configurator.py +5 -1
- fastlife/config/registry.py +14 -3
- fastlife/config/views.py +8 -2
- fastlife/security/policy.py +1 -0
- fastlife/services/templates.py +19 -7
- fastlife/services/translations.py +67 -36
- fastlife/templates/__init__.py +2 -0
- fastlife/templates/inline.py +22 -0
- {fastlifeweb-0.18.0.dist-info → fastlifeweb-0.20.0.dist-info}/METADATA +29 -29
- {fastlifeweb-0.18.0.dist-info → fastlifeweb-0.20.0.dist-info}/RECORD +18 -14
- {fastlifeweb-0.18.0.dist-info → fastlifeweb-0.20.0.dist-info}/WHEEL +1 -1
- fastlifeweb-0.20.0.dist-info/entry_points.txt +4 -0
- fastlifeweb-0.20.0.dist-info/licenses/LICENSE +21 -0
- tailwind.config.js +57 -0
- fastlifeweb-0.18.0.dist-info/LICENSE +0 -28
CHANGELOG.md
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
## 0.20.0 - Released on 2024-11-09
|
2
|
+
* Add a new class GenericRegistry in order to properly type custom Configurator / Registry / Settings
|
3
|
+
* Using InlineTemplate, we can pass arbitrary types for pydantic form
|
4
|
+
|
5
|
+
## 0.19.0 - Released on 2024-11-07
|
6
|
+
* Drop Babel from depenencies for i18n, rely on GNUTranslations only
|
7
|
+
* Change License to MIT
|
8
|
+
* Replace poetry by uv/pdm
|
9
|
+
* Update CI workflows
|
10
|
+
|
11
|
+
## 0.18.0 - Released on 2024-10-13
|
12
|
+
* Make the sphinx pluging {mod}`fastlife.adapters.jinjax.jinjax_ext.jinjax_doc`
|
13
|
+
parts from the API in order to let users build their own component documentation.
|
14
|
+
|
15
|
+
## 0.17.0 - Released on 2024-10-08
|
16
|
+
* Fix @configure decorator signature for GenericConfigurator
|
17
|
+
* Breaking change - rename Configurator.set_open_tag to Configurator.set_openapi_tag
|
18
|
+
|
19
|
+
## 0.16.4 - Released on 2024-10-04
|
20
|
+
* Add support of x-real-port for port detection, fallback port to 0 instead of None if missing
|
21
|
+
|
22
|
+
## 0.16.3 - Released on 2024-10-03
|
23
|
+
* Fix middleware that process the x-forwarded-headers to respect ASGI spec for client
|
24
|
+
|
25
|
+
## 0.16.2 - Released on 2024-10-03
|
26
|
+
* Add a new property all_registered_permissions on the Configurator class
|
27
|
+
|
28
|
+
## 0.16.1 - Released on 2024-10-03
|
29
|
+
* Fix import in the SecurityPolicy that make it unusable.
|
30
|
+
|
31
|
+
## 0.16.0 - Released on 2024-10-02
|
32
|
+
* Make the Configurator, Request and Registry Generic.
|
33
|
+
* Breaking change, remove settings `api_swagger_ui_url` and `api_redoc_url`
|
34
|
+
now to register those url, use
|
35
|
+
{meth}`fastlife.config.configurator.GenericConfigurator.set_api_documentation_info`
|
36
|
+
* Breaking change, in the method
|
37
|
+
{meth}`fastlife.config.configurator.GenericConfigurator.set_api_documentation_info`
|
38
|
+
summary is now kwargs only.
|
39
|
+
|
40
|
+
## 0.15.1 - Released on 2024-09-29
|
41
|
+
* Hotfix components to create tables
|
42
|
+
|
43
|
+
## 0.15.0 - Released on 2024-09-29
|
44
|
+
* Add an {class}`fastlife.security.policy.AbstractSecurityPolicy` class
|
45
|
+
* New method {meth}`fastlife.config.configurator.GenericConfigurator.set_security_policy`
|
46
|
+
* Breaking change, the check_permission has been removed from the settings.
|
47
|
+
to configure the permission policy, a security policy has to be implemented.
|
48
|
+
|
49
|
+
## 0.14.0 - Released on 2024-09-26
|
50
|
+
* Implemement method add_template_search_path in the configurator
|
51
|
+
* Add a route_prefix in the configurator for configurator.include
|
52
|
+
|
53
|
+
## 0.13.0 - Released on 2024-09-25
|
54
|
+
* Add a way to handle api
|
55
|
+
* Add a @view_config decorator to register route
|
56
|
+
* Add a @resource decorator to handle CRUD resource in rest format
|
57
|
+
* Add @exception_handler decorator
|
58
|
+
* Add i18n support
|
59
|
+
|
60
|
+
## 0.12.0 - Released on 2024-09-19
|
61
|
+
* Add a way to register API routes and expose api doc
|
62
|
+
|
63
|
+
## 0.11.1 - Released on 2024-09-18
|
64
|
+
* Update FastAPI version
|
65
|
+
|
66
|
+
## 0.11.0 - Released on 2024-09-18
|
67
|
+
* Huge documentation update
|
68
|
+
* Use sphinx-autodoc2
|
69
|
+
* Add documentation for the components.
|
70
|
+
* Breaking change in the configurator.
|
71
|
+
* get_app has been renamed get_asgi_app
|
72
|
+
* a few internals classes moved/renamed.
|
73
|
+
|
74
|
+
## 0.10.0 - Released on 2024-08-24
|
75
|
+
|
76
|
+
* Rename model_result and ModelResult to form_model and FormModel
|
77
|
+
* Add an edit method for FormModel
|
78
|
+
* Add a Textarea widget and fix Hidden widget
|
79
|
+
* Fix rendering of sequence
|
80
|
+
* Do not render main form as nested models
|
81
|
+
* Add many functional tests for form field generations
|
82
|
+
|
83
|
+
## 0.9.7 - Released on 2024-08-21
|
84
|
+
|
85
|
+
* Add title attribute to icons
|
86
|
+
|
87
|
+
## 0.9.6 - Released on 2024-08-18
|
88
|
+
|
89
|
+
* Add more buttons options for htmx ajax call
|
90
|
+
* Fix Option id
|
91
|
+
|
92
|
+
## 0.9.5 - Released on 2024-08-17
|
93
|
+
|
94
|
+
* Use icons to customize collapsible widget for sequence
|
95
|
+
* Add parameter for button to avoid send params
|
96
|
+
|
97
|
+
## 0.9.4 - Released on 2024-08-16
|
98
|
+
|
99
|
+
* Don't update browser url while manipulating autoform lists
|
100
|
+
|
101
|
+
## 0.9.3 - Released on 2024-08-16
|
102
|
+
|
103
|
+
* Fix autoform widgets from jinjax migration
|
104
|
+
|
105
|
+
## 0.9.2 - Released on 2024-08-13
|
106
|
+
|
107
|
+
* Add a constants class for global variable in templates
|
108
|
+
* Use icons to customize collapsible widget
|
109
|
+
|
110
|
+
## 0.9.1 - Released on 2024-08-12
|
111
|
+
|
112
|
+
* Replace fa icons by hero icons
|
113
|
+
|
114
|
+
## 0.9.0 - Released on 2024-08-12
|
115
|
+
|
116
|
+
* Add fa Icons (extra)
|
117
|
+
|
118
|
+
## 0.8.0 - Released on 2024-08-10
|
119
|
+
|
120
|
+
* Upgrade JinjaX (Template update required, use vue-like syntax now)
|
121
|
+
|
122
|
+
## 0.7.3 - Released on 2024-08-10
|
123
|
+
|
124
|
+
* Add some HTML markup
|
125
|
+
|
126
|
+
## 0.7.2 - Released on 2024-08-07
|
127
|
+
|
128
|
+
* Fix https behind a reverse proxy
|
129
|
+
|
130
|
+
## 0.7.1 - Released on 2024-08-04
|
131
|
+
|
132
|
+
* Add the registry on request for exception handler
|
133
|
+
|
134
|
+
## 0.7.0 - Released on 2024-08-04
|
135
|
+
|
136
|
+
* Rewrite how the registry is handled, now part of the request (request.registry)
|
137
|
+
* Update to get hx-confirm and hx-delete on button
|
138
|
+
|
139
|
+
## 0.6.1 - Released on 2024-04-27
|
140
|
+
|
141
|
+
* Display errors on every widget
|
142
|
+
|
143
|
+
## 0.6.0 - Released on 2024-04-25
|
144
|
+
|
145
|
+
* Refactor the pydantic_form to start handling errors in form.
|
146
|
+
|
147
|
+
## 0.5.1 - Released on 2024-04-24
|
148
|
+
|
149
|
+
* Fix minimum dependency version for JinjaX
|
150
|
+
|
151
|
+
## 0.5.0 - Released on 2024-04-24
|
152
|
+
|
153
|
+
* Implement new types for pydantic form: Enum, Set[Literal] and Set[Enum]
|
154
|
+
|
155
|
+
## 0.4.1 - Released on 2024-04-20
|
156
|
+
|
157
|
+
* Add globals to render custom widget with global data
|
158
|
+
|
159
|
+
## 0.4.0 - Released on 2024-04-20
|
160
|
+
|
161
|
+
* Update JinjaX for global template var support
|
162
|
+
* Add lots of missing unit tests
|
163
|
+
* Add support of more html form element
|
164
|
+
* Update deps
|
165
|
+
|
166
|
+
## 0.3.1 - Released on 2024-03-29
|
167
|
+
|
168
|
+
* Update FastAPI
|
169
|
+
|
170
|
+
## 0.3.0 - Released on 2024-03-29
|
171
|
+
|
172
|
+
* Replace jinja2 by JinjaX
|
173
|
+
|
174
|
+
## 0.2.3 - Released on 2024-01-29
|
175
|
+
|
176
|
+
* Add support of relative import in :class:`Configurator.include` method
|
177
|
+
|
178
|
+
## 0.2.2 - Released on 2024-01-28
|
179
|
+
|
180
|
+
* Add another settings for session domain cookie
|
181
|
+
* Update test client wrapper and also wrap bs4 tag
|
182
|
+
* Fix session cleanup to properly logout
|
183
|
+
|
184
|
+
## 0.2.1 - Released on 2024-01-27
|
185
|
+
|
186
|
+
* Change add_route signature
|
187
|
+
* Set the name of the route mandatory and first argument (breaking change)
|
188
|
+
* Add a permission argument
|
189
|
+
* Add a settings to inject a check_permission handler
|
190
|
+
|
191
|
+
## 0.2.0 - Released on 2024-01-24
|
192
|
+
|
193
|
+
* Add a session wrapper in the test client
|
194
|
+
Allows to initialize session data in tests
|
195
|
+
|
196
|
+
## 0.1.2 - Released on 2024-01-15
|
197
|
+
|
198
|
+
* Handle sessions
|
199
|
+
|
200
|
+
## 0.1.1 - Released on 2024-01-05
|
201
|
+
|
202
|
+
* Update fastapi depencency
|
203
|
+
|
204
|
+
## 0.1.0 - Released on 2024-01-05
|
205
|
+
|
206
|
+
* Initial release
|
fastlife/__init__.py
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
+
from importlib import metadata
|
2
|
+
|
3
|
+
__version__ = metadata.version("fastlifeweb")
|
4
|
+
|
1
5
|
from fastapi import Response
|
2
6
|
|
3
7
|
from .config import (
|
4
8
|
Configurator,
|
5
9
|
DefaultRegistry,
|
6
10
|
GenericConfigurator,
|
11
|
+
GenericRegistry,
|
7
12
|
Settings,
|
8
13
|
configure,
|
9
14
|
resource,
|
@@ -21,6 +26,7 @@ __all__ = [
|
|
21
26
|
"GenericConfigurator",
|
22
27
|
"Configurator",
|
23
28
|
"DefaultRegistry",
|
29
|
+
"GenericRegistry",
|
24
30
|
"TemplateParams",
|
25
31
|
"Settings",
|
26
32
|
"view_config",
|
@@ -3,6 +3,7 @@ Template rending based on JinjaX.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
+
import textwrap
|
6
7
|
from collections.abc import Mapping, MutableMapping, Sequence
|
7
8
|
from typing import (
|
8
9
|
TYPE_CHECKING,
|
@@ -16,6 +17,7 @@ from fastlife import Request
|
|
16
17
|
from fastlife.adapters.jinjax.widget_factory.factory import WidgetFactory
|
17
18
|
from fastlife.request.form import FormModel
|
18
19
|
from fastlife.request.localizer import get_localizer
|
20
|
+
from fastlife.templates.inline import InlineTemplate
|
19
21
|
|
20
22
|
if TYPE_CHECKING:
|
21
23
|
from fastlife.config.settings import Settings # coverage: ignore
|
@@ -102,8 +104,7 @@ class JinjaxRenderer(AbstractTemplateRenderer):
|
|
102
104
|
child components without "props drilling".
|
103
105
|
:param params: parameters used to render the template.
|
104
106
|
"""
|
105
|
-
|
106
|
-
# we strip it before rendering.
|
107
|
+
|
107
108
|
template = template[: -len(self.settings.jinjax_file_ext) - 1]
|
108
109
|
if globals:
|
109
110
|
self.globals.update(globals)
|
@@ -116,6 +117,27 @@ class JinjaxRenderer(AbstractTemplateRenderer):
|
|
116
117
|
template, __globals=self.build_globals(), **params
|
117
118
|
)
|
118
119
|
|
120
|
+
def render_inline(self, template: InlineTemplate) -> str:
|
121
|
+
"""
|
122
|
+
Render the JinjaX component with the given parameter.
|
123
|
+
|
124
|
+
:param template: the template to render
|
125
|
+
:param globals: parameters that will be used by the JinjaX component and all its
|
126
|
+
child components without "props drilling".
|
127
|
+
:param params: parameters used to render the template.
|
128
|
+
"""
|
129
|
+
params = template.model_dump()
|
130
|
+
src = (
|
131
|
+
f"{{# def {', '.join(params.keys())} #}}\n"
|
132
|
+
f"{textwrap.dedent(template.template)}"
|
133
|
+
)
|
134
|
+
return self.catalog.render(
|
135
|
+
template.__class__.__qualname__,
|
136
|
+
__source=src,
|
137
|
+
__globals=self.build_globals(),
|
138
|
+
**params,
|
139
|
+
)
|
140
|
+
|
119
141
|
def pydantic_form(
|
120
142
|
self, model: FormModel[Any], *, token: str | None = None
|
121
143
|
) -> Markup:
|
fastlife/config/__init__.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""Configure fastlife app for dependency injection."""
|
2
2
|
|
3
3
|
from .configurator import Configurator, GenericConfigurator, configure
|
4
|
-
from .registry import DefaultRegistry
|
4
|
+
from .registry import DefaultRegistry, GenericRegistry
|
5
5
|
from .resources import resource, resource_view
|
6
6
|
from .settings import Settings
|
7
7
|
from .views import view_config
|
@@ -13,6 +13,7 @@ __all__ = [
|
|
13
13
|
"view_config",
|
14
14
|
"resource",
|
15
15
|
"resource_view",
|
16
|
+
"GenericRegistry",
|
16
17
|
"DefaultRegistry",
|
17
18
|
"Settings",
|
18
19
|
]
|
fastlife/config/configurator.py
CHANGED
@@ -466,7 +466,11 @@ class GenericConfigurator(Generic[TRegistry]):
|
|
466
466
|
:param path: path of the route, use `{curly_brace}` to inject FastAPI Path
|
467
467
|
parameters.
|
468
468
|
:param endpoint: the function that will reveive the request.
|
469
|
-
:param
|
469
|
+
:param template: the template rendered by the
|
470
|
+
{class}`fastlife.service.templates.AbstractTemplateRenderer`.
|
471
|
+
:param permission: a permission to validate by the
|
472
|
+
{class}`Security Policy <fastlife.security.policy.AbstractSecurityPolicy>`.
|
473
|
+
:param status_code: customize response status code.
|
470
474
|
:param methods: restrict route to a list of http methods.
|
471
475
|
:return: the configurator.
|
472
476
|
"""
|
fastlife/config/registry.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from collections.abc import Mapping
|
2
|
-
from typing import TYPE_CHECKING, TypeVar
|
2
|
+
from typing import TYPE_CHECKING, Generic, TypeVar
|
3
3
|
|
4
4
|
from fastlife.services.locale_negociator import LocaleNegociator, default_negociator
|
5
5
|
from fastlife.services.translations import LocalizerFactory
|
@@ -11,10 +11,15 @@ if TYPE_CHECKING:
|
|
11
11
|
|
12
12
|
from .settings import Settings
|
13
13
|
|
14
|
+
TSettings = TypeVar("TSettings", bound=Settings, covariant=True)
|
15
|
+
"""
|
16
|
+
A TypeVar used to override the DefaultRegistry to add more helpers in the registry.
|
17
|
+
"""
|
18
|
+
|
14
19
|
|
15
|
-
class
|
20
|
+
class GenericRegistry(Generic[TSettings]):
|
16
21
|
"""
|
17
|
-
|
22
|
+
Application registry for fastlife dependency injection.
|
18
23
|
It is initialized by the configurator and accessed by the `fastlife.Registry`.
|
19
24
|
"""
|
20
25
|
|
@@ -36,6 +41,12 @@ class DefaultRegistry:
|
|
36
41
|
raise RuntimeError(f"No renderer registered for template {template}")
|
37
42
|
|
38
43
|
|
44
|
+
DefaultRegistry = GenericRegistry[Settings]
|
45
|
+
"""
|
46
|
+
The default registry until you need to inject more component in the registry.
|
47
|
+
"""
|
48
|
+
|
49
|
+
|
39
50
|
TRegistry = TypeVar("TRegistry", bound=DefaultRegistry, covariant=True)
|
40
51
|
"""
|
41
52
|
A TypeVar used to override the DefaultRegistry to add more helpers in the registry.
|
fastlife/config/views.py
CHANGED
@@ -36,14 +36,20 @@ def view_config(
|
|
36
36
|
methods: list[str] | None = None,
|
37
37
|
) -> Callable[..., Any]:
|
38
38
|
"""
|
39
|
-
A decorator function to
|
39
|
+
A decorator function to register a view in the
|
40
|
+
{class}`Configurator <fastlife.config.configurator.GenericConfigurator>`
|
41
|
+
while scaning a module using {func}`include
|
42
|
+
<fastlife.config.configurator.GenericConfigurator.include>`.
|
40
43
|
|
41
44
|
:param name: name of the route, used to build route from the helper
|
42
45
|
{meth}`fastlife.request.request.Request.url_for` in order to create links.
|
43
46
|
:param path: path of the route, use `{curly_brace}` to inject FastAPI Path
|
44
47
|
parameters.
|
48
|
+
:param template: the template rendered by the
|
49
|
+
{class}`fastlife.services.templates.AbstractTemplateRenderer`.
|
45
50
|
:param permission: a permission to validate by the
|
46
|
-
{
|
51
|
+
{class}`Security Policy <fastlife.security.policy.AbstractSecurityPolicy>`.
|
52
|
+
:param status_code: customize response status code.
|
47
53
|
:param methods: restrict route to a list of http methods.
|
48
54
|
|
49
55
|
:return: the configuration callback.
|
fastlife/security/policy.py
CHANGED
@@ -63,6 +63,7 @@ class HasPermission(int, metaclass=BoolMeta):
|
|
63
63
|
where authenticated user are not redirected. they have an error message,
|
64
64
|
or the frontend may use the information to adapt its interface.
|
65
65
|
"""
|
66
|
+
|
66
67
|
kind: Literal["allowed", "unauthenticated", "denied"]
|
67
68
|
reason: str
|
68
69
|
|
fastlife/services/templates.py
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
"""
|
2
2
|
Base class to of the template renderer.
|
3
3
|
|
4
|
-
Fastlife comes with {class}`fastlife.
|
5
|
-
the rendering engine
|
6
|
-
:attr:`fastlife.config.settings.Settings.template_renderer_class`.
|
7
|
-
|
8
|
-
In that case, those base classes have to be implemented.
|
4
|
+
Fastlife comes with {class}`fastlife.adapters.jinjax.renderer.JinjaxEngine`,
|
5
|
+
the rendering engine.
|
9
6
|
|
7
|
+
More template engine can be registered using the configurator method
|
8
|
+
{meth}`add_renderer <fastlife.config.configurator.GenericConfigurator.add_renderer>`
|
10
9
|
"""
|
11
10
|
|
12
11
|
import abc
|
@@ -15,6 +14,7 @@ from typing import Any
|
|
15
14
|
|
16
15
|
from fastlife import Request, Response
|
17
16
|
from fastlife.security.csrf import create_csrf_token
|
17
|
+
from fastlife.templates.inline import InlineTemplate
|
18
18
|
|
19
19
|
TemplateParams = Mapping[str, Any]
|
20
20
|
|
@@ -43,7 +43,7 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
43
43
|
status_code: int = 200,
|
44
44
|
content_type: str = "text/html",
|
45
45
|
globals: Mapping[str, Any] | None = None,
|
46
|
-
params: TemplateParams,
|
46
|
+
params: TemplateParams | InlineTemplate,
|
47
47
|
_create_csrf_token: Callable[..., str] = create_csrf_token,
|
48
48
|
) -> Response:
|
49
49
|
"""
|
@@ -54,7 +54,10 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
54
54
|
request.scope[reg.settings.csrf_token_name] = (
|
55
55
|
request.cookies.get(reg.settings.csrf_token_name) or _create_csrf_token()
|
56
56
|
)
|
57
|
-
|
57
|
+
if isinstance(params, InlineTemplate):
|
58
|
+
data = self.render_inline(params)
|
59
|
+
else:
|
60
|
+
data = self.render_template(template, **params)
|
58
61
|
resp = Response(
|
59
62
|
data, status_code=status_code, headers={"Content-Type": content_type}
|
60
63
|
)
|
@@ -94,6 +97,15 @@ class AbstractTemplateRenderer(abc.ABC):
|
|
94
97
|
:return: The template rendering result.
|
95
98
|
"""
|
96
99
|
|
100
|
+
@abc.abstractmethod
|
101
|
+
def render_inline(self, template: InlineTemplate) -> str:
|
102
|
+
"""
|
103
|
+
Render an inline template.
|
104
|
+
|
105
|
+
:param template: the template to render.
|
106
|
+
:return: The template rendering result.
|
107
|
+
"""
|
108
|
+
|
97
109
|
|
98
110
|
class AbstractTemplateRendererFactory(abc.ABC):
|
99
111
|
"""
|
@@ -1,18 +1,21 @@
|
|
1
1
|
import pathlib
|
2
|
-
from collections
|
2
|
+
from collections import defaultdict
|
3
|
+
from collections.abc import Callable, Iterator
|
4
|
+
from gettext import GNUTranslations
|
5
|
+
from io import BufferedReader
|
3
6
|
from typing import TYPE_CHECKING
|
4
7
|
|
5
|
-
from babel.support import NullTranslations, Translations
|
6
|
-
|
7
8
|
from fastlife.shared_utils.resolver import resolve_path
|
8
9
|
|
9
10
|
if TYPE_CHECKING:
|
10
11
|
from fastlife import Request # coverage: ignore
|
11
12
|
|
12
|
-
|
13
|
+
LocaleName = str
|
14
|
+
Domain = str
|
15
|
+
CONTEXT_ENCODING = "%s\x04%s"
|
13
16
|
|
14
17
|
|
15
|
-
def find_mo_files(root_path: str) -> Iterator[tuple[
|
18
|
+
def find_mo_files(root_path: str) -> Iterator[tuple[LocaleName, Domain, pathlib.Path]]:
|
16
19
|
"""
|
17
20
|
Find .mo files in a locales directory.
|
18
21
|
|
@@ -30,30 +33,54 @@ def find_mo_files(root_path: str) -> Iterator[tuple[str, str, pathlib.Path]]:
|
|
30
33
|
yield locale_dir.name, mo_file.stem, mo_file
|
31
34
|
|
32
35
|
|
36
|
+
def _default_plural(n: int) -> int:
|
37
|
+
return int(n != 1) # germanic plural by default
|
38
|
+
|
39
|
+
|
40
|
+
class MergedTranslations(GNUTranslations):
|
41
|
+
_catalog: dict[str, str]
|
42
|
+
|
43
|
+
def __init__(self) -> None:
|
44
|
+
super().__init__()
|
45
|
+
self._catalog = {}
|
46
|
+
self.plural: Callable[[int], int] = _default_plural
|
47
|
+
|
48
|
+
def merge(self, other: GNUTranslations) -> None:
|
49
|
+
if hasattr(other, "_catalog"):
|
50
|
+
self._catalog.update(other._catalog) # type: ignore
|
51
|
+
if hasattr(other, "plural"):
|
52
|
+
self.plural = other.plural # type: ignore
|
53
|
+
|
54
|
+
|
33
55
|
class Localizer:
|
34
|
-
def __init__(
|
35
|
-
self
|
36
|
-
|
37
|
-
|
38
|
-
self.
|
56
|
+
def __init__(self) -> None:
|
57
|
+
self.translations: dict[Domain, MergedTranslations] = defaultdict(
|
58
|
+
MergedTranslations
|
59
|
+
)
|
60
|
+
self.global_translations = MergedTranslations()
|
61
|
+
|
62
|
+
def register(self, domain: str, file: BufferedReader) -> None:
|
63
|
+
trans = GNUTranslations(file)
|
64
|
+
self.translations[domain].merge(trans)
|
65
|
+
self.global_translations.merge(trans)
|
39
66
|
|
40
67
|
def gettext(self, message: str, mapping: dict[str, str] | None = None) -> str:
|
41
|
-
ret = self.
|
42
|
-
if mapping
|
68
|
+
ret = self.global_translations.gettext(message)
|
69
|
+
if mapping:
|
43
70
|
ret = ret.format(**mapping)
|
44
71
|
return ret
|
45
72
|
|
46
73
|
def ngettext(
|
47
74
|
self, singular: str, plural: str, n: int, mapping: dict[str, str] | None = None
|
48
75
|
) -> str:
|
49
|
-
ret = self.
|
76
|
+
ret = self.global_translations.ngettext(singular, plural, n)
|
50
77
|
mapping_num = {"num": n, **(mapping or {})}
|
51
78
|
return ret.format(**mapping_num)
|
52
79
|
|
53
80
|
def dgettext(
|
54
81
|
self, domain: str, message: str, mapping: dict[str, str] | None = None
|
55
82
|
) -> str:
|
56
|
-
ret = self.translations.
|
83
|
+
ret = self.translations[domain].gettext(message)
|
57
84
|
if mapping:
|
58
85
|
ret = ret.format(**mapping)
|
59
86
|
return ret
|
@@ -66,14 +93,14 @@ class Localizer:
|
|
66
93
|
n: int,
|
67
94
|
mapping: dict[str, str] | None = None,
|
68
95
|
) -> str:
|
69
|
-
ret = self.translations.
|
96
|
+
ret = self.translations[domain].ngettext(singular, plural, n)
|
70
97
|
mapping_num = {"num": n, **(mapping or {})}
|
71
98
|
return ret.format(**mapping_num)
|
72
99
|
|
73
100
|
def pgettext(
|
74
101
|
self, context: str, message: str, mapping: dict[str, str] | None = None
|
75
102
|
) -> str:
|
76
|
-
ret =
|
103
|
+
ret = self.global_translations.pgettext(context, message)
|
77
104
|
if mapping:
|
78
105
|
ret = ret.format(**mapping)
|
79
106
|
return ret
|
@@ -85,7 +112,7 @@ class Localizer:
|
|
85
112
|
message: str,
|
86
113
|
mapping: dict[str, str] | None = None,
|
87
114
|
) -> str:
|
88
|
-
ret =
|
115
|
+
ret = self.translations[domain].pgettext(context, message)
|
89
116
|
if mapping:
|
90
117
|
ret = ret.format(**mapping)
|
91
118
|
return ret
|
@@ -99,18 +126,33 @@ class Localizer:
|
|
99
126
|
n: int,
|
100
127
|
mapping: dict[str, str] | None = None,
|
101
128
|
) -> str:
|
102
|
-
ret = self.translations.
|
129
|
+
ret = self.translations[domain].npgettext(context, singular, plural, n)
|
103
130
|
mapping_num = {"num": n, **(mapping or {})}
|
104
131
|
return ret.format(**mapping_num)
|
105
132
|
|
106
133
|
|
134
|
+
class TranslationDictionary:
|
135
|
+
def __init__(self) -> None:
|
136
|
+
self.translations: dict[LocaleName, Localizer] = defaultdict(Localizer)
|
137
|
+
|
138
|
+
def load(self, root_path: str) -> None:
|
139
|
+
for locale_name, domain, file_ in find_mo_files(root_path):
|
140
|
+
with file_.open("rb") as stream:
|
141
|
+
self.translations[locale_name].register(domain, stream)
|
142
|
+
|
143
|
+
def get(self, locale_name: LocaleName) -> Localizer:
|
144
|
+
return self.translations[locale_name]
|
145
|
+
|
146
|
+
def __contains__(self, other: LocaleName) -> bool:
|
147
|
+
return other in self.translations
|
148
|
+
|
149
|
+
|
107
150
|
class LocalizerFactory:
|
108
151
|
"""Initialize the proper translation context per request."""
|
109
152
|
|
110
|
-
_translations: dict[locale_name, Translations]
|
111
|
-
|
112
153
|
def __init__(self) -> None:
|
113
|
-
self._translations =
|
154
|
+
self._translations = TranslationDictionary()
|
155
|
+
self.null_localizer = Localizer()
|
114
156
|
|
115
157
|
def load(self, path: str) -> None:
|
116
158
|
"""
|
@@ -118,21 +160,10 @@ class LocalizerFactory:
|
|
118
160
|
:param path: a python module and the locales dir separated by a `:`
|
119
161
|
"""
|
120
162
|
root_path = resolve_path(path)
|
121
|
-
|
122
|
-
with file_.open("rb") as f:
|
123
|
-
t = Translations(f, domain)
|
124
|
-
if locale_name not in self._translations:
|
125
|
-
self._translations[locale_name] = Translations()
|
126
|
-
self._translations[locale_name].add(t)
|
127
|
-
self._translations[locale_name].merge(t)
|
163
|
+
self._translations.load(root_path)
|
128
164
|
|
129
165
|
def __call__(self, request: "Request") -> Localizer:
|
130
166
|
"""Create the translation context for the given request."""
|
131
|
-
|
132
|
-
|
133
|
-
)
|
134
|
-
if not trans:
|
135
|
-
trans = self._translations.get(request.registry.settings.default_locale)
|
136
|
-
if not trans:
|
137
|
-
trans = NullTranslations()
|
138
|
-
return Localizer(request, trans)
|
167
|
+
if request.locale_name not in self._translations:
|
168
|
+
return self.null_localizer
|
169
|
+
return self._translations.get(request.locale_name)
|
fastlife/templates/__init__.py
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
"""Inline templates."""
|
2
|
+
|
3
|
+
from typing import ClassVar
|
4
|
+
|
5
|
+
from pydantic import BaseModel
|
6
|
+
from pydantic.config import ConfigDict
|
7
|
+
|
8
|
+
|
9
|
+
class InlineTemplate(BaseModel):
|
10
|
+
"""
|
11
|
+
Inline templates are used to encourage the location of behavior and the view typing.
|
12
|
+
|
13
|
+
Pages produce templates that are not reusable and don't need to be reusable
|
14
|
+
in there essence, they don't need to be in a component library.
|
15
|
+
They use a component lirary to stay small but contains a view logic
|
16
|
+
tighly coupled with the view and its code can stay in the same module of that view.
|
17
|
+
"""
|
18
|
+
|
19
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
20
|
+
|
21
|
+
template: ClassVar[str]
|
22
|
+
"""The template string to render."""
|
@@ -1,43 +1,44 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fastlifeweb
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20.0
|
4
4
|
Summary: High-level web framework
|
5
|
-
|
6
|
-
License:
|
7
|
-
Author: Guillaume Gauvrit
|
8
|
-
Author-email: guillaume@gauvr.it
|
9
|
-
Requires-Python: >=3.11,<4.0
|
5
|
+
Author-Email: Guillaume Gauvrit <guillaume@gauvr.it>
|
6
|
+
License: MIT License
|
10
7
|
Classifier: Development Status :: 4 - Beta
|
11
8
|
Classifier: Framework :: AsyncIO
|
12
9
|
Classifier: Intended Audience :: Developers
|
13
|
-
Classifier: License :: OSI Approved ::
|
14
|
-
Classifier: License :: Other/Proprietary License
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
16
|
-
Classifier: Programming Language :: Python :: 3.11
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
18
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
19
11
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
20
|
-
|
12
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
13
|
+
Project-URL: Homepage, https://mardiros.github.io/fastlife
|
14
|
+
Project-URL: Documentation, https://mardiros.github.io/fastlife
|
15
|
+
Project-URL: Repository, https://github.com/mardiros/fastlife.git
|
16
|
+
Project-URL: Issues, https://github.com/mardiros/fastlife/issues
|
17
|
+
Project-URL: Changelog, https://mardiros.github.io/fastlife/user/changelog.html
|
18
|
+
Requires-Python: >=3.11
|
19
|
+
Requires-Dist: fastapi[standard]<1,>=0.115.0
|
20
|
+
Requires-Dist: itsdangerous<3,>=2.1.2
|
21
|
+
Requires-Dist: jinjax<0.45,>=0.44
|
22
|
+
Requires-Dist: markupsafe<3,>=2.1.3
|
23
|
+
Requires-Dist: multidict<7,>=6.0.5
|
24
|
+
Requires-Dist: pydantic<3,>=2.5.3
|
25
|
+
Requires-Dist: pydantic-settings<3,>=2.0.3
|
26
|
+
Requires-Dist: python-multipart<1,>=0.0.9
|
27
|
+
Requires-Dist: venusian<4,>=3.0.0
|
21
28
|
Provides-Extra: testing
|
22
|
-
Requires-Dist:
|
23
|
-
|
24
|
-
Requires-Dist:
|
25
|
-
Requires-Dist:
|
26
|
-
Requires-Dist:
|
27
|
-
Requires-Dist:
|
28
|
-
Requires-Dist:
|
29
|
-
Requires-Dist: pydantic (>=2.5.3,<3.0.0)
|
30
|
-
Requires-Dist: pydantic-settings (>=2.0.3,<3.0.0)
|
31
|
-
Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
|
32
|
-
Requires-Dist: sphinx (>=7.0.1,<8.0.0) ; extra == "doc"
|
33
|
-
Requires-Dist: venusian (>=3.0.0,<4.0.0)
|
34
|
-
Project-URL: Repository, https://github.com/mardiros/fastlife
|
29
|
+
Requires-Dist: beautifulsoup4; extra == "testing"
|
30
|
+
Provides-Extra: docs
|
31
|
+
Requires-Dist: furo>=2024.5.6; extra == "docs"
|
32
|
+
Requires-Dist: linkify-it-py<3,>=2.0.3; extra == "docs"
|
33
|
+
Requires-Dist: myst-parser<5,>=4.0.0; extra == "docs"
|
34
|
+
Requires-Dist: sphinx<8,>=7.0.1; extra == "docs"
|
35
|
+
Requires-Dist: sphinx-autodoc2<1,>=0.5.0; extra == "docs"
|
35
36
|
Description-Content-Type: text/markdown
|
36
37
|
|
37
38
|
# Fastlife
|
38
39
|
|
39
|
-
[](https://mardiros.github.io/fastlife/)
|
41
|
+
[](https://github.com/mardiros/fastlife/actions/workflows/tests.yml)
|
41
42
|
[](https://codecov.io/gh/mardiros/fastlife)
|
42
43
|
[](https://codeclimate.com/github/mardiros/fastlife/maintainability)
|
43
44
|
|
@@ -85,4 +86,3 @@ The package is available on pypi with the name fastlifeweb.
|
|
85
86
|
```bash
|
86
87
|
pip install fastlifeweb
|
87
88
|
```
|
88
|
-
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
CHANGELOG.md,sha256=O4fJAjGiCGsX1L56Rdtl_4KbruwAsqsxihHgtd5FnDc,6032
|
2
|
+
fastlife/__init__.py,sha256=l8SVz6RNy8QJNKFwUx9OHt-V21r9yeKK8pLuUY1u4ps,799
|
2
3
|
fastlife/adapters/__init__.py,sha256=WYjEN8gp4r7LCHqmIO5VzzvsT8QGRE3w4G47UwYDtAo,94
|
3
4
|
fastlife/adapters/jinjax/__init__.py,sha256=4JRAUwFGpTxYtRlg5sU79AahxyAiRMhllRFHoI-dnug,117
|
4
5
|
fastlife/adapters/jinjax/jinjax_ext/__init__.py,sha256=z6NWvHzTNEq6bVO4iJoTR6-y4A6UtS_VuSMV_tff1jY,49
|
@@ -6,7 +7,7 @@ fastlife/adapters/jinjax/jinjax_ext/docstring.py,sha256=Zlx0oSxsRU9vQvGoyScXf9uB
|
|
6
7
|
fastlife/adapters/jinjax/jinjax_ext/inspectable_catalog.py,sha256=KHOYTT6UNA41QwyLNQVoG4trOVXYdChlRmqq_G1pv1s,2987
|
7
8
|
fastlife/adapters/jinjax/jinjax_ext/inspectable_component.py,sha256=Cz6PrRhO3lXUI9baxosOr6MtZFjuOki9YTEWMkfbbR0,2909
|
8
9
|
fastlife/adapters/jinjax/jinjax_ext/jinjax_doc.py,sha256=uPcYiUTrliR2lLpRuQUrirmFzTEVRTwo1mUZE4Z5onc,10225
|
9
|
-
fastlife/adapters/jinjax/renderer.py,sha256=
|
10
|
+
fastlife/adapters/jinjax/renderer.py,sha256=6HjngkDNWUTlR_ST3v_WDu8KD4PJnniK74eQl_RXdzU,6866
|
10
11
|
fastlife/adapters/jinjax/widget_factory/__init__.py,sha256=Dy_2xr_YDAyEF9WtNpjV-aYaehRO1iKEIHVFdfFeszw,59
|
11
12
|
fastlife/adapters/jinjax/widget_factory/base.py,sha256=TLEpYdekR4AeHhIie_DICc_oSvQaUaL8GlatqNiRewg,1046
|
12
13
|
fastlife/adapters/jinjax/widget_factory/bool_builder.py,sha256=2-Hv5w4hfBfGWGetb00I8Lm1FDAputH2MNt3tCx-RbA,1280
|
@@ -1682,14 +1683,14 @@ fastlife/components/pydantic_form/Text.jinja,sha256=2f_3Q32GySHTLFt-YO8gEJNCY-3X
|
|
1682
1683
|
fastlife/components/pydantic_form/Textarea.jinja,sha256=NzfCi5agRUSVcb5RXw0QamM8P1lZ-CdNI6P30zb2948,1155
|
1683
1684
|
fastlife/components/pydantic_form/Union.jinja,sha256=czTska54z9KCZKu-FaycLmOvtH6y6CGUFQ8DHnkjrJk,1461
|
1684
1685
|
fastlife/components/pydantic_form/Widget.jinja,sha256=EXskDqt22D5grpGVwlZA3ndve2Wr_6yQH4qVE9c31Og,397
|
1685
|
-
fastlife/config/__init__.py,sha256=
|
1686
|
-
fastlife/config/configurator.py,sha256=
|
1686
|
+
fastlife/config/__init__.py,sha256=R0HXh0rt6Aecf5kJmXvhH-6QrQb1SyuI83bOU8T80fc,488
|
1687
|
+
fastlife/config/configurator.py,sha256=sVecDJbjeRQRjULjbQH1JCeBOqvS6DUeQGJ5Nh2HQlw,22649
|
1687
1688
|
fastlife/config/exceptions.py,sha256=kH2-akbzGeODlY_1bUhbzDKqBFrpOoqnVom0WPm0IGg,1237
|
1688
1689
|
fastlife/config/openapiextra.py,sha256=rYoerrn9sni2XwnO3gIWqaz7M0aDZPhVLjzqhDxue0o,514
|
1689
|
-
fastlife/config/registry.py,sha256=
|
1690
|
+
fastlife/config/registry.py,sha256=zP_LPvwTcUZdrrsCnRaUfJzeYq7M_eVdRWlN0oO52RE,1754
|
1690
1691
|
fastlife/config/resources.py,sha256=Wu3vVr7XD18Gf4-MYYCxAAnuRmsAJmpllonts_BVGdQ,8593
|
1691
1692
|
fastlife/config/settings.py,sha256=t9goMfnc_oWrS_c3vgivIe0w6N2Byohcfol2CAnLiJs,3892
|
1692
|
-
fastlife/config/views.py,sha256=
|
1693
|
+
fastlife/config/views.py,sha256=C6Ot3mxTyN5isVsIe5b9x-VzD2q76pJQ8xY2aPWdMZM,2460
|
1693
1694
|
fastlife/middlewares/__init__.py,sha256=C3DUOzR5EhlAv5Zq7h-Abyvkd7bUsJohTRSB2wpRYQE,220
|
1694
1695
|
fastlife/middlewares/base.py,sha256=9OYqByRuVoIrLt353NOedPQTLdr7LSmxhb2BZcp20qk,638
|
1695
1696
|
fastlife/middlewares/reverse_proxy/__init__.py,sha256=g1SoVDmenKzpAAPYHTEsWgdBByOxtLg9fGx6RV3i0ok,846
|
@@ -1708,18 +1709,19 @@ fastlife/routing/route.py,sha256=vqjfMsHAVO0l2B8fuB8t19CKMtE7WoBkG4kvi4lUonM,144
|
|
1708
1709
|
fastlife/routing/router.py,sha256=ho9TvTkX2iUW6GEh99FgclZVFKkCCCxYG4pPHeUtGn8,482
|
1709
1710
|
fastlife/security/__init__.py,sha256=QYDcJ3oXQzqXQxoDD_6biGAtercFrtePttoifiL1j34,25
|
1710
1711
|
fastlife/security/csrf.py,sha256=PIKG83LPqKz4kDALnZxIyPdYVwbNqsIryi7JPqRPQag,2168
|
1711
|
-
fastlife/security/policy.py,sha256=
|
1712
|
+
fastlife/security/policy.py,sha256=4WHu8xhR7tAdXUHpHY1kdVIdTOFsUl92R8IsIRyYMSU,5579
|
1712
1713
|
fastlife/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1713
1714
|
fastlife/services/locale_negociator.py,sha256=Np2O8s7xnYTpf5eCG7LvcfFJ2LV7p_k86NNrU9Lju88,846
|
1714
1715
|
fastlife/services/policy.py,sha256=RfYGPjfEAAoHECUnZVLPZgN0iRanu8UKQSky6oAz81o,1687
|
1715
|
-
fastlife/services/templates.py,sha256=
|
1716
|
-
fastlife/services/translations.py,sha256=
|
1716
|
+
fastlife/services/templates.py,sha256=xSoVofpKXrZc1fDLAJyDDceJrKZaZRL1K8wFp2rGsE0,4272
|
1717
|
+
fastlife/services/translations.py,sha256=Akazow9YTb6N2IDdMZOAMef-grbAC7fM0nA3hw-mHt4,5315
|
1717
1718
|
fastlife/shared_utils/__init__.py,sha256=i66ytuf-Ezo7jSiNQHIsBMVIcB-tDX0tg28-pUOlhzE,26
|
1718
1719
|
fastlife/shared_utils/infer.py,sha256=3G_u6q2aWzeiVlAyGaWIlnAcz90m4bFNwpPYd5JIqfE,723
|
1719
1720
|
fastlife/shared_utils/resolver.py,sha256=Wb9cO2MWavpti63hju15xmwFMgaD5DsQaxikRpB39E8,3713
|
1720
|
-
fastlife/templates/__init__.py,sha256=
|
1721
|
+
fastlife/templates/__init__.py,sha256=yB6Zpz-jsFytLAz4Z6Nmuvr7Z8kbCXSdgw6kMpggTxk,241
|
1721
1722
|
fastlife/templates/binding.py,sha256=0pE2btOwLf4xOEgBXVOyz_dIX9tBCYCaJ7RhZI3knbs,1464
|
1722
1723
|
fastlife/templates/constants.py,sha256=MGdUjkF9hsPMN8rOS49eWbAApcb8FL-FAeFvJU8k90M,8387
|
1724
|
+
fastlife/templates/inline.py,sha256=wrZDAtd7zXcACHl2LVz0HqDT1VSPsiVy4srt3JSDiu4,704
|
1723
1725
|
fastlife/testing/__init__.py,sha256=VpxkS3Zp3t_hH8dBiLaGFGhsvt511dhBS_8fMoFXdmU,99
|
1724
1726
|
fastlife/testing/dom.py,sha256=dVzDoZokn-ii681UaEwAr-khM5KE-CHgXSSLSo24oH0,4489
|
1725
1727
|
fastlife/testing/form.py,sha256=ST0xNCoUqz_oD92cWHzQ6CbJ5hFopvu_NNKpOfiuYWY,7874
|
@@ -1727,7 +1729,9 @@ fastlife/testing/session.py,sha256=LEFFbiR67_x_g-ioudkY0C7PycHdbDfaIaoo_G7GXQ8,2
|
|
1727
1729
|
fastlife/testing/testclient.py,sha256=WmUnGkDPuSd4dKzTiXWyHWlJ31zBbySvMH9m8p0acg8,6741
|
1728
1730
|
fastlife/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1729
1731
|
fastlife/views/pydantic_form.py,sha256=4dv37JORLpvkgCgMGZfUN_qy7wme040GLZAzOTFqdnU,1367
|
1730
|
-
fastlifeweb-0.
|
1731
|
-
fastlifeweb-0.
|
1732
|
-
fastlifeweb-0.
|
1733
|
-
fastlifeweb-0.
|
1732
|
+
fastlifeweb-0.20.0.dist-info/METADATA,sha256=CRTUb1xqKtSTjd5lhZ-YRY9AAJZHjJW14vMvFJngjmk,3663
|
1733
|
+
fastlifeweb-0.20.0.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
|
1734
|
+
fastlifeweb-0.20.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
1735
|
+
fastlifeweb-0.20.0.dist-info/licenses/LICENSE,sha256=NlRX9Z-dcv8X1VFW9odlIQBbgNN9pcO94XzvKp2R16o,1075
|
1736
|
+
tailwind.config.js,sha256=y_xTuRmjIdrfyo92PoUW0wjFTnkO27xepwfirkaCFno,1351
|
1737
|
+
fastlifeweb-0.20.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024, Guillaume Gauvrit
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
tailwind.config.js
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
2
|
+
module.exports = {
|
3
|
+
darkMode: "class",
|
4
|
+
content: [
|
5
|
+
"./src/fastlife/templates/constants.py",
|
6
|
+
"./src/fastlife/components/*.jinja",
|
7
|
+
"./src/fastlife/components/**/*.jinja",
|
8
|
+
"./tests/fastlife_app/templates/*.jinja",
|
9
|
+
"./tests/fastlife_app/templates/**/*.jinja",
|
10
|
+
],
|
11
|
+
theme: {
|
12
|
+
extend: {
|
13
|
+
colors: {
|
14
|
+
primary: {
|
15
|
+
50: "#f0f9ff",
|
16
|
+
100: "#e0f2fe",
|
17
|
+
200: "#bae6fd",
|
18
|
+
300: "#7dd3fc",
|
19
|
+
400: "#38bdf8",
|
20
|
+
500: "#0ea5e9",
|
21
|
+
600: "#0284c7",
|
22
|
+
700: "#0369a1",
|
23
|
+
800: "#075985",
|
24
|
+
900: "#0c4a6e",
|
25
|
+
950: "#082f49",
|
26
|
+
},
|
27
|
+
danger: {
|
28
|
+
50: "#fef2f2",
|
29
|
+
100: "#fee2e2",
|
30
|
+
200: "#fecaca",
|
31
|
+
300: "#fca5a5",
|
32
|
+
400: "#f87171",
|
33
|
+
500: "#ef4444",
|
34
|
+
600: "#dc2626",
|
35
|
+
700: "#b91c1c",
|
36
|
+
800: "#991b1b",
|
37
|
+
900: "#7f1d1d",
|
38
|
+
950: "#450a0a",
|
39
|
+
},
|
40
|
+
neutral: {
|
41
|
+
50: "#fafaf9",
|
42
|
+
100: "#f5f5f4",
|
43
|
+
200: "#e7e5e4",
|
44
|
+
300: "#d6d3d1",
|
45
|
+
400: "#a8a29e",
|
46
|
+
500: "#78716c",
|
47
|
+
600: "#57534e",
|
48
|
+
700: "#44403c",
|
49
|
+
800: "#292524",
|
50
|
+
900: "#1c1917",
|
51
|
+
950: "#0c0a09",
|
52
|
+
},
|
53
|
+
},
|
54
|
+
},
|
55
|
+
},
|
56
|
+
plugins: [],
|
57
|
+
};
|
@@ -1,28 +0,0 @@
|
|
1
|
-
BSD 3-Clause License
|
2
|
-
|
3
|
-
Copyright (c) 2024, Guillaume Gauvrit
|
4
|
-
|
5
|
-
Redistribution and use in source and binary forms, with or without
|
6
|
-
modification, are permitted provided that the following conditions are met:
|
7
|
-
|
8
|
-
1. Redistributions of source code must retain the above copyright notice, this
|
9
|
-
list of conditions and the following disclaimer.
|
10
|
-
|
11
|
-
2. Redistributions in binary form must reproduce the above copyright notice,
|
12
|
-
this list of conditions and the following disclaimer in the documentation
|
13
|
-
and/or other materials provided with the distribution.
|
14
|
-
|
15
|
-
3. Neither the name of the copyright holder nor the names of its
|
16
|
-
contributors may be used to endorse or promote products derived from
|
17
|
-
this software without specific prior written permission.
|
18
|
-
|
19
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|