fastlifeweb 0.18.0__py3-none-any.whl → 0.19.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 ADDED
@@ -0,0 +1,202 @@
1
+ ## 0.19.0 - Released on 2024-11-07
2
+ * Drop Babel from depenencies for i18n, rely on GNUTranslations only
3
+ * Change License to MIT
4
+ * Replace poetry by uv/pdm
5
+ * Update CI workflows
6
+
7
+ ## 0.18.0 - Released on 2024-10-13
8
+ * Make the sphinx pluging {mod}`fastlife.adapters.jinjax.jinjax_ext.jinjax_doc`
9
+ parts from the API in order to let users build their own component documentation.
10
+
11
+ ## 0.17.0 - Released on 2024-10-08
12
+ * Fix @configure decorator signature for GenericConfigurator
13
+ * Breaking change - rename Configurator.set_open_tag to Configurator.set_openapi_tag
14
+
15
+ ## 0.16.4 - Released on 2024-10-04
16
+ * Add support of x-real-port for port detection, fallback port to 0 instead of None if missing
17
+
18
+ ## 0.16.3 - Released on 2024-10-03
19
+ * Fix middleware that process the x-forwarded-headers to respect ASGI spec for client
20
+
21
+ ## 0.16.2 - Released on 2024-10-03
22
+ * Add a new property all_registered_permissions on the Configurator class
23
+
24
+ ## 0.16.1 - Released on 2024-10-03
25
+ * Fix import in the SecurityPolicy that make it unusable.
26
+
27
+ ## 0.16.0 - Released on 2024-10-02
28
+ * Make the Configurator, Request and Registry Generic.
29
+ * Breaking change, remove settings `api_swagger_ui_url` and `api_redoc_url`
30
+ now to register those url, use
31
+ {meth}`fastlife.config.configurator.GenericConfigurator.set_api_documentation_info`
32
+ * Breaking change, in the method
33
+ {meth}`fastlife.config.configurator.GenericConfigurator.set_api_documentation_info`
34
+ summary is now kwargs only.
35
+
36
+ ## 0.15.1 - Released on 2024-09-29
37
+ * Hotfix components to create tables
38
+
39
+ ## 0.15.0 - Released on 2024-09-29
40
+ * Add an {class}`fastlife.security.policy.AbstractSecurityPolicy` class
41
+ * New method {meth}`fastlife.config.configurator.GenericConfigurator.set_security_policy`
42
+ * Breaking change, the check_permission has been removed from the settings.
43
+ to configure the permission policy, a security policy has to be implemented.
44
+
45
+ ## 0.14.0 - Released on 2024-09-26
46
+ * Implemement method add_template_search_path in the configurator
47
+ * Add a route_prefix in the configurator for configurator.include
48
+
49
+ ## 0.13.0 - Released on 2024-09-25
50
+ * Add a way to handle api
51
+ * Add a @view_config decorator to register route
52
+ * Add a @resource decorator to handle CRUD resource in rest format
53
+ * Add @exception_handler decorator
54
+ * Add i18n support
55
+
56
+ ## 0.12.0 - Released on 2024-09-19
57
+ * Add a way to register API routes and expose api doc
58
+
59
+ ## 0.11.1 - Released on 2024-09-18
60
+ * Update FastAPI version
61
+
62
+ ## 0.11.0 - Released on 2024-09-18
63
+ * Huge documentation update
64
+ * Use sphinx-autodoc2
65
+ * Add documentation for the components.
66
+ * Breaking change in the configurator.
67
+ * get_app has been renamed get_asgi_app
68
+ * a few internals classes moved/renamed.
69
+
70
+ ## 0.10.0 - Released on 2024-08-24
71
+
72
+ * Rename model_result and ModelResult to form_model and FormModel
73
+ * Add an edit method for FormModel
74
+ * Add a Textarea widget and fix Hidden widget
75
+ * Fix rendering of sequence
76
+ * Do not render main form as nested models
77
+ * Add many functional tests for form field generations
78
+
79
+ ## 0.9.7 - Released on 2024-08-21
80
+
81
+ * Add title attribute to icons
82
+
83
+ ## 0.9.6 - Released on 2024-08-18
84
+
85
+ * Add more buttons options for htmx ajax call
86
+ * Fix Option id
87
+
88
+ ## 0.9.5 - Released on 2024-08-17
89
+
90
+ * Use icons to customize collapsible widget for sequence
91
+ * Add parameter for button to avoid send params
92
+
93
+ ## 0.9.4 - Released on 2024-08-16
94
+
95
+ * Don't update browser url while manipulating autoform lists
96
+
97
+ ## 0.9.3 - Released on 2024-08-16
98
+
99
+ * Fix autoform widgets from jinjax migration
100
+
101
+ ## 0.9.2 - Released on 2024-08-13
102
+
103
+ * Add a constants class for global variable in templates
104
+ * Use icons to customize collapsible widget
105
+
106
+ ## 0.9.1 - Released on 2024-08-12
107
+
108
+ * Replace fa icons by hero icons
109
+
110
+ ## 0.9.0 - Released on 2024-08-12
111
+
112
+ * Add fa Icons (extra)
113
+
114
+ ## 0.8.0 - Released on 2024-08-10
115
+
116
+ * Upgrade JinjaX (Template update required, use vue-like syntax now)
117
+
118
+ ## 0.7.3 - Released on 2024-08-10
119
+
120
+ * Add some HTML markup
121
+
122
+ ## 0.7.2 - Released on 2024-08-07
123
+
124
+ * Fix https behind a reverse proxy
125
+
126
+ ## 0.7.1 - Released on 2024-08-04
127
+
128
+ * Add the registry on request for exception handler
129
+
130
+ ## 0.7.0 - Released on 2024-08-04
131
+
132
+ * Rewrite how the registry is handled, now part of the request (request.registry)
133
+ * Update to get hx-confirm and hx-delete on button
134
+
135
+ ## 0.6.1 - Released on 2024-04-27
136
+
137
+ * Display errors on every widget
138
+
139
+ ## 0.6.0 - Released on 2024-04-25
140
+
141
+ * Refactor the pydantic_form to start handling errors in form.
142
+
143
+ ## 0.5.1 - Released on 2024-04-24
144
+
145
+ * Fix minimum dependency version for JinjaX
146
+
147
+ ## 0.5.0 - Released on 2024-04-24
148
+
149
+ * Implement new types for pydantic form: Enum, Set[Literal] and Set[Enum]
150
+
151
+ ## 0.4.1 - Released on 2024-04-20
152
+
153
+ * Add globals to render custom widget with global data
154
+
155
+ ## 0.4.0 - Released on 2024-04-20
156
+
157
+ * Update JinjaX for global template var support
158
+ * Add lots of missing unit tests
159
+ * Add support of more html form element
160
+ * Update deps
161
+
162
+ ## 0.3.1 - Released on 2024-03-29
163
+
164
+ * Update FastAPI
165
+
166
+ ## 0.3.0 - Released on 2024-03-29
167
+
168
+ * Replace jinja2 by JinjaX
169
+
170
+ ## 0.2.3 - Released on 2024-01-29
171
+
172
+ * Add support of relative import in :class:`Configurator.include` method
173
+
174
+ ## 0.2.2 - Released on 2024-01-28
175
+
176
+ * Add another settings for session domain cookie
177
+ * Update test client wrapper and also wrap bs4 tag
178
+ * Fix session cleanup to properly logout
179
+
180
+ ## 0.2.1 - Released on 2024-01-27
181
+
182
+ * Change add_route signature
183
+ * Set the name of the route mandatory and first argument (breaking change)
184
+ * Add a permission argument
185
+ * Add a settings to inject a check_permission handler
186
+
187
+ ## 0.2.0 - Released on 2024-01-24
188
+
189
+ * Add a session wrapper in the test client
190
+ Allows to initialize session data in tests
191
+
192
+ ## 0.1.2 - Released on 2024-01-15
193
+
194
+ * Handle sessions
195
+
196
+ ## 0.1.1 - Released on 2024-01-05
197
+
198
+ * Update fastapi depencency
199
+
200
+ ## 0.1.0 - Released on 2024-01-05
201
+
202
+ * Initial release
fastlife/__init__.py CHANGED
@@ -1,3 +1,7 @@
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 (
@@ -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
- # Jinja template does accept the file extention while rendering the template
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:
@@ -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 permission: a permission to validate by the security policy.
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/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 add a view in the app.
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
- {attr}`fastlife.config.settings.Settings.check_permission` function.
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.
@@ -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
 
@@ -1,12 +1,11 @@
1
1
  """
2
2
  Base class to of the template renderer.
3
3
 
4
- Fastlife comes with {class}`fastlife.templating.renderer.jinjax.JinjaxEngine`,
5
- the rendering engine, it can be overriden from the setting
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
- data = self.render_template(template, **params)
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.abc import Iterator
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
- locale_name = str
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[str, str, pathlib.Path]]:
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, request: "Request", translations: Translations | NullTranslations
36
- ) -> None:
37
- self.locale_name = request.locale_name
38
- self.translations = translations
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.translations.gettext(message)
42
- if mapping is not None:
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.translations.ngettext(singular, plural, n)
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.dgettext(domain, message)
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.dngettext(domain, singular, plural, n)
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 = str(self.translations.pgettext(context, message))
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 = str(self.translations.dpgettext(domain, context, message))
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.dnpgettext(domain, context, singular, plural, n)
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
- for locale_name, domain, file_ in find_mo_files(root_path):
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
- trans: Translations | NullTranslations | None = self._translations.get(
132
- request.locale_name
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)
@@ -3,8 +3,10 @@ Utilities for rendering HTML templates for page and components as FastAPI depend
3
3
  """
4
4
 
5
5
  from .binding import Template, template
6
+ from .inline import InlineTemplate
6
7
 
7
8
  __all__ = [
8
9
  "Template",
9
10
  "template",
11
+ "InlineTemplate",
10
12
  ]
@@ -0,0 +1,19 @@
1
+ """Inline templates."""
2
+
3
+ from typing import ClassVar
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class InlineTemplate(BaseModel):
9
+ """
10
+ Inline templates are used to encourage the location of behavior and the view typing.
11
+
12
+ Pages produce templates that are not reusable and don't need to be reusable
13
+ in there essence, they don't need to be in a component library.
14
+ They use a component lirary to stay small but contains a view logic
15
+ tighly coupled with the view and its code can stay in the same module of that view.
16
+ """
17
+
18
+ template: ClassVar[str]
19
+ """The template string to render."""
@@ -1,37 +1,38 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastlifeweb
3
- Version: 0.18.0
3
+ Version: 0.19.0
4
4
  Summary: High-level web framework
5
- Home-page: https://github.com/mardiros/fastlife
6
- License: BSD-derived
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 :: BSD License
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
- Provides-Extra: doc
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: babel (>=2.16.0,<3.0.0)
23
- Requires-Dist: beautifulsoup4 (>=4.12.2,<5.0.0) ; extra == "testing"
24
- Requires-Dist: fastapi[standard] (>=0.115.0,<0.116.0)
25
- Requires-Dist: itsdangerous (>=2.1.2,<3.0.0)
26
- Requires-Dist: jinjax (>=0.44,<0.45)
27
- Requires-Dist: markupsafe (>=2.1.3,<3.0.0)
28
- Requires-Dist: multidict (>=6.0.5,<7.0.0)
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
@@ -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
- fastlife/__init__.py,sha256=fokakuhI0fdAjHP5w6GWi-YfCx7iTnrVzjSyZ11Cdgg,676
1
+ CHANGELOG.md,sha256=y7v81-bRMbfVQj3MFLZROiFv5PByE4MqdeMYm85gHfg,5823
2
+ fastlife/__init__.py,sha256=a4-GFYXAQ0zLnEIQNIpZUpXtNkuPHIOfzLP5xOn2SRo,755
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=Ii6qhbQEQmpbvJAafRwn88ePuA2ySB_nJ11BloCjltg,6160
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
@@ -1683,13 +1684,13 @@ fastlife/components/pydantic_form/Textarea.jinja,sha256=NzfCi5agRUSVcb5RXw0QamM8
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
1686
  fastlife/config/__init__.py,sha256=ThosRIPZ_fpD0exZu-kUC_f8ZNa5KyDlleWMmEHkjEo,448
1686
- fastlife/config/configurator.py,sha256=Elf6S9syFDnpX6TWCcF7lZhwXI4W573htuaeWzGj8oI,22390
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
1690
  fastlife/config/registry.py,sha256=geny-liWxciPpHjmeFba9zLuXNWbz-R99VtzPS0whg0,1439
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=V-P53GSnvqEPzkvEWNuI4ofcdbFur2Dl-s6BeKXObwI,2086
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=t7u6PRysvhJNgU-rWsANpXQvNrTTlHizxWaV7LJ4wfo,5578
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=XHl6AqyAWNNeSJpaal5EZCptx9dYusTDNQbZwjofAPc,3857
1716
- fastlife/services/translations.py,sha256=Fu93zSc3ajNVFfAqw_G0nBV9bitss9Xy-he9lSHx0V8,4387
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=QrP_5UAOgxqC-jOu5tcjd-l6GOYrS4dka6vmWMxWqfo,184
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=amxtgWqN_NFXIVVkr_LGebRj55T_QwsDJdO42_mZGRI,604
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.18.0.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
1731
- fastlifeweb-0.18.0.dist-info/METADATA,sha256=u09i6bAle2qtATT2ViL5zvi7o3BpV_TN9U4NnLJTcmQ,3556
1732
- fastlifeweb-0.18.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1733
- fastlifeweb-0.18.0.dist-info/RECORD,,
1732
+ fastlifeweb-0.19.0.dist-info/METADATA,sha256=fZYDwl_GvvfyJHuqTPLkWAYnm_uRpt85nEn2TcU73BM,3658
1733
+ fastlifeweb-0.19.0.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
1734
+ fastlifeweb-0.19.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
1735
+ fastlifeweb-0.19.0.dist-info/licenses/LICENSE,sha256=NlRX9Z-dcv8X1VFW9odlIQBbgNN9pcO94XzvKp2R16o,1075
1736
+ tailwind.config.js,sha256=y_xTuRmjIdrfyo92PoUW0wjFTnkO27xepwfirkaCFno,1351
1737
+ fastlifeweb-0.19.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: pdm-backend (2.4.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+
3
+ [gui_scripts]
4
+
@@ -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.