fastlifeweb 0.13.0__py3-none-any.whl → 0.14.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.
@@ -14,11 +14,12 @@ phase.
14
14
  import importlib
15
15
  import inspect
16
16
  import logging
17
- from collections.abc import Mapping
17
+ from collections import defaultdict
18
+ from collections.abc import Mapping, Sequence
18
19
  from enum import Enum
19
20
  from pathlib import Path
20
21
  from types import ModuleType
21
- from typing import TYPE_CHECKING, Annotated, Any, Callable, Self, Tuple, Type, cast
22
+ from typing import TYPE_CHECKING, Annotated, Any, Callable, Self, Tuple, Type
22
23
 
23
24
  import venusian
24
25
  from fastapi import Depends, FastAPI
@@ -48,6 +49,8 @@ if TYPE_CHECKING:
48
49
  log = logging.getLogger(__name__)
49
50
  VENUSIAN_CATEGORY = "fastlife"
50
51
 
52
+ venusian_ignored_item = str | Callable[[str], bool]
53
+
51
54
 
52
55
  class ConfigurationError(Exception):
53
56
  """
@@ -102,18 +105,34 @@ class Configurator:
102
105
  self.api_description: str = ""
103
106
  self.api_summary: str | None = None
104
107
 
105
- self.router = Router()
108
+ self._route_prefix: str = ""
109
+ self._routers: dict[str, Router] = defaultdict(Router)
110
+
106
111
  self.scanner = venusian.Scanner(fastlife=self)
107
112
  self.include("fastlife.views")
108
113
  self.include("fastlife.middlewares")
109
114
 
115
+ @property
116
+ def _current_router(self) -> Router:
117
+ return self._routers[self._route_prefix]
118
+
110
119
  def build_asgi_app(self) -> FastAPI:
111
120
  """
112
121
  Build the app after configuration in order to start after beeing configured.
113
122
 
114
123
  :return: FastAPI application.
115
124
  """
116
- _app = FastAPI(
125
+
126
+ # register our main template renderer at then end, to ensure that
127
+ # if settings have been manipulated, everythins is taken into account.
128
+ self.add_renderer(
129
+ self.registry.settings.jinjax_file_ext,
130
+ resolve("fastlife.adapters.jinjax.renderer:JinjaxTemplateRenderer")(
131
+ self.registry.settings
132
+ ),
133
+ )
134
+
135
+ app = FastAPI(
117
136
  title=self.api_title,
118
137
  version=self.api_version,
119
138
  description=self.api_description,
@@ -125,48 +144,34 @@ class Configurator:
125
144
  if self.tags
126
145
  else None,
127
146
  )
128
- _app.router.route_class = Route
129
- for _route in self.router.routes:
130
- route = cast(Route, _route)
131
- _app.router.add_api_route(
132
- path=route.path,
133
- endpoint=route.endpoint,
134
- response_model=route.response_model,
135
- status_code=route.status_code,
136
- tags=route.tags,
137
- dependencies=route.dependencies,
138
- summary=route.summary,
139
- description=route.description,
140
- response_description=route.response_description,
141
- deprecated=route.deprecated,
142
- methods=route.methods,
143
- operation_id=route.operation_id,
144
- response_model_include=route.response_model_include,
145
- response_model_exclude=route.response_model_exclude,
146
- response_model_by_alias=route.response_model_by_alias,
147
- response_model_exclude_unset=route.response_model_exclude_unset,
148
- response_model_exclude_defaults=route.response_model_exclude_defaults,
149
- response_model_exclude_none=route.response_model_exclude_none,
150
- include_in_schema=route.include_in_schema,
151
- name=route.name,
152
- openapi_extra=route.openapi_extra,
153
- )
147
+ app.router.route_class = Route
148
+ for prefix, router in self._routers.items():
149
+ app.include_router(router, prefix=prefix)
154
150
 
155
151
  for middleware_class, options in self.middlewares:
156
- _app.add_middleware(middleware_class, **options) # type: ignore
152
+ app.add_middleware(middleware_class, **options) # type: ignore
157
153
 
158
154
  for status_code_or_exc, exception_handler in self.exception_handlers:
159
- _app.add_exception_handler(status_code_or_exc, exception_handler)
155
+ app.add_exception_handler(status_code_or_exc, exception_handler)
160
156
 
161
157
  for route_path, directory, name in self.mounts:
162
- _app.mount(route_path, StaticFiles(directory=directory), name=name)
163
- return _app
158
+ app.mount(route_path, StaticFiles(directory=directory), name=name)
159
+ return app
164
160
 
165
- def include(self, module: str | ModuleType) -> Self:
161
+ def include(
162
+ self,
163
+ module: str | ModuleType,
164
+ route_prefix: str = "",
165
+ ignore: venusian_ignored_item | Sequence[venusian_ignored_item] | None = None,
166
+ ) -> Self:
166
167
  """
167
168
  Include a module in order to load its configuration.
168
169
 
169
- It will load and include all the submodule as well.
170
+ It will scan and load all the submodule until you add an ignore rule.
171
+
172
+ The `ignore` argument allows you to ignore certain modules.
173
+ If it is a scrint, it can be an absolute module name or a relative
174
+ one, if starts with a dot.
170
175
 
171
176
  Here is an example.
172
177
 
@@ -182,6 +187,8 @@ class Configurator:
182
187
  ```
183
188
 
184
189
  :param module: a module to include.
190
+ :param route_prefix: prepend all included route with a prefix
191
+ :param ignore: ignore submodules
185
192
  """
186
193
  if isinstance(module, str):
187
194
  package = None
@@ -190,7 +197,15 @@ class Configurator:
190
197
  package = caller_module.__name__ if caller_module else "__main__"
191
198
 
192
199
  module = importlib.import_module(module, package)
193
- self.scanner.scan(module, categories=[VENUSIAN_CATEGORY]) # type: ignore
200
+ old, self._route_prefix = self._route_prefix, route_prefix
201
+ try:
202
+ self.scanner.scan( # type: ignore
203
+ module,
204
+ categories=[VENUSIAN_CATEGORY],
205
+ ignore=ignore,
206
+ )
207
+ finally:
208
+ self._route_prefix = old
194
209
  return self
195
210
 
196
211
  def set_locale_negociator(self, locale_negociator: "LocaleNegociator") -> Self:
@@ -326,7 +341,7 @@ class Configurator:
326
341
  if permission:
327
342
  dependencies.append(Depends(self.registry.check_permission(permission)))
328
343
 
329
- self.router.add_api_route(
344
+ self._current_router.add_api_route(
330
345
  path,
331
346
  endpoint,
332
347
  # response_model=response_model,
@@ -404,7 +419,7 @@ class Configurator:
404
419
 
405
420
  endpoint = render
406
421
 
407
- self.router.add_api_route(
422
+ self._current_router.add_api_route(
408
423
  path,
409
424
  endpoint,
410
425
  status_code=status_code,
@@ -479,6 +494,17 @@ class Configurator:
479
494
  self.registry.renderers[f".{file_ext.lstrip('.')}"] = renderer # type: ignore
480
495
  return self
481
496
 
497
+ def add_template_search_path(self, path: str) -> Self:
498
+ """
499
+ Add a template search path directly from the code.
500
+
501
+ :param path: template path.
502
+ """
503
+ self.registry.settings.template_search_path = (
504
+ f"{self.registry.settings.template_search_path},{path}"
505
+ )
506
+ return self
507
+
482
508
 
483
509
  def configure(
484
510
  wrapped: Callable[[Configurator], None],
@@ -42,11 +42,7 @@ class AppRegistry:
42
42
  self.settings = settings
43
43
  self.check_permission = resolve(settings.check_permission)
44
44
  self.locale_negociator = _default_negociator(self.settings)
45
- self.renderers = {
46
- f".{settings.jinjax_file_ext}": resolve(
47
- "fastlife.adapters.jinjax.renderer:JinjaxTemplateRenderer"
48
- )(settings),
49
- }
45
+ self.renderers = {}
50
46
  self.localizer = LocalizerFactory()
51
47
 
52
48
  def get_renderer(self, template: str) -> "AbstractTemplateRendererFactory":
@@ -114,10 +114,10 @@ class LocalizerFactory:
114
114
  for locale_name, domain, file_ in find_mo_files(root_path):
115
115
  with file_.open("rb") as f:
116
116
  t = Translations(f, domain)
117
- if locale_name in self._translations:
118
- self._translations[locale_name].merge(t)
119
- else:
120
- self._translations[locale_name] = t
117
+ if locale_name not in self._translations:
118
+ self._translations[locale_name] = Translations()
119
+ self._translations[locale_name].add(t)
120
+ self._translations[locale_name].merge(t)
121
121
 
122
122
  def __call__(self, request: "Request") -> Localizer:
123
123
  """Create the translation context for the given request."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastlifeweb
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Summary: High-level web framework
5
5
  Home-page: https://github.com/mardiros/fastlife
6
6
  License: BSD-derived
@@ -1666,9 +1666,9 @@ fastlife/components/pydantic_form/Textarea.jinja,sha256=NzfCi5agRUSVcb5RXw0QamM8
1666
1666
  fastlife/components/pydantic_form/Union.jinja,sha256=czTska54z9KCZKu-FaycLmOvtH6y6CGUFQ8DHnkjrJk,1461
1667
1667
  fastlife/components/pydantic_form/Widget.jinja,sha256=EXskDqt22D5grpGVwlZA3ndve2Wr_6yQH4qVE9c31Og,397
1668
1668
  fastlife/config/__init__.py,sha256=O_Mw2XOxo55SArHdGKRhlTrroRN8ymwfzYKlHG0eV_s,418
1669
- fastlife/config/configurator.py,sha256=gfHCnPzxXJGnZ6rTbBy2lkGSGRZOEyGjJnVWbMCDrdo,18048
1669
+ fastlife/config/configurator.py,sha256=LNLFLyuqhoz6y6ORX6qeU0ZycPBSKu0KVZYcARAge7k,18540
1670
1670
  fastlife/config/exceptions.py,sha256=2MS2MFgb3mDbtdHEwnC-QYubob3Rl0v8O8y615LY0ds,1180
1671
- fastlife/config/registry.py,sha256=pqOnbUPP7Boc42nndvweturuA5X61yhC2bbe8G08T4o,2120
1671
+ fastlife/config/registry.py,sha256=rwCS_vyu1Hli8-4Cs6Z-Jxn7N7ZXWSyZqnXIIV0EJ5I,1958
1672
1672
  fastlife/config/resources.py,sha256=Db183g_CC0Voa6IblaNSzcv7XBH1S3s2nTAFNXtz9Cg,8732
1673
1673
  fastlife/config/settings.py,sha256=ZMjfx_XQhu6mPE5q3eI9dG5disnbGUcALUqNYDCknhk,4199
1674
1674
  fastlife/config/views.py,sha256=Dxi6lO4gFs6GriAW7Rh5GDvebwbrpS2HzYhf30pXJiE,2058
@@ -1693,7 +1693,7 @@ fastlife/security/csrf.py,sha256=PvC9Fqdb6c0IzzsnaMx2quQdjjKrb-nOPoAHfcwoAe8,214
1693
1693
  fastlife/security/policy.py,sha256=MYNPQlvnTBe1XsYNJoIwiGW2DFb8vciU3XyUi9ZlLt0,945
1694
1694
  fastlife/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1695
1695
  fastlife/services/templates.py,sha256=7gPJxGWD-XqputbZpy_Icsz3WHKJaWg2JgkVOeKrjfA,3840
1696
- fastlife/services/translations.py,sha256=R0KXVrm4leUu6p1zJp0QvLUwrQNW5mO88cQ5CZ2mlCE,4317
1696
+ fastlife/services/translations.py,sha256=Bo5CIjdbQ3g_ihbv4Bz60hzd8VOtqEEPOyhJEbGcvP4,4363
1697
1697
  fastlife/shared_utils/__init__.py,sha256=i66ytuf-Ezo7jSiNQHIsBMVIcB-tDX0tg28-pUOlhzE,26
1698
1698
  fastlife/shared_utils/infer.py,sha256=CJjsL_F5OQRG7-0_89MQiZhyd7IcMGyirlQhjtcaIT4,728
1699
1699
  fastlife/shared_utils/resolver.py,sha256=BRU88Ej4oA1iDIyG4Z6T7Q9WFvPHiMm6zuSh623312A,1725
@@ -1704,7 +1704,7 @@ fastlife/testing/__init__.py,sha256=vuqwoNUd3BuIp3fm7nkvmYkIGjIimf5zUGhDkeWrg2s,
1704
1704
  fastlife/testing/testclient.py,sha256=BC7lLQ_jc59UmknAKzgRtW9a3cpX_V_QLp9Mg2ScLA8,20546
1705
1705
  fastlife/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1706
1706
  fastlife/views/pydantic_form.py,sha256=fvjk_5-JAugpBPxwD5GkxxsQiz9eAxzWeCSU9kiyc6s,1438
1707
- fastlifeweb-0.13.0.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
1708
- fastlifeweb-0.13.0.dist-info/METADATA,sha256=yhn48LhvuV19WZhJI4HykIgEjmd85Mu3n3Tv_GqkpQo,3278
1709
- fastlifeweb-0.13.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1710
- fastlifeweb-0.13.0.dist-info/RECORD,,
1707
+ fastlifeweb-0.14.0.dist-info/LICENSE,sha256=F75xSseSKMwqzFj8rswYU6NWS3VoWOc_gY3fJYf9_LI,1504
1708
+ fastlifeweb-0.14.0.dist-info/METADATA,sha256=fvjfxD3v-keA5bZ1agcr4wjeJ3VxhBwwtR0Gs7yHamk,3278
1709
+ fastlifeweb-0.14.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1710
+ fastlifeweb-0.14.0.dist-info/RECORD,,