2019-06-06 22:22:21 +02:00
|
|
|
from collections import defaultdict
|
2016-05-29 16:52:55 +02:00
|
|
|
from functools import wraps
|
2019-08-07 11:15:46 +02:00
|
|
|
from types import FunctionType
|
2020-06-13 03:34:01 +02:00
|
|
|
from typing import (
|
|
|
|
Any,
|
|
|
|
Callable,
|
|
|
|
Dict,
|
|
|
|
Generic,
|
|
|
|
List,
|
|
|
|
Optional,
|
|
|
|
Sequence,
|
|
|
|
TypeVar,
|
|
|
|
Union,
|
|
|
|
cast,
|
|
|
|
overload,
|
|
|
|
)
|
2016-05-29 16:52:55 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2020-06-21 02:36:20 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
2016-05-25 15:02:02 +02:00
|
|
|
from django.utils.translation import ugettext as _
|
2020-06-11 00:54:34 +02:00
|
|
|
from typing_extensions import Literal
|
2016-05-25 15:02:02 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.exceptions import ErrorCode, InvalidJSONError, JsonableError
|
2019-08-07 11:15:46 +02:00
|
|
|
from zerver.lib.types import Validator, ViewFuncT
|
2016-05-29 16:52:55 +02:00
|
|
|
|
2018-11-09 19:45:25 +01:00
|
|
|
|
|
|
|
class RequestConfusingParmsError(JsonableError):
|
|
|
|
code = ErrorCode.REQUEST_CONFUSING_VAR
|
|
|
|
data_fields = ['var_name1', 'var_name2']
|
|
|
|
|
|
|
|
def __init__(self, var_name1: str, var_name2: str) -> None:
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
self.var_name1: str = var_name1
|
|
|
|
self.var_name2: str = var_name2
|
2018-11-09 19:45:25 +01:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def msg_format() -> str:
|
|
|
|
return _("Can't decide between '{var_name1}' and '{var_name2}' arguments")
|
2017-11-05 11:15:10 +01:00
|
|
|
|
2016-05-29 16:52:55 +02:00
|
|
|
class RequestVariableMissingError(JsonableError):
|
2017-07-21 02:17:28 +02:00
|
|
|
code = ErrorCode.REQUEST_VARIABLE_MISSING
|
|
|
|
data_fields = ['var_name']
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __init__(self, var_name: str) -> None:
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
self.var_name: str = var_name
|
2016-05-29 16:52:55 +02:00
|
|
|
|
2017-07-21 02:17:28 +02:00
|
|
|
@staticmethod
|
2017-11-05 11:15:10 +01:00
|
|
|
def msg_format() -> str:
|
2017-07-21 02:17:28 +02:00
|
|
|
return _("Missing '{var_name}' argument")
|
2016-05-29 16:52:55 +02:00
|
|
|
|
|
|
|
class RequestVariableConversionError(JsonableError):
|
2017-07-21 02:17:28 +02:00
|
|
|
code = ErrorCode.REQUEST_VARIABLE_INVALID
|
|
|
|
data_fields = ['var_name', 'bad_value']
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def __init__(self, var_name: str, bad_value: Any) -> None:
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
self.var_name: str = var_name
|
2016-05-29 16:52:55 +02:00
|
|
|
self.bad_value = bad_value
|
|
|
|
|
2017-07-21 02:17:28 +02:00
|
|
|
@staticmethod
|
2017-11-05 11:15:10 +01:00
|
|
|
def msg_format() -> str:
|
2017-07-21 02:17:28 +02:00
|
|
|
return _("Bad value for '{var_name}': {bad_value}")
|
2016-05-29 16:52:55 +02:00
|
|
|
|
|
|
|
# Used in conjunction with @has_request_variables, below
|
2019-08-07 11:15:46 +02:00
|
|
|
ResultT = TypeVar('ResultT')
|
|
|
|
|
|
|
|
class _REQ(Generic[ResultT]):
|
2016-05-29 16:52:55 +02:00
|
|
|
# NotSpecified is a sentinel value for determining whether a
|
|
|
|
# default value was specified for a request variable. We can't
|
|
|
|
# use None because that could be a valid, user-specified default
|
2017-11-05 11:37:41 +01:00
|
|
|
class _NotSpecified:
|
2016-05-29 16:52:55 +02:00
|
|
|
pass
|
|
|
|
NotSpecified = _NotSpecified()
|
|
|
|
|
2019-08-07 11:15:46 +02:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
whence: Optional[str] = None,
|
|
|
|
*,
|
|
|
|
converter: Optional[Callable[[str], ResultT]] = None,
|
|
|
|
default: Union[_NotSpecified, ResultT, None] = NotSpecified,
|
2020-06-21 02:36:20 +02:00
|
|
|
validator: Optional[Validator[ResultT]] = None,
|
|
|
|
str_validator: Optional[Validator[ResultT]] = None,
|
2019-08-07 11:15:46 +02:00
|
|
|
argument_type: Optional[str] = None,
|
|
|
|
intentionally_undocumented: bool=False,
|
|
|
|
documentation_pending: bool=False,
|
2020-06-13 03:34:01 +02:00
|
|
|
aliases: Sequence[str] = [],
|
2019-08-20 00:47:40 +02:00
|
|
|
path_only: bool=False
|
2019-08-07 11:15:46 +02:00
|
|
|
) -> None:
|
2016-05-29 16:52:55 +02:00
|
|
|
"""whence: the name of the request variable that should be used
|
|
|
|
for this parameter. Defaults to a request variable of the
|
|
|
|
same name as the parameter.
|
|
|
|
|
|
|
|
converter: a function that takes a string and returns a new
|
|
|
|
value. If specified, this will be called on the request
|
|
|
|
variable value before passing to the function
|
|
|
|
|
|
|
|
default: a value to be used for the argument if the parameter
|
|
|
|
is missing in the request
|
|
|
|
|
|
|
|
validator: similar to converter, but takes an already parsed JSON
|
|
|
|
data structure. If specified, we will parse the JSON request
|
|
|
|
variable value before passing to the function
|
|
|
|
|
2018-05-04 00:22:00 +02:00
|
|
|
str_validator: Like validator, but doesn't parse JSON first.
|
|
|
|
|
2016-05-29 16:52:55 +02:00
|
|
|
argument_type: pass 'body' to extract the parsed JSON
|
|
|
|
corresponding to the request body
|
2017-12-24 03:06:17 +01:00
|
|
|
|
2018-11-09 19:45:25 +01:00
|
|
|
aliases: alternate names for the POST var
|
2019-08-17 01:21:08 +02:00
|
|
|
|
|
|
|
path_only: Used for parameters included in the URL that we still want
|
|
|
|
to validate via REQ's hooks.
|
2016-05-29 16:52:55 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
self.post_var_name = whence
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
self.func_var_name: Optional[str] = None
|
2016-05-29 16:52:55 +02:00
|
|
|
self.converter = converter
|
|
|
|
self.validator = validator
|
2018-05-04 00:22:00 +02:00
|
|
|
self.str_validator = str_validator
|
2016-05-29 16:52:55 +02:00
|
|
|
self.default = default
|
|
|
|
self.argument_type = argument_type
|
2018-11-09 19:45:25 +01:00
|
|
|
self.aliases = aliases
|
2019-06-30 19:16:33 +02:00
|
|
|
self.intentionally_undocumented = intentionally_undocumented
|
2019-07-04 08:15:10 +02:00
|
|
|
self.documentation_pending = documentation_pending
|
2019-08-17 01:21:08 +02:00
|
|
|
self.path_only = path_only
|
2016-05-29 16:52:55 +02:00
|
|
|
|
2020-07-01 01:12:06 +02:00
|
|
|
assert converter is None or (validator is None and str_validator is None), 'converter and validator are mutually exclusive'
|
|
|
|
assert validator is None or str_validator is None, 'validator and str_validator are mutually exclusive'
|
2016-05-29 16:52:55 +02:00
|
|
|
|
2019-08-07 11:15:46 +02:00
|
|
|
# This factory function ensures that mypy can correctly analyze REQ.
|
|
|
|
#
|
|
|
|
# Note that REQ claims to return a type matching that of the parameter
|
|
|
|
# of which it is the default value, allowing type checking of view
|
|
|
|
# functions using has_request_variables. In reality, REQ returns an
|
|
|
|
# instance of class _REQ to enable the decorator to scan the parameter
|
|
|
|
# list for _REQ objects and patch the parameters as the true types.
|
2020-06-21 19:51:35 +02:00
|
|
|
#
|
|
|
|
# See also this documentation to learn how @overload helps here.
|
|
|
|
# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations
|
|
|
|
#
|
2019-11-13 08:17:49 +01:00
|
|
|
# Overload 1: converter
|
|
|
|
@overload
|
|
|
|
def REQ(
|
|
|
|
whence: Optional[str] = ...,
|
|
|
|
*,
|
|
|
|
converter: Callable[[str], ResultT],
|
|
|
|
default: ResultT = ...,
|
|
|
|
intentionally_undocumented: bool = ...,
|
|
|
|
documentation_pending: bool = ...,
|
2020-06-13 03:34:01 +02:00
|
|
|
aliases: Sequence[str] = ...,
|
2019-11-13 08:17:49 +01:00
|
|
|
path_only: bool = ...
|
|
|
|
) -> ResultT:
|
|
|
|
...
|
|
|
|
|
|
|
|
# Overload 2: validator
|
|
|
|
@overload
|
|
|
|
def REQ(
|
|
|
|
whence: Optional[str] = ...,
|
|
|
|
*,
|
|
|
|
default: ResultT = ...,
|
2020-06-21 02:36:20 +02:00
|
|
|
validator: Validator[ResultT],
|
2019-11-13 08:17:49 +01:00
|
|
|
intentionally_undocumented: bool = ...,
|
|
|
|
documentation_pending: bool = ...,
|
2020-06-13 03:34:01 +02:00
|
|
|
aliases: Sequence[str] = ...,
|
2019-11-13 08:17:49 +01:00
|
|
|
path_only: bool = ...
|
|
|
|
) -> ResultT:
|
|
|
|
...
|
|
|
|
|
|
|
|
# Overload 3: no converter/validator, default: str or unspecified, argument_type=None
|
|
|
|
@overload
|
|
|
|
def REQ(
|
|
|
|
whence: Optional[str] = ...,
|
|
|
|
*,
|
|
|
|
default: str = ...,
|
2020-06-21 02:36:20 +02:00
|
|
|
str_validator: Optional[Validator[str]] = ...,
|
2019-11-13 08:17:49 +01:00
|
|
|
intentionally_undocumented: bool = ...,
|
|
|
|
documentation_pending: bool = ...,
|
2020-06-13 03:34:01 +02:00
|
|
|
aliases: Sequence[str] = ...,
|
2019-11-13 08:17:49 +01:00
|
|
|
path_only: bool = ...
|
|
|
|
) -> str:
|
|
|
|
...
|
|
|
|
|
|
|
|
# Overload 4: no converter/validator, default=None, argument_type=None
|
|
|
|
@overload
|
|
|
|
def REQ(
|
|
|
|
whence: Optional[str] = ...,
|
|
|
|
*,
|
|
|
|
default: None,
|
2020-06-21 02:36:20 +02:00
|
|
|
str_validator: Optional[Validator[str]] = ...,
|
2019-11-13 08:17:49 +01:00
|
|
|
intentionally_undocumented: bool = ...,
|
|
|
|
documentation_pending: bool = ...,
|
2020-06-13 03:34:01 +02:00
|
|
|
aliases: Sequence[str] = ...,
|
2019-11-13 08:17:49 +01:00
|
|
|
path_only: bool = ...
|
|
|
|
) -> Optional[str]:
|
|
|
|
...
|
|
|
|
|
|
|
|
# Overload 5: argument_type="body"
|
|
|
|
@overload
|
|
|
|
def REQ(
|
|
|
|
whence: Optional[str] = ...,
|
|
|
|
*,
|
|
|
|
default: ResultT = ...,
|
|
|
|
argument_type: Literal["body"],
|
|
|
|
intentionally_undocumented: bool = ...,
|
|
|
|
documentation_pending: bool = ...,
|
2020-06-13 03:34:01 +02:00
|
|
|
aliases: Sequence[str] = ...,
|
2019-11-13 08:17:49 +01:00
|
|
|
path_only: bool = ...
|
|
|
|
) -> ResultT:
|
|
|
|
...
|
|
|
|
|
|
|
|
# Implementation
|
2019-08-07 11:15:46 +02:00
|
|
|
def REQ(
|
|
|
|
whence: Optional[str] = None,
|
|
|
|
*,
|
|
|
|
converter: Optional[Callable[[str], ResultT]] = None,
|
2019-11-13 08:17:49 +01:00
|
|
|
default: Union[_REQ._NotSpecified, ResultT] = _REQ.NotSpecified,
|
2020-06-21 02:36:20 +02:00
|
|
|
validator: Optional[Validator[ResultT]] = None,
|
|
|
|
str_validator: Optional[Validator[ResultT]] = None,
|
2019-08-07 11:15:46 +02:00
|
|
|
argument_type: Optional[str] = None,
|
|
|
|
intentionally_undocumented: bool=False,
|
|
|
|
documentation_pending: bool=False,
|
2020-06-13 03:34:01 +02:00
|
|
|
aliases: Sequence[str] = [],
|
2019-08-20 00:58:12 +02:00
|
|
|
path_only: bool = False
|
2019-08-07 11:15:46 +02:00
|
|
|
) -> ResultT:
|
|
|
|
return cast(ResultT, _REQ(
|
|
|
|
whence,
|
|
|
|
converter=converter,
|
|
|
|
default=default,
|
|
|
|
validator=validator,
|
|
|
|
str_validator=str_validator,
|
|
|
|
argument_type=argument_type,
|
|
|
|
intentionally_undocumented=intentionally_undocumented,
|
|
|
|
documentation_pending=documentation_pending,
|
|
|
|
aliases=aliases,
|
2019-08-17 01:21:08 +02:00
|
|
|
path_only=path_only,
|
2019-08-07 11:15:46 +02:00
|
|
|
))
|
|
|
|
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
arguments_map: Dict[str, List[str]] = defaultdict(list)
|
2019-06-06 22:22:21 +02:00
|
|
|
|
2016-05-29 16:52:55 +02:00
|
|
|
# Extracts variables from the request object and passes them as
|
|
|
|
# named function arguments. The request object must be the first
|
|
|
|
# argument to the function.
|
|
|
|
#
|
|
|
|
# To use, assign a function parameter a default value that is an
|
2019-08-07 11:15:46 +02:00
|
|
|
# instance of the _REQ class. That parameter will then be automatically
|
2016-05-29 16:52:55 +02:00
|
|
|
# populated from the HTTP request. The request object must be the
|
|
|
|
# first argument to the decorated function.
|
|
|
|
#
|
|
|
|
# This should generally be the innermost (syntactically bottommost)
|
|
|
|
# decorator applied to a view, since other decorators won't preserve
|
|
|
|
# the default parameter values used by has_request_variables.
|
|
|
|
#
|
|
|
|
# Note that this can't be used in helper functions which are not
|
|
|
|
# expected to call json_error or json_success, as it uses json_error
|
|
|
|
# internally when it encounters an error
|
2019-02-01 14:08:45 +01:00
|
|
|
def has_request_variables(view_func: ViewFuncT) -> ViewFuncT:
|
2016-05-29 16:52:55 +02:00
|
|
|
num_params = view_func.__code__.co_argcount
|
2019-08-07 11:15:46 +02:00
|
|
|
default_param_values = cast(FunctionType, view_func).__defaults__
|
2016-05-29 16:52:55 +02:00
|
|
|
if default_param_values is None:
|
2019-08-07 11:15:46 +02:00
|
|
|
default_param_values = ()
|
|
|
|
num_default_params = len(default_param_values)
|
|
|
|
default_param_names = view_func.__code__.co_varnames[num_params - num_default_params:]
|
2016-05-29 16:52:55 +02:00
|
|
|
|
|
|
|
post_params = []
|
|
|
|
|
2019-06-06 22:22:21 +02:00
|
|
|
view_func_full_name = '.'.join([view_func.__module__, view_func.__name__])
|
|
|
|
|
2016-05-29 16:52:55 +02:00
|
|
|
for (name, value) in zip(default_param_names, default_param_values):
|
2019-08-07 11:15:46 +02:00
|
|
|
if isinstance(value, _REQ):
|
2016-05-29 16:52:55 +02:00
|
|
|
value.func_var_name = name
|
|
|
|
if value.post_var_name is None:
|
|
|
|
value.post_var_name = name
|
|
|
|
post_params.append(value)
|
2019-06-30 19:16:33 +02:00
|
|
|
|
|
|
|
# Record arguments that should be documented so that our
|
|
|
|
# automated OpenAPI docs tests can compare these against the code.
|
2019-08-17 01:21:08 +02:00
|
|
|
if (not value.intentionally_undocumented
|
|
|
|
and not value.documentation_pending
|
|
|
|
and not value.path_only):
|
2019-06-30 19:16:33 +02:00
|
|
|
arguments_map[view_func_full_name].append(value.post_var_name)
|
2016-05-29 16:52:55 +02:00
|
|
|
|
|
|
|
@wraps(view_func)
|
2020-06-24 02:10:50 +02:00
|
|
|
def _wrapped_view_func(request: HttpRequest, *args: object, **kwargs: object) -> HttpResponse:
|
2016-05-29 16:52:55 +02:00
|
|
|
for param in post_params:
|
2019-08-07 11:15:46 +02:00
|
|
|
func_var_name = param.func_var_name
|
2019-08-17 01:21:08 +02:00
|
|
|
if param.path_only:
|
|
|
|
# For path_only parameters, they should already have
|
|
|
|
# been passed via the URL, so there's no need for REQ
|
|
|
|
# to do anything.
|
|
|
|
#
|
|
|
|
# TODO: Either run validators for path_only parameters
|
|
|
|
# or don't declare them using REQ.
|
|
|
|
assert func_var_name in kwargs
|
2019-08-07 11:15:46 +02:00
|
|
|
if func_var_name in kwargs:
|
2016-05-29 16:52:55 +02:00
|
|
|
continue
|
2019-08-07 11:15:46 +02:00
|
|
|
assert func_var_name is not None
|
2016-05-29 16:52:55 +02:00
|
|
|
|
|
|
|
if param.argument_type == 'body':
|
|
|
|
try:
|
2020-08-07 01:09:47 +02:00
|
|
|
val = orjson.loads(request.body)
|
2020-08-12 20:23:23 +02:00
|
|
|
except orjson.JSONDecodeError:
|
2018-11-15 05:31:34 +01:00
|
|
|
raise InvalidJSONError(_("Malformed JSON"))
|
2019-08-07 11:15:46 +02:00
|
|
|
kwargs[func_var_name] = val
|
2016-05-29 16:52:55 +02:00
|
|
|
continue
|
2020-07-01 01:12:06 +02:00
|
|
|
else:
|
2016-05-29 16:52:55 +02:00
|
|
|
# This is a view bug, not a user error, and thus should throw a 500.
|
2020-07-01 01:12:06 +02:00
|
|
|
assert param.argument_type is None, "Invalid argument type"
|
2016-05-29 16:52:55 +02:00
|
|
|
|
2018-11-09 19:45:25 +01:00
|
|
|
post_var_names = [param.post_var_name]
|
2020-06-13 03:34:01 +02:00
|
|
|
post_var_names += param.aliases
|
2018-11-09 19:45:25 +01:00
|
|
|
|
2016-05-29 16:52:55 +02:00
|
|
|
default_assigned = False
|
2018-11-09 19:45:25 +01:00
|
|
|
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
post_var_name: Optional[str] = None
|
2018-11-09 19:45:25 +01:00
|
|
|
|
|
|
|
for req_var in post_var_names:
|
2020-02-15 02:36:19 +01:00
|
|
|
if req_var in request.POST:
|
|
|
|
val = request.POST[req_var]
|
|
|
|
elif req_var in request.GET:
|
|
|
|
val = request.GET[req_var]
|
|
|
|
else:
|
|
|
|
# This is covered by test_REQ_aliases, but coverage.py
|
|
|
|
# fails to recognize this for some reason.
|
|
|
|
continue # nocoverage
|
2018-11-09 19:45:25 +01:00
|
|
|
if post_var_name is not None:
|
2019-08-07 11:15:46 +02:00
|
|
|
assert req_var is not None
|
2018-11-09 19:45:25 +01:00
|
|
|
raise RequestConfusingParmsError(post_var_name, req_var)
|
|
|
|
post_var_name = req_var
|
|
|
|
|
|
|
|
if post_var_name is None:
|
|
|
|
post_var_name = param.post_var_name
|
2019-08-07 11:15:46 +02:00
|
|
|
assert post_var_name is not None
|
|
|
|
if param.default is _REQ.NotSpecified:
|
2018-11-09 19:45:25 +01:00
|
|
|
raise RequestVariableMissingError(post_var_name)
|
2016-05-29 16:52:55 +02:00
|
|
|
val = param.default
|
|
|
|
default_assigned = True
|
|
|
|
|
|
|
|
if param.converter is not None and not default_assigned:
|
|
|
|
try:
|
|
|
|
val = param.converter(val)
|
|
|
|
except JsonableError:
|
|
|
|
raise
|
2017-03-05 10:25:27 +01:00
|
|
|
except Exception:
|
2018-11-09 19:45:25 +01:00
|
|
|
raise RequestVariableConversionError(post_var_name, val)
|
2016-05-29 16:52:55 +02:00
|
|
|
|
|
|
|
# Validators are like converters, but they don't handle JSON parsing; we do.
|
|
|
|
if param.validator is not None and not default_assigned:
|
|
|
|
try:
|
2020-08-07 01:09:47 +02:00
|
|
|
val = orjson.loads(val)
|
2017-03-05 10:25:27 +01:00
|
|
|
except Exception:
|
2020-06-15 23:22:24 +02:00
|
|
|
raise JsonableError(_('Argument "{}" is not valid JSON.').format(post_var_name))
|
2016-05-29 16:52:55 +02:00
|
|
|
|
2020-06-21 02:36:20 +02:00
|
|
|
try:
|
|
|
|
val = param.validator(post_var_name, val)
|
|
|
|
except ValidationError as error:
|
|
|
|
raise JsonableError(error.message)
|
2016-05-29 16:52:55 +02:00
|
|
|
|
2018-05-04 00:22:00 +02:00
|
|
|
# str_validators is like validator, but for direct strings (no JSON parsing).
|
|
|
|
if param.str_validator is not None and not default_assigned:
|
2020-06-21 02:36:20 +02:00
|
|
|
try:
|
|
|
|
val = param.str_validator(post_var_name, val)
|
|
|
|
except ValidationError as error:
|
|
|
|
raise JsonableError(error.message)
|
2018-05-04 00:22:00 +02:00
|
|
|
|
2019-08-07 11:15:46 +02:00
|
|
|
kwargs[func_var_name] = val
|
2016-05-29 16:52:55 +02:00
|
|
|
|
|
|
|
return view_func(request, *args, **kwargs)
|
|
|
|
|
2020-06-24 02:10:50 +02:00
|
|
|
return cast(ViewFuncT, _wrapped_view_func) # https://github.com/python/mypy/issues/1927
|