zulip/zerver/lib/request.py

102 lines
3.3 KiB
Python

from collections import defaultdict
from collections.abc import MutableMapping
from dataclasses import dataclass, field
from typing import Any, Optional
from django.conf import settings
from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _
from typing_extensions import override
from zerver.lib import rate_limiter
from zerver.lib.exceptions import ErrorCode, JsonableError
from zerver.lib.notes import BaseNotes
from zerver.models import Client, Realm
if settings.ZILENCER_ENABLED:
from zilencer.models import RemoteZulipServer
@dataclass
class RequestNotes(BaseNotes[HttpRequest, "RequestNotes"]):
"""This class contains extra metadata that Zulip associated with a
Django HttpRequest object. See the docstring for BaseNotes for
details on how it works.
Note that most Optional fields will be definitely not None once
middleware has run. In the future, we may want to express that in
the types by having different types EarlyRequestNotes and
post-middleware RequestNotes types, but for now we have a lot
of `assert request_notes.foo is not None` when accessing them.
"""
client: Client | None = None
client_name: str | None = None
client_version: str | None = None
log_data: MutableMapping[str, Any] | None = None
rate_limit: str | None = None
requester_for_logs: str | None = None
# We use realm_cached to indicate whether the realm is cached or not.
# Because the default value of realm is None, which can indicate "unset"
# and "nonexistence" at the same time.
realm: Realm | None = None
has_fetched_realm: bool = False
set_language: str | None = None
ratelimits_applied: list[rate_limiter.RateLimitResult] = field(default_factory=list)
query: str | None = None
error_format: str | None = None
saved_response: HttpResponse | None = None
tornado_handler_id: int | None = None
processed_parameters: set[str] = field(default_factory=set)
remote_server: Optional["RemoteZulipServer"] = None
is_webhook_view: bool = False
@classmethod
@override
def init_notes(cls) -> "RequestNotes":
return RequestNotes()
class RequestConfusingParamsError(JsonableError):
code = ErrorCode.REQUEST_CONFUSING_VAR
data_fields = ["var_name1", "var_name2"]
def __init__(self, var_name1: str, var_name2: str) -> None:
self.var_name1: str = var_name1
self.var_name2: str = var_name2
@staticmethod
@override
def msg_format() -> str:
return _("Can't decide between '{var_name1}' and '{var_name2}' arguments")
class RequestVariableMissingError(JsonableError):
code = ErrorCode.REQUEST_VARIABLE_MISSING
data_fields = ["var_name"]
def __init__(self, var_name: str) -> None:
self.var_name: str = var_name
@staticmethod
@override
def msg_format() -> str:
return _("Missing '{var_name}' argument")
class RequestVariableConversionError(JsonableError):
code = ErrorCode.REQUEST_VARIABLE_INVALID
data_fields = ["var_name", "bad_value"]
def __init__(self, var_name: str, bad_value: Any) -> None:
self.var_name: str = var_name
self.bad_value = bad_value
@staticmethod
@override
def msg_format() -> str:
return _("Bad value for '{var_name}': {bad_value}")
arguments_map: dict[str, list[str]] = defaultdict(list)