fastlifeweb 0.16.3__py3-none-any.whl → 0.17.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastlife/adapters/jinjax/renderer.py +49 -25
 - fastlife/adapters/jinjax/widget_factory/__init__.py +1 -0
 - fastlife/adapters/jinjax/widget_factory/base.py +38 -0
 - fastlife/adapters/jinjax/widget_factory/bool_builder.py +43 -0
 - fastlife/adapters/jinjax/widget_factory/emailstr_builder.py +46 -0
 - fastlife/adapters/jinjax/widget_factory/enum_builder.py +47 -0
 - fastlife/adapters/jinjax/widget_factory/factory.py +165 -0
 - fastlife/adapters/jinjax/widget_factory/literal_builder.py +52 -0
 - fastlife/adapters/jinjax/widget_factory/model_builder.py +64 -0
 - fastlife/adapters/jinjax/widget_factory/secretstr_builder.py +47 -0
 - fastlife/adapters/jinjax/widget_factory/sequence_builder.py +58 -0
 - fastlife/adapters/jinjax/widget_factory/set_builder.py +80 -0
 - fastlife/adapters/jinjax/widget_factory/simpletype_builder.py +47 -0
 - fastlife/adapters/jinjax/widget_factory/union_builder.py +90 -0
 - fastlife/adapters/jinjax/widgets/base.py +6 -4
 - fastlife/adapters/jinjax/widgets/checklist.py +1 -1
 - fastlife/adapters/jinjax/widgets/dropdown.py +7 -7
 - fastlife/adapters/jinjax/widgets/hidden.py +2 -0
 - fastlife/adapters/jinjax/widgets/model.py +4 -1
 - fastlife/adapters/jinjax/widgets/sequence.py +3 -2
 - fastlife/adapters/jinjax/widgets/text.py +9 -10
 - fastlife/adapters/jinjax/widgets/union.py +9 -7
 - fastlife/components/Form.jinja +12 -0
 - fastlife/config/configurator.py +23 -24
 - fastlife/config/exceptions.py +4 -1
 - fastlife/config/openapiextra.py +1 -0
 - fastlife/config/resources.py +26 -27
 - fastlife/config/settings.py +2 -0
 - fastlife/config/views.py +3 -1
 - fastlife/middlewares/reverse_proxy/x_forwarded.py +22 -15
 - fastlife/middlewares/session/middleware.py +2 -2
 - fastlife/middlewares/session/serializer.py +6 -5
 - fastlife/request/form.py +7 -6
 - fastlife/request/form_data.py +2 -6
 - fastlife/routing/route.py +3 -1
 - fastlife/routing/router.py +1 -0
 - fastlife/security/csrf.py +2 -1
 - fastlife/security/policy.py +2 -1
 - fastlife/services/locale_negociator.py +2 -1
 - fastlife/services/policy.py +3 -2
 - fastlife/services/templates.py +2 -1
 - fastlife/services/translations.py +15 -8
 - fastlife/shared_utils/infer.py +4 -3
 - fastlife/shared_utils/resolver.py +64 -4
 - fastlife/templates/binding.py +2 -1
 - fastlife/testing/__init__.py +1 -0
 - fastlife/testing/dom.py +140 -0
 - fastlife/testing/form.py +204 -0
 - fastlife/testing/session.py +67 -0
 - fastlife/testing/testclient.py +7 -390
 - fastlife/views/pydantic_form.py +4 -4
 - {fastlifeweb-0.16.3.dist-info → fastlifeweb-0.17.0.dist-info}/METADATA +6 -6
 - {fastlifeweb-0.16.3.dist-info → fastlifeweb-0.17.0.dist-info}/RECORD +55 -40
 - fastlife/adapters/jinjax/widgets/factory.py +0 -525
 - {fastlifeweb-0.16.3.dist-info → fastlifeweb-0.17.0.dist-info}/LICENSE +0 -0
 - {fastlifeweb-0.16.3.dist-info → fastlifeweb-0.17.0.dist-info}/WHEEL +0 -0
 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """Handle Sequence type."""
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            from collections.abc import Mapping, MutableSequence, Sequence
         
     | 
| 
      
 4 
     | 
    
         
            +
            from typing import Any
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            from pydantic.fields import FieldInfo
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widget_factory.base import BaseWidgetBuilder
         
     | 
| 
      
 9 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widgets.base import Widget
         
     | 
| 
      
 10 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widgets.sequence import SequenceWidget
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            class SequenceBuilder(BaseWidgetBuilder[Sequence[Any]]):
         
     | 
| 
      
 14 
     | 
    
         
            +
                """Builder for Sequence values."""
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def accept(self, typ: type[Any], origin: type[Any] | None) -> bool:
         
     | 
| 
      
 17 
     | 
    
         
            +
                    """True for Sequence, MutableSequence or list"""
         
     | 
| 
      
 18 
     | 
    
         
            +
                    return origin is Sequence or origin is MutableSequence or origin is list
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def build(
         
     | 
| 
      
 21 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 22 
     | 
    
         
            +
                    *,
         
     | 
| 
      
 23 
     | 
    
         
            +
                    field_name: str,
         
     | 
| 
      
 24 
     | 
    
         
            +
                    field_type: type[Any],
         
     | 
| 
      
 25 
     | 
    
         
            +
                    field: FieldInfo | None,
         
     | 
| 
      
 26 
     | 
    
         
            +
                    value: Sequence[Any] | None,
         
     | 
| 
      
 27 
     | 
    
         
            +
                    form_errors: Mapping[str, Any],
         
     | 
| 
      
 28 
     | 
    
         
            +
                    removable: bool,
         
     | 
| 
      
 29 
     | 
    
         
            +
                ) -> Widget[Sequence[Any]]:
         
     | 
| 
      
 30 
     | 
    
         
            +
                    """Build the widget."""
         
     | 
| 
      
 31 
     | 
    
         
            +
                    typ = field_type.__args__[0]  # type: ignore
         
     | 
| 
      
 32 
     | 
    
         
            +
                    value = value or []
         
     | 
| 
      
 33 
     | 
    
         
            +
                    items: Sequence[Any] = [
         
     | 
| 
      
 34 
     | 
    
         
            +
                        self.factory.build(
         
     | 
| 
      
 35 
     | 
    
         
            +
                            typ,  # type: ignore
         
     | 
| 
      
 36 
     | 
    
         
            +
                            name=f"{field_name}.{idx}",
         
     | 
| 
      
 37 
     | 
    
         
            +
                            value=v,
         
     | 
| 
      
 38 
     | 
    
         
            +
                            field=field,
         
     | 
| 
      
 39 
     | 
    
         
            +
                            form_errors=form_errors,
         
     | 
| 
      
 40 
     | 
    
         
            +
                            removable=True,
         
     | 
| 
      
 41 
     | 
    
         
            +
                        )
         
     | 
| 
      
 42 
     | 
    
         
            +
                        for idx, v in enumerate(value)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 44 
     | 
    
         
            +
                    return SequenceWidget(
         
     | 
| 
      
 45 
     | 
    
         
            +
                        field_name,
         
     | 
| 
      
 46 
     | 
    
         
            +
                        title=field.title if field else "",
         
     | 
| 
      
 47 
     | 
    
         
            +
                        hint=field.description if field else None,
         
     | 
| 
      
 48 
     | 
    
         
            +
                        aria_label=(
         
     | 
| 
      
 49 
     | 
    
         
            +
                            field.json_schema_extra.get("aria_label")  # type:ignore
         
     | 
| 
      
 50 
     | 
    
         
            +
                            if field and field.json_schema_extra
         
     | 
| 
      
 51 
     | 
    
         
            +
                            else None
         
     | 
| 
      
 52 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 53 
     | 
    
         
            +
                        value=items,
         
     | 
| 
      
 54 
     | 
    
         
            +
                        item_type=typ,  # type: ignore
         
     | 
| 
      
 55 
     | 
    
         
            +
                        token=self.factory.token,
         
     | 
| 
      
 56 
     | 
    
         
            +
                        removable=removable,
         
     | 
| 
      
 57 
     | 
    
         
            +
                        error=form_errors.get(field_name),
         
     | 
| 
      
 58 
     | 
    
         
            +
                    )
         
     | 
| 
         @@ -0,0 +1,80 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """Handle Set type."""
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            from collections.abc import Mapping
         
     | 
| 
      
 4 
     | 
    
         
            +
            from enum import Enum
         
     | 
| 
      
 5 
     | 
    
         
            +
            from typing import Any, Literal, get_origin
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            from pydantic.fields import FieldInfo
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widget_factory.base import BaseWidgetBuilder
         
     | 
| 
      
 10 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widgets.base import Widget
         
     | 
| 
      
 11 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widgets.checklist import Checkable, ChecklistWidget
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            class SetBuilder(BaseWidgetBuilder[set[Any]]):
         
     | 
| 
      
 15 
     | 
    
         
            +
                """Builder for Set."""
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def accept(self, typ: type[Any], origin: type[Any] | None) -> bool:
         
     | 
| 
      
 18 
     | 
    
         
            +
                    """True for Set"""
         
     | 
| 
      
 19 
     | 
    
         
            +
                    return origin is set
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def build(
         
     | 
| 
      
 22 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 23 
     | 
    
         
            +
                    *,
         
     | 
| 
      
 24 
     | 
    
         
            +
                    field_name: str,
         
     | 
| 
      
 25 
     | 
    
         
            +
                    field_type: type[Any],
         
     | 
| 
      
 26 
     | 
    
         
            +
                    field: FieldInfo | None,
         
     | 
| 
      
 27 
     | 
    
         
            +
                    value: set[Any] | None,
         
     | 
| 
      
 28 
     | 
    
         
            +
                    form_errors: Mapping[str, Any],
         
     | 
| 
      
 29 
     | 
    
         
            +
                    removable: bool,
         
     | 
| 
      
 30 
     | 
    
         
            +
                ) -> Widget[Any]:
         
     | 
| 
      
 31 
     | 
    
         
            +
                    """Build the widget."""
         
     | 
| 
      
 32 
     | 
    
         
            +
                    choice_wrapper = field_type.__args__[0]
         
     | 
| 
      
 33 
     | 
    
         
            +
                    choices = []
         
     | 
| 
      
 34 
     | 
    
         
            +
                    choice_wrapper_origin = get_origin(choice_wrapper)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    if choice_wrapper_origin:
         
     | 
| 
      
 36 
     | 
    
         
            +
                        if choice_wrapper_origin is Literal:
         
     | 
| 
      
 37 
     | 
    
         
            +
                            litchoice: list[str] = choice_wrapper.__args__  # type: ignore
         
     | 
| 
      
 38 
     | 
    
         
            +
                            choices = [
         
     | 
| 
      
 39 
     | 
    
         
            +
                                Checkable(
         
     | 
| 
      
 40 
     | 
    
         
            +
                                    label=c,
         
     | 
| 
      
 41 
     | 
    
         
            +
                                    value=c,
         
     | 
| 
      
 42 
     | 
    
         
            +
                                    checked=c in value if value else False,  # type: ignore
         
     | 
| 
      
 43 
     | 
    
         
            +
                                    name=field_name,
         
     | 
| 
      
 44 
     | 
    
         
            +
                                    token=self.factory.token,
         
     | 
| 
      
 45 
     | 
    
         
            +
                                    error=form_errors.get(f"{field_name}-{c}"),
         
     | 
| 
      
 46 
     | 
    
         
            +
                                )
         
     | 
| 
      
 47 
     | 
    
         
            +
                                for c in litchoice
         
     | 
| 
      
 48 
     | 
    
         
            +
                            ]
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 51 
     | 
    
         
            +
                            raise NotImplementedError  # coverage: ignore
         
     | 
| 
      
 52 
     | 
    
         
            +
                    elif issubclass(choice_wrapper, Enum):
         
     | 
| 
      
 53 
     | 
    
         
            +
                        choices = [
         
     | 
| 
      
 54 
     | 
    
         
            +
                            Checkable(
         
     | 
| 
      
 55 
     | 
    
         
            +
                                label=e.value,
         
     | 
| 
      
 56 
     | 
    
         
            +
                                value=e.name,
         
     | 
| 
      
 57 
     | 
    
         
            +
                                checked=e.name in value if value else False,  # type: ignore
         
     | 
| 
      
 58 
     | 
    
         
            +
                                name=field_name,
         
     | 
| 
      
 59 
     | 
    
         
            +
                                token=self.factory.token,
         
     | 
| 
      
 60 
     | 
    
         
            +
                                error=form_errors.get(f"{field_name}-{e.name}"),
         
     | 
| 
      
 61 
     | 
    
         
            +
                            )
         
     | 
| 
      
 62 
     | 
    
         
            +
                            for e in choice_wrapper
         
     | 
| 
      
 63 
     | 
    
         
            +
                        ]
         
     | 
| 
      
 64 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 65 
     | 
    
         
            +
                        raise NotImplementedError  # coverage: ignore
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                    return ChecklistWidget(
         
     | 
| 
      
 68 
     | 
    
         
            +
                        field_name,
         
     | 
| 
      
 69 
     | 
    
         
            +
                        title=field.title if field else "",
         
     | 
| 
      
 70 
     | 
    
         
            +
                        hint=field.description if field else None,
         
     | 
| 
      
 71 
     | 
    
         
            +
                        aria_label=(
         
     | 
| 
      
 72 
     | 
    
         
            +
                            field.json_schema_extra.get("aria_label")  # type:ignore
         
     | 
| 
      
 73 
     | 
    
         
            +
                            if field and field.json_schema_extra
         
     | 
| 
      
 74 
     | 
    
         
            +
                            else None
         
     | 
| 
      
 75 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 76 
     | 
    
         
            +
                        token=self.factory.token,
         
     | 
| 
      
 77 
     | 
    
         
            +
                        value=choices,
         
     | 
| 
      
 78 
     | 
    
         
            +
                        removable=removable,
         
     | 
| 
      
 79 
     | 
    
         
            +
                        error=form_errors.get(field_name),
         
     | 
| 
      
 80 
     | 
    
         
            +
                    )
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """Handle simple types (str, int, float, ...)."""
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            from collections.abc import Mapping
         
     | 
| 
      
 4 
     | 
    
         
            +
            from decimal import Decimal
         
     | 
| 
      
 5 
     | 
    
         
            +
            from typing import Any
         
     | 
| 
      
 6 
     | 
    
         
            +
            from uuid import UUID
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            from pydantic.fields import FieldInfo
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widget_factory.base import BaseWidgetBuilder
         
     | 
| 
      
 11 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widgets.base import Widget
         
     | 
| 
      
 12 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widgets.text import TextWidget
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            class SimpleTypeBuilder(BaseWidgetBuilder[str | int | str | float | Decimal | UUID]):
         
     | 
| 
      
 16 
     | 
    
         
            +
                """Builder for simple types."""
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def accept(self, typ: type[Any], origin: type[Any] | None) -> bool:
         
     | 
| 
      
 19 
     | 
    
         
            +
                    """True for simple types: int, str, float, Decimal, UUID"""
         
     | 
| 
      
 20 
     | 
    
         
            +
                    return issubclass(typ, int | str | float | Decimal | UUID)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def build(
         
     | 
| 
      
 23 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 24 
     | 
    
         
            +
                    *,
         
     | 
| 
      
 25 
     | 
    
         
            +
                    field_name: str,
         
     | 
| 
      
 26 
     | 
    
         
            +
                    field_type: type[Any],
         
     | 
| 
      
 27 
     | 
    
         
            +
                    field: FieldInfo | None,
         
     | 
| 
      
 28 
     | 
    
         
            +
                    value: int | str | float | Decimal | UUID | None,
         
     | 
| 
      
 29 
     | 
    
         
            +
                    form_errors: Mapping[str, Any],
         
     | 
| 
      
 30 
     | 
    
         
            +
                    removable: bool,
         
     | 
| 
      
 31 
     | 
    
         
            +
                ) -> Widget[int | str | float | Decimal | UUID]:
         
     | 
| 
      
 32 
     | 
    
         
            +
                    """Build the widget."""
         
     | 
| 
      
 33 
     | 
    
         
            +
                    return TextWidget(
         
     | 
| 
      
 34 
     | 
    
         
            +
                        field_name,
         
     | 
| 
      
 35 
     | 
    
         
            +
                        placeholder=str(field.examples[0]) if field and field.examples else None,
         
     | 
| 
      
 36 
     | 
    
         
            +
                        title=field.title if field else "",
         
     | 
| 
      
 37 
     | 
    
         
            +
                        hint=field.description if field else None,
         
     | 
| 
      
 38 
     | 
    
         
            +
                        aria_label=(
         
     | 
| 
      
 39 
     | 
    
         
            +
                            field.json_schema_extra.get("aria_label")  # type:ignore
         
     | 
| 
      
 40 
     | 
    
         
            +
                            if field and field.json_schema_extra
         
     | 
| 
      
 41 
     | 
    
         
            +
                            else None
         
     | 
| 
      
 42 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 43 
     | 
    
         
            +
                        removable=removable,
         
     | 
| 
      
 44 
     | 
    
         
            +
                        token=self.factory.token,
         
     | 
| 
      
 45 
     | 
    
         
            +
                        value=str(value) if value else "",
         
     | 
| 
      
 46 
     | 
    
         
            +
                        error=form_errors.get(field_name),
         
     | 
| 
      
 47 
     | 
    
         
            +
                    )
         
     | 
| 
         @@ -0,0 +1,90 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """Handle Union type."""
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            from collections.abc import Mapping
         
     | 
| 
      
 4 
     | 
    
         
            +
            from types import NoneType
         
     | 
| 
      
 5 
     | 
    
         
            +
            from typing import Any
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            from pydantic import ValidationError
         
     | 
| 
      
 8 
     | 
    
         
            +
            from pydantic.fields import FieldInfo
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widget_factory.base import BaseWidgetBuilder
         
     | 
| 
      
 11 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widgets.base import Widget
         
     | 
| 
      
 12 
     | 
    
         
            +
            from fastlife.adapters.jinjax.widgets.union import UnionWidget
         
     | 
| 
      
 13 
     | 
    
         
            +
            from fastlife.shared_utils.infer import is_complex_type, is_union
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            class UnionBuilder(BaseWidgetBuilder[Any]):
         
     | 
| 
      
 17 
     | 
    
         
            +
                """Builder for Union."""
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def accept(self, typ: type[Any], origin: type[Any] | None) -> bool:
         
     | 
| 
      
 20 
     | 
    
         
            +
                    """True for unions Union[A,B], A | B or event Optional[A], A | None"""
         
     | 
| 
      
 21 
     | 
    
         
            +
                    return is_union(typ)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def build(
         
     | 
| 
      
 24 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 25 
     | 
    
         
            +
                    *,
         
     | 
| 
      
 26 
     | 
    
         
            +
                    field_name: str,
         
     | 
| 
      
 27 
     | 
    
         
            +
                    field_type: type[Any],
         
     | 
| 
      
 28 
     | 
    
         
            +
                    field: FieldInfo | None,
         
     | 
| 
      
 29 
     | 
    
         
            +
                    value: Any | None,
         
     | 
| 
      
 30 
     | 
    
         
            +
                    form_errors: Mapping[str, Any],
         
     | 
| 
      
 31 
     | 
    
         
            +
                    removable: bool,
         
     | 
| 
      
 32 
     | 
    
         
            +
                ) -> Widget[Any]:
         
     | 
| 
      
 33 
     | 
    
         
            +
                    """Build the widget."""
         
     | 
| 
      
 34 
     | 
    
         
            +
                    types: list[type[Any]] = []
         
     | 
| 
      
 35 
     | 
    
         
            +
                    # required = True
         
     | 
| 
      
 36 
     | 
    
         
            +
                    for typ in field_type.__args__:  # type: ignore
         
     | 
| 
      
 37 
     | 
    
         
            +
                        if typ is NoneType:
         
     | 
| 
      
 38 
     | 
    
         
            +
                            # required = False
         
     | 
| 
      
 39 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 40 
     | 
    
         
            +
                        types.append(typ)  # type: ignore
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    if (
         
     | 
| 
      
 43 
     | 
    
         
            +
                        not removable
         
     | 
| 
      
 44 
     | 
    
         
            +
                        and len(types) == 1
         
     | 
| 
      
 45 
     | 
    
         
            +
                        # if the optional type is a complex type,
         
     | 
| 
      
 46 
     | 
    
         
            +
                        and not is_complex_type(types[0])
         
     | 
| 
      
 47 
     | 
    
         
            +
                    ):
         
     | 
| 
      
 48 
     | 
    
         
            +
                        return self.factory.build(  # coverage: ignore
         
     | 
| 
      
 49 
     | 
    
         
            +
                            types[0],
         
     | 
| 
      
 50 
     | 
    
         
            +
                            name=field_name,
         
     | 
| 
      
 51 
     | 
    
         
            +
                            field=field,
         
     | 
| 
      
 52 
     | 
    
         
            +
                            value=value,
         
     | 
| 
      
 53 
     | 
    
         
            +
                            form_errors=form_errors,
         
     | 
| 
      
 54 
     | 
    
         
            +
                            removable=False,
         
     | 
| 
      
 55 
     | 
    
         
            +
                        )
         
     | 
| 
      
 56 
     | 
    
         
            +
                    child = None
         
     | 
| 
      
 57 
     | 
    
         
            +
                    if value:
         
     | 
| 
      
 58 
     | 
    
         
            +
                        for typ in types:
         
     | 
| 
      
 59 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 60 
     | 
    
         
            +
                                typ(**value)
         
     | 
| 
      
 61 
     | 
    
         
            +
                            except ValidationError:
         
     | 
| 
      
 62 
     | 
    
         
            +
                                pass
         
     | 
| 
      
 63 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 64 
     | 
    
         
            +
                                child = self.factory.build(
         
     | 
| 
      
 65 
     | 
    
         
            +
                                    typ,
         
     | 
| 
      
 66 
     | 
    
         
            +
                                    name=field_name,
         
     | 
| 
      
 67 
     | 
    
         
            +
                                    field=field,
         
     | 
| 
      
 68 
     | 
    
         
            +
                                    value=value,
         
     | 
| 
      
 69 
     | 
    
         
            +
                                    form_errors=form_errors,
         
     | 
| 
      
 70 
     | 
    
         
            +
                                    removable=False,
         
     | 
| 
      
 71 
     | 
    
         
            +
                                )
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                    widget = UnionWidget(
         
     | 
| 
      
 74 
     | 
    
         
            +
                        field_name,
         
     | 
| 
      
 75 
     | 
    
         
            +
                        # we assume those types are BaseModel
         
     | 
| 
      
 76 
     | 
    
         
            +
                        value=child,
         
     | 
| 
      
 77 
     | 
    
         
            +
                        children_types=types,  # type: ignore
         
     | 
| 
      
 78 
     | 
    
         
            +
                        title=field.title if field else "",
         
     | 
| 
      
 79 
     | 
    
         
            +
                        hint=field.description if field else None,
         
     | 
| 
      
 80 
     | 
    
         
            +
                        aria_label=(
         
     | 
| 
      
 81 
     | 
    
         
            +
                            field.json_schema_extra.get("aria_label")  # type:ignore
         
     | 
| 
      
 82 
     | 
    
         
            +
                            if field and field.json_schema_extra
         
     | 
| 
      
 83 
     | 
    
         
            +
                            else None
         
     | 
| 
      
 84 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 85 
     | 
    
         
            +
                        token=self.factory.token,
         
     | 
| 
      
 86 
     | 
    
         
            +
                        removable=removable,
         
     | 
| 
      
 87 
     | 
    
         
            +
                        error=form_errors.get(field_name),
         
     | 
| 
      
 88 
     | 
    
         
            +
                    )
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    return widget
         
     | 
| 
         @@ -1,7 +1,9 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            """Widget base class."""
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       2 
3 
     | 
    
         
             
            import abc
         
     | 
| 
       3 
4 
     | 
    
         
             
            import secrets
         
     | 
| 
       4 
     | 
    
         
            -
            from  
     | 
| 
      
 5 
     | 
    
         
            +
            from collections.abc import Mapping
         
     | 
| 
      
 6 
     | 
    
         
            +
            from typing import Any, Generic, TypeVar
         
     | 
| 
       5 
7 
     | 
    
         | 
| 
       6 
8 
     | 
    
         
             
            from markupsafe import Markup
         
     | 
| 
       7 
9 
     | 
    
         | 
| 
         @@ -11,7 +13,7 @@ from fastlife.shared_utils.infer import is_union 
     | 
|
| 
       11 
13 
     | 
    
         
             
            T = TypeVar("T")
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
15 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
            def get_title(typ:  
     | 
| 
      
 16 
     | 
    
         
            +
            def get_title(typ: type[Any]) -> str:
         
     | 
| 
       15 
17 
     | 
    
         
             
                return getattr(
         
     | 
| 
       16 
18 
     | 
    
         
             
                    getattr(typ, "__meta__", None),
         
     | 
| 
       17 
19 
     | 
    
         
             
                    "title",
         
     | 
| 
         @@ -81,7 +83,7 @@ class Widget(abc.ABC, Generic[T]): 
     | 
|
| 
       81 
83 
     | 
    
         
             
                    return Markup(renderer.render_template(self.get_template(), widget=self))
         
     | 
| 
       82 
84 
     | 
    
         | 
| 
       83 
85 
     | 
    
         | 
| 
       84 
     | 
    
         
            -
            def _get_fullname(typ:  
     | 
| 
      
 86 
     | 
    
         
            +
            def _get_fullname(typ: type[Any]) -> str:
         
     | 
| 
       85 
87 
     | 
    
         
             
                if is_union(typ):
         
     | 
| 
       86 
88 
     | 
    
         
             
                    typs = [_get_fullname(t) for t in typ.__args__]  # type: ignore
         
     | 
| 
       87 
89 
     | 
    
         
             
                    return "|".join(typs)  # type: ignore
         
     | 
| 
         @@ -102,7 +104,7 @@ class TypeWrapper: 
     | 
|
| 
       102 
104 
     | 
    
         | 
| 
       103 
105 
     | 
    
         
             
                def __init__(
         
     | 
| 
       104 
106 
     | 
    
         
             
                    self,
         
     | 
| 
       105 
     | 
    
         
            -
                    typ:  
     | 
| 
      
 107 
     | 
    
         
            +
                    typ: type[Any],
         
     | 
| 
       106 
108 
     | 
    
         
             
                    route_prefix: str,
         
     | 
| 
       107 
109 
     | 
    
         
             
                    name: str,
         
     | 
| 
       108 
110 
     | 
    
         
             
                    token: str,
         
     | 
| 
         @@ -2,7 +2,7 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            Widget for field of type Enum or Literal.
         
     | 
| 
       3 
3 
     | 
    
         
             
            """
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
            from  
     | 
| 
      
 5 
     | 
    
         
            +
            from collections.abc import Sequence
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            from .base import Widget
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
         @@ -26,14 +26,14 @@ class DropDownWidget(Widget[str]): 
     | 
|
| 
       26 
26 
     | 
    
         
             
                    self,
         
     | 
| 
       27 
27 
     | 
    
         
             
                    name: str,
         
     | 
| 
       28 
28 
     | 
    
         
             
                    *,
         
     | 
| 
       29 
     | 
    
         
            -
                    title:  
     | 
| 
       30 
     | 
    
         
            -
                    hint:  
     | 
| 
       31 
     | 
    
         
            -
                    aria_label:  
     | 
| 
       32 
     | 
    
         
            -
                    value:  
     | 
| 
      
 29 
     | 
    
         
            +
                    title: str | None,
         
     | 
| 
      
 30 
     | 
    
         
            +
                    hint: str | None = None,
         
     | 
| 
      
 31 
     | 
    
         
            +
                    aria_label: str | None = None,
         
     | 
| 
      
 32 
     | 
    
         
            +
                    value: str | None = None,
         
     | 
| 
       33 
33 
     | 
    
         
             
                    error: str | None = None,
         
     | 
| 
       34 
     | 
    
         
            -
                    options: Sequence[ 
     | 
| 
      
 34 
     | 
    
         
            +
                    options: Sequence[tuple[str, str]] | Sequence[str],
         
     | 
| 
       35 
35 
     | 
    
         
             
                    removable: bool = False,
         
     | 
| 
       36 
     | 
    
         
            -
                    token:  
     | 
| 
      
 36 
     | 
    
         
            +
                    token: str | None = None,
         
     | 
| 
       37 
37 
     | 
    
         
             
                ) -> None:
         
     | 
| 
       38 
38 
     | 
    
         
             
                    super().__init__(
         
     | 
| 
       39 
39 
     | 
    
         
             
                        name,
         
     | 
| 
         @@ -1,4 +1,5 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            from  
     | 
| 
      
 1 
     | 
    
         
            +
            from collections.abc import Sequence
         
     | 
| 
      
 2 
     | 
    
         
            +
            from typing import Any
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
       3 
4 
     | 
    
         
             
            from markupsafe import Markup
         
     | 
| 
       4 
5 
     | 
    
         | 
| 
         @@ -17,7 +18,7 @@ class SequenceWidget(Widget[Sequence[Widget[Any]]]): 
     | 
|
| 
       17 
18 
     | 
    
         
             
                    aria_label: str | None = None,
         
     | 
| 
       18 
19 
     | 
    
         
             
                    value: Sequence[Widget[Any]] | None,
         
     | 
| 
       19 
20 
     | 
    
         
             
                    error: str | None = None,
         
     | 
| 
       20 
     | 
    
         
            -
                    item_type:  
     | 
| 
      
 21 
     | 
    
         
            +
                    item_type: type[Any],
         
     | 
| 
       21 
22 
     | 
    
         
             
                    token: str,
         
     | 
| 
       22 
23 
     | 
    
         
             
                    removable: bool,
         
     | 
| 
       23 
24 
     | 
    
         
             
                ):
         
     | 
| 
         @@ -1,5 +1,4 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            from collections.abc import Sequence
         
     | 
| 
       2 
     | 
    
         
            -
            from typing import Optional
         
     | 
| 
       3 
2 
     | 
    
         | 
| 
       4 
3 
     | 
    
         
             
            from .base import Widget
         
     | 
| 
       5 
4 
     | 
    
         | 
| 
         @@ -23,10 +22,10 @@ class TextWidget(Widget[str]): 
     | 
|
| 
       23 
22 
     | 
    
         
             
                    self,
         
     | 
| 
       24 
23 
     | 
    
         
             
                    name: str,
         
     | 
| 
       25 
24 
     | 
    
         
             
                    *,
         
     | 
| 
       26 
     | 
    
         
            -
                    title:  
     | 
| 
       27 
     | 
    
         
            -
                    hint:  
     | 
| 
       28 
     | 
    
         
            -
                    aria_label:  
     | 
| 
       29 
     | 
    
         
            -
                    placeholder:  
     | 
| 
      
 25 
     | 
    
         
            +
                    title: str | None,
         
     | 
| 
      
 26 
     | 
    
         
            +
                    hint: str | None = None,
         
     | 
| 
      
 27 
     | 
    
         
            +
                    aria_label: str | None = None,
         
     | 
| 
      
 28 
     | 
    
         
            +
                    placeholder: str | None = None,
         
     | 
| 
       30 
29 
     | 
    
         
             
                    error: str | None = None,
         
     | 
| 
       31 
30 
     | 
    
         
             
                    value: str = "",
         
     | 
| 
       32 
31 
     | 
    
         
             
                    input_type: str = "text",
         
     | 
| 
         @@ -87,12 +86,12 @@ class TextareaWidget(Widget[Sequence[str]]): 
     | 
|
| 
       87 
86 
     | 
    
         
             
                    self,
         
     | 
| 
       88 
87 
     | 
    
         
             
                    name: str,
         
     | 
| 
       89 
88 
     | 
    
         
             
                    *,
         
     | 
| 
       90 
     | 
    
         
            -
                    title:  
     | 
| 
       91 
     | 
    
         
            -
                    hint:  
     | 
| 
       92 
     | 
    
         
            -
                    aria_label:  
     | 
| 
       93 
     | 
    
         
            -
                    placeholder:  
     | 
| 
      
 89 
     | 
    
         
            +
                    title: str | None,
         
     | 
| 
      
 90 
     | 
    
         
            +
                    hint: str | None = None,
         
     | 
| 
      
 91 
     | 
    
         
            +
                    aria_label: str | None = None,
         
     | 
| 
      
 92 
     | 
    
         
            +
                    placeholder: str | None = None,
         
     | 
| 
       94 
93 
     | 
    
         
             
                    error: str | None = None,
         
     | 
| 
       95 
     | 
    
         
            -
                    value:  
     | 
| 
      
 94 
     | 
    
         
            +
                    value: Sequence[str] | None = None,
         
     | 
| 
       96 
95 
     | 
    
         
             
                    removable: bool = False,
         
     | 
| 
       97 
96 
     | 
    
         
             
                    token: str,
         
     | 
| 
       98 
97 
     | 
    
         
             
                ) -> None:
         
     | 
| 
         @@ -1,7 +1,9 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            """
         
     | 
| 
       2 
2 
     | 
    
         
             
            Widget for field of type Union.
         
     | 
| 
       3 
3 
     | 
    
         
             
            """
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            from collections.abc import Sequence
         
     | 
| 
      
 6 
     | 
    
         
            +
            from typing import Any, Union
         
     | 
| 
       5 
7 
     | 
    
         | 
| 
       6 
8 
     | 
    
         
             
            from markupsafe import Markup
         
     | 
| 
       7 
9 
     | 
    
         
             
            from pydantic import BaseModel
         
     | 
| 
         @@ -31,12 +33,12 @@ class UnionWidget(Widget[Widget[Any]]): 
     | 
|
| 
       31 
33 
     | 
    
         
             
                    self,
         
     | 
| 
       32 
34 
     | 
    
         
             
                    name: str,
         
     | 
| 
       33 
35 
     | 
    
         
             
                    *,
         
     | 
| 
       34 
     | 
    
         
            -
                    title:  
     | 
| 
       35 
     | 
    
         
            -
                    hint:  
     | 
| 
       36 
     | 
    
         
            -
                    aria_label:  
     | 
| 
       37 
     | 
    
         
            -
                    value:  
     | 
| 
      
 36 
     | 
    
         
            +
                    title: str | None,
         
     | 
| 
      
 37 
     | 
    
         
            +
                    hint: str | None = None,
         
     | 
| 
      
 38 
     | 
    
         
            +
                    aria_label: str | None = None,
         
     | 
| 
      
 39 
     | 
    
         
            +
                    value: Widget[Any] | None,
         
     | 
| 
       38 
40 
     | 
    
         
             
                    error: str | None = None,
         
     | 
| 
       39 
     | 
    
         
            -
                    children_types: Sequence[ 
     | 
| 
      
 41 
     | 
    
         
            +
                    children_types: Sequence[type[BaseModel]],
         
     | 
| 
       40 
42 
     | 
    
         
             
                    removable: bool = False,
         
     | 
| 
       41 
43 
     | 
    
         
             
                    token: str,
         
     | 
| 
       42 
44 
     | 
    
         
             
                ):
         
     | 
| 
         @@ -72,7 +74,7 @@ class UnionWidget(Widget[Widget[Any]]): 
     | 
|
| 
       72 
74 
     | 
    
         
             
                            widget=self,
         
     | 
| 
       73 
75 
     | 
    
         
             
                            types=self.build_types(renderer.route_prefix),
         
     | 
| 
       74 
76 
     | 
    
         
             
                            parent_type=TypeWrapper(
         
     | 
| 
       75 
     | 
    
         
            -
                                Union[tuple(self.children_types)],  # type: ignore
         
     | 
| 
      
 77 
     | 
    
         
            +
                                Union[tuple(self.children_types)],  # type: ignore # noqa: UP007
         
     | 
| 
       76 
78 
     | 
    
         
             
                                renderer.route_prefix,
         
     | 
| 
       77 
79 
     | 
    
         
             
                                self.parent_name,
         
     | 
| 
       78 
80 
     | 
    
         
             
                                self.token,
         
     | 
    
        fastlife/components/Form.jinja
    CHANGED
    
    | 
         @@ -22,6 +22,15 @@ 
     | 
|
| 
       22 
22 
     | 
    
         
             
              ] = None,
         
     | 
| 
       23 
23 
     | 
    
         
             
              method: Annotated[Literal["get", "post"] | None, "Http method used"] = None,
         
     | 
| 
       24 
24 
     | 
    
         
             
              action: Annotated[str | None, "url where the form will be submitted"] = None,
         
     | 
| 
      
 25 
     | 
    
         
            +
              hx_target: Annotated[
         
     | 
| 
      
 26 
     | 
    
         
            +
                str | None,
         
     | 
| 
      
 27 
     | 
    
         
            +
                "target the element for swapping than the one issuing the AJAX request."
         
     | 
| 
      
 28 
     | 
    
         
            +
              ] = None,
         
     | 
| 
      
 29 
     | 
    
         
            +
              hx_select: Annotated[str | None, "select the content swapped from response of the AJAX request."] = None,
         
     | 
| 
      
 30 
     | 
    
         
            +
              hx_swap: Annotated[
         
     | 
| 
      
 31 
     | 
    
         
            +
                str | None,
         
     | 
| 
      
 32 
     | 
    
         
            +
                "specify how the response will be swapped in relative to the target of an AJAX request."
         
     | 
| 
      
 33 
     | 
    
         
            +
              ] = None,
         
     | 
| 
       25 
34 
     | 
    
         
             
              hx_post: Annotated[
         
     | 
| 
       26 
35 
     | 
    
         
             
                str | Literal[True] | None,
         
     | 
| 
       27 
36 
     | 
    
         
             
                "url where the form will be submitted using htmx. if ``True``, the current url is used."\
         
     | 
| 
         @@ -40,6 +49,9 @@ 
     | 
|
| 
       40 
49 
     | 
    
         
             
                  {%- if hx_post is not none %}
         
     | 
| 
       41 
50 
     | 
    
         
             
                    hx-post="{% if hx_post is not true %}{{hx_post}}{% endif %}"
         
     | 
| 
       42 
51 
     | 
    
         
             
                  {%- endif %}
         
     | 
| 
      
 52 
     | 
    
         
            +
                  {%- if hx_select %} hx-select="{{ hx_select }}" {%- endif %}
         
     | 
| 
      
 53 
     | 
    
         
            +
                  {%- if hx_swap %} hx-swap="{{ hx_swap }}" {%- endif %}
         
     | 
| 
      
 54 
     | 
    
         
            +
                  {%- if hx_target %} hx-target="{{ hx_target }}" {%- endif %}
         
     | 
| 
       43 
55 
     | 
    
         
             
                {%- endif %}
         
     | 
| 
       44 
56 
     | 
    
         
             
                {%- if action is not none %} action="{{action}}" {%- endif %}
         
     | 
| 
       45 
57 
     | 
    
         
             
                {%- if method %} method="{{method}}" {%- endif -%}
         
     | 
    
        fastlife/config/configurator.py
    CHANGED
    
    | 
         @@ -11,32 +11,29 @@ The configurator is designed to handle the setup during the configuration 
     | 
|
| 
       11 
11 
     | 
    
         
             
            phase.
         
     | 
| 
       12 
12 
     | 
    
         
             
            """
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
            import importlib
         
     | 
| 
       15 
     | 
    
         
            -
            import inspect
         
     | 
| 
       16 
14 
     | 
    
         
             
            import logging
         
     | 
| 
       17 
15 
     | 
    
         
             
            from collections import defaultdict
         
     | 
| 
       18 
     | 
    
         
            -
            from collections.abc import Mapping, Sequence
         
     | 
| 
      
 16 
     | 
    
         
            +
            from collections.abc import Callable, Mapping, Sequence
         
     | 
| 
       19 
17 
     | 
    
         
             
            from enum import Enum
         
     | 
| 
       20 
18 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       21 
19 
     | 
    
         
             
            from types import ModuleType
         
     | 
| 
       22 
     | 
    
         
            -
            from typing import TYPE_CHECKING, Annotated, Any,  
     | 
| 
      
 20 
     | 
    
         
            +
            from typing import TYPE_CHECKING, Annotated, Any, Generic, Self, TypeVar
         
     | 
| 
       23 
21 
     | 
    
         | 
| 
       24 
22 
     | 
    
         
             
            import venusian
         
     | 
| 
       25 
     | 
    
         
            -
            from fastapi import Depends, FastAPI
         
     | 
| 
      
 23 
     | 
    
         
            +
            from fastapi import Depends, FastAPI, Response
         
     | 
| 
       26 
24 
     | 
    
         
             
            from fastapi import Request as BaseRequest
         
     | 
| 
       27 
     | 
    
         
            -
            from fastapi import Response
         
     | 
| 
       28 
25 
     | 
    
         
             
            from fastapi.params import Depends as DependsType
         
     | 
| 
       29 
26 
     | 
    
         
             
            from fastapi.staticfiles import StaticFiles
         
     | 
| 
       30 
27 
     | 
    
         
             
            from fastapi.types import IncEx
         
     | 
| 
       31 
28 
     | 
    
         | 
| 
       32 
29 
     | 
    
         
             
            from fastlife.config.openapiextra import OpenApiTag
         
     | 
| 
       33 
30 
     | 
    
         
             
            from fastlife.middlewares.base import AbstractMiddleware
         
     | 
| 
       34 
     | 
    
         
            -
            from fastlife.request.request import Request
         
     | 
| 
      
 31 
     | 
    
         
            +
            from fastlife.request.request import GenericRequest, Request
         
     | 
| 
       35 
32 
     | 
    
         
             
            from fastlife.routing.route import Route
         
     | 
| 
       36 
33 
     | 
    
         
             
            from fastlife.routing.router import Router
         
     | 
| 
       37 
34 
     | 
    
         
             
            from fastlife.security.csrf import check_csrf
         
     | 
| 
       38 
35 
     | 
    
         
             
            from fastlife.services.policy import check_permission
         
     | 
| 
       39 
     | 
    
         
            -
            from fastlife.shared_utils.resolver import resolve
         
     | 
| 
      
 36 
     | 
    
         
            +
            from fastlife.shared_utils.resolver import resolve, resolve_maybe_relative
         
     | 
| 
       40 
37 
     | 
    
         | 
| 
       41 
38 
     | 
    
         
             
            from .registry import DefaultRegistry, TRegistry
         
     | 
| 
       42 
39 
     | 
    
         
             
            from .settings import Settings
         
     | 
| 
         @@ -127,9 +124,9 @@ class GenericConfigurator(Generic[TRegistry]): 
     | 
|
| 
       127 
124 
     | 
    
         
             
                    self.registry = registry_cls(settings)
         
     | 
| 
       128 
125 
     | 
    
         
             
                    Route._registry = self.registry  # type: ignore
         
     | 
| 
       129 
126 
     | 
    
         | 
| 
       130 
     | 
    
         
            -
                    self.middlewares: list[ 
     | 
| 
       131 
     | 
    
         
            -
                    self.exception_handlers: list[ 
     | 
| 
       132 
     | 
    
         
            -
                    self.mounts: list[ 
     | 
| 
      
 127 
     | 
    
         
            +
                    self.middlewares: list[tuple[type[AbstractMiddleware], Any]] = []
         
     | 
| 
      
 128 
     | 
    
         
            +
                    self.exception_handlers: list[tuple[int | type[Exception], Any]] = []
         
     | 
| 
      
 129 
     | 
    
         
            +
                    self.mounts: list[tuple[str, Path, str]] = []
         
     | 
| 
       133 
130 
     | 
    
         
             
                    self.tags: dict[str, OpenApiTag] = {}
         
     | 
| 
       134 
131 
     | 
    
         | 
| 
       135 
132 
     | 
    
         
             
                    self.api_title = "OpenAPI"
         
     | 
| 
         @@ -142,7 +139,7 @@ class GenericConfigurator(Generic[TRegistry]): 
     | 
|
| 
       142 
139 
     | 
    
         
             
                    self._route_prefix: str = ""
         
     | 
| 
       143 
140 
     | 
    
         
             
                    self._routers: dict[str, Router] = defaultdict(Router)
         
     | 
| 
       144 
141 
     | 
    
         
             
                    self._security_policies: dict[
         
     | 
| 
       145 
     | 
    
         
            -
                        str,  
     | 
| 
      
 142 
     | 
    
         
            +
                        str, type[AbstractSecurityPolicy[Any, TRegistry]]
         
     | 
| 
       146 
143 
     | 
    
         
             
                    ] = {}
         
     | 
| 
       147 
144 
     | 
    
         | 
| 
       148 
145 
     | 
    
         
             
                    self._registered_permissions: set[str] = set()
         
     | 
| 
         @@ -239,12 +236,11 @@ class GenericConfigurator(Generic[TRegistry]): 
     | 
|
| 
       239 
236 
     | 
    
         
             
                    :param ignore: ignore submodules
         
     | 
| 
       240 
237 
     | 
    
         
             
                    """
         
     | 
| 
       241 
238 
     | 
    
         
             
                    if isinstance(module, str):
         
     | 
| 
       242 
     | 
    
         
            -
                         
     | 
| 
       243 
     | 
    
         
            -
             
     | 
| 
       244 
     | 
    
         
            -
             
     | 
| 
       245 
     | 
    
         
            -
                             
     | 
| 
      
 239 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 240 
     | 
    
         
            +
                            module = resolve_maybe_relative(module, stack_depth=2)
         
     | 
| 
      
 241 
     | 
    
         
            +
                        except ModuleNotFoundError as exc:
         
     | 
| 
      
 242 
     | 
    
         
            +
                            raise ConfigurationError(f"Can't resolve {module}") from exc
         
     | 
| 
       246 
243 
     | 
    
         | 
| 
       247 
     | 
    
         
            -
                        module = importlib.import_module(module, package)
         
     | 
| 
       248 
244 
     | 
    
         
             
                    old, self._route_prefix = self._route_prefix, route_prefix
         
     | 
| 
       249 
245 
     | 
    
         
             
                    try:
         
     | 
| 
       250 
246 
     | 
    
         
             
                        self.scanner.scan(  # type: ignore
         
     | 
| 
         @@ -302,7 +298,7 @@ class GenericConfigurator(Generic[TRegistry]): 
     | 
|
| 
       302 
298 
     | 
    
         
             
                    self.api_redoc_url = redoc_url
         
     | 
| 
       303 
299 
     | 
    
         
             
                    return self
         
     | 
| 
       304 
300 
     | 
    
         | 
| 
       305 
     | 
    
         
            -
                def  
     | 
| 
      
 301 
     | 
    
         
            +
                def add_openapi_tag(self, tag: OpenApiTag) -> Self:
         
     | 
| 
       306 
302 
     | 
    
         
             
                    """Register a tag description in the documentation."""
         
     | 
| 
       307 
303 
     | 
    
         
             
                    if tag.name in self.tags:
         
     | 
| 
       308 
304 
     | 
    
         
             
                        raise ConfigurationError(f"Tag {tag.name} can't be registered twice.")
         
     | 
| 
         @@ -310,7 +306,7 @@ class GenericConfigurator(Generic[TRegistry]): 
     | 
|
| 
       310 
306 
     | 
    
         
             
                    return self
         
     | 
| 
       311 
307 
     | 
    
         | 
| 
       312 
308 
     | 
    
         
             
                def add_middleware(
         
     | 
| 
       313 
     | 
    
         
            -
                    self, middleware_class:  
     | 
| 
      
 309 
     | 
    
         
            +
                    self, middleware_class: type[AbstractMiddleware], **options: Any
         
     | 
| 
       314 
310 
     | 
    
         
             
                ) -> Self:
         
     | 
| 
       315 
311 
     | 
    
         
             
                    """
         
     | 
| 
       316 
312 
     | 
    
         
             
                    Add a starlette middleware to the FastAPI app.
         
     | 
| 
         @@ -522,7 +518,7 @@ class GenericConfigurator(Generic[TRegistry]): 
     | 
|
| 
       522 
518 
     | 
    
         | 
| 
       523 
519 
     | 
    
         
             
                def add_exception_handler(
         
     | 
| 
       524 
520 
     | 
    
         
             
                    self,
         
     | 
| 
       525 
     | 
    
         
            -
                    status_code_or_exc: int |  
     | 
| 
      
 521 
     | 
    
         
            +
                    status_code_or_exc: int | type[Exception],
         
     | 
| 
       526 
522 
     | 
    
         
             
                    handler: Any,
         
     | 
| 
       527 
523 
     | 
    
         
             
                    *,
         
     | 
| 
       528 
524 
     | 
    
         
             
                    template: str | None = None,
         
     | 
| 
         @@ -539,8 +535,8 @@ class GenericConfigurator(Generic[TRegistry]): 
     | 
|
| 
       539 
535 
     | 
    
         
             
                        # class is wrong.
         
     | 
| 
       540 
536 
     | 
    
         
             
                        # Until we store a security policy per rooter, we rebuild an
         
     | 
| 
       541 
537 
     | 
    
         
             
                        # incomplete request here.
         
     | 
| 
       542 
     | 
    
         
            -
                         
     | 
| 
       543 
     | 
    
         
            -
                        resp = handler( 
     | 
| 
      
 538 
     | 
    
         
            +
                        req = GenericRequest[DefaultRegistry](self.registry, request)
         
     | 
| 
      
 539 
     | 
    
         
            +
                        resp = handler(req, exc)
         
     | 
| 
       544 
540 
     | 
    
         
             
                        if isinstance(resp, Response):
         
     | 
| 
       545 
541 
     | 
    
         
             
                            return resp
         
     | 
| 
       546 
542 
     | 
    
         | 
| 
         @@ -552,7 +548,7 @@ class GenericConfigurator(Generic[TRegistry]): 
     | 
|
| 
       552 
548 
     | 
    
         
             
                                "did not return a Response"
         
     | 
| 
       553 
549 
     | 
    
         
             
                            )
         
     | 
| 
       554 
550 
     | 
    
         | 
| 
       555 
     | 
    
         
            -
                        return  
     | 
| 
      
 551 
     | 
    
         
            +
                        return req.registry.get_renderer(template)(req).render(
         
     | 
| 
       556 
552 
     | 
    
         
             
                            template,
         
     | 
| 
       557 
553 
     | 
    
         
             
                            params=resp,
         
     | 
| 
       558 
554 
     | 
    
         
             
                            status_code=status_code,
         
     | 
| 
         @@ -594,8 +590,11 @@ class Configurator(GenericConfigurator[DefaultRegistry]): 
     | 
|
| 
       594 
590 
     | 
    
         
             
                """
         
     | 
| 
       595 
591 
     | 
    
         | 
| 
       596 
592 
     | 
    
         | 
| 
      
 593 
     | 
    
         
            +
            TConfigurator = TypeVar("TConfigurator", bound=GenericConfigurator[Any])
         
     | 
| 
      
 594 
     | 
    
         
            +
             
     | 
| 
      
 595 
     | 
    
         
            +
             
     | 
| 
       597 
596 
     | 
    
         
             
            def configure(
         
     | 
| 
       598 
     | 
    
         
            -
                wrapped: Callable[[ 
     | 
| 
      
 597 
     | 
    
         
            +
                wrapped: Callable[[TConfigurator], None],
         
     | 
| 
       599 
598 
     | 
    
         
             
            ) -> Callable[[Any], None]:
         
     | 
| 
       600 
599 
     | 
    
         
             
                """
         
     | 
| 
       601 
600 
     | 
    
         
             
                Decorator used to attach route in a submodule while using the configurator.include.
         
     | 
    
        fastlife/config/exceptions.py
    CHANGED