From 6590674f1e4362bf95dfb04999abb9ed0e8ad7f2 Mon Sep 17 00:00:00 2001 From: tdadela Date: Thu, 2 May 2024 23:24:42 +0200 Subject: [PATCH 01/18] update: HttpSession.request (copied from requests.Session.request) --- locust/clients.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index 7d3997fe74..4a013d8d14 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -108,7 +108,8 @@ def request(self, method, url, name=None, catch_response=False, context={}, **kw response, even if the response code is ok (2xx). The opposite also works, one can use catch_response to catch a request and then mark it as successful even if the response code was not (i.e 500 or 404). :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. - :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. :param files: (optional) Dictionary of ``'filename': file-like-objects`` for multipart encoding upload. @@ -117,9 +118,17 @@ def request(self, method, url, name=None, catch_response=False, context={}, **kw :type timeout: float or tuple :param allow_redirects: (optional) Set to True by default. :type allow_redirects: bool - :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param proxies: (optional) Dictionary mapping protocol or protocol and hostname to the URL of the proxy. + :param hooks: (optional) Dictionary mapping hook name to one event or list of events, event must be callable. :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. - :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. """ From e47e8681bef1a9ec10a6291532e869d25f035f86 Mon Sep 17 00:00:00 2001 From: tdadela Date: Sat, 4 May 2024 23:05:51 +0200 Subject: [PATCH 02/18] annotate HttpSession.request --- locust/clients.py | 38 +++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/locust/clients.py b/locust/clients.py index 4a013d8d14..42819d1936 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -4,6 +4,7 @@ import time from collections.abc import Generator from contextlib import contextmanager +from typing import TYPE_CHECKING from urllib.parse import urlparse, urlunparse import requests @@ -15,6 +16,33 @@ from .exception import CatchResponseError, LocustError, ResponseError +if TYPE_CHECKING: + from collections.abc import Callable, Iterable, Mapping, MutableMapping + from typing import Any, TypedDict + + from requests.cookies import RequestsCookieJar + from typing_extensions import Unpack + + # Annotations below were generated using output from mypy. + # Mypy underneath uses information from the https://github.com/python/typeshed repo. + + class RequestKwargs(TypedDict, total=False): + params: Any | None # simplified signature + data: Any | None # simplified signature + headers: Mapping[str, str | bytes | None] | None + cookies: RequestsCookieJar | MutableMapping[str, str] | None + files: Any | None # simplified signature + auth: Any | None # simplified signature + timeout: float | tuple[float, float] | tuple[float, None] | None + allow_redirects: bool + proxies: MutableMapping[str, str] | None + hooks: Mapping[str, Iterable[Callable[[Response], Any]] | Callable[[Response], Any]] | None + stream: bool | None + verify: bool | str | None + cert: str | tuple[str, str] | None + json: Any | None + + absolute_http_url_regexp = re.compile(r"^https?://", re.I) @@ -94,7 +122,15 @@ def rename_request(self, name: str) -> Generator[None, None, None]: finally: self.request_name = None - def request(self, method, url, name=None, catch_response=False, context={}, **kwargs): + def request( # type: ignore[override] + self, + method: str | bytes, + url: str | bytes, + name: str | None = None, + catch_response: bool = False, + context: dict = {}, + **kwargs: Unpack[RequestKwargs], + ): """ Constructs and sends a :py:class:`requests.Request`. Returns :py:class:`requests.Response` object. diff --git a/pyproject.toml b/pyproject.toml index 3667b085da..8d4358f501 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "geventhttpclient ==2.2.1", "ConfigArgParse >=1.5.5", "tomli >=1.1.0; python_version<'3.11'", + "typing_extensions >=4.6.0", "psutil >=5.9.1", "Flask-Login >=0.6.3", "Flask-Cors >=3.0.10", From 125b7238bf5487ea2cd471760c62b6b99438097f Mon Sep 17 00:00:00 2001 From: tdadela Date: Sun, 5 May 2024 00:33:49 +0200 Subject: [PATCH 03/18] feat(HttpSession): override requests.Session methods to provide better type annotations support --- locust/clients.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/locust/clients.py b/locust/clients.py index 42819d1936..1d9c14f787 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -42,6 +42,11 @@ class RequestKwargs(TypedDict, total=False): cert: str | tuple[str, str] | None json: Any | None + class RESTKwargs(RequestKwargs, total=False): + name: str | None + catch_response: bool + context: dict + absolute_http_url_regexp = re.compile(r"^https?://", re.I) @@ -232,6 +237,27 @@ def _send_request_safe_mode(self, method, url, **kwargs): r.request = Request(method, url).prepare() return r + def get(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + return super().get(url, **kwargs) # type: ignore[misc] + + def options(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + return super().options(url, **kwargs) # type: ignore[misc] + + def head(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + return super().head(url, **kwargs) # type: ignore[misc] + + def post(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + return super().post(url, **kwargs) # type: ignore[misc] + + def put(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + return super().put(url, **kwargs) # type: ignore[misc] + + def patch(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + return super().patch(url, **kwargs) # type: ignore[misc] + + def delete(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + return super().delete(url, **kwargs) # type: ignore[misc] + class ResponseContextManager(LocustResponse): """ From 5cd067537be1b33ef18d6008d16b53cc6e36df8c Mon Sep 17 00:00:00 2001 From: tdadela Date: Sun, 5 May 2024 01:20:45 +0200 Subject: [PATCH 04/18] fix(HttpSession): rest methods positional arguments --- locust/clients.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index 1d9c14f787..1059a6ab05 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -246,13 +246,13 @@ def options(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: igno def head(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] return super().head(url, **kwargs) # type: ignore[misc] - def post(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + def post(self, url: str | bytes, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] return super().post(url, **kwargs) # type: ignore[misc] - def put(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + def put(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] return super().put(url, **kwargs) # type: ignore[misc] - def patch(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + def patch(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] return super().patch(url, **kwargs) # type: ignore[misc] def delete(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] From c716225443fe24c77f937e8fb32076608067ecd6 Mon Sep 17 00:00:00 2001 From: tdadela Date: Sun, 5 May 2024 11:11:57 +0200 Subject: [PATCH 05/18] fix(HttpSession): rest methods pass positional arguments to super() --- locust/clients.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index 1059a6ab05..73033f30a7 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -247,13 +247,13 @@ def head(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[ return super().head(url, **kwargs) # type: ignore[misc] def post(self, url: str | bytes, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] - return super().post(url, **kwargs) # type: ignore[misc] + return super().post(url, data=data, json=json, **kwargs) # type: ignore[misc] def put(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] - return super().put(url, **kwargs) # type: ignore[misc] + return super().put(url, data=data, **kwargs) # type: ignore[misc] def patch(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] - return super().patch(url, **kwargs) # type: ignore[misc] + return super().patch(url, data=data, **kwargs) # type: ignore[misc] def delete(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] return super().delete(url, **kwargs) # type: ignore[misc] From 108e17d91e5dd8bacf25c32e729dbee61de6ed91 Mon Sep 17 00:00:00 2001 From: tdadela Date: Sun, 5 May 2024 12:55:42 +0200 Subject: [PATCH 06/18] minor: collections.abc import --- locust/clients.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index 73033f30a7..12f47ccaa7 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -2,7 +2,6 @@ import re import time -from collections.abc import Generator from contextlib import contextmanager from typing import TYPE_CHECKING from urllib.parse import urlparse, urlunparse @@ -17,7 +16,7 @@ from .exception import CatchResponseError, LocustError, ResponseError if TYPE_CHECKING: - from collections.abc import Callable, Iterable, Mapping, MutableMapping + from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping from typing import Any, TypedDict from requests.cookies import RequestsCookieJar From f2b1247a4b2b750ab7494c3324b5e66116fa9942 Mon Sep 17 00:00:00 2001 From: tdadela Date: Sun, 5 May 2024 21:10:08 +0200 Subject: [PATCH 07/18] minor: comments explaining ignores --- locust/clients.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/locust/clients.py b/locust/clients.py index 12f47ccaa7..8e48dfee0f 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -236,6 +236,8 @@ def _send_request_safe_mode(self, method, url, **kwargs): r.request = Request(method, url).prepare() return r + # These # type: ignore[override] comments below are needed because our overridden version of functions receives + # more arguments than functions in the base class. def get(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] return super().get(url, **kwargs) # type: ignore[misc] @@ -245,6 +247,8 @@ def options(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: igno def head(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] return super().head(url, **kwargs) # type: ignore[misc] + # These # type: ignore[misc] comments below are needed because data and json parameters are already defined in the + # RESTKwargs TypedDict. An alternative approach is to define another TypedDict which doesn't contain them. def post(self, url: str | bytes, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] return super().post(url, data=data, json=json, **kwargs) # type: ignore[misc] From e464109fd3e206adaa6bb2f37d4106a87bfa2b06 Mon Sep 17 00:00:00 2001 From: tdadela Date: Tue, 18 Jun 2024 22:53:54 +0200 Subject: [PATCH 08/18] refactor: call self.requests directly inside HttpSession REST methods --- locust/clients.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index 8e48dfee0f..cc11dd91fc 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -239,27 +239,30 @@ def _send_request_safe_mode(self, method, url, **kwargs): # These # type: ignore[override] comments below are needed because our overridden version of functions receives # more arguments than functions in the base class. def get(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] - return super().get(url, **kwargs) # type: ignore[misc] + kwargs.setdefault("allow_redirects", True) + return self.request("GET", url, **kwargs) # type: ignore[misc] def options(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] - return super().options(url, **kwargs) # type: ignore[misc] + kwargs.setdefault("allow_redirects", True) + return self.request("OPTIONS", url, **kwargs) # type: ignore[misc] def head(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] - return super().head(url, **kwargs) # type: ignore[misc] + kwargs.setdefault("allow_redirects", False) + return self.request("HEAD", url, **kwargs) # type: ignore[misc] # These # type: ignore[misc] comments below are needed because data and json parameters are already defined in the # RESTKwargs TypedDict. An alternative approach is to define another TypedDict which doesn't contain them. def post(self, url: str | bytes, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] - return super().post(url, data=data, json=json, **kwargs) # type: ignore[misc] + return self.request("POST", url, data=data, json=json, **kwargs) # type: ignore[misc] def put(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] - return super().put(url, data=data, **kwargs) # type: ignore[misc] + return self.request("PUT", url, data=data, **kwargs) # type: ignore[misc] def patch(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] - return super().patch(url, data=data, **kwargs) # type: ignore[misc] + return self.request("PATCH", url, data=data, **kwargs) # type: ignore[misc] def delete(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] - return super().delete(url, **kwargs) # type: ignore[misc] + return self.request("DELETE", url, **kwargs) # type: ignore[misc] class ResponseContextManager(LocustResponse): From e489d9b449dfc00199f8e9a2f3850ea0919dc14a Mon Sep 17 00:00:00 2001 From: tdadela Date: Wed, 19 Jun 2024 22:59:02 +0200 Subject: [PATCH 09/18] refactor: use typing_extensions.Unpack only if python version < 3.11 --- locust/clients.py | 7 ++++++- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index cc11dd91fc..e1c1e329a7 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -16,11 +16,16 @@ from .exception import CatchResponseError, LocustError, ResponseError if TYPE_CHECKING: + import sys from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping from typing import Any, TypedDict from requests.cookies import RequestsCookieJar - from typing_extensions import Unpack + + if sys.version_info >= (3, 11): + from typing import Unpack + else: + from typing_extensions import Unpack # Annotations below were generated using output from mypy. # Mypy underneath uses information from the https://github.com/python/typeshed repo. diff --git a/pyproject.toml b/pyproject.toml index b1a284336e..ee6e2d3170 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "geventhttpclient >=2.3.1", "ConfigArgParse >=1.5.5", "tomli >=1.1.0; python_version<'3.11'", - "typing_extensions >=4.6.0", + "typing_extensions >=4.6.0; python_version<'3.11'", "psutil >=5.9.1", "Flask-Login >=0.6.3", "Flask-Cors >=3.0.10", From 472d5972acea074c43dc83c7785bc21cff6c4221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20D=C4=85dela?= <39437649+tdadela@users.noreply.github.com> Date: Sun, 23 Jun 2024 21:49:55 +0200 Subject: [PATCH 10/18] feat: patch, post, put return type Co-authored-by: Oscar Butler-Aldridge --- locust/clients.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index e1c1e329a7..fdef4ea3c1 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -257,13 +257,13 @@ def head(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[ # These # type: ignore[misc] comments below are needed because data and json parameters are already defined in the # RESTKwargs TypedDict. An alternative approach is to define another TypedDict which doesn't contain them. - def post(self, url: str | bytes, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] + def post(self, url: str | bytes, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs]) -> (ResponseContextManager | Response | LocustResponse): # type: ignore[override, misc] return self.request("POST", url, data=data, json=json, **kwargs) # type: ignore[misc] - def put(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] + def put(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]) -> (ResponseContextManager | Response | LocustResponse): # type: ignore[override, misc] return self.request("PUT", url, data=data, **kwargs) # type: ignore[misc] - def patch(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]): # type: ignore[override, misc] + def patch(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]) -> (ResponseContextManager | Response | LocustResponse): # type: ignore[override, misc] return self.request("PATCH", url, data=data, **kwargs) # type: ignore[misc] def delete(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] From 6790aabe00ecaa91c780ec843e5717fdc94d3784 Mon Sep 17 00:00:00 2001 From: tdadela Date: Sun, 23 Jun 2024 22:26:09 +0200 Subject: [PATCH 11/18] mypy: enable_incomplete_feature - Unpack; annotate all REST functions return type for consistency --- locust/clients.py | 52 ++++++++++++++++++++++++++++++++++++----------- pyproject.toml | 1 + 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index fdef4ea3c1..261df3ac6a 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -243,31 +243,59 @@ def _send_request_safe_mode(self, method, url, **kwargs): # These # type: ignore[override] comments below are needed because our overridden version of functions receives # more arguments than functions in the base class. - def get(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + def get(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager | Response | LocustResponse: # type: ignore[override] kwargs.setdefault("allow_redirects", True) - return self.request("GET", url, **kwargs) # type: ignore[misc] + return self.request("GET", url, **kwargs) - def options(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + def options( + self, + url: str | bytes, + **kwargs: Unpack[RESTKwargs], # type: ignore[override] + ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", True) - return self.request("OPTIONS", url, **kwargs) # type: ignore[misc] + return self.request("OPTIONS", url, **kwargs) - def head(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] + def head( + self, + url: str | bytes, + **kwargs: Unpack[RESTKwargs], # type: ignore[override] + ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", False) - return self.request("HEAD", url, **kwargs) # type: ignore[misc] + return self.request("HEAD", url, **kwargs) # These # type: ignore[misc] comments below are needed because data and json parameters are already defined in the # RESTKwargs TypedDict. An alternative approach is to define another TypedDict which doesn't contain them. - def post(self, url: str | bytes, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs]) -> (ResponseContextManager | Response | LocustResponse): # type: ignore[override, misc] + def post( + self, + url: str | bytes, + data: Any | None = None, + json: Any | None = None, + **kwargs: Unpack[RESTKwargs], # type: ignore[override, misc] + ) -> ResponseContextManager | Response | LocustResponse: return self.request("POST", url, data=data, json=json, **kwargs) # type: ignore[misc] - def put(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]) -> (ResponseContextManager | Response | LocustResponse): # type: ignore[override, misc] + def put( + self, + url: str | bytes, + data: Any | None = None, + **kwargs: Unpack[RESTKwargs], # type: ignore[override, misc] + ) -> ResponseContextManager | Response | LocustResponse: return self.request("PUT", url, data=data, **kwargs) # type: ignore[misc] - def patch(self, url: str | bytes, data: Any | None = None, **kwargs: Unpack[RESTKwargs]) -> (ResponseContextManager | Response | LocustResponse): # type: ignore[override, misc] + def patch( + self, + url: str | bytes, + data: Any | None = None, + **kwargs: Unpack[RESTKwargs], # type: ignore[override, misc] + ) -> ResponseContextManager | Response | LocustResponse: return self.request("PATCH", url, data=data, **kwargs) # type: ignore[misc] - def delete(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]): # type: ignore[override] - return self.request("DELETE", url, **kwargs) # type: ignore[misc] + def delete( + self, + url: str | bytes, + **kwargs: Unpack[RESTKwargs], # type: ignore[override] + ) -> ResponseContextManager | Response | LocustResponse: + return self.request("DELETE", url, **kwargs) class ResponseContextManager(LocustResponse): @@ -293,7 +321,7 @@ def __enter__(self): self._entered = True return self - def __exit__(self, exc, value, traceback): + def __exit__(self, exc, value, traceback): # type: ignore[override] # if the user has already manually marked this response as failure or success # we can ignore the default behaviour of letting the response code determine the outcome if self._manual_result is not None: diff --git a/pyproject.toml b/pyproject.toml index ee6e2d3170..afa0aa73fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,7 @@ locust = "locust.main:main" [tool.mypy] # missing type stubs ignore_missing_imports = true +enable_incomplete_feature = ["Unpack"] python_version = "3.9" [[tool.mypy.overrides]] From 3500c237d9b47d5e5b88d5b20e85b121dd960dbb Mon Sep 17 00:00:00 2001 From: tdadela Date: Sun, 23 Jun 2024 22:42:00 +0200 Subject: [PATCH 12/18] fix: mypy & pyright errors --- locust/clients.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index 261df3ac6a..73b6dadb71 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -265,28 +265,28 @@ def head( # These # type: ignore[misc] comments below are needed because data and json parameters are already defined in the # RESTKwargs TypedDict. An alternative approach is to define another TypedDict which doesn't contain them. - def post( + def post( # type: ignore[override, misc] # <-- misc in this position to suppress mypy error self, url: str | bytes, data: Any | None = None, json: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[override, misc] + **kwargs: Unpack[RESTKwargs], # type: ignore[misc] # <-- misc in this position to suppress pyright error ) -> ResponseContextManager | Response | LocustResponse: return self.request("POST", url, data=data, json=json, **kwargs) # type: ignore[misc] - def put( + def put( # type: ignore[override, misc] # <-- misc in this position to suppress mypy error self, url: str | bytes, data: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[override, misc] + **kwargs: Unpack[RESTKwargs], # type: ignore[misc] # <-- misc in this position to suppress pyright error ) -> ResponseContextManager | Response | LocustResponse: return self.request("PUT", url, data=data, **kwargs) # type: ignore[misc] - def patch( + def patch( # type: ignore[override, misc] # <-- misc in this position to suppress mypy error self, url: str | bytes, data: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[override, misc] + **kwargs: Unpack[RESTKwargs], # type: ignore[misc] # <-- misc in this position to suppress pyright error ) -> ResponseContextManager | Response | LocustResponse: return self.request("PATCH", url, data=data, **kwargs) # type: ignore[misc] From 91f6aeb44af89095a6442b465fcd79a8653f6378 Mon Sep 17 00:00:00 2001 From: tdadela Date: Sun, 23 Jun 2024 23:10:12 +0200 Subject: [PATCH 13/18] fix: ignore[misc] in REST functions by changing TypedDicts --- locust/clients.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index 73b6dadb71..f6bdf61b03 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -32,7 +32,6 @@ class RequestKwargs(TypedDict, total=False): params: Any | None # simplified signature - data: Any | None # simplified signature headers: Mapping[str, str | bytes | None] | None cookies: RequestsCookieJar | MutableMapping[str, str] | None files: Any | None # simplified signature @@ -44,7 +43,6 @@ class RequestKwargs(TypedDict, total=False): stream: bool | None verify: bool | str | None cert: str | tuple[str, str] | None - json: Any | None class RESTKwargs(RequestKwargs, total=False): name: str | None @@ -138,6 +136,9 @@ def request( # type: ignore[override] name: str | None = None, catch_response: bool = False, context: dict = {}, + *, + data: Any | None = None, + json: Any | None = None, **kwargs: Unpack[RequestKwargs], ): """ @@ -243,13 +244,18 @@ def _send_request_safe_mode(self, method, url, **kwargs): # These # type: ignore[override] comments below are needed because our overridden version of functions receives # more arguments than functions in the base class. - def get(self, url: str | bytes, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager | Response | LocustResponse: # type: ignore[override] + def get( + self, url: str | bytes, *, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs] + ) -> ResponseContextManager | Response | LocustResponse: # type: ignore[override] kwargs.setdefault("allow_redirects", True) return self.request("GET", url, **kwargs) def options( self, url: str | bytes, + *, + data: Any | None = None, + json: Any | None = None, **kwargs: Unpack[RESTKwargs], # type: ignore[override] ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", True) @@ -258,41 +264,49 @@ def options( def head( self, url: str | bytes, + *, + data: Any | None = None, + json: Any | None = None, **kwargs: Unpack[RESTKwargs], # type: ignore[override] ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", False) return self.request("HEAD", url, **kwargs) - # These # type: ignore[misc] comments below are needed because data and json parameters are already defined in the - # RESTKwargs TypedDict. An alternative approach is to define another TypedDict which doesn't contain them. - def post( # type: ignore[override, misc] # <-- misc in this position to suppress mypy error + def post( self, url: str | bytes, data: Any | None = None, json: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[misc] # <-- misc in this position to suppress pyright error + **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: return self.request("POST", url, data=data, json=json, **kwargs) # type: ignore[misc] - def put( # type: ignore[override, misc] # <-- misc in this position to suppress mypy error + def put( self, url: str | bytes, data: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[misc] # <-- misc in this position to suppress pyright error + *, + json: Any | None = None, + **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: return self.request("PUT", url, data=data, **kwargs) # type: ignore[misc] - def patch( # type: ignore[override, misc] # <-- misc in this position to suppress mypy error + def patch( self, url: str | bytes, data: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[misc] # <-- misc in this position to suppress pyright error + *, + json: Any | None = None, + **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: return self.request("PATCH", url, data=data, **kwargs) # type: ignore[misc] def delete( self, url: str | bytes, + *, + data: Any | None = None, + json: Any | None = None, **kwargs: Unpack[RESTKwargs], # type: ignore[override] ) -> ResponseContextManager | Response | LocustResponse: return self.request("DELETE", url, **kwargs) From 357d4046554d8c9c28880db2ddebdef8e23c8f09 Mon Sep 17 00:00:00 2001 From: tdadela Date: Mon, 24 Jun 2024 00:03:01 +0200 Subject: [PATCH 14/18] fix: pass all arguments in REST functions --- locust/clients.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index f6bdf61b03..2e54be2c0b 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -248,7 +248,7 @@ def get( self, url: str | bytes, *, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs] ) -> ResponseContextManager | Response | LocustResponse: # type: ignore[override] kwargs.setdefault("allow_redirects", True) - return self.request("GET", url, **kwargs) + return self.request("GET", url, data=data, json=json, **kwargs) def options( self, @@ -259,7 +259,7 @@ def options( **kwargs: Unpack[RESTKwargs], # type: ignore[override] ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", True) - return self.request("OPTIONS", url, **kwargs) + return self.request("OPTIONS", url, data=data, json=json, **kwargs) def head( self, @@ -270,7 +270,7 @@ def head( **kwargs: Unpack[RESTKwargs], # type: ignore[override] ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", False) - return self.request("HEAD", url, **kwargs) + return self.request("HEAD", url, data=data, json=json, **kwargs) def post( self, @@ -289,7 +289,7 @@ def put( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: - return self.request("PUT", url, data=data, **kwargs) # type: ignore[misc] + return self.request("PUT", url, data=data, json=json, **kwargs) # type: ignore[misc] def patch( self, @@ -299,7 +299,7 @@ def patch( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: - return self.request("PATCH", url, data=data, **kwargs) # type: ignore[misc] + return self.request("PATCH", url, data=data, json=json, **kwargs) # type: ignore[misc] def delete( self, @@ -309,7 +309,7 @@ def delete( json: Any | None = None, **kwargs: Unpack[RESTKwargs], # type: ignore[override] ) -> ResponseContextManager | Response | LocustResponse: - return self.request("DELETE", url, **kwargs) + return self.request("DELETE", url, data=data, json=json, **kwargs) class ResponseContextManager(LocustResponse): From c2140c8359a6fa8b79145a311c34f12adaa64a98 Mon Sep 17 00:00:00 2001 From: tdadela Date: Mon, 24 Jun 2024 00:20:37 +0200 Subject: [PATCH 15/18] fix: pass all arguments to the request function --- locust/clients.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locust/clients.py b/locust/clients.py index 2e54be2c0b..be1ff74a2b 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -187,7 +187,7 @@ def request( # type: ignore[override] start_time = time.time() start_perf_counter = time.perf_counter() - response = self._send_request_safe_mode(method, url, **kwargs) + response = self._send_request_safe_mode(method, url, data=data, json=json, **kwargs) response_time = (time.perf_counter() - start_perf_counter) * 1000 request_before_redirect = (response.history and response.history[0] or response).request From a68a2e0269fe3fc06cb14f4c120ff94810f3e5df Mon Sep 17 00:00:00 2001 From: tdadela Date: Mon, 24 Jun 2024 18:13:58 +0200 Subject: [PATCH 16/18] rm outdated ignore[override] --- locust/clients.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index be1ff74a2b..3c5eb06429 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -242,11 +242,9 @@ def _send_request_safe_mode(self, method, url, **kwargs): r.request = Request(method, url).prepare() return r - # These # type: ignore[override] comments below are needed because our overridden version of functions receives - # more arguments than functions in the base class. def get( self, url: str | bytes, *, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs] - ) -> ResponseContextManager | Response | LocustResponse: # type: ignore[override] + ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", True) return self.request("GET", url, data=data, json=json, **kwargs) @@ -256,7 +254,7 @@ def options( *, data: Any | None = None, json: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[override] + **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", True) return self.request("OPTIONS", url, data=data, json=json, **kwargs) @@ -267,7 +265,7 @@ def head( *, data: Any | None = None, json: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[override] + **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: kwargs.setdefault("allow_redirects", False) return self.request("HEAD", url, data=data, json=json, **kwargs) @@ -307,7 +305,7 @@ def delete( *, data: Any | None = None, json: Any | None = None, - **kwargs: Unpack[RESTKwargs], # type: ignore[override] + **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: return self.request("DELETE", url, data=data, json=json, **kwargs) From f9bcd7c8a8744aadf35c0343054c57d692159f81 Mon Sep 17 00:00:00 2001 From: tdadela Date: Mon, 24 Jun 2024 18:35:31 +0200 Subject: [PATCH 17/18] rm outdated ignore[misc], mypy setting --- locust/clients.py | 6 +++--- pyproject.toml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/locust/clients.py b/locust/clients.py index 3c5eb06429..fa08e69884 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -277,7 +277,7 @@ def post( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: - return self.request("POST", url, data=data, json=json, **kwargs) # type: ignore[misc] + return self.request("POST", url, data=data, json=json, **kwargs) def put( self, @@ -287,7 +287,7 @@ def put( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: - return self.request("PUT", url, data=data, json=json, **kwargs) # type: ignore[misc] + return self.request("PUT", url, data=data, json=json, **kwargs) def patch( self, @@ -297,7 +297,7 @@ def patch( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: - return self.request("PATCH", url, data=data, json=json, **kwargs) # type: ignore[misc] + return self.request("PATCH", url, data=data, json=json, **kwargs) def delete( self, diff --git a/pyproject.toml b/pyproject.toml index afa0aa73fa..ee6e2d3170 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,6 @@ locust = "locust.main:main" [tool.mypy] # missing type stubs ignore_missing_imports = true -enable_incomplete_feature = ["Unpack"] python_version = "3.9" [[tool.mypy.overrides]] From b04aec43344410e069a03e24ca24266bde864d9f Mon Sep 17 00:00:00 2001 From: tdadela Date: Mon, 24 Jun 2024 19:02:07 +0200 Subject: [PATCH 18/18] feat: HttpSession docstrings --- locust/clients.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/locust/clients.py b/locust/clients.py index fa08e69884..70471282f0 100644 --- a/locust/clients.py +++ b/locust/clients.py @@ -245,6 +245,7 @@ def _send_request_safe_mode(self, method, url, **kwargs): def get( self, url: str | bytes, *, data: Any | None = None, json: Any | None = None, **kwargs: Unpack[RESTKwargs] ) -> ResponseContextManager | Response | LocustResponse: + """Sends a GET request""" kwargs.setdefault("allow_redirects", True) return self.request("GET", url, data=data, json=json, **kwargs) @@ -256,6 +257,7 @@ def options( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: + """Sends a OPTIONS request""" kwargs.setdefault("allow_redirects", True) return self.request("OPTIONS", url, data=data, json=json, **kwargs) @@ -267,6 +269,7 @@ def head( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: + """Sends a HEAD request""" kwargs.setdefault("allow_redirects", False) return self.request("HEAD", url, data=data, json=json, **kwargs) @@ -277,6 +280,7 @@ def post( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: + """Sends a POST request""" return self.request("POST", url, data=data, json=json, **kwargs) def put( @@ -287,6 +291,7 @@ def put( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: + """Sends a PUT request""" return self.request("PUT", url, data=data, json=json, **kwargs) def patch( @@ -297,6 +302,7 @@ def patch( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: + """Sends a PATCH request""" return self.request("PATCH", url, data=data, json=json, **kwargs) def delete( @@ -307,6 +313,7 @@ def delete( json: Any | None = None, **kwargs: Unpack[RESTKwargs], ) -> ResponseContextManager | Response | LocustResponse: + """Sends a DELETE request""" return self.request("DELETE", url, data=data, json=json, **kwargs)