Source code for dmr.validation.payload

import dataclasses
from collections.abc import Callable, Mapping, Sequence, Set
from http import HTTPStatus
from typing import TYPE_CHECKING, Any, ClassVar, TypeAlias, final

from dmr.cookies import CookieSpec, NewCookie
from dmr.errors import AsyncErrorHandler, SyncErrorHandler
from dmr.headers import HeaderSpec, NewHeader
from dmr.internal.types import EMPTY, StrOrPromise
from dmr.metadata import ResponseSpec
from dmr.parsers import Parser
from dmr.renderers import Renderer
from dmr.settings import HttpSpec

if TYPE_CHECKING:
    from dmr.controller import Controller
    from dmr.internal.endpoint import Extras
    from dmr.openapi.objects import (
        Callback,
        ExternalDocumentation,
        Link,
        Reference,
        SecurityRequirement,
        Server,
    )
    from dmr.security.base import AsyncAuth, SyncAuth
    from dmr.serializer import BaseSerializer
    from dmr.throttling import AsyncThrottle, SyncThrottle


@dataclasses.dataclass(slots=True, frozen=True, kw_only=True, init=False)
class _BasePayload:
    # OpenAPI stuff:
    summary: StrOrPromise | EMPTY | None
    description: StrOrPromise | EMPTY | None
    tags: Sequence[str] | EMPTY | None
    operation_id: str | EMPTY
    deprecated: bool | EMPTY
    security: Sequence['SecurityRequirement'] | EMPTY | None
    external_docs: 'ExternalDocumentation | EMPTY | None'
    callbacks: Mapping[str, 'Callback | Reference'] | EMPTY | None
    servers: Sequence['Server'] | EMPTY | None
    x_extensions: Mapping[str, Any] | EMPTY
    ignore_from_spec: bool | EMPTY

    # Extras:
    extras: 'Extras[Any] | EMPTY'
    extras_cls: 'type[Extras[Any]] | EMPTY'

    # Common fields:
    validate_responses: bool | EMPTY
    exclude_validate_responses: Set[HTTPStatus] | EMPTY | None
    semantic_schema: bool | EMPTY
    semantic_responses: bool | EMPTY
    exclude_semantic_responses: Set[HTTPStatus] | EMPTY | None
    semantic_auth: bool | EMPTY
    exclude_semantic_auth: Set[str] | EMPTY | None
    error_handler: SyncErrorHandler | AsyncErrorHandler | EMPTY
    no_validate_http_spec: Set[HttpSpec] | EMPTY | None
    parsers: Sequence[Parser] | EMPTY
    renderers: Sequence[Renderer] | EMPTY
    validate_negotiation: bool | EMPTY
    auth: Sequence['SyncAuth'] | Sequence['AsyncAuth'] | EMPTY | None
    throttling: (
        Sequence['SyncThrottle'] | Sequence['AsyncThrottle'] | EMPTY | None
    )


[docs] @final @dataclasses.dataclass(slots=True, frozen=True, kw_only=True) class ValidateEndpointPayload(_BasePayload): """Payload created by ``@validate``.""" # `EMPTY` is only used by `implicit()`, `@validate` always sets a list: responses: list[ResponseSpec] | EMPTY
[docs] @classmethod def implicit(cls) -> 'ValidateEndpointPayload': """ Create a payload for endpoints that return ``HttpResponse``. Such endpoints do not have to use ``@validate`` explicitly, when responses are defined on the controller or settings level. All values are the same as ``@validate`` defaults, except ``responses`` which is ``EMPTY`` and not an explicit ``[]``, so the controller and settings levels are used. """ return cls( responses=EMPTY, summary=EMPTY, description=EMPTY, tags=EMPTY, operation_id=EMPTY, deprecated=EMPTY, security=EMPTY, external_docs=EMPTY, callbacks=EMPTY, servers=EMPTY, x_extensions=EMPTY, ignore_from_spec=EMPTY, validate_responses=EMPTY, exclude_validate_responses=EMPTY, semantic_schema=EMPTY, semantic_responses=EMPTY, exclude_semantic_responses=EMPTY, semantic_auth=EMPTY, exclude_semantic_auth=EMPTY, error_handler=EMPTY, no_validate_http_spec=EMPTY, parsers=EMPTY, renderers=EMPTY, validate_negotiation=EMPTY, auth=EMPTY, throttling=EMPTY, extras=EMPTY, extras_cls=EMPTY, )
[docs] @final @dataclasses.dataclass(slots=True, frozen=True, kw_only=True) class ModifyEndpointPayload(_BasePayload): """Payload created by ``@modify``.""" responses: Sequence[ResponseSpec] | EMPTY | None status_code: HTTPStatus | EMPTY # Headers and cookies can be set via a middleware # after a response itself is formed. We need a way to describe this. # That's why `HeaderSpec` and `CookieSpec` are allowed. headers: Mapping[str, NewHeader | HeaderSpec] | EMPTY cookies: Mapping[str, NewCookie | CookieSpec] | EMPTY # OpenAPI metadata: response_description: str | EMPTY links: Mapping[str, 'Link | Reference'] | EMPTY
#: Alias for different payload types: Payload: TypeAlias = ValidateEndpointPayload | ModifyEndpointPayload | None _PayloadOrLazy: TypeAlias = ( Callable[[type['Controller[BaseSerializer]']], Callable[..., Any]] | Payload )
[docs] @dataclasses.dataclass(slots=True, frozen=True) class PayloadBuilder: """ Builds the payload from the endpoint function and the controller class. .. versionadded:: 0.15.0 """ func: Callable[..., Any] # Class-level API: _payload_key: ClassVar[str] = '__dmr_payload__'
[docs] def __call__( self, controller_cls: type['Controller[BaseSerializer]'], ) -> Payload: """Processes the callable payloads from ``modify.lazy`` and others.""" payload: _PayloadOrLazy = getattr( self.func, self._payload_key, None, ) # `modify.lazy` and `validate.lazy` can provide callable payloads: if callable(payload): return getattr( # type: ignore[no-any-return] payload(controller_cls)( # What happens here? We need to extract `__dmr_payload__` # from a function that our decorators attach it to. # But, we don't want to over-write the original function's # payload metadata. So, we create a throw-away one. lambda: ..., ), self._payload_key, ) return payload