Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
cofin committed Nov 22, 2023
1 parent 2829696 commit eced6de
Show file tree
Hide file tree
Showing 22 changed files with 513 additions and 16 deletions.
6 changes: 3 additions & 3 deletions examples/htmx/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"**/*.d.ts",
"resources/**/*.js" ,
"resources/**/*.ts" ,


"vite.config.ts"
]
}
}
10 changes: 5 additions & 5 deletions examples/htmx/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import litestar from "litestar-vite-plugin";

export default defineConfig({
plugins: [


litestar({
input: [
input: [
"resources/styles.css"
],
assetUrl: "/static/",
Expand All @@ -21,5 +21,5 @@ export default defineConfig({
alias: {
"@": "resources",
},
},
});
},
});
4 changes: 2 additions & 2 deletions examples/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
"devDependencies": {
"axios": "^1.1.2",
"litestar-vite-plugin": "^0.1.0",
"vite": "^4.0.0"
"vite": "^4.0.0"
}
}
}
2 changes: 1 addition & 1 deletion examples/vue/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
"resources/**/*.tsx",
"resources/**/*.vue"
]
}
}
8 changes: 4 additions & 4 deletions examples/vue/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import litestar from "litestar-vite-plugin";

export default defineConfig({
plugins: [

litestar({
input: [
input: [
"resources/styles.css"
],
assetUrl: "/static/",
Expand All @@ -19,5 +19,5 @@ export default defineConfig({
alias: {
"@": "resources",
},
},
});
},
});
Empty file.
62 changes: 62 additions & 0 deletions litestar_vite/inertia/_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from __future__ import annotations

from enum import Enum
from typing import TYPE_CHECKING, Any, Callable

if TYPE_CHECKING:
from litestar_vite.inertia.types import InertiaHeaderType


class InertiaHeaders(str, Enum):
"""Enum for Inertia Headers"""

ENABLED = "X-Inertia"
VERSION = "X-Inertia-Version"
PARTIAL_DATA = "X-Inertia-Partial-Data"
PARTIAL_COMPONENT = "X-Inertia-Partial-Component"
LOCATION = "X-Inertia-Location"


def get_enabled_header(enabled: bool = True) -> dict[str, Any]:
"""True if inertia is enabled."""

return {InertiaHeaders.ENABLED.value: enabled}


def get_version_header(version: str) -> dict[str, Any]:
"""Return headers for change swap method response."""
return {InertiaHeaders.VERSION.value: version}


def get_partial_data_header(partial: str) -> dict[str, Any]:
"""Return headers for a partial data response."""
return {InertiaHeaders.PARTIAL_DATA.value: partial}


def get_partial_component_header(partial: str) -> dict[str, Any]:
"""Return headers for a partial data response."""
return {InertiaHeaders.PARTIAL_COMPONENT.value: partial}


def get_headers(inertia_headers: InertiaHeaderType) -> dict[str, Any]:
"""Return headers for Inertia responses."""
if not inertia_headers:
msg = "Value for inertia_headers cannot be None."
raise ValueError(msg)
inertia_headers_dict: dict[str, Callable] = {
"enabled": get_enabled_header,
"partial_data": get_partial_data_header,
"partial_component": get_partial_component_header,
"version": get_version_header,
}

header: dict[str, Any] = {}
response: dict[str, Any]
key: str
value: Any

for key, value in inertia_headers.items():
if value is not None:
response = inertia_headers_dict[key](value)
header.update(response)
return header
30 changes: 30 additions & 0 deletions litestar_vite/inertia/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, MutableMapping, TypeVar

from litestar.template import TemplateEngineProtocol

__all__ = ("InertiaConfig",)


if TYPE_CHECKING:
from pathlib import Path


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


@dataclass
class InertiaConfig:
"""Configuration for InertiaJS support."""

root_template: Path
"""Name of the root template to use.
This must be a path that is found by the Vite Plugin template config
"""
default_props: MutableMapping[str, Any] = field(default_factory=dict)
"""The additional default props and their types you would like to include on a response."""
component_opt_key: str = "component"
"""An identifier to use on routes to get the inertia component to render."""
Empty file.
37 changes: 37 additions & 0 deletions litestar_vite/inertia/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from litestar.plugins import InitPluginProtocol

from litestar_vite.inertia import InertiaResponse
from litestar_vite.inertia.request import InertiaRequest

if TYPE_CHECKING:
from litestar.config.app import AppConfig

from litestar_vite.inertia.config import InertiaConfig


class InertiaPlugin(InitPluginProtocol):
"""Inertia plugin."""

__slots__ = ("_config",)

def __init__(self, config: InertiaConfig) -> None:
"""Initialize ``Inertia``.
Args:
config: configure and start Vite.
"""
self._config = config

def on_app_init(self, app_config: AppConfig) -> AppConfig:
"""Configure application for use with Vite.
Args:
app_config: The :class:`AppConfig <.config.app.AppConfig>` instance.
"""
app_config.request_class = InertiaRequest
app_config.response_class = InertiaResponse
return app_config
85 changes: 85 additions & 0 deletions litestar_vite/inertia/request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from __future__ import annotations

from functools import cached_property
from typing import TYPE_CHECKING
from urllib.parse import unquote

from litestar import Request
from litestar.connection.base import (
AuthT,
StateT,
UserT,
empty_receive,
empty_send,
)

from litestar_vite.inertia._utils import InertiaHeaders

__all__ = ("InertiaDetails", "InertiaRequest")


if TYPE_CHECKING:
from litestar.types import Receive, Scope, Send


class InertiaDetails:
"""InertiaDetails holds all the values sent by Inertia client in headers and provide convenient properties."""

def __init__(self, request: Request) -> None:
"""Initialize :class:`InertiaDetails`"""
self.request = request

def _get_header_value(self, name: InertiaHeaders) -> str | None:
"""Parse request header
Check for uri encoded header and unquotes it in readable format.
"""

if value := self.request.headers.get(name.value.lower()):
is_uri_encoded = self.request.headers.get(f"{name.value.lower()}-uri-autoencoded") == "true"
return unquote(value) if is_uri_encoded else value
return None

def __bool__(self) -> bool:
"""Check if request is sent by an Inertia client."""
return self._get_header_value(InertiaHeaders.ENABLED) == "true"

@cached_property
def partial_component(self) -> str | None:
"""Partial Data Reload."""
return self._get_header_value(InertiaHeaders.PARTIAL_COMPONENT)

@cached_property
def partial_data(self) -> str | None:
"""Partial Data Reload."""
return self._get_header_value(InertiaHeaders.PARTIAL_DATA)


class InertiaRequest(Request[UserT, AuthT, StateT]):
"""Inertia Request class to work with Inertia client."""

__slots__ = (
"_json",
"_form",
"_body",
"_msgpack",
"_content_type",
"_accept",
"_inertia",
"is_connected",
"is_inertia",
)

def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send) -> None:
"""Initialize :class:`InertiaRequest`"""
super().__init__(scope=scope, receive=receive, send=send)
self._inertia = InertiaDetails(self)

@property
def is_inertia(self) -> bool:
"""Return the request method.
Returns:
The request :class:`Method <litestar.types.Method>`
"""
return bool(self._inertia)
42 changes: 42 additions & 0 deletions litestar_vite/inertia/response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from __future__ import annotations

from typing import Any, MutableMapping

from litestar import Response
from litestar.response import Template

from litestar_vite.inertia._utils import get_headers
from litestar_vite.inertia.types import InertiaHeaderType


class InertiaJSON(Response):
"""Inertia JSON Response"""

def __init__(self, props: MutableMapping[str, Any] | None = None, **kwargs: Any) -> None:
"""Set Status code to 200 and set headers."""
super().__init__(**kwargs)
self._props = props
self.headers.update(
get_headers(InertiaHeaderType(enabled=True)),
)


class InertiaTemplate(Template):
"""Inertia template wrapper"""

def __init__(
self,
props: MutableMapping[str, Any] | None = None,
**kwargs: Any,
) -> None:
"""Create InertiaTemplate response.
Args:
props: Dictionary or Prop type to serialize to JSON
**kwargs: Additional arguments to pass to ``Template``.
"""
super().__init__(**kwargs)
self._props = props
self.headers.update(
get_headers(InertiaHeaderType(enabled=True)),
)
39 changes: 39 additions & 0 deletions litestar_vite/inertia/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Generic, TypedDict, TypeVar

__all__ = (
"InertiaHeaderType",
"PageProps",
)


T = TypeVar("T")


@dataclass
class PageProps(Generic[T]):
"""Inertia Page Props Type."""

component: str
url: str
version: str
props: T


@dataclass
class InertiaProps(Generic[T]):
"""Inertia Props Type."""

page: PageProps[T]


class InertiaHeaderType(TypedDict, total=False):
"""Type for hx_headers parameter in get_headers()."""

enabled: bool | None
version: str | None
location: str | None
partial_data: str | None
partial_component: str | None
Loading

0 comments on commit eced6de

Please sign in to comment.