Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions inertia/helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
from typing import Any
from __future__ import annotations

from typing import TYPE_CHECKING, cast

def deep_transform_callables(prop: Any) -> Any:
if TYPE_CHECKING:
from collections.abc import Callable
from typing import Any, TypeVar

T = TypeVar("T", bound=Any)


def deep_transform_callables(prop: T | Callable[[], T]) -> T:
if not isinstance(prop, dict):
return prop() if callable(prop) else prop
return cast("T", prop() if callable(prop) else prop)

for key in list(prop.keys()):
prop[key] = deep_transform_callables(prop[key])
Expand Down
45 changes: 33 additions & 12 deletions inertia/http.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import logging
from functools import wraps
from http import HTTPStatus
from json import dumps as json_encode
from typing import Any, Callable
from typing import TYPE_CHECKING

from django.core.exceptions import ImproperlyConfigured
from django.http import HttpRequest, HttpResponse
Expand All @@ -19,6 +21,25 @@
except ImportError:
requests = None # type: ignore[assignment]


if TYPE_CHECKING:
from collections.abc import Callable
from typing import Any, Concatenate, NotRequired, ParamSpec, TypedDict, TypeVar

P = ParamSpec("P")
R = TypeVar("R", HttpResponse, "InertiaResponse", dict[str, Any])

class PageDataDict(TypedDict):
component: str
props: dict[str, Any]
url: str
version: str
encryptHistory: bool
clearHistory: bool
deferredProps: NotRequired[dict[str, Any] | None]
mergeProps: NotRequired[list[str]]


logger = logging.getLogger(__name__)

INERTIA_REQUEST_ENCRYPT_HISTORY = "_inertia_encrypt_history"
Expand All @@ -29,7 +50,7 @@


class InertiaRequest(HttpRequest):
def __init__(self, request: HttpRequest):
def __init__(self, request: HttpRequest) -> None:
super().__init__()
self.__dict__.update(request.__dict__)

Expand Down Expand Up @@ -72,14 +93,14 @@ class BaseInertiaResponseMixin:
props: dict[str, Any]
template_data: dict[str, Any]

def page_data(self) -> dict[str, Any]:
def page_data(self) -> PageDataDict:
clear_history = self.request.session.pop(INERTIA_SESSION_CLEAR_HISTORY, False)
if not isinstance(clear_history, bool):
raise TypeError(
f"Expected bool for clear_history, got {type(clear_history).__name__}"
)

_page = {
_page: PageDataDict = {
"component": self.component,
"props": self.build_props(),
"url": self.request.get_full_path(),
Expand All @@ -98,9 +119,9 @@ def page_data(self) -> dict[str, Any]:

return _page

def build_props(self) -> Any:
def build_props(self) -> dict[str, Any]:
_props = {
**(self.request.inertia),
**self.request.inertia,
**self.props,
}

Expand Down Expand Up @@ -178,7 +199,7 @@ def build_first_load_context_and_template(

return {
"page": data,
**(self.template_data),
**self.template_data,
}, INERTIA_TEMPLATE


Expand Down Expand Up @@ -261,15 +282,15 @@ def clear_history(request: HttpRequest) -> None:
def inertia(
component: str,
) -> Callable[
[Callable[..., HttpResponse | InertiaResponse | dict[str, Any]]],
Callable[..., HttpResponse],
[Callable[Concatenate[HttpRequest, P], R]],
Callable[Concatenate[HttpRequest, P], HttpResponse],
]:
def decorator(
func: Callable[..., HttpResponse | InertiaResponse | dict[str, Any]],
) -> Callable[..., HttpResponse]:
func: Callable[Concatenate[HttpRequest, P], R],
) -> Callable[Concatenate[HttpRequest, P], HttpResponse]:
@wraps(func)
def process_inertia_response(
request: HttpRequest, *args: Any, **kwargs: Any
request: HttpRequest, /, *args: P.args, **kwargs: P.kwargs
) -> HttpResponse:
props = func(request, *args, **kwargs)

Expand Down
10 changes: 8 additions & 2 deletions inertia/middleware.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
from typing import Callable
from __future__ import annotations

from typing import TYPE_CHECKING

from django.contrib import messages
from django.http import HttpRequest, HttpResponse
from django.middleware.csrf import get_token

from .http import location
from .settings import settings

if TYPE_CHECKING:
from collections.abc import Callable

from django.http import HttpRequest, HttpResponse


class InertiaMiddleware:
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None:
Expand Down
26 changes: 17 additions & 9 deletions inertia/prop_classes.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast

if TYPE_CHECKING:
from collections.abc import Callable


T = TypeVar("T", bound=Any)


class CallableProp:
def __init__(self, prop: Any) -> None:
class CallableProp(Generic[T]):
def __init__(self, prop: T | Callable[[], T]) -> None:
self.prop = prop

def __call__(self) -> Any:
return self.prop() if callable(self.prop) else self.prop
def __call__(self) -> T:
return cast(T, self.prop() if callable(self.prop) else self.prop)


class MergeableProp(ABC):
Expand All @@ -20,12 +28,12 @@ class IgnoreOnFirstLoadProp:
pass


class OptionalProp(CallableProp, IgnoreOnFirstLoadProp):
class OptionalProp(CallableProp[T], IgnoreOnFirstLoadProp):
pass


class DeferredProp(CallableProp, MergeableProp, IgnoreOnFirstLoadProp):
def __init__(self, prop: Any, group: str, merge: bool = False) -> None:
class DeferredProp(CallableProp[T], MergeableProp, IgnoreOnFirstLoadProp):
def __init__(self, prop: T | Callable[[], T], group: str, merge: bool = False) -> None:
super().__init__(prop)
self.group = group
self.merge = merge
Expand All @@ -34,6 +42,6 @@ def should_merge(self) -> bool:
return self.merge


class MergeProp(CallableProp, MergeableProp):
class MergeProp(CallableProp[T], MergeableProp):
def should_merge(self) -> bool:
return True
10 changes: 8 additions & 2 deletions inertia/share.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from typing import Any
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Any

from django.http import HttpRequest

from django.http import HttpRequest

__all__ = ["share"]

Expand Down
18 changes: 13 additions & 5 deletions inertia/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import warnings
from typing import Any
from typing import TYPE_CHECKING, Any, TypeVar

from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
Expand All @@ -8,6 +10,12 @@

from .prop_classes import DeferredProp, MergeProp, OptionalProp

if TYPE_CHECKING:
from collections.abc import Callable


T = TypeVar("T", bound=Any)


def model_to_dict(model: models.Model) -> dict[str, Any]:
return base_model_to_dict(model, exclude=("password",))
Expand All @@ -32,7 +40,7 @@ def default(self, o: Any) -> Any:
return super().default(o)


def lazy(prop: Any) -> OptionalProp:
def lazy(prop: T | Callable[[], T]) -> OptionalProp[T]:
warnings.warn(
"lazy is deprecated and will be removed in a future version. Please use optional instead.",
DeprecationWarning,
Expand All @@ -41,13 +49,13 @@ def lazy(prop: Any) -> OptionalProp:
return optional(prop)


def optional(prop: Any) -> OptionalProp:
def optional(prop: T | Callable[[], T]) -> OptionalProp[T]:
return OptionalProp(prop)


def defer(prop: Any, group: str = "default", merge: bool = False) -> DeferredProp:
def defer(prop: T | Callable[[], T], group: str = "default", merge: bool = False) -> DeferredProp[T]:
return DeferredProp(prop, group=group, merge=merge)


def merge(prop: Any) -> MergeProp:
def merge(prop: T | Callable[[], T]) -> MergeProp[T]:
return MergeProp(prop)