diff options
Diffstat (limited to 'venv/lib/python3.11/site-packages/litestar/contrib')
114 files changed, 3020 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/__init__.py diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..fc2f5bc --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/jinja.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/jinja.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..f58d015 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/jinja.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/mako.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/mako.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..09bede9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/mako.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/minijinja.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/minijinja.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..05ada30 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/minijinja.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/minijnja.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/minijnja.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..3006e82 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/minijnja.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/piccolo.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/piccolo.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..20ea290 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/__pycache__/piccolo.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__init__.py new file mode 100644 index 0000000..ddd2a3f --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__init__.py @@ -0,0 +1,3 @@ +from .attrs_schema_plugin import AttrsSchemaPlugin + +__all__ = ("AttrsSchemaPlugin",) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..a224be6 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__pycache__/attrs_schema_plugin.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__pycache__/attrs_schema_plugin.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..730252a --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/attrs/__pycache__/attrs_schema_plugin.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/attrs/attrs_schema_plugin.py b/venv/lib/python3.11/site-packages/litestar/contrib/attrs/attrs_schema_plugin.py new file mode 100644 index 0000000..cf67fe4 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/attrs/attrs_schema_plugin.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from litestar.exceptions import MissingDependencyException +from litestar.plugins import OpenAPISchemaPluginProtocol +from litestar.typing import FieldDefinition +from litestar.utils import is_attrs_class, is_optional_union + +try: + import attr + import attrs +except ImportError as e: + raise MissingDependencyException("attrs") from e + +if TYPE_CHECKING: + from litestar._openapi.schema_generation import SchemaCreator + from litestar.openapi.spec import Schema + + +class AttrsSchemaPlugin(OpenAPISchemaPluginProtocol): + @staticmethod + def is_plugin_supported_type(value: Any) -> bool: + return is_attrs_class(value) or is_attrs_class(type(value)) + + def to_openapi_schema(self, field_definition: FieldDefinition, schema_creator: SchemaCreator) -> Schema: + """Given a type annotation, transform it into an OpenAPI schema class. + + Args: + field_definition: FieldDefinition instance. + schema_creator: An instance of the schema creator class + + Returns: + An :class:`OpenAPI <litestar.openapi.spec.schema.Schema>` instance. + """ + + type_hints = field_definition.get_type_hints(include_extras=True, resolve_generics=True) + attr_fields = attr.fields_dict(field_definition.type_) + return schema_creator.create_component_schema( + field_definition, + required=sorted( + field_name + for field_name, attribute in attr_fields.items() + if attribute.default is attrs.NOTHING and not is_optional_union(type_hints[field_name]) + ), + property_fields={ + field_name: FieldDefinition.from_kwarg(type_hints[field_name], field_name) for field_name in attr_fields + }, + ) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__init__.py diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..31d4982 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/_utils.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/_utils.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..d860774 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/_utils.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/request.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/request.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..65b99d9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/request.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/response.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/response.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..0bb64b8 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/response.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/types.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/types.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..0af7128 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/__pycache__/types.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/_utils.py b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/_utils.py new file mode 100644 index 0000000..894fd25 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/_utils.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +from enum import Enum +from typing import TYPE_CHECKING, Any, Callable, cast +from urllib.parse import quote + +from litestar.exceptions import ImproperlyConfiguredException +from litestar.serialization import encode_json + +__all__ = ( + "HTMXHeaders", + "get_headers", + "get_location_headers", + "get_push_url_header", + "get_redirect_header", + "get_refresh_header", + "get_replace_url_header", + "get_reswap_header", + "get_retarget_header", + "get_trigger_event_headers", +) + + +if TYPE_CHECKING: + from litestar.contrib.htmx.types import ( + EventAfterType, + HtmxHeaderType, + LocationType, + PushUrlType, + ReSwapMethod, + TriggerEventType, + ) + +HTMX_STOP_POLLING = 286 + + +class HTMXHeaders(str, Enum): + """Enum for HTMX Headers""" + + REDIRECT = "HX-Redirect" + REFRESH = "HX-Refresh" + PUSH_URL = "HX-Push-Url" + REPLACE_URL = "HX-Replace-Url" + RE_SWAP = "HX-Reswap" + RE_TARGET = "HX-Retarget" + LOCATION = "HX-Location" + + TRIGGER_EVENT = "HX-Trigger" + TRIGGER_AFTER_SETTLE = "HX-Trigger-After-Settle" + TRIGGER_AFTER_SWAP = "HX-Trigger-After-Swap" + + REQUEST = "HX-Request" + BOOSTED = "HX-Boosted" + CURRENT_URL = "HX-Current-URL" + HISTORY_RESTORE_REQUEST = "HX-History-Restore-Request" + PROMPT = "HX-Prompt" + TARGET = "HX-Target" + TRIGGER_ID = "HX-Trigger" # noqa: PIE796 + TRIGGER_NAME = "HX-Trigger-Name" + TRIGGERING_EVENT = "Triggering-Event" + + +def get_trigger_event_headers(trigger_event: TriggerEventType) -> dict[str, Any]: + """Return headers for trigger event response.""" + after_params: dict[EventAfterType, str] = { + "receive": HTMXHeaders.TRIGGER_EVENT.value, + "settle": HTMXHeaders.TRIGGER_AFTER_SETTLE.value, + "swap": HTMXHeaders.TRIGGER_AFTER_SWAP.value, + } + + if trigger_header := after_params.get(trigger_event["after"]): + return {trigger_header: encode_json({trigger_event["name"]: trigger_event["params"] or {}}).decode()} + + raise ImproperlyConfiguredException( + "invalid value for 'after' param- allowed values are 'receive', 'settle' or 'swap'." + ) + + +def get_redirect_header(url: str) -> dict[str, Any]: + """Return headers for redirect response.""" + return {HTMXHeaders.REDIRECT.value: quote(url, safe="/#%[]=:;$&()+,!?*@'~"), "Location": ""} + + +def get_push_url_header(url: PushUrlType) -> dict[str, Any]: + """Return headers for push url to browser history response.""" + if isinstance(url, str): + url = url if url != "False" else "false" + elif isinstance(url, bool): + url = "false" + + return {HTMXHeaders.PUSH_URL.value: url} + + +def get_replace_url_header(url: PushUrlType) -> dict[str, Any]: + """Return headers for replace url in browser tab response.""" + url = (url if url != "False" else "false") if isinstance(url, str) else "false" + return {HTMXHeaders.REPLACE_URL: url} + + +def get_refresh_header(refresh: bool) -> dict[str, Any]: + """Return headers for client refresh response.""" + return {HTMXHeaders.REFRESH.value: "true" if refresh else ""} + + +def get_reswap_header(method: ReSwapMethod) -> dict[str, Any]: + """Return headers for change swap method response.""" + return {HTMXHeaders.RE_SWAP.value: method} + + +def get_retarget_header(target: str) -> dict[str, Any]: + """Return headers for change target element response.""" + return {HTMXHeaders.RE_TARGET.value: target} + + +def get_location_headers(location: LocationType) -> dict[str, Any]: + """Return headers for redirect without page-reload response.""" + if spec := {key: value for key, value in location.items() if value}: + return {HTMXHeaders.LOCATION.value: encode_json(spec).decode()} + raise ValueError("redirect_to is required parameter.") + + +def get_headers(hx_headers: HtmxHeaderType) -> dict[str, Any]: + """Return headers for HTMX responses.""" + if not hx_headers: + raise ValueError("Value for hx_headers cannot be None.") + htmx_headers_dict: dict[str, Callable] = { + "redirect": get_redirect_header, + "refresh": get_refresh_header, + "push_url": get_push_url_header, + "replace_url": get_replace_url_header, + "re_swap": get_reswap_header, + "re_target": get_retarget_header, + "trigger_event": get_trigger_event_headers, + "location": get_location_headers, + } + + header: dict[str, Any] = {} + response: dict[str, Any] + key: str + value: Any + + for key, value in hx_headers.items(): + if key in ["redirect", "refresh", "location", "replace_url"]: + return cast("dict[str, Any]", htmx_headers_dict[key](value)) + if value is not None: + response = htmx_headers_dict[key](value) + header.update(response) + return header diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/request.py b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/request.py new file mode 100644 index 0000000..b4fad18 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/request.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +from contextlib import suppress +from functools import cached_property +from typing import TYPE_CHECKING, Any +from urllib.parse import unquote, urlsplit, urlunsplit + +from litestar import Request +from litestar.connection.base import empty_receive, empty_send +from litestar.contrib.htmx._utils import HTMXHeaders +from litestar.exceptions import SerializationException +from litestar.serialization import decode_json + +__all__ = ("HTMXDetails", "HTMXRequest") + + +if TYPE_CHECKING: + from litestar.types import Receive, Scope, Send + + +class HTMXDetails: + """HTMXDetails holds all the values sent by HTMX client in headers and provide convenient properties.""" + + def __init__(self, request: Request) -> None: + """Initialize :class:`HTMXDetails`""" + self.request = request + + def _get_header_value(self, name: HTMXHeaders) -> 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 HTMX client.""" + return self._get_header_value(HTMXHeaders.REQUEST) == "true" + + @cached_property + def boosted(self) -> bool: + """Check if request is boosted.""" + return self._get_header_value(HTMXHeaders.BOOSTED) == "true" + + @cached_property + def current_url(self) -> str | None: + """Current url value sent by HTMX client.""" + return self._get_header_value(HTMXHeaders.CURRENT_URL) + + @cached_property + def current_url_abs_path(self) -> str | None: + """Current url abs path value, to get query and path parameter sent by HTMX client.""" + if self.current_url: + split = urlsplit(self.current_url) + if split.scheme == self.request.scope["scheme"] and split.netloc == self.request.headers.get("host"): + return str(urlunsplit(split._replace(scheme="", netloc=""))) + return None + return self.current_url + + @cached_property + def history_restore_request(self) -> bool: + """If True then, request is for history restoration after a miss in the local history cache.""" + return self._get_header_value(HTMXHeaders.HISTORY_RESTORE_REQUEST) == "true" + + @cached_property + def prompt(self) -> str | None: + """User Response to prompt. + + .. code-block:: html + + <button hx-delete="/account" hx-prompt="Enter your account name to confirm deletion">Delete My Account</button> + """ + return self._get_header_value(HTMXHeaders.PROMPT) + + @cached_property + def target(self) -> str | None: + """ID of the target element if provided on the element.""" + return self._get_header_value(HTMXHeaders.TARGET) + + @cached_property + def trigger(self) -> str | None: + """ID of the triggered element if provided on the element.""" + return self._get_header_value(HTMXHeaders.TRIGGER_ID) + + @cached_property + def trigger_name(self) -> str | None: + """Name of the triggered element if provided on the element.""" + return self._get_header_value(HTMXHeaders.TRIGGER_NAME) + + @cached_property + def triggering_event(self) -> Any: + """Name of the triggered event. + + This value is added by ``event-header`` extension of HTMX to the ``Triggering-Event`` header to requests. + """ + if value := self._get_header_value(HTMXHeaders.TRIGGERING_EVENT): + with suppress(SerializationException): + return decode_json(value=value, type_decoders=self.request.route_handler.resolve_type_decoders()) + return None + + +class HTMXRequest(Request): + """HTMX Request class to work with HTMX client.""" + + __slots__ = ("htmx",) + + def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send) -> None: + """Initialize :class:`HTMXRequest`""" + super().__init__(scope=scope, receive=receive, send=send) + self.htmx = HTMXDetails(self) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/response.py b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/response.py new file mode 100644 index 0000000..0a56e1f --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/response.py @@ -0,0 +1,200 @@ +from __future__ import annotations + +from typing import Any, Generic, TypeVar +from urllib.parse import quote + +from litestar import Response +from litestar.contrib.htmx._utils import HTMX_STOP_POLLING, get_headers +from litestar.contrib.htmx.types import ( + EventAfterType, + HtmxHeaderType, + LocationType, + PushUrlType, + ReSwapMethod, + TriggerEventType, +) +from litestar.response import Template +from litestar.status_codes import HTTP_200_OK + +__all__ = ( + "ClientRedirect", + "ClientRefresh", + "HTMXTemplate", + "HXLocation", + "HXStopPolling", + "PushUrl", + "ReplaceUrl", + "Reswap", + "Retarget", + "TriggerEvent", +) + + +# HTMX defined HTTP status code. +# Response carrying this status code will ask client to stop Polling. +T = TypeVar("T") + + +class HXStopPolling(Response): + """Stop HTMX client from Polling.""" + + def __init__(self) -> None: + """Initialize""" + super().__init__(content=None) + self.status_code = HTMX_STOP_POLLING + + +class ClientRedirect(Response): + """HTMX Response class to support client side redirect.""" + + def __init__(self, redirect_to: str) -> None: + """Set status code to 200 (required by HTMX), and pass redirect url.""" + super().__init__(content=None, headers=get_headers(hx_headers=HtmxHeaderType(redirect=redirect_to))) + del self.headers["Location"] + + +class ClientRefresh(Response): + """Response to support HTMX client page refresh""" + + def __init__(self) -> None: + """Set Status code to 200 and set headers.""" + super().__init__(content=None, headers=get_headers(hx_headers=HtmxHeaderType(refresh=True))) + + +class PushUrl(Generic[T], Response[T]): + """Response to push new url into the history stack.""" + + def __init__(self, content: T, push_url: PushUrlType, **kwargs: Any) -> None: + """Initialize PushUrl.""" + super().__init__( + content=content, + status_code=HTTP_200_OK, + headers=get_headers(hx_headers=HtmxHeaderType(push_url=push_url)), + **kwargs, + ) + + +class ReplaceUrl(Generic[T], Response[T]): + """Response to replace url in the Browser Location bar.""" + + def __init__(self, content: T, replace_url: PushUrlType, **kwargs: Any) -> None: + """Initialize ReplaceUrl.""" + super().__init__( + content=content, + status_code=HTTP_200_OK, + headers=get_headers(hx_headers=HtmxHeaderType(replace_url=replace_url)), + **kwargs, + ) + + +class Reswap(Generic[T], Response[T]): + """Response to specify how the response will be swapped.""" + + def __init__( + self, + content: T, + method: ReSwapMethod, + **kwargs: Any, + ) -> None: + """Initialize Reswap.""" + super().__init__(content=content, headers=get_headers(hx_headers=HtmxHeaderType(re_swap=method)), **kwargs) + + +class Retarget(Generic[T], Response[T]): + """Response to target different element on the page.""" + + def __init__(self, content: T, target: str, **kwargs: Any) -> None: + """Initialize Retarget.""" + super().__init__(content=content, headers=get_headers(hx_headers=HtmxHeaderType(re_target=target)), **kwargs) + + +class TriggerEvent(Generic[T], Response[T]): + """Trigger Client side event.""" + + def __init__( + self, + content: T, + name: str, + after: EventAfterType, + params: dict[str, Any] | None = None, + **kwargs: Any, + ) -> None: + """Initialize TriggerEvent.""" + event = TriggerEventType(name=name, params=params, after=after) + headers = get_headers(hx_headers=HtmxHeaderType(trigger_event=event)) + super().__init__(content=content, headers=headers, **kwargs) + + +class HXLocation(Response): + """Client side redirect without full page reload.""" + + def __init__( + self, + redirect_to: str, + source: str | None = None, + event: str | None = None, + target: str | None = None, + swap: ReSwapMethod | None = None, + hx_headers: dict[str, Any] | None = None, + values: dict[str, str] | None = None, + **kwargs: Any, + ) -> None: + """Initialize HXLocation, Set status code to 200 (required by HTMX), + and pass redirect url. + """ + super().__init__( + content=None, + headers={"Location": quote(redirect_to, safe="/#%[]=:;$&()+,!?*@'~")}, + **kwargs, + ) + spec: dict[str, Any] = get_headers( + hx_headers=HtmxHeaderType( + location=LocationType( + path=str(self.headers.get("Location")), + source=source, + event=event, + target=target, + swap=swap, + values=values, + hx_headers=hx_headers, + ) + ) + ) + del self.headers["Location"] + self.headers.update(spec) + + +class HTMXTemplate(Template): + """HTMX template wrapper""" + + def __init__( + self, + push_url: PushUrlType | None = None, + re_swap: ReSwapMethod | None = None, + re_target: str | None = None, + trigger_event: str | None = None, + params: dict[str, Any] | None = None, + after: EventAfterType | None = None, + **kwargs: Any, + ) -> None: + """Create HTMXTemplate response. + + Args: + push_url: Either a string value specifying a URL to push to browser history or ``False`` to prevent HTMX client from + pushing a url to browser history. + re_swap: Method value to instruct HTMX which swapping method to use. + re_target: Value for 'id of target element' to apply changes to. + trigger_event: Event name to trigger. + params: Dictionary of parameters if any required with trigger event parameter. + after: Changes to apply after ``receive``, ``settle`` or ``swap`` event. + **kwargs: Additional arguments to pass to ``Template``. + """ + super().__init__(**kwargs) + + event: TriggerEventType | None = None + if trigger_event: + event = TriggerEventType(name=str(trigger_event), params=params, after=after) + + self.headers.update( + get_headers(HtmxHeaderType(push_url=push_url, re_swap=re_swap, re_target=re_target, trigger_event=event)) + ) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/htmx/types.py b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/types.py new file mode 100644 index 0000000..aa8f9cd --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/htmx/types.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Literal, TypedDict, Union + +__all__ = ( + "HtmxHeaderType", + "LocationType", + "TriggerEventType", +) + +if TYPE_CHECKING: + from typing_extensions import Required + + +EventAfterType = Literal["receive", "settle", "swap", None] + +PushUrlType = Union[str, bool] + +ReSwapMethod = Literal[ + "innerHTML", "outerHTML", "beforebegin", "afterbegin", "beforeend", "afterend", "delete", "none", None +] + + +class LocationType(TypedDict): + """Type for HX-Location header.""" + + path: Required[str] + source: str | None + event: str | None + target: str | None + swap: ReSwapMethod | None + values: dict[str, str] | None + hx_headers: dict[str, Any] | None + + +class TriggerEventType(TypedDict): + """Type for HX-Trigger header.""" + + name: Required[str] + params: dict[str, Any] | None + after: EventAfterType | None + + +class HtmxHeaderType(TypedDict, total=False): + """Type for hx_headers parameter in get_headers().""" + + location: LocationType | None + redirect: str | None + refresh: bool + push_url: PushUrlType | None + replace_url: PushUrlType | None + re_swap: ReSwapMethod | None + re_target: str | None + trigger_event: TriggerEventType | None diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jinja.py b/venv/lib/python3.11/site-packages/litestar/contrib/jinja.py new file mode 100644 index 0000000..4e8057b --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jinja.py @@ -0,0 +1,114 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Mapping, TypeVar + +from typing_extensions import ParamSpec + +from litestar.exceptions import ImproperlyConfiguredException, MissingDependencyException, TemplateNotFoundException +from litestar.template.base import ( + TemplateCallableType, + TemplateEngineProtocol, + csrf_token, + url_for, + url_for_static_asset, +) + +try: + from jinja2 import Environment, FileSystemLoader, pass_context + from jinja2 import TemplateNotFound as JinjaTemplateNotFound +except ImportError as e: + raise MissingDependencyException("jinja2", extra="jinja") from e + +if TYPE_CHECKING: + from pathlib import Path + + from jinja2 import Template as JinjaTemplate + +__all__ = ("JinjaTemplateEngine",) + +P = ParamSpec("P") +T = TypeVar("T") + + +class JinjaTemplateEngine(TemplateEngineProtocol["JinjaTemplate", Mapping[str, Any]]): + """The engine instance.""" + + def __init__( + self, + directory: Path | list[Path] | None = None, + engine_instance: Environment | None = None, + ) -> None: + """Jinja-based TemplateEngine. + + Args: + directory: Direct path or list of directory paths from which to serve templates. + engine_instance: A jinja Environment instance. + """ + + super().__init__(directory, engine_instance) + if directory and engine_instance: + raise ImproperlyConfiguredException("You must provide either a directory or a jinja2 Environment instance.") + if directory: + loader = FileSystemLoader(searchpath=directory) + self.engine = Environment(loader=loader, autoescape=True) + elif engine_instance: + self.engine = engine_instance + self.register_template_callable(key="url_for_static_asset", template_callable=url_for_static_asset) + self.register_template_callable(key="csrf_token", template_callable=csrf_token) + self.register_template_callable(key="url_for", template_callable=url_for) + + def get_template(self, template_name: str) -> JinjaTemplate: + """Retrieve a template by matching its name (dotted path) with files in the directory or directories provided. + + Args: + template_name: A dotted path + + Returns: + JinjaTemplate instance + + Raises: + TemplateNotFoundException: if no template is found. + """ + try: + return self.engine.get_template(name=template_name) + except JinjaTemplateNotFound as exc: + raise TemplateNotFoundException(template_name=template_name) from exc + + def register_template_callable( + self, key: str, template_callable: TemplateCallableType[Mapping[str, Any], P, T] + ) -> None: + """Register a callable on the template engine. + + Args: + key: The callable key, i.e. the value to use inside the template to call the callable. + template_callable: A callable to register. + + Returns: + None + """ + self.engine.globals[key] = pass_context(template_callable) + + def render_string(self, template_string: str, context: Mapping[str, Any]) -> str: + """Render a template from a string with the given context. + + Args: + template_string: The template string to render. + context: A dictionary of variables to pass to the template. + + Returns: + The rendered template as a string. + """ + template = self.engine.from_string(template_string) + return template.render(context) + + @classmethod + def from_environment(cls, jinja_environment: Environment) -> JinjaTemplateEngine: + """Create a JinjaTemplateEngine from an existing jinja Environment instance. + + Args: + jinja_environment (jinja2.environment.Environment): A jinja Environment instance. + + Returns: + JinjaTemplateEngine instance + """ + return cls(directory=None, engine_instance=jinja_environment) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__init__.py new file mode 100644 index 0000000..70a4f75 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__init__.py @@ -0,0 +1,32 @@ +from litestar.contrib.jwt.jwt_auth import ( + BaseJWTAuth, + JWTAuth, + JWTCookieAuth, + OAuth2Login, + OAuth2PasswordBearerAuth, +) +from litestar.contrib.jwt.jwt_token import Token +from litestar.contrib.jwt.middleware import ( + JWTAuthenticationMiddleware, + JWTCookieAuthenticationMiddleware, +) +from litestar.utils import warn_deprecation + +__all__ = ( + "BaseJWTAuth", + "JWTAuth", + "JWTAuthenticationMiddleware", + "JWTCookieAuth", + "JWTCookieAuthenticationMiddleware", + "OAuth2Login", + "OAuth2PasswordBearerAuth", + "Token", +) + +warn_deprecation( + deprecated_name="litestar.contrib.jwt", + version="2.3.2", + kind="import", + removal_in="3.0", + info="importing from 'litestar.contrib.jwt' is deprecated, please import from 'litestar.security.jwt' instead", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..1515862 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/jwt_auth.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/jwt_auth.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..44907f6 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/jwt_auth.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/jwt_token.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/jwt_token.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..2ffdb24 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/jwt_token.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/middleware.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/middleware.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..334e3fa --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/__pycache__/middleware.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jwt/jwt_auth.py b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/jwt_auth.py new file mode 100644 index 0000000..e8ffb48 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/jwt_auth.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from litestar.security.jwt.auth import BaseJWTAuth, JWTAuth, JWTCookieAuth, OAuth2Login, OAuth2PasswordBearerAuth + +__all__ = ("BaseJWTAuth", "JWTAuth", "JWTCookieAuth", "OAuth2Login", "OAuth2PasswordBearerAuth") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jwt/jwt_token.py b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/jwt_token.py new file mode 100644 index 0000000..882373f --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/jwt_token.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from litestar.security.jwt.token import Token + +__all__ = ("Token",) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/jwt/middleware.py b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/middleware.py new file mode 100644 index 0000000..e0ad413 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/jwt/middleware.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from litestar.security.jwt.middleware import JWTAuthenticationMiddleware, JWTCookieAuthenticationMiddleware + +__all__ = ("JWTAuthenticationMiddleware", "JWTCookieAuthenticationMiddleware") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/mako.py b/venv/lib/python3.11/site-packages/litestar/contrib/mako.py new file mode 100644 index 0000000..9cb4c47 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/mako.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +from functools import partial +from typing import TYPE_CHECKING, Any, Mapping, TypeVar + +from typing_extensions import ParamSpec + +from litestar.exceptions import ImproperlyConfiguredException, MissingDependencyException, TemplateNotFoundException +from litestar.template.base import ( + TemplateCallableType, + TemplateEngineProtocol, + TemplateProtocol, + csrf_token, + url_for, + url_for_static_asset, +) + +try: + from mako.exceptions import TemplateLookupException as MakoTemplateNotFound # type: ignore[import-untyped] + from mako.lookup import TemplateLookup # type: ignore[import-untyped] + from mako.template import Template as _MakoTemplate # type: ignore[import-untyped] +except ImportError as e: + raise MissingDependencyException("mako") from e + +if TYPE_CHECKING: + from pathlib import Path + +__all__ = ("MakoTemplate", "MakoTemplateEngine") + +P = ParamSpec("P") +T = TypeVar("T") + + +class MakoTemplate(TemplateProtocol): + """Mako template, implementing ``TemplateProtocol``""" + + def __init__(self, template: _MakoTemplate, template_callables: list[tuple[str, TemplateCallableType]]) -> None: + """Initialize a template. + + Args: + template: Base ``MakoTemplate`` used by the underlying mako-engine + template_callables: List of callables passed to the template + """ + super().__init__() + self.template = template + self.template_callables = template_callables + + def render(self, *args: Any, **kwargs: Any) -> str: + """Render a template. + + Args: + args: Positional arguments passed to the engines ``render`` function + kwargs: Keyword arguments passed to the engines ``render`` function + + Returns: + Rendered template as a string + """ + for callable_key, template_callable in self.template_callables: + kwargs_copy = {**kwargs} + kwargs[callable_key] = partial(template_callable, kwargs_copy) + + return str(self.template.render(*args, **kwargs)) + + +class MakoTemplateEngine(TemplateEngineProtocol[MakoTemplate, Mapping[str, Any]]): + """Mako-based TemplateEngine.""" + + def __init__(self, directory: Path | list[Path] | None = None, engine_instance: Any | None = None) -> None: + """Initialize template engine. + + Args: + directory: Direct path or list of directory paths from which to serve templates. + engine_instance: A mako TemplateLookup instance. + """ + super().__init__(directory, engine_instance) + if directory and engine_instance: + raise ImproperlyConfiguredException("You must provide either a directory or a mako TemplateLookup.") + if directory: + self.engine = TemplateLookup( + directories=directory if isinstance(directory, (list, tuple)) else [directory], default_filters=["h"] + ) + elif engine_instance: + self.engine = engine_instance + + self._template_callables: list[tuple[str, TemplateCallableType]] = [] + self.register_template_callable(key="url_for_static_asset", template_callable=url_for_static_asset) + self.register_template_callable(key="csrf_token", template_callable=csrf_token) + self.register_template_callable(key="url_for", template_callable=url_for) + + def get_template(self, template_name: str) -> MakoTemplate: + """Retrieve a template by matching its name (dotted path) with files in the directory or directories provided. + + Args: + template_name: A dotted path + + Returns: + MakoTemplate instance + + Raises: + TemplateNotFoundException: if no template is found. + """ + try: + return MakoTemplate( + template=self.engine.get_template(template_name), template_callables=self._template_callables + ) + except MakoTemplateNotFound as exc: + raise TemplateNotFoundException(template_name=template_name) from exc + + def register_template_callable( + self, key: str, template_callable: TemplateCallableType[Mapping[str, Any], P, T] + ) -> None: + """Register a callable on the template engine. + + Args: + key: The callable key, i.e. the value to use inside the template to call the callable. + template_callable: A callable to register. + + Returns: + None + """ + self._template_callables.append((key, template_callable)) + + def render_string(self, template_string: str, context: Mapping[str, Any]) -> str: # pyright: ignore + """Render a template from a string with the given context. + + Args: + template_string: The template string to render. + context: A dictionary of variables to pass to the template. + + Returns: + The rendered template as a string. + """ + template = _MakoTemplate(template_string) # noqa: S702 + return template.render(**context) # type: ignore[no-any-return] + + @classmethod + def from_template_lookup(cls, template_lookup: TemplateLookup) -> MakoTemplateEngine: + """Create a template engine from an existing mako TemplateLookup instance. + + Args: + template_lookup: A mako TemplateLookup instance. + + Returns: + MakoTemplateEngine instance + """ + return cls(directory=None, engine_instance=template_lookup) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/minijinja.py b/venv/lib/python3.11/site-packages/litestar/contrib/minijinja.py new file mode 100644 index 0000000..6007a18 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/minijinja.py @@ -0,0 +1,216 @@ +from __future__ import annotations + +import functools +from pathlib import Path +from typing import TYPE_CHECKING, Any, Mapping, Protocol, TypeVar, cast + +from typing_extensions import ParamSpec + +from litestar.exceptions import ImproperlyConfiguredException, MissingDependencyException, TemplateNotFoundException +from litestar.template.base import ( + TemplateCallableType, + TemplateEngineProtocol, + TemplateProtocol, + csrf_token, + url_for, + url_for_static_asset, +) +from litestar.utils.deprecation import warn_deprecation + +try: + from minijinja import Environment # type:ignore[import-untyped] + from minijinja import TemplateError as MiniJinjaTemplateNotFound +except ImportError as e: + raise MissingDependencyException("minijinja") from e + +if TYPE_CHECKING: + from typing import Callable + + C = TypeVar("C", bound="Callable") + + def pass_state(func: C) -> C: ... + +else: + from minijinja import pass_state + +__all__ = ( + "MiniJinjaTemplateEngine", + "StateProtocol", +) + +P = ParamSpec("P") +T = TypeVar("T") + + +class StateProtocol(Protocol): + auto_escape: str | None + current_block: str | None + env: Environment + name: str + + def lookup(self, key: str) -> Any | None: ... + + +def _transform_state(func: TemplateCallableType[Mapping[str, Any], P, T]) -> TemplateCallableType[StateProtocol, P, T]: + """Transform a template callable to receive a ``StateProtocol`` instance as first argument. + + This is for wrapping callables like ``url_for()`` that receive a mapping as first argument so they can be used + with minijinja which passes a ``StateProtocol`` instance as first argument. + """ + + @functools.wraps(func) + @pass_state + def wrapped(state: StateProtocol, /, *args: P.args, **kwargs: P.kwargs) -> T: + template_context = {"request": state.lookup("request"), "csrf_input": state.lookup("csrf_input")} + return func(template_context, *args, **kwargs) + + return wrapped + + +class MiniJinjaTemplate(TemplateProtocol): + """Initialize a template. + + Args: + template: Base ``MiniJinjaTemplate`` used by the underlying minijinja engine + """ + + def __init__(self, engine: Environment, template_name: str) -> None: + super().__init__() + self.engine = engine + self.template_name = template_name + + def render(self, *args: Any, **kwargs: Any) -> str: + """Render a template. + + Args: + args: Positional arguments passed to the engines ``render`` function + kwargs: Keyword arguments passed to the engines ``render`` function + + Returns: + Rendered template as a string + """ + try: + return str(self.engine.render_template(self.template_name, *args, **kwargs)) + except MiniJinjaTemplateNotFound as err: + raise TemplateNotFoundException(template_name=self.template_name) from err + + +class MiniJinjaTemplateEngine(TemplateEngineProtocol["MiniJinjaTemplate", StateProtocol]): + """The engine instance.""" + + def __init__(self, directory: Path | list[Path] | None = None, engine_instance: Environment | None = None) -> None: + """Minijinja based TemplateEngine. + + Args: + directory: Direct path or list of directory paths from which to serve templates. + engine_instance: A Minijinja Environment instance. + """ + super().__init__(directory, engine_instance) + if directory and engine_instance: + raise ImproperlyConfiguredException( + "You must provide either a directory or a minijinja Environment instance." + ) + if directory: + + def _loader(name: str) -> str: + """Load a template from a directory. + + Args: + name: The name of the template + + Returns: + The template as a string + + Raises: + TemplateNotFoundException: if no template is found. + """ + directories = directory if isinstance(directory, list) else [directory] + + for d in directories: + template_path = Path(d) / name # pyright: ignore[reportGeneralTypeIssues] + if template_path.exists(): + return template_path.read_text() + raise TemplateNotFoundException(template_name=name) + + self.engine = Environment(loader=_loader) + elif engine_instance: + self.engine = engine_instance + else: + raise ImproperlyConfiguredException( + "You must provide either a directory or a minijinja Environment instance." + ) + + self.register_template_callable("url_for", _transform_state(url_for)) + self.register_template_callable("csrf_token", _transform_state(csrf_token)) + self.register_template_callable("url_for_static_asset", _transform_state(url_for_static_asset)) + + def get_template(self, template_name: str) -> MiniJinjaTemplate: + """Retrieve a template by matching its name (dotted path) with files in the directory or directories provided. + + Args: + template_name: A dotted path + + Returns: + MiniJinjaTemplate instance + + Raises: + TemplateNotFoundException: if no template is found. + """ + return MiniJinjaTemplate(self.engine, template_name) + + def register_template_callable( + self, key: str, template_callable: TemplateCallableType[StateProtocol, P, T] + ) -> None: + """Register a callable on the template engine. + + Args: + key: The callable key, i.e. the value to use inside the template to call the callable. + template_callable: A callable to register. + + Returns: + None + """ + self.engine.add_global(key, pass_state(template_callable)) + + def render_string(self, template_string: str, context: Mapping[str, Any]) -> str: + """Render a template from a string with the given context. + + Args: + template_string: The template string to render. + context: A dictionary of variables to pass to the template. + + Returns: + The rendered template as a string. + """ + return self.engine.render_str(template_string, **context) # type: ignore[no-any-return] + + @classmethod + def from_environment(cls, minijinja_environment: Environment) -> MiniJinjaTemplateEngine: + """Create a MiniJinjaTemplateEngine from an existing minijinja Environment instance. + + Args: + minijinja_environment (Environment): A minijinja Environment instance. + + Returns: + MiniJinjaTemplateEngine instance + """ + return cls(directory=None, engine_instance=minijinja_environment) + + +@pass_state +def _minijinja_from_state(func: Callable, state: StateProtocol, *args: Any, **kwargs: Any) -> str: # pragma: no cover + template_context = {"request": state.lookup("request"), "csrf_input": state.lookup("csrf_input")} + return cast(str, func(template_context, *args, **kwargs)) + + +def __getattr__(name: str) -> Any: + if name == "minijinja_from_state": + warn_deprecation( + "2.3.0", + "minijinja_from_state", + "import", + removal_in="3.0.0", + alternative="Use a callable that receives the minijinja State object as first argument.", + ) + return _minijinja_from_state + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/minijnja.py b/venv/lib/python3.11/site-packages/litestar/contrib/minijnja.py new file mode 100644 index 0000000..13c295a --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/minijnja.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from typing import Any + +from litestar.utils.deprecation import warn_deprecation + +from . import minijinja as _minijinja + + +def __getattr__(name: str) -> Any: + warn_deprecation( + "2.3.0", + "contrib.minijnja", + "import", + removal_in="3.0.0", + alternative="contrib.minijinja", + ) + return getattr(_minijinja, name) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__init__.py new file mode 100644 index 0000000..3f93611 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__init__.py @@ -0,0 +1,4 @@ +from .config import OpenTelemetryConfig +from .middleware import OpenTelemetryInstrumentationMiddleware + +__all__ = ("OpenTelemetryConfig", "OpenTelemetryInstrumentationMiddleware") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..799a915 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/_utils.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/_utils.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..c401f9c --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/_utils.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/config.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/config.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..b41bc7e --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/config.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/middleware.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/middleware.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..291b510 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/__pycache__/middleware.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/_utils.py b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/_utils.py new file mode 100644 index 0000000..0ba7cb9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/_utils.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from litestar.exceptions import MissingDependencyException + +__all__ = ("get_route_details_from_scope",) + + +try: + import opentelemetry # noqa: F401 +except ImportError as e: + raise MissingDependencyException("opentelemetry") from e + +from opentelemetry.semconv.trace import SpanAttributes + +if TYPE_CHECKING: + from litestar.types import Scope + + +def get_route_details_from_scope(scope: Scope) -> tuple[str, dict[Any, str]]: + """Retrieve the span name and attributes from the ASGI scope. + + Args: + scope: The ASGI scope instance. + + Returns: + A tuple of the span name and a dict of attrs. + """ + route_handler_fn_name = scope["route_handler"].handler_name + return route_handler_fn_name, {SpanAttributes.HTTP_ROUTE: route_handler_fn_name} diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/config.py b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/config.py new file mode 100644 index 0000000..c0cce8a --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/config.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Callable + +from litestar.contrib.opentelemetry._utils import get_route_details_from_scope +from litestar.contrib.opentelemetry.middleware import ( + OpenTelemetryInstrumentationMiddleware, +) +from litestar.exceptions import MissingDependencyException +from litestar.middleware.base import DefineMiddleware + +__all__ = ("OpenTelemetryConfig",) + + +try: + import opentelemetry # noqa: F401 +except ImportError as e: + raise MissingDependencyException("opentelemetry") from e + + +from opentelemetry.trace import Span, TracerProvider # pyright: ignore + +if TYPE_CHECKING: + from opentelemetry.metrics import Meter, MeterProvider + + from litestar.types import Scope, Scopes + +OpenTelemetryHookHandler = Callable[[Span, dict], None] + + +@dataclass +class OpenTelemetryConfig: + """Configuration class for the OpenTelemetry middleware. + + Consult the [OpenTelemetry ASGI documentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asgi/asgi.html) for more info about the configuration options. + """ + + scope_span_details_extractor: Callable[[Scope], tuple[str, dict[str, Any]]] = field( + default=get_route_details_from_scope + ) + """Callback which should return a string and a tuple, representing the desired default span name and a dictionary + with any additional span attributes to set. + """ + server_request_hook_handler: OpenTelemetryHookHandler | None = field(default=None) + """Optional callback which is called with the server span and ASGI scope object for every incoming request.""" + client_request_hook_handler: OpenTelemetryHookHandler | None = field(default=None) + """Optional callback which is called with the internal span and an ASGI scope which is sent as a dictionary for when + the method receive is called. + """ + client_response_hook_handler: OpenTelemetryHookHandler | None = field(default=None) + """Optional callback which is called with the internal span and an ASGI event which is sent as a dictionary for when + the method send is called. + """ + meter_provider: MeterProvider | None = field(default=None) + """Optional meter provider to use. + + If omitted the current globally configured one is used. + """ + tracer_provider: TracerProvider | None = field(default=None) + """Optional tracer provider to use. + + If omitted the current globally configured one is used. + """ + meter: Meter | None = field(default=None) + """Optional meter to use. + + If omitted the provided meter provider or the global one will be used. + """ + exclude: str | list[str] | None = field(default=None) + """A pattern or list of patterns to skip in the Allowed Hosts middleware.""" + exclude_opt_key: str | None = field(default=None) + """An identifier to use on routes to disable hosts check for a particular route.""" + exclude_urls_env_key: str = "LITESTAR" + """Key to use when checking whether a list of excluded urls is passed via ENV. + + OpenTelemetry supports excluding urls by passing an env in the format '{exclude_urls_env_key}_EXCLUDED_URLS'. With + the default being ``LITESTAR_EXCLUDED_URLS``. + """ + scopes: Scopes | None = field(default=None) + """ASGI scopes processed by the middleware, if None both ``http`` and ``websocket`` will be processed.""" + middleware_class: type[OpenTelemetryInstrumentationMiddleware] = field( + default=OpenTelemetryInstrumentationMiddleware + ) + """The middleware class to use. + + Should be a subclass of OpenTelemetry + InstrumentationMiddleware][litestar.contrib.opentelemetry.OpenTelemetryInstrumentationMiddleware]. + """ + + @property + def middleware(self) -> DefineMiddleware: + """Create an instance of :class:`DefineMiddleware <litestar.middleware.base.DefineMiddleware>` that wraps with. + + [OpenTelemetry + InstrumentationMiddleware][litestar.contrib.opentelemetry.OpenTelemetryInstrumentationMiddleware] or a subclass + of this middleware. + + Returns: + An instance of ``DefineMiddleware``. + """ + return DefineMiddleware(self.middleware_class, config=self) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/middleware.py b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/middleware.py new file mode 100644 index 0000000..762bae9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/opentelemetry/middleware.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from litestar.exceptions import MissingDependencyException +from litestar.middleware.base import AbstractMiddleware + +__all__ = ("OpenTelemetryInstrumentationMiddleware",) + + +try: + import opentelemetry # noqa: F401 +except ImportError as e: + raise MissingDependencyException("opentelemetry") from e + +from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware +from opentelemetry.util.http import get_excluded_urls + +if TYPE_CHECKING: + from litestar.contrib.opentelemetry import OpenTelemetryConfig + from litestar.types import ASGIApp, Receive, Scope, Send + + +class OpenTelemetryInstrumentationMiddleware(AbstractMiddleware): + """OpenTelemetry Middleware.""" + + __slots__ = ("open_telemetry_middleware",) + + def __init__(self, app: ASGIApp, config: OpenTelemetryConfig) -> None: + """Middleware that adds OpenTelemetry instrumentation to the application. + + Args: + app: The ``next`` ASGI app to call. + config: An instance of :class:`OpenTelemetryConfig <.contrib.opentelemetry.OpenTelemetryConfig>` + """ + super().__init__(app=app, scopes=config.scopes, exclude=config.exclude, exclude_opt_key=config.exclude_opt_key) + self.open_telemetry_middleware = OpenTelemetryMiddleware( + app=app, + client_request_hook=config.client_request_hook_handler, + client_response_hook=config.client_response_hook_handler, + default_span_details=config.scope_span_details_extractor, + excluded_urls=get_excluded_urls(config.exclude_urls_env_key), + meter=config.meter, + meter_provider=config.meter_provider, + server_request_hook=config.server_request_hook_handler, + tracer_provider=config.tracer_provider, + ) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + """ASGI callable. + + Args: + scope: The ASGI connection scope. + receive: The ASGI receive function. + send: The ASGI send function. + + Returns: + None + """ + await self.open_telemetry_middleware(scope, receive, send) # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues] diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/piccolo.py b/venv/lib/python3.11/site-packages/litestar/contrib/piccolo.py new file mode 100644 index 0000000..73bd271 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/piccolo.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import warnings +from dataclasses import replace +from decimal import Decimal +from typing import Any, Generator, Generic, List, Optional, TypeVar + +from msgspec import Meta +from typing_extensions import Annotated + +from litestar.dto import AbstractDTO, DTOField, Mark +from litestar.dto.data_structures import DTOFieldDefinition +from litestar.exceptions import LitestarWarning, MissingDependencyException +from litestar.types import Empty +from litestar.typing import FieldDefinition +from litestar.utils import warn_deprecation + +try: + from piccolo.columns import Column, column_types + from piccolo.table import Table +except ImportError as e: + raise MissingDependencyException("piccolo") from e + + +T = TypeVar("T", bound=Table) + +__all__ = ("PiccoloDTO",) + + +def __getattr__(name: str) -> Any: + warn_deprecation( + deprecated_name=f"litestar.contrib.piccolo.{name}", + version="2.3.2", + kind="import", + removal_in="3.0.0", + info="importing from 'litestar.contrib.piccolo' is deprecated and will be removed in 3.0, please import from 'litestar_piccolo' package directly instead", + ) + return getattr(name, name) + + +def _parse_piccolo_type(column: Column, extra: dict[str, Any]) -> FieldDefinition: + is_optional = not column._meta.required + + if isinstance(column, (column_types.Decimal, column_types.Numeric)): + column_type: Any = Decimal + meta = Meta(extra=extra) + elif isinstance(column, (column_types.Email, column_types.Varchar)): + column_type = str + if is_optional: + meta = Meta(extra=extra) + warnings.warn( + f"Dropping max_length constraint for column {column!r} because the " "column is optional", + category=LitestarWarning, + stacklevel=2, + ) + else: + meta = Meta(max_length=column.length, extra=extra) + elif isinstance(column, column_types.Array): + column_type = List[column.base_column.value_type] # type: ignore[name-defined] + meta = Meta(extra=extra) + elif isinstance(column, (column_types.JSON, column_types.JSONB)): + column_type = str + meta = Meta(extra={**extra, "format": "json"}) + elif isinstance(column, column_types.Text): + column_type = str + meta = Meta(extra={**extra, "format": "text-area"}) + else: + column_type = column.value_type + meta = Meta(extra=extra) + + if is_optional: + column_type = Optional[column_type] + + return FieldDefinition.from_annotation(Annotated[column_type, meta]) + + +def _create_column_extra(column: Column) -> dict[str, Any]: + extra: dict[str, Any] = {} + + if column._meta.help_text: + extra["description"] = column._meta.help_text + + if column._meta.get_choices_dict(): + extra["enum"] = column._meta.get_choices_dict() + + return extra + + +class PiccoloDTO(AbstractDTO[T], Generic[T]): + @classmethod + def generate_field_definitions(cls, model_type: type[Table]) -> Generator[DTOFieldDefinition, None, None]: + for column in model_type._meta.columns: + mark = Mark.WRITE_ONLY if column._meta.secret else Mark.READ_ONLY if column._meta.primary_key else None + yield replace( + DTOFieldDefinition.from_field_definition( + field_definition=_parse_piccolo_type(column, _create_column_extra(column)), + dto_field=DTOField(mark=mark), + model_name=model_type.__name__, + default_factory=None, + ), + default=Empty if column._meta.required else None, + name=column._meta.name, + ) + + @classmethod + def detect_nested_field(cls, field_definition: FieldDefinition) -> bool: + return field_definition.is_subclass_of(Table) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__init__.py new file mode 100644 index 0000000..1ccb494 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__init__.py @@ -0,0 +1,5 @@ +from .config import PrometheusConfig +from .controller import PrometheusController +from .middleware import PrometheusMiddleware + +__all__ = ("PrometheusMiddleware", "PrometheusConfig", "PrometheusController") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..c6c5558 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/config.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/config.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..a998104 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/config.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/controller.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/controller.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..012b4be --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/controller.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/middleware.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/middleware.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..2c08508 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/__pycache__/middleware.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/config.py b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/config.py new file mode 100644 index 0000000..b77dab0 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/config.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Callable, Mapping, Sequence + +from litestar.contrib.prometheus.middleware import ( + PrometheusMiddleware, +) +from litestar.exceptions import MissingDependencyException +from litestar.middleware.base import DefineMiddleware + +__all__ = ("PrometheusConfig",) + + +try: + import prometheus_client # noqa: F401 +except ImportError as e: + raise MissingDependencyException("prometheus_client", "prometheus-client", "prometheus") from e + + +if TYPE_CHECKING: + from litestar.connection.request import Request + from litestar.types import Method, Scopes + + +@dataclass +class PrometheusConfig: + """Configuration class for the PrometheusConfig middleware.""" + + app_name: str = field(default="litestar") + """The name of the application to use in the metrics.""" + prefix: str = "litestar" + """The prefix to use for the metrics.""" + labels: Mapping[str, str | Callable] | None = field(default=None) + """A mapping of labels to add to the metrics. The values can be either a string or a callable that returns a string.""" + exemplars: Callable[[Request], dict] | None = field(default=None) + """A callable that returns a list of exemplars to add to the metrics. Only supported in opementrics-text exposition format.""" + buckets: list[str | float] | None = field(default=None) + """A list of buckets to use for the histogram.""" + excluded_http_methods: Method | Sequence[Method] | None = field(default=None) + """A list of http methods to exclude from the metrics.""" + exclude_unhandled_paths: bool = field(default=False) + """Whether to ignore requests for unhandled paths from the metrics.""" + exclude: str | list[str] | None = field(default=None) + """A pattern or list of patterns for routes to exclude from the metrics.""" + exclude_opt_key: str | None = field(default=None) + """A key or list of keys in ``opt`` with which a route handler can "opt-out" of the middleware.""" + scopes: Scopes | None = field(default=None) + """ASGI scopes processed by the middleware, if None both ``http`` and ``websocket`` will be processed.""" + middleware_class: type[PrometheusMiddleware] = field(default=PrometheusMiddleware) + """The middleware class to use. + """ + + @property + def middleware(self) -> DefineMiddleware: + """Create an instance of :class:`DefineMiddleware <litestar.middleware.base.DefineMiddleware>` that wraps with. + + [PrometheusMiddleware][litestar.contrib.prometheus.PrometheusMiddleware]. or a subclass + of this middleware. + + Returns: + An instance of ``DefineMiddleware``. + """ + return DefineMiddleware(self.middleware_class, config=self) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/controller.py b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/controller.py new file mode 100644 index 0000000..15f5bf1 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/controller.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import os + +from litestar import Controller, get +from litestar.exceptions import MissingDependencyException +from litestar.response import Response + +try: + import prometheus_client # noqa: F401 +except ImportError as e: + raise MissingDependencyException("prometheus_client", "prometheus-client", "prometheus") from e + +from prometheus_client import ( + CONTENT_TYPE_LATEST, + REGISTRY, + CollectorRegistry, + generate_latest, + multiprocess, +) +from prometheus_client.openmetrics.exposition import ( + CONTENT_TYPE_LATEST as OPENMETRICS_CONTENT_TYPE_LATEST, +) +from prometheus_client.openmetrics.exposition import ( + generate_latest as openmetrics_generate_latest, +) + +__all__ = [ + "PrometheusController", +] + + +class PrometheusController(Controller): + """Controller for Prometheus endpoints.""" + + path: str = "/metrics" + """The path to expose the metrics on.""" + openmetrics_format: bool = False + """Whether to expose the metrics in OpenMetrics format.""" + + @get() + async def get(self) -> Response: + registry = REGISTRY + if "prometheus_multiproc_dir" in os.environ or "PROMETHEUS_MULTIPROC_DIR" in os.environ: + registry = CollectorRegistry() + multiprocess.MultiProcessCollector(registry) # type: ignore[no-untyped-call] + + if self.openmetrics_format: + headers = {"Content-Type": OPENMETRICS_CONTENT_TYPE_LATEST} + return Response(openmetrics_generate_latest(registry), status_code=200, headers=headers) # type: ignore[no-untyped-call] + + headers = {"Content-Type": CONTENT_TYPE_LATEST} + return Response(generate_latest(registry), status_code=200, headers=headers) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/middleware.py b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/middleware.py new file mode 100644 index 0000000..50bc7cb --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/prometheus/middleware.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +import time +from functools import wraps +from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast + +from litestar.connection.request import Request +from litestar.enums import ScopeType +from litestar.exceptions import MissingDependencyException +from litestar.middleware.base import AbstractMiddleware + +__all__ = ("PrometheusMiddleware",) + +from litestar.status_codes import HTTP_500_INTERNAL_SERVER_ERROR + +try: + import prometheus_client # noqa: F401 +except ImportError as e: + raise MissingDependencyException("prometheus_client", "prometheus-client", "prometheus") from e + +from prometheus_client import Counter, Gauge, Histogram + +if TYPE_CHECKING: + from prometheus_client.metrics import MetricWrapperBase + + from litestar.contrib.prometheus import PrometheusConfig + from litestar.types import ASGIApp, Message, Receive, Scope, Send + + +class PrometheusMiddleware(AbstractMiddleware): + """Prometheus Middleware.""" + + _metrics: ClassVar[dict[str, MetricWrapperBase]] = {} + + def __init__(self, app: ASGIApp, config: PrometheusConfig) -> None: + """Middleware that adds Prometheus instrumentation to the application. + + Args: + app: The ``next`` ASGI app to call. + config: An instance of :class:`PrometheusConfig <.contrib.prometheus.PrometheusConfig>` + """ + super().__init__(app=app, scopes=config.scopes, exclude=config.exclude, exclude_opt_key=config.exclude_opt_key) + self._config = config + self._kwargs: dict[str, Any] = {} + + if self._config.buckets is not None: + self._kwargs["buckets"] = self._config.buckets + + def request_count(self, labels: dict[str, str | int | float]) -> Counter: + metric_name = f"{self._config.prefix}_requests_total" + + if metric_name not in PrometheusMiddleware._metrics: + PrometheusMiddleware._metrics[metric_name] = Counter( + name=metric_name, + documentation="Total requests", + labelnames=[*labels.keys()], + ) + + return cast("Counter", PrometheusMiddleware._metrics[metric_name]) + + def request_time(self, labels: dict[str, str | int | float]) -> Histogram: + metric_name = f"{self._config.prefix}_request_duration_seconds" + + if metric_name not in PrometheusMiddleware._metrics: + PrometheusMiddleware._metrics[metric_name] = Histogram( + name=metric_name, + documentation="Request duration, in seconds", + labelnames=[*labels.keys()], + **self._kwargs, + ) + return cast("Histogram", PrometheusMiddleware._metrics[metric_name]) + + def requests_in_progress(self, labels: dict[str, str | int | float]) -> Gauge: + metric_name = f"{self._config.prefix}_requests_in_progress" + + if metric_name not in PrometheusMiddleware._metrics: + PrometheusMiddleware._metrics[metric_name] = Gauge( + name=metric_name, + documentation="Total requests currently in progress", + labelnames=[*labels.keys()], + multiprocess_mode="livesum", + ) + return cast("Gauge", PrometheusMiddleware._metrics[metric_name]) + + def requests_error_count(self, labels: dict[str, str | int | float]) -> Counter: + metric_name = f"{self._config.prefix}_requests_error_total" + + if metric_name not in PrometheusMiddleware._metrics: + PrometheusMiddleware._metrics[metric_name] = Counter( + name=metric_name, + documentation="Total errors in requests", + labelnames=[*labels.keys()], + ) + return cast("Counter", PrometheusMiddleware._metrics[metric_name]) + + def _get_extra_labels(self, request: Request[Any, Any, Any]) -> dict[str, str]: + """Get extra labels provided by the config and if they are callable, parse them. + + Args: + request: The request object. + + Returns: + A dictionary of extra labels. + """ + + return {k: str(v(request) if callable(v) else v) for k, v in (self._config.labels or {}).items()} + + def _get_default_labels(self, request: Request[Any, Any, Any]) -> dict[str, str | int | float]: + """Get default label values from the request. + + Args: + request: The request object. + + Returns: + A dictionary of default labels. + """ + + return { + "method": request.method if request.scope["type"] == ScopeType.HTTP else request.scope["type"], + "path": request.url.path, + "status_code": 200, + "app_name": self._config.app_name, + } + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + """ASGI callable. + + Args: + scope: The ASGI connection scope. + receive: The ASGI receive function. + send: The ASGI send function. + + Returns: + None + """ + + request = Request[Any, Any, Any](scope, receive) + + if self._config.excluded_http_methods and request.method in self._config.excluded_http_methods: + await self.app(scope, receive, send) + return + + labels = {**self._get_default_labels(request), **self._get_extra_labels(request)} + + request_span = {"start_time": time.perf_counter(), "end_time": 0, "duration": 0, "status_code": 200} + + wrapped_send = self._get_wrapped_send(send, request_span) + + self.requests_in_progress(labels).labels(*labels.values()).inc() + + try: + await self.app(scope, receive, wrapped_send) + finally: + extra: dict[str, Any] = {} + if self._config.exemplars: + extra["exemplar"] = self._config.exemplars(request) + + self.requests_in_progress(labels).labels(*labels.values()).dec() + + labels["status_code"] = request_span["status_code"] + label_values = [*labels.values()] + + if request_span["status_code"] >= HTTP_500_INTERNAL_SERVER_ERROR: + self.requests_error_count(labels).labels(*label_values).inc(**extra) + + self.request_count(labels).labels(*label_values).inc(**extra) + self.request_time(labels).labels(*label_values).observe(request_span["duration"], **extra) + + def _get_wrapped_send(self, send: Send, request_span: dict[str, float]) -> Callable: + @wraps(send) + async def wrapped_send(message: Message) -> None: + if message["type"] == "http.response.start": + request_span["status_code"] = message["status"] + + if message["type"] == "http.response.body": + end = time.perf_counter() + request_span["duration"] = end - request_span["start_time"] + request_span["end_time"] = end + await send(message) + + return wrapped_send diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__init__.py new file mode 100644 index 0000000..9bab707 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__init__.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from litestar.plugins import InitPluginProtocol + +from .pydantic_di_plugin import PydanticDIPlugin +from .pydantic_dto_factory import PydanticDTO +from .pydantic_init_plugin import PydanticInitPlugin +from .pydantic_schema_plugin import PydanticSchemaPlugin + +if TYPE_CHECKING: + from pydantic import BaseModel + from pydantic.v1 import BaseModel as BaseModelV1 + + from litestar.config.app import AppConfig + +__all__ = ( + "PydanticDTO", + "PydanticInitPlugin", + "PydanticSchemaPlugin", + "PydanticPlugin", + "PydanticDIPlugin", +) + + +def _model_dump(model: BaseModel | BaseModelV1, *, by_alias: bool = False) -> dict[str, Any]: + return ( + model.model_dump(mode="json", by_alias=by_alias) # pyright: ignore + if hasattr(model, "model_dump") + else {k: v.decode() if isinstance(v, bytes) else v for k, v in model.dict(by_alias=by_alias).items()} + ) + + +def _model_dump_json(model: BaseModel | BaseModelV1, by_alias: bool = False) -> str: + return ( + model.model_dump_json(by_alias=by_alias) # pyright: ignore + if hasattr(model, "model_dump_json") + else model.json(by_alias=by_alias) # pyright: ignore + ) + + +class PydanticPlugin(InitPluginProtocol): + """A plugin that provides Pydantic integration.""" + + __slots__ = ("prefer_alias",) + + def __init__(self, prefer_alias: bool = False) -> None: + """Initialize ``PydanticPlugin``. + + Args: + prefer_alias: OpenAPI and ``type_encoders`` will export by alias + """ + self.prefer_alias = prefer_alias + + def on_app_init(self, app_config: AppConfig) -> AppConfig: + """Configure application for use with Pydantic. + + Args: + app_config: The :class:`AppConfig <.config.app.AppConfig>` instance. + """ + app_config.plugins.extend( + [ + PydanticInitPlugin(prefer_alias=self.prefer_alias), + PydanticSchemaPlugin(prefer_alias=self.prefer_alias), + PydanticDIPlugin(), + ] + ) + return app_config diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..a62eb0f --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/config.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/config.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..5515df6 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/config.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_di_plugin.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_di_plugin.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..f8c4e00 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_di_plugin.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_dto_factory.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_dto_factory.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..490108e --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_dto_factory.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_init_plugin.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_init_plugin.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..13788d4 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_init_plugin.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_schema_plugin.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_schema_plugin.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..8b0946b --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/pydantic_schema_plugin.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/utils.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/utils.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..dbb0e54 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/__pycache__/utils.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/config.py b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/config.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/config.py diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_di_plugin.py b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_di_plugin.py new file mode 100644 index 0000000..2096fd4 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_di_plugin.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +import inspect +from inspect import Signature +from typing import Any + +from litestar.contrib.pydantic.utils import is_pydantic_model_class +from litestar.plugins import DIPlugin + + +class PydanticDIPlugin(DIPlugin): + def has_typed_init(self, type_: Any) -> bool: + return is_pydantic_model_class(type_) + + def get_typed_init(self, type_: Any) -> tuple[Signature, dict[str, Any]]: + try: + model_fields = dict(type_.model_fields) + except AttributeError: + model_fields = {k: model_field.field_info for k, model_field in type_.__fields__.items()} + + parameters = [ + inspect.Parameter(name=field_name, kind=inspect.Parameter.KEYWORD_ONLY, annotation=Any) + for field_name in model_fields + ] + type_hints = {field_name: Any for field_name in model_fields} + return Signature(parameters), type_hints diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_dto_factory.py b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_dto_factory.py new file mode 100644 index 0000000..d61f95d --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_dto_factory.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from dataclasses import replace +from typing import TYPE_CHECKING, Collection, Generic, TypeVar + +from typing_extensions import TypeAlias, override + +from litestar.contrib.pydantic.utils import is_pydantic_undefined +from litestar.dto.base_dto import AbstractDTO +from litestar.dto.data_structures import DTOFieldDefinition +from litestar.dto.field import DTO_FIELD_META_KEY, DTOField +from litestar.exceptions import MissingDependencyException, ValidationException +from litestar.types.empty import Empty + +if TYPE_CHECKING: + from typing import Any, Generator + + from litestar.typing import FieldDefinition + +try: + import pydantic as _ # noqa: F401 +except ImportError as e: + raise MissingDependencyException("pydantic") from e + + +try: + import pydantic as pydantic_v2 + from pydantic import ValidationError as ValidationErrorV2 + from pydantic import v1 as pydantic_v1 + from pydantic.v1 import ValidationError as ValidationErrorV1 + + ModelType: TypeAlias = "pydantic_v1.BaseModel | pydantic_v2.BaseModel" + +except ImportError: + import pydantic as pydantic_v1 # type: ignore[no-redef] + + pydantic_v2 = Empty # type: ignore[assignment] + from pydantic import ValidationError as ValidationErrorV1 # type: ignore[assignment] + + ValidationErrorV2 = ValidationErrorV1 # type: ignore[assignment, misc] + ModelType = "pydantic_v1.BaseModel" # type: ignore[misc] + + +T = TypeVar("T", bound="ModelType | Collection[ModelType]") + + +__all__ = ("PydanticDTO",) + + +class PydanticDTO(AbstractDTO[T], Generic[T]): + """Support for domain modelling with Pydantic.""" + + @override + def decode_builtins(self, value: dict[str, Any]) -> Any: + try: + return super().decode_builtins(value) + except (ValidationErrorV2, ValidationErrorV1) as ex: + raise ValidationException(extra=ex.errors()) from ex + + @override + def decode_bytes(self, value: bytes) -> Any: + try: + return super().decode_bytes(value) + except (ValidationErrorV2, ValidationErrorV1) as ex: + raise ValidationException(extra=ex.errors()) from ex + + @classmethod + def generate_field_definitions( + cls, model_type: type[pydantic_v1.BaseModel | pydantic_v2.BaseModel] + ) -> Generator[DTOFieldDefinition, None, None]: + model_field_definitions = cls.get_model_type_hints(model_type) + + model_fields: dict[str, pydantic_v1.fields.FieldInfo | pydantic_v2.fields.FieldInfo] + try: + model_fields = dict(model_type.model_fields) # type: ignore[union-attr] + except AttributeError: + model_fields = { + k: model_field.field_info + for k, model_field in model_type.__fields__.items() # type: ignore[union-attr] + } + + for field_name, field_info in model_fields.items(): + field_definition = model_field_definitions[field_name] + dto_field = (field_definition.extra or {}).pop(DTO_FIELD_META_KEY, DTOField()) + + if not is_pydantic_undefined(field_info.default): + default = field_info.default + elif field_definition.is_optional: + default = None + else: + default = Empty + + yield replace( + DTOFieldDefinition.from_field_definition( + field_definition=field_definition, + dto_field=dto_field, + model_name=model_type.__name__, + default_factory=field_info.default_factory + if field_info.default_factory and not is_pydantic_undefined(field_info.default_factory) + else None, + ), + default=default, + name=field_name, + ) + + @classmethod + def detect_nested_field(cls, field_definition: FieldDefinition) -> bool: + if pydantic_v2 is not Empty: # type: ignore[comparison-overlap] + return field_definition.is_subclass_of((pydantic_v1.BaseModel, pydantic_v2.BaseModel)) + return field_definition.is_subclass_of(pydantic_v1.BaseModel) # type: ignore[unreachable] diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_init_plugin.py b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_init_plugin.py new file mode 100644 index 0000000..1261cd8 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_init_plugin.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +from contextlib import suppress +from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast +from uuid import UUID + +from msgspec import ValidationError +from typing_extensions import Buffer, TypeGuard + +from litestar._signature.types import ExtendedMsgSpecValidationError +from litestar.contrib.pydantic.utils import is_pydantic_constrained_field +from litestar.exceptions import MissingDependencyException +from litestar.plugins import InitPluginProtocol +from litestar.typing import _KWARG_META_EXTRACTORS +from litestar.utils import is_class_and_subclass + +try: + # check if we have pydantic v2 installed, and try to import both versions + import pydantic as pydantic_v2 + from pydantic import v1 as pydantic_v1 +except ImportError: + # check if pydantic 1 is installed and import it + try: + import pydantic as pydantic_v1 # type: ignore[no-redef] + + pydantic_v2 = None # type: ignore[assignment] + except ImportError as e: + raise MissingDependencyException("pydantic") from e + + +if TYPE_CHECKING: + from litestar.config.app import AppConfig + + +T = TypeVar("T") + + +def _dec_pydantic_v1(model_type: type[pydantic_v1.BaseModel], value: Any) -> pydantic_v1.BaseModel: + try: + return model_type.parse_obj(value) + except pydantic_v1.ValidationError as e: + raise ExtendedMsgSpecValidationError(errors=cast("list[dict[str, Any]]", e.errors())) from e + + +def _dec_pydantic_v2(model_type: type[pydantic_v2.BaseModel], value: Any) -> pydantic_v2.BaseModel: + try: + return model_type.model_validate(value, strict=False) + except pydantic_v2.ValidationError as e: + raise ExtendedMsgSpecValidationError(errors=cast("list[dict[str, Any]]", e.errors())) from e + + +def _dec_pydantic_uuid( + uuid_type: type[pydantic_v1.UUID1] | type[pydantic_v1.UUID3] | type[pydantic_v1.UUID4] | type[pydantic_v1.UUID5], + value: Any, +) -> ( + type[pydantic_v1.UUID1] | type[pydantic_v1.UUID3] | type[pydantic_v1.UUID4] | type[pydantic_v1.UUID5] +): # pragma: no cover + if isinstance(value, str): + value = uuid_type(value) + + elif isinstance(value, Buffer): + value = bytes(value) + try: + value = uuid_type(value.decode()) + except ValueError: + # 16 bytes in big-endian order as the bytes argument fail + # the above check + value = uuid_type(bytes=value) + elif isinstance(value, UUID): + value = uuid_type(str(value)) + + if not isinstance(value, uuid_type): + raise ValidationError(f"Invalid UUID: {value!r}") + + if value._required_version != value.version: + raise ValidationError(f"Invalid UUID version: {value!r}") + + return cast( + "type[pydantic_v1.UUID1] | type[pydantic_v1.UUID3] | type[pydantic_v1.UUID4] | type[pydantic_v1.UUID5]", value + ) + + +def _is_pydantic_v1_uuid(value: Any) -> bool: # pragma: no cover + return is_class_and_subclass(value, (pydantic_v1.UUID1, pydantic_v1.UUID3, pydantic_v1.UUID4, pydantic_v1.UUID5)) + + +_base_encoders: dict[Any, Callable[[Any], Any]] = { + pydantic_v1.EmailStr: str, + pydantic_v1.NameEmail: str, + pydantic_v1.ByteSize: lambda val: val.real, +} + +if pydantic_v2 is not None: # pragma: no cover + _base_encoders.update( + { + pydantic_v2.EmailStr: str, + pydantic_v2.NameEmail: str, + pydantic_v2.ByteSize: lambda val: val.real, + } + ) + + +def is_pydantic_v1_model_class(annotation: Any) -> TypeGuard[type[pydantic_v1.BaseModel]]: + return is_class_and_subclass(annotation, pydantic_v1.BaseModel) + + +def is_pydantic_v2_model_class(annotation: Any) -> TypeGuard[type[pydantic_v2.BaseModel]]: + return is_class_and_subclass(annotation, pydantic_v2.BaseModel) + + +class ConstrainedFieldMetaExtractor: + @staticmethod + def matches(annotation: Any, name: str | None, default: Any) -> bool: + return is_pydantic_constrained_field(annotation) + + @staticmethod + def extract(annotation: Any, default: Any) -> Any: + return [annotation] + + +class PydanticInitPlugin(InitPluginProtocol): + __slots__ = ("prefer_alias",) + + def __init__(self, prefer_alias: bool = False) -> None: + self.prefer_alias = prefer_alias + + @classmethod + def encoders(cls, prefer_alias: bool = False) -> dict[Any, Callable[[Any], Any]]: + encoders = {**_base_encoders, **cls._create_pydantic_v1_encoders(prefer_alias)} + if pydantic_v2 is not None: # pragma: no cover + encoders.update(cls._create_pydantic_v2_encoders(prefer_alias)) + return encoders + + @classmethod + def decoders(cls) -> list[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]]]: + decoders: list[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]]] = [ + (is_pydantic_v1_model_class, _dec_pydantic_v1) + ] + + if pydantic_v2 is not None: # pragma: no cover + decoders.append((is_pydantic_v2_model_class, _dec_pydantic_v2)) + + decoders.append((_is_pydantic_v1_uuid, _dec_pydantic_uuid)) + + return decoders + + @staticmethod + def _create_pydantic_v1_encoders(prefer_alias: bool = False) -> dict[Any, Callable[[Any], Any]]: # pragma: no cover + return { + pydantic_v1.BaseModel: lambda model: { + k: v.decode() if isinstance(v, bytes) else v for k, v in model.dict(by_alias=prefer_alias).items() + }, + pydantic_v1.SecretField: str, + pydantic_v1.StrictBool: int, + pydantic_v1.color.Color: str, + pydantic_v1.ConstrainedBytes: lambda val: val.decode("utf-8"), + pydantic_v1.ConstrainedDate: lambda val: val.isoformat(), + pydantic_v1.AnyUrl: str, + } + + @staticmethod + def _create_pydantic_v2_encoders(prefer_alias: bool = False) -> dict[Any, Callable[[Any], Any]]: + encoders: dict[Any, Callable[[Any], Any]] = { + pydantic_v2.BaseModel: lambda model: model.model_dump(mode="json", by_alias=prefer_alias), + pydantic_v2.types.SecretStr: lambda val: "**********" if val else "", + pydantic_v2.types.SecretBytes: lambda val: "**********" if val else "", + pydantic_v2.AnyUrl: str, + } + + with suppress(ImportError): + from pydantic_extra_types import color + + encoders[color.Color] = str + + return encoders + + def on_app_init(self, app_config: AppConfig) -> AppConfig: + app_config.type_encoders = {**self.encoders(self.prefer_alias), **(app_config.type_encoders or {})} + app_config.type_decoders = [*self.decoders(), *(app_config.type_decoders or [])] + + _KWARG_META_EXTRACTORS.add(ConstrainedFieldMetaExtractor) + return app_config diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_schema_plugin.py b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_schema_plugin.py new file mode 100644 index 0000000..2c189e4 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/pydantic_schema_plugin.py @@ -0,0 +1,317 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional + +from typing_extensions import Annotated + +from litestar.contrib.pydantic.utils import ( + create_field_definitions_for_computed_fields, + is_pydantic_2_model, + is_pydantic_constrained_field, + is_pydantic_model_class, + is_pydantic_undefined, + pydantic_get_type_hints_with_generics_resolved, + pydantic_unwrap_and_get_origin, +) +from litestar.exceptions import MissingDependencyException +from litestar.openapi.spec import OpenAPIFormat, OpenAPIType, Schema +from litestar.plugins import OpenAPISchemaPlugin +from litestar.types import Empty +from litestar.typing import FieldDefinition +from litestar.utils import is_class_and_subclass, is_generic + +try: + # check if we have pydantic v2 installed, and try to import both versions + import pydantic as pydantic_v2 + from pydantic import v1 as pydantic_v1 +except ImportError: + # check if pydantic 1 is installed and import it + try: + import pydantic as pydantic_v1 # type: ignore[no-redef] + + pydantic_v2 = None # type: ignore[assignment] + except ImportError as e: + raise MissingDependencyException("pydantic") from e + +if TYPE_CHECKING: + from litestar._openapi.schema_generation.schema import SchemaCreator + +PYDANTIC_TYPE_MAP: dict[type[Any] | None | Any, Schema] = { + pydantic_v1.ByteSize: Schema(type=OpenAPIType.INTEGER), + pydantic_v1.EmailStr: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.EMAIL), + pydantic_v1.IPvAnyAddress: Schema( + one_of=[ + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV4, + description="IPv4 address", + ), + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV6, + description="IPv6 address", + ), + ] + ), + pydantic_v1.IPvAnyInterface: Schema( + one_of=[ + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV4, + description="IPv4 interface", + ), + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV6, + description="IPv6 interface", + ), + ] + ), + pydantic_v1.IPvAnyNetwork: Schema( + one_of=[ + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV4, + description="IPv4 network", + ), + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV6, + description="IPv6 network", + ), + ] + ), + pydantic_v1.Json: Schema(type=OpenAPIType.OBJECT, format=OpenAPIFormat.JSON_POINTER), + pydantic_v1.NameEmail: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.EMAIL, description="Name and email"), + # removed in v2 + pydantic_v1.PyObject: Schema( + type=OpenAPIType.STRING, + description="dot separated path identifying a python object, e.g. 'decimal.Decimal'", + ), + # annotated in v2 + pydantic_v1.UUID1: Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.UUID, + description="UUID1 string", + ), + pydantic_v1.UUID3: Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.UUID, + description="UUID3 string", + ), + pydantic_v1.UUID4: Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.UUID, + description="UUID4 string", + ), + pydantic_v1.UUID5: Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.UUID, + description="UUID5 string", + ), + pydantic_v1.DirectoryPath: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.URI_REFERENCE), + pydantic_v1.AnyUrl: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.URL), + pydantic_v1.AnyHttpUrl: Schema( + type=OpenAPIType.STRING, format=OpenAPIFormat.URL, description="must be a valid HTTP based URL" + ), + pydantic_v1.FilePath: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.URI_REFERENCE), + pydantic_v1.HttpUrl: Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.URL, + description="must be a valid HTTP based URL", + max_length=2083, + ), + pydantic_v1.RedisDsn: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.URI, description="redis DSN"), + pydantic_v1.PostgresDsn: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.URI, description="postgres DSN"), + pydantic_v1.SecretBytes: Schema(type=OpenAPIType.STRING), + pydantic_v1.SecretStr: Schema(type=OpenAPIType.STRING), + pydantic_v1.StrictBool: Schema(type=OpenAPIType.BOOLEAN), + pydantic_v1.StrictBytes: Schema(type=OpenAPIType.STRING), + pydantic_v1.StrictFloat: Schema(type=OpenAPIType.NUMBER), + pydantic_v1.StrictInt: Schema(type=OpenAPIType.INTEGER), + pydantic_v1.StrictStr: Schema(type=OpenAPIType.STRING), + pydantic_v1.NegativeFloat: Schema(type=OpenAPIType.NUMBER, exclusive_maximum=0.0), + pydantic_v1.NegativeInt: Schema(type=OpenAPIType.INTEGER, exclusive_maximum=0), + pydantic_v1.NonNegativeInt: Schema(type=OpenAPIType.INTEGER, minimum=0), + pydantic_v1.NonPositiveFloat: Schema(type=OpenAPIType.NUMBER, maximum=0.0), + pydantic_v1.PaymentCardNumber: Schema(type=OpenAPIType.STRING, min_length=12, max_length=19), + pydantic_v1.PositiveFloat: Schema(type=OpenAPIType.NUMBER, exclusive_minimum=0.0), + pydantic_v1.PositiveInt: Schema(type=OpenAPIType.INTEGER, exclusive_minimum=0), +} + +if pydantic_v2 is not None: # pragma: no cover + PYDANTIC_TYPE_MAP.update( + { + pydantic_v2.SecretStr: Schema(type=OpenAPIType.STRING), + pydantic_v2.SecretBytes: Schema(type=OpenAPIType.STRING), + pydantic_v2.ByteSize: Schema(type=OpenAPIType.INTEGER), + pydantic_v2.EmailStr: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.EMAIL), + pydantic_v2.IPvAnyAddress: Schema( + one_of=[ + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV4, + description="IPv4 address", + ), + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV6, + description="IPv6 address", + ), + ] + ), + pydantic_v2.IPvAnyInterface: Schema( + one_of=[ + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV4, + description="IPv4 interface", + ), + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV6, + description="IPv6 interface", + ), + ] + ), + pydantic_v2.IPvAnyNetwork: Schema( + one_of=[ + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV4, + description="IPv4 network", + ), + Schema( + type=OpenAPIType.STRING, + format=OpenAPIFormat.IPV6, + description="IPv6 network", + ), + ] + ), + pydantic_v2.Json: Schema(type=OpenAPIType.OBJECT, format=OpenAPIFormat.JSON_POINTER), + pydantic_v2.NameEmail: Schema( + type=OpenAPIType.STRING, format=OpenAPIFormat.EMAIL, description="Name and email" + ), + pydantic_v2.AnyUrl: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.URL), + } + ) + + +_supported_types = (pydantic_v1.BaseModel, *PYDANTIC_TYPE_MAP.keys()) +if pydantic_v2 is not None: # pragma: no cover + _supported_types = (pydantic_v2.BaseModel, *_supported_types) + + +class PydanticSchemaPlugin(OpenAPISchemaPlugin): + __slots__ = ("prefer_alias",) + + def __init__(self, prefer_alias: bool = False) -> None: + self.prefer_alias = prefer_alias + + @staticmethod + def is_plugin_supported_type(value: Any) -> bool: + return isinstance(value, _supported_types) or is_class_and_subclass(value, _supported_types) # type: ignore[arg-type] + + @staticmethod + def is_undefined_sentinel(value: Any) -> bool: + return is_pydantic_undefined(value) + + @staticmethod + def is_constrained_field(field_definition: FieldDefinition) -> bool: + return is_pydantic_constrained_field(field_definition.annotation) + + def to_openapi_schema(self, field_definition: FieldDefinition, schema_creator: SchemaCreator) -> Schema: + """Given a type annotation, transform it into an OpenAPI schema class. + + Args: + field_definition: FieldDefinition instance. + schema_creator: An instance of the schema creator class + + Returns: + An :class:`OpenAPI <litestar.openapi.spec.schema.Schema>` instance. + """ + if schema_creator.prefer_alias != self.prefer_alias: + schema_creator.prefer_alias = True + if is_pydantic_model_class(field_definition.annotation): + return self.for_pydantic_model(field_definition=field_definition, schema_creator=schema_creator) + return PYDANTIC_TYPE_MAP[field_definition.annotation] # pragma: no cover + + @classmethod + def for_pydantic_model(cls, field_definition: FieldDefinition, schema_creator: SchemaCreator) -> Schema: # pyright: ignore + """Create a schema object for a given pydantic model class. + + Args: + field_definition: FieldDefinition instance. + schema_creator: An instance of the schema creator class + + Returns: + A schema instance. + """ + + annotation = field_definition.annotation + if is_generic(annotation): + is_generic_model = True + model = pydantic_unwrap_and_get_origin(annotation) or annotation + else: + is_generic_model = False + model = annotation + + if is_pydantic_2_model(model): + model_config = model.model_config + model_field_info = model.model_fields + title = model_config.get("title") + example = model_config.get("example") + is_v2_model = True + else: + model_config = annotation.__config__ + model_field_info = model.__fields__ + title = getattr(model_config, "title", None) + example = getattr(model_config, "example", None) + is_v2_model = False + + model_fields: dict[str, pydantic_v1.fields.FieldInfo | pydantic_v2.fields.FieldInfo] = { # pyright: ignore + k: getattr(f, "field_info", f) for k, f in model_field_info.items() + } + + if is_v2_model: + # extract the annotations from the FieldInfo. This allows us to skip fields + # which have been marked as private + model_annotations = {k: field_info.annotation for k, field_info in model_fields.items()} # type: ignore[union-attr] + + else: + # pydantic v1 requires some workarounds here + model_annotations = { + k: f.outer_type_ if f.required else Optional[f.outer_type_] for k, f in model.__fields__.items() + } + + if is_generic_model: + # if the model is generic, resolve the type variables. We pass in the + # already extracted annotations, to keep the logic of respecting private + # fields consistent with the above + model_annotations = pydantic_get_type_hints_with_generics_resolved( + annotation, model_annotations=model_annotations, include_extras=True + ) + + property_fields = { + field_info.alias if field_info.alias and schema_creator.prefer_alias else k: FieldDefinition.from_kwarg( + annotation=Annotated[model_annotations[k], field_info, field_info.metadata] # type: ignore[union-attr] + if is_v2_model + else Annotated[model_annotations[k], field_info], # pyright: ignore + name=field_info.alias if field_info.alias and schema_creator.prefer_alias else k, + default=Empty if schema_creator.is_undefined(field_info.default) else field_info.default, + ) + for k, field_info in model_fields.items() + } + + computed_field_definitions = create_field_definitions_for_computed_fields( + annotation, schema_creator.prefer_alias + ) + property_fields.update(computed_field_definitions) + + return schema_creator.create_component_schema( + field_definition, + required=sorted(f.name for f in property_fields.values() if f.is_required), + property_fields=property_fields, + title=title, + examples=None if example is None else [example], + ) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py new file mode 100644 index 0000000..6aee322 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/pydantic/utils.py @@ -0,0 +1,214 @@ +# mypy: strict-equality=False +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from typing_extensions import Annotated, get_type_hints + +from litestar.params import KwargDefinition +from litestar.types import Empty +from litestar.typing import FieldDefinition +from litestar.utils import deprecated, is_class_and_subclass +from litestar.utils.predicates import is_generic +from litestar.utils.typing import ( + _substitute_typevars, + get_origin_or_inner_type, + get_type_hints_with_generics_resolved, + normalize_type_annotation, +) + +# isort: off +try: + from pydantic import v1 as pydantic_v1 + import pydantic as pydantic_v2 + from pydantic.fields import PydanticUndefined as Pydantic2Undefined # type: ignore[attr-defined] + from pydantic.v1.fields import Undefined as Pydantic1Undefined + + PYDANTIC_UNDEFINED_SENTINELS = {Pydantic1Undefined, Pydantic2Undefined} +except ImportError: + try: + import pydantic as pydantic_v1 # type: ignore[no-redef] + from pydantic.fields import Undefined as Pydantic1Undefined # type: ignore[attr-defined, no-redef] + + pydantic_v2 = Empty # type: ignore[assignment] + PYDANTIC_UNDEFINED_SENTINELS = {Pydantic1Undefined} + + except ImportError: # pyright: ignore + pydantic_v1 = Empty # type: ignore[assignment] + pydantic_v2 = Empty # type: ignore[assignment] + PYDANTIC_UNDEFINED_SENTINELS = set() +# isort: on + + +if TYPE_CHECKING: + from typing_extensions import TypeGuard + + +def is_pydantic_model_class( + annotation: Any, +) -> TypeGuard[type[pydantic_v1.BaseModel | pydantic_v2.BaseModel]]: # pyright: ignore + """Given a type annotation determine if the annotation is a subclass of pydantic's BaseModel. + + Args: + annotation: A type. + + Returns: + A typeguard determining whether the type is :data:`BaseModel pydantic.BaseModel>`. + """ + tests: list[bool] = [] + + if pydantic_v1 is not Empty: # pragma: no cover + tests.append(is_class_and_subclass(annotation, pydantic_v1.BaseModel)) + + if pydantic_v2 is not Empty: # pragma: no cover + tests.append(is_class_and_subclass(annotation, pydantic_v2.BaseModel)) + + return any(tests) + + +def is_pydantic_model_instance( + annotation: Any, +) -> TypeGuard[pydantic_v1.BaseModel | pydantic_v2.BaseModel]: # pyright: ignore + """Given a type annotation determine if the annotation is an instance of pydantic's BaseModel. + + Args: + annotation: A type. + + Returns: + A typeguard determining whether the type is :data:`BaseModel pydantic.BaseModel>`. + """ + tests: list[bool] = [] + + if pydantic_v1 is not Empty: # pragma: no cover + tests.append(isinstance(annotation, pydantic_v1.BaseModel)) + + if pydantic_v2 is not Empty: # pragma: no cover + tests.append(isinstance(annotation, pydantic_v2.BaseModel)) + + return any(tests) + + +def is_pydantic_constrained_field(annotation: Any) -> bool: + """Check if the given annotation is a constrained pydantic type. + + Args: + annotation: A type annotation + + Returns: + True if pydantic is installed and the type is a constrained type, otherwise False. + """ + if pydantic_v1 is Empty: # pragma: no cover + return False # type: ignore[unreachable] + + return any( + is_class_and_subclass(annotation, constrained_type) # pyright: ignore + for constrained_type in ( + pydantic_v1.ConstrainedBytes, + pydantic_v1.ConstrainedDate, + pydantic_v1.ConstrainedDecimal, + pydantic_v1.ConstrainedFloat, + pydantic_v1.ConstrainedFrozenSet, + pydantic_v1.ConstrainedInt, + pydantic_v1.ConstrainedList, + pydantic_v1.ConstrainedSet, + pydantic_v1.ConstrainedStr, + ) + ) + + +def pydantic_unwrap_and_get_origin(annotation: Any) -> Any | None: + if pydantic_v2 is Empty or (pydantic_v1 is not Empty and is_class_and_subclass(annotation, pydantic_v1.BaseModel)): + return get_origin_or_inner_type(annotation) + + origin = annotation.__pydantic_generic_metadata__["origin"] + return normalize_type_annotation(origin) + + +def pydantic_get_type_hints_with_generics_resolved( + annotation: Any, + globalns: dict[str, Any] | None = None, + localns: dict[str, Any] | None = None, + include_extras: bool = False, + model_annotations: dict[str, Any] | None = None, +) -> dict[str, Any]: + if pydantic_v2 is Empty or (pydantic_v1 is not Empty and is_class_and_subclass(annotation, pydantic_v1.BaseModel)): + return get_type_hints_with_generics_resolved(annotation, type_hints=model_annotations) + + origin = pydantic_unwrap_and_get_origin(annotation) + if origin is None: + if model_annotations is None: # pragma: no cover + model_annotations = get_type_hints( + annotation, globalns=globalns, localns=localns, include_extras=include_extras + ) + typevar_map = {p: p for p in annotation.__pydantic_generic_metadata__["parameters"]} + else: + if model_annotations is None: + model_annotations = get_type_hints( + origin, globalns=globalns, localns=localns, include_extras=include_extras + ) + args = annotation.__pydantic_generic_metadata__["args"] + parameters = origin.__pydantic_generic_metadata__["parameters"] + typevar_map = dict(zip(parameters, args)) + + return {n: _substitute_typevars(type_, typevar_map) for n, type_ in model_annotations.items()} + + +@deprecated(version="2.6.2") +def pydantic_get_unwrapped_annotation_and_type_hints(annotation: Any) -> tuple[Any, dict[str, Any]]: # pragma: pver + """Get the unwrapped annotation and the type hints after resolving generics. + + Args: + annotation: A type annotation. + + Returns: + A tuple containing the unwrapped annotation and the type hints. + """ + + if is_generic(annotation): + origin = pydantic_unwrap_and_get_origin(annotation) + return origin or annotation, pydantic_get_type_hints_with_generics_resolved(annotation, include_extras=True) + return annotation, get_type_hints(annotation, include_extras=True) + + +def is_pydantic_2_model( + obj: type[pydantic_v1.BaseModel | pydantic_v2.BaseModel], # pyright: ignore +) -> TypeGuard[pydantic_v2.BaseModel]: # pyright: ignore + return pydantic_v2 is not Empty and issubclass(obj, pydantic_v2.BaseModel) + + +def is_pydantic_undefined(value: Any) -> bool: + return any(v is value for v in PYDANTIC_UNDEFINED_SENTINELS) + + +def create_field_definitions_for_computed_fields( + model: type[pydantic_v1.BaseModel | pydantic_v2.BaseModel], # pyright: ignore + prefer_alias: bool, +) -> dict[str, FieldDefinition]: + """Create field definitions for computed fields. + + Args: + model: A pydantic model. + prefer_alias: Whether to prefer the alias or the name of the field. + + Returns: + A dictionary containing the field definitions for the computed fields. + """ + pydantic_decorators = getattr(model, "__pydantic_decorators__", None) + if pydantic_decorators is None: + return {} + + def get_name(k: str, dec: Any) -> str: + if not dec.info.alias: + return k + return dec.info.alias if prefer_alias else k # type: ignore[no-any-return] + + return { + (name := get_name(k, dec)): FieldDefinition.from_annotation( + Annotated[ + dec.info.return_type, + KwargDefinition(title=dec.info.title, description=dec.info.description, read_only=True), + ], + name=name, + ) + for k, dec in pydantic_decorators.computed_fields.items() + } diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__init__.py new file mode 100644 index 0000000..3a329c9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__init__.py @@ -0,0 +1,20 @@ +from litestar.utils import warn_deprecation + + +def __getattr__(attr_name: str) -> object: + from litestar import repository + + if attr_name in repository.__all__: + warn_deprecation( + deprecated_name=f"litestar.contrib.repository.{attr_name}", + version="2.1", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.contrib.repository' is deprecated, please" + f"import it from 'litestar.repository.{attr_name}' instead", + ) + + value = globals()[attr_name] = getattr(repository, attr_name) + return value + + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..1b3c01a --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/exceptions.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/exceptions.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..29868c6 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/exceptions.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/filters.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/filters.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..a69925c --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/filters.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/handlers.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/handlers.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..5d82074 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/handlers.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/testing.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/testing.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..474bdcf --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/__pycache__/testing.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/abc/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/repository/abc/__init__.py new file mode 100644 index 0000000..17efc1b --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/abc/__init__.py @@ -0,0 +1,20 @@ +from litestar.utils import warn_deprecation + + +def __getattr__(attr_name: str) -> object: + from litestar.repository import abc + + if attr_name in abc.__all__: + warn_deprecation( + deprecated_name=f"litestar.contrib.repository.abc.{attr_name}", + version="2.1", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.contrib.repository.abc' is deprecated, please" + f"import it from 'litestar.repository.abc.{attr_name}' instead", + ) + + value = globals()[attr_name] = getattr(abc, attr_name) + return value + + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/abc/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/repository/abc/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..666c136 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/abc/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/exceptions.py b/venv/lib/python3.11/site-packages/litestar/contrib/repository/exceptions.py new file mode 100644 index 0000000..1e7e738 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/exceptions.py @@ -0,0 +1,20 @@ +from litestar.utils import warn_deprecation + + +def __getattr__(attr_name: str) -> object: + from litestar.repository import exceptions + + if attr_name in exceptions.__all__: + warn_deprecation( + deprecated_name=f"litestar.repository.contrib.exceptions.{attr_name}", + version="2.1", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.contrib.repository.exceptions' is deprecated, please" + f"import it from 'litestar.repository.exceptions.{attr_name}' instead", + ) + + value = globals()[attr_name] = getattr(exceptions, attr_name) + return value + + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/filters.py b/venv/lib/python3.11/site-packages/litestar/contrib/repository/filters.py new file mode 100644 index 0000000..3736a76 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/filters.py @@ -0,0 +1,20 @@ +from litestar.utils import warn_deprecation + + +def __getattr__(attr_name: str) -> object: + from litestar.repository import filters + + if attr_name in filters.__all__: + warn_deprecation( + deprecated_name=f"litestar.repository.contrib.filters.{attr_name}", + version="2.1", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.contrib.repository.filters' is deprecated, please" + f"import it from 'litestar.repository.filters.{attr_name}' instead", + ) + + value = globals()[attr_name] = getattr(filters, attr_name) + return value + + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/handlers.py b/venv/lib/python3.11/site-packages/litestar/contrib/repository/handlers.py new file mode 100644 index 0000000..b1174e4 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/handlers.py @@ -0,0 +1,20 @@ +from litestar.utils import warn_deprecation + + +def __getattr__(attr_name: str) -> object: + from litestar.repository import handlers + + if attr_name in handlers.__all__: + warn_deprecation( + deprecated_name=f"litestar.repository.contrib.handlers.{attr_name}", + version="2.1", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.contrib.repository.handlers' is deprecated, please" + f"import it from 'litestar.repository.handlers.{attr_name}' instead", + ) + + value = globals()[attr_name] = getattr(handlers, attr_name) + return value + + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/repository/testing.py b/venv/lib/python3.11/site-packages/litestar/contrib/repository/testing.py new file mode 100644 index 0000000..b78fea8 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/repository/testing.py @@ -0,0 +1,20 @@ +from litestar.utils import warn_deprecation + + +def __getattr__(attr_name: str) -> object: + from litestar.repository.testing import generic_mock_repository + + if attr_name in generic_mock_repository.__all__: + warn_deprecation( + deprecated_name=f"litestar.repository.contrib.testing.{attr_name}", + version="2.1", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.contrib.repository.testing' is deprecated, please" + f"import it from 'litestar.repository.testing.{attr_name}' instead", + ) + + value = globals()[attr_name] = getattr(generic_mock_repository, attr_name) + return value + + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__init__.py diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..1b0f40a --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/base.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..199862b --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/base.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/dto.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/dto.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..5467843 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/dto.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/types.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/types.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..09819d1 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/__pycache__/types.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/base.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/base.py new file mode 100644 index 0000000..9ce9608 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/base.py @@ -0,0 +1,38 @@ +"""Application ORM configuration.""" + +from __future__ import annotations + +try: + # v0.6.0+ + from advanced_alchemy._listeners import touch_updated_timestamp # pyright: ignore +except ImportError: + from advanced_alchemy.base import touch_updated_timestamp # type: ignore[no-redef,attr-defined] + +from advanced_alchemy.base import ( + AuditColumns, + BigIntAuditBase, + BigIntBase, + BigIntPrimaryKey, + CommonTableAttributes, + ModelProtocol, + UUIDAuditBase, + UUIDBase, + UUIDPrimaryKey, + create_registry, + orm_registry, +) + +__all__ = ( + "AuditColumns", + "BigIntAuditBase", + "BigIntBase", + "BigIntPrimaryKey", + "CommonTableAttributes", + "create_registry", + "ModelProtocol", + "touch_updated_timestamp", + "UUIDAuditBase", + "UUIDBase", + "UUIDPrimaryKey", + "orm_registry", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/dto.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/dto.py new file mode 100644 index 0000000..beea75d --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/dto.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from advanced_alchemy.extensions.litestar.dto import SQLAlchemyDTO, SQLAlchemyDTOConfig + +__all__ = ("SQLAlchemyDTO", "SQLAlchemyDTOConfig") diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__init__.py new file mode 100644 index 0000000..5bc913c --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__init__.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from advanced_alchemy.extensions.litestar.plugins import SQLAlchemyPlugin + +from .init import ( + AsyncSessionConfig, + EngineConfig, + GenericSessionConfig, + GenericSQLAlchemyConfig, + SQLAlchemyAsyncConfig, + SQLAlchemyInitPlugin, + SQLAlchemySyncConfig, + SyncSessionConfig, +) +from .serialization import SQLAlchemySerializationPlugin + +__all__ = ( + "AsyncSessionConfig", + "EngineConfig", + "GenericSQLAlchemyConfig", + "GenericSessionConfig", + "SQLAlchemyAsyncConfig", + "SQLAlchemyInitPlugin", + "SQLAlchemyPlugin", + "SQLAlchemySerializationPlugin", + "SQLAlchemySyncConfig", + "SyncSessionConfig", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..019ff72 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__pycache__/serialization.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__pycache__/serialization.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..7bd0360 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/__pycache__/serialization.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__init__.py new file mode 100644 index 0000000..2e507c1 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__init__.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from .config import ( + AsyncSessionConfig, + EngineConfig, + GenericSessionConfig, + GenericSQLAlchemyConfig, + SQLAlchemyAsyncConfig, + SQLAlchemySyncConfig, + SyncSessionConfig, +) +from .plugin import SQLAlchemyInitPlugin + +__all__ = ( + "AsyncSessionConfig", + "EngineConfig", + "GenericSQLAlchemyConfig", + "GenericSessionConfig", + "SQLAlchemyAsyncConfig", + "SQLAlchemyInitPlugin", + "SQLAlchemySyncConfig", + "SyncSessionConfig", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..43da1aa --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__pycache__/plugin.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__pycache__/plugin.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..e06ec42 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/__pycache__/plugin.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__init__.py new file mode 100644 index 0000000..f2e39da --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__init__.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from .asyncio import AsyncSessionConfig, SQLAlchemyAsyncConfig +from .common import GenericSessionConfig, GenericSQLAlchemyConfig +from .engine import EngineConfig +from .sync import SQLAlchemySyncConfig, SyncSessionConfig + +__all__ = ( + "AsyncSessionConfig", + "EngineConfig", + "GenericSQLAlchemyConfig", + "GenericSessionConfig", + "SQLAlchemyAsyncConfig", + "SQLAlchemySyncConfig", + "SyncSessionConfig", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..2a316ef --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/asyncio.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/asyncio.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..23fe455 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/asyncio.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/common.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/common.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..6e83a95 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/common.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/compat.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/compat.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..4ba72bb --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/compat.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/engine.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/engine.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..1d4553b --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/engine.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/sync.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/sync.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..d777bb9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/__pycache__/sync.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/asyncio.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/asyncio.py new file mode 100644 index 0000000..434c761 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/asyncio.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from advanced_alchemy.config.asyncio import AlembicAsyncConfig, AsyncSessionConfig +from advanced_alchemy.extensions.litestar.plugins.init.config.asyncio import ( + SQLAlchemyAsyncConfig as _SQLAlchemyAsyncConfig, +) +from advanced_alchemy.extensions.litestar.plugins.init.config.asyncio import ( + autocommit_before_send_handler, + default_before_send_handler, +) +from sqlalchemy.ext.asyncio import AsyncEngine + +from litestar.contrib.sqlalchemy.plugins.init.config.compat import _CreateEngineMixin + +__all__ = ( + "SQLAlchemyAsyncConfig", + "AlembicAsyncConfig", + "AsyncSessionConfig", + "default_before_send_handler", + "autocommit_before_send_handler", +) + + +class SQLAlchemyAsyncConfig(_SQLAlchemyAsyncConfig, _CreateEngineMixin[AsyncEngine]): ... diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/common.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/common.py new file mode 100644 index 0000000..9afc48c --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/common.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from advanced_alchemy.config.common import GenericAlembicConfig, GenericSessionConfig, GenericSQLAlchemyConfig +from advanced_alchemy.extensions.litestar.plugins.init.config.common import ( + SESSION_SCOPE_KEY, + SESSION_TERMINUS_ASGI_EVENTS, +) + +__all__ = ( + "SESSION_SCOPE_KEY", + "SESSION_TERMINUS_ASGI_EVENTS", + "GenericSQLAlchemyConfig", + "GenericSessionConfig", + "GenericAlembicConfig", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/compat.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/compat.py new file mode 100644 index 0000000..d76dea7 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/compat.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Generic, Protocol, TypeVar + +from litestar.utils.deprecation import deprecated + +if TYPE_CHECKING: + from sqlalchemy import Engine + from sqlalchemy.ext.asyncio import AsyncEngine + + +EngineT_co = TypeVar("EngineT_co", bound="Engine | AsyncEngine", covariant=True) + + +class HasGetEngine(Protocol[EngineT_co]): + def get_engine(self) -> EngineT_co: ... + + +class _CreateEngineMixin(Generic[EngineT_co]): + @deprecated(version="2.1.1", removal_in="3.0.0", alternative="get_engine()") + def create_engine(self: HasGetEngine[EngineT_co]) -> EngineT_co: + return self.get_engine() diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/engine.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/engine.py new file mode 100644 index 0000000..31c3f5e --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/engine.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from advanced_alchemy.config.engine import EngineConfig + +__all__ = ("EngineConfig",) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/sync.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/sync.py new file mode 100644 index 0000000..48a029b --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/config/sync.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from advanced_alchemy.config.sync import AlembicSyncConfig, SyncSessionConfig +from advanced_alchemy.extensions.litestar.plugins.init.config.sync import ( + SQLAlchemySyncConfig as _SQLAlchemySyncConfig, +) +from advanced_alchemy.extensions.litestar.plugins.init.config.sync import ( + autocommit_before_send_handler, + default_before_send_handler, +) +from sqlalchemy import Engine + +from litestar.contrib.sqlalchemy.plugins.init.config.compat import _CreateEngineMixin + +__all__ = ( + "SQLAlchemySyncConfig", + "AlembicSyncConfig", + "SyncSessionConfig", + "default_before_send_handler", + "autocommit_before_send_handler", +) + + +class SQLAlchemySyncConfig(_SQLAlchemySyncConfig, _CreateEngineMixin[Engine]): ... diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/plugin.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/plugin.py new file mode 100644 index 0000000..dbf814b --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/init/plugin.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from advanced_alchemy.extensions.litestar.plugins import SQLAlchemyInitPlugin + +__all__ = ("SQLAlchemyInitPlugin",) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/serialization.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/serialization.py new file mode 100644 index 0000000..539b194 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/plugins/serialization.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from advanced_alchemy.extensions.litestar.plugins import SQLAlchemySerializationPlugin + +__all__ = ("SQLAlchemySerializationPlugin",) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__init__.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__init__.py new file mode 100644 index 0000000..64a8359 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__init__.py @@ -0,0 +1,11 @@ +from ._async import SQLAlchemyAsyncRepository +from ._sync import SQLAlchemySyncRepository +from ._util import wrap_sqlalchemy_exception +from .types import ModelT + +__all__ = ( + "SQLAlchemyAsyncRepository", + "SQLAlchemySyncRepository", + "ModelT", + "wrap_sqlalchemy_exception", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..3e6dacf --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_async.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_async.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..461fbad --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_async.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_sync.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_sync.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..bd6d80f --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_sync.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_util.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_util.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..561fd53 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/_util.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/types.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/types.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..fa8191e --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/__pycache__/types.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_async.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_async.py new file mode 100644 index 0000000..417ec35 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_async.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from advanced_alchemy.repository import SQLAlchemyAsyncRepository + +__all__ = ("SQLAlchemyAsyncRepository",) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_sync.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_sync.py new file mode 100644 index 0000000..58ccbb8 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_sync.py @@ -0,0 +1,7 @@ +# Do not edit this file directly. It has been autogenerated from +# litestar/contrib/sqlalchemy/repository/_async.py +from __future__ import annotations + +from advanced_alchemy.repository import SQLAlchemySyncRepository + +__all__ = ("SQLAlchemySyncRepository",) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_util.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_util.py new file mode 100644 index 0000000..c0ce747 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/_util.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from advanced_alchemy.repository._util import get_instrumented_attr, wrap_sqlalchemy_exception + +__all__ = ( + "wrap_sqlalchemy_exception", + "get_instrumented_attr", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/types.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/types.py new file mode 100644 index 0000000..2a4204c --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/repository/types.py @@ -0,0 +1,15 @@ +from advanced_alchemy.repository.typing import ( + ModelT, + RowT, + SelectT, + SQLAlchemyAsyncRepositoryT, + SQLAlchemySyncRepositoryT, +) + +__all__ = ( + "ModelT", + "SelectT", + "RowT", + "SQLAlchemySyncRepositoryT", + "SQLAlchemyAsyncRepositoryT", +) diff --git a/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/types.py b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/types.py new file mode 100644 index 0000000..61fb75a --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/contrib/sqlalchemy/types.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from advanced_alchemy.types import GUID, ORA_JSONB, BigIntIdentity, DateTimeUTC, JsonB + +__all__ = ( + "GUID", + "ORA_JSONB", + "DateTimeUTC", + "BigIntIdentity", + "JsonB", +) |