Source code for dmr.cookies

# Parts of the code is taken from
# https://github.com/litestar-org/litestar/blob/main/litestar/datastructures/cookie.py
# under MIT license.

# Original license:
# https://github.com/litestar-org/litestar/blob/main/LICENSE

# The MIT License (MIT)

# Copyright (c) 2021, 2022, 2023, 2024, 2025, 2026 Litestar Org.

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import dataclasses
from collections.abc import Mapping
from http.cookies import Morsel, SimpleCookie
from typing import Any, ClassVar, Literal, final

from django.http import HttpResponseBase


@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
class _BaseCookie:
    """Base class for all cookies."""

    path: str = '/'
    max_age: int | None = None
    expires: int | None = None
    domain: str | None = None
    secure: bool | None = None
    httponly: bool | None = None
    samesite: Literal['lax', 'strict', 'none'] = 'lax'


[docs] @final @dataclasses.dataclass(frozen=True, slots=True, kw_only=True) class CookieSpec(_BaseCookie): """ Description of a single cookie in ``Set-Cookie`` header. Attributes: path: Path fragment that must exist in the request url for the cookie to be valid. Defaults to ``/``. max_age: Maximal age of the cookie before its invalidated. expires: Seconds from now until the cookie expires. domain: Domain for which the cookie is valid. secure: Https is required for the cookie. httponly: Forbids javascript to access the cookie via ``document.cookie``. samesite: Controls whether or not a cookie is sent with cross-site requests. Defaults to ``'lax'``. description: Description of the response cookie header for OpenAPI documentation. required: Defines that this cookie can be missing in some cases. skip_validation: Is true, when cookie is only used for schema purposes, without any runtime validation. This might be useful, when this cookie will be set after our framework's validation. For example, by :class:`django.contrib.sessions.middleware.SessionMiddleware` or by HTTP proxy. This cookie might be present in runtime or might be missing. .. seealso:: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie """ #: This fields are not a part of the `cookie` spec: _extra_fields: ClassVar[frozenset[str]] = frozenset(( 'description', 'required', 'skip_validation', )) is_actionable: ClassVar[Literal[False]] = False description: str | None = None required: bool = True skip_validation: bool = False
[docs] def is_equal(self, other: Morsel[str]) -> bool: """Compare this object with ``SimpleCookie`` like object.""" # We have already compared keys, value is not important. cookie = SimpleCookie() cookie[other.key] = other.value namespace = cookie[other.key] for field in dataclasses.fields(self): if field.name in self._extra_fields: continue if field.name == 'expires': # It is relative to the current time, can't check it. namespace[field.name] = other[field.name] continue field_name = 'max-age' if field.name == 'max_age' else field.name namespace[field_name] = getattr(self, field.name) or '' return cookie[other.key] == other
[docs] def to_spec(self) -> 'CookieSpec': """API for compatibility with ``NewCookie``.""" return self
[docs] @final @dataclasses.dataclass(frozen=True, slots=True, kw_only=True) class NewCookie(_BaseCookie): """ New cookie to be set for the response. Attributes: value: Value for the cookie. path: Path fragment that must exist in the request url for the cookie to be valid. Defaults to ``/``. max_age: Maximal age of the cookie before its invalidated. expires: Seconds from now until the cookie expires. domain: Domain for which the cookie is valid. secure: Https is required for the cookie. httponly: Forbids javascript to access the cookie via ``document.cookie``. samesite: Controls whether or not a cookie is sent with cross-site requests. Defaults to ``'lax'``. .. seealso:: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie """ is_actionable: ClassVar[Literal[True]] = True value: str # noqa: WPS110
[docs] def to_spec(self) -> CookieSpec: """Converts the modification to spec.""" namespace = dataclasses.asdict(self) namespace.pop('value') return CookieSpec(**namespace)
[docs] def as_dict(self) -> dict[str, Any]: """Converts to a dictionary .""" return dataclasses.asdict(self)
[docs] def set_cookies( response: HttpResponseBase, cookies: Mapping[str, NewCookie] | None, ) -> None: """Set cookies for the HTTP response.""" if cookies: for cookie_key, cookie in cookies.items(): response.set_cookie(cookie_key, **cookie.as_dict())