locust 2.40.2.dev15__py3-none-any.whl → 2.40.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- locust/_version.py +2 -2
- locust/clients.py +14 -5
- locust/contrib/fasthttp.py +48 -37
- locust/exception.py +5 -0
- locust/user/users.py +3 -1
- {locust-2.40.2.dev15.dist-info → locust-2.40.3.dist-info}/METADATA +2 -3
- {locust-2.40.2.dev15.dist-info → locust-2.40.3.dist-info}/RECORD +10 -10
- {locust-2.40.2.dev15.dist-info → locust-2.40.3.dist-info}/WHEEL +0 -0
- {locust-2.40.2.dev15.dist-info → locust-2.40.3.dist-info}/entry_points.txt +0 -0
- {locust-2.40.2.dev15.dist-info → locust-2.40.3.dist-info}/licenses/LICENSE +0 -0
locust/_version.py
CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
28
28
|
commit_id: COMMIT_ID
|
29
29
|
__commit_id__: COMMIT_ID
|
30
30
|
|
31
|
-
__version__ = version = '2.40.
|
32
|
-
__version_tuple__ = version_tuple = (2, 40,
|
31
|
+
__version__ = version = '2.40.3'
|
32
|
+
__version_tuple__ = version_tuple = (2, 40, 3)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
locust/clients.py
CHANGED
@@ -233,7 +233,7 @@ class HttpSession(requests.Session):
|
|
233
233
|
except (MissingSchema, InvalidSchema, InvalidURL):
|
234
234
|
raise
|
235
235
|
except RequestException as e:
|
236
|
-
return ResponseContextManager(e)
|
236
|
+
return ResponseContextManager(e) # this is inconsistent, we should fix this some time
|
237
237
|
|
238
238
|
def get(
|
239
239
|
self, url: str | bytes, *, data: Any = None, json: Any = None, **kwargs: Unpack[RESTKwargs]
|
@@ -321,16 +321,23 @@ class ResponseContextManager(Response):
|
|
321
321
|
"""
|
322
322
|
|
323
323
|
error: Exception | None = None
|
324
|
-
_manual_result: bool | Exception | None
|
324
|
+
_manual_result: bool | Exception | None = None
|
325
325
|
_entered: bool
|
326
326
|
_request_event: EventHook
|
327
327
|
request_meta: Mapping[str, Any]
|
328
328
|
_catch_response: bool
|
329
329
|
|
330
330
|
def raise_for_status(self) -> None:
|
331
|
-
|
332
|
-
|
333
|
-
|
331
|
+
"""Wrapper around :py:meth:`Response.raise_for_status <requests.Response.raise_for_status>`
|
332
|
+
but also taking into account explicit failure()/success() calls.
|
333
|
+
"""
|
334
|
+
__tracebackhide__ = True
|
335
|
+
if not self._manual_result:
|
336
|
+
if self.error:
|
337
|
+
raise self.error
|
338
|
+
Response.raise_for_status(self)
|
339
|
+
elif isinstance(self._manual_result, Exception):
|
340
|
+
raise self._manual_result
|
334
341
|
|
335
342
|
def __init__(self, error: RequestException):
|
336
343
|
"""
|
@@ -340,6 +347,8 @@ class ResponseContextManager(Response):
|
|
340
347
|
super().__init__()
|
341
348
|
self.error = error
|
342
349
|
self.status_code = 0
|
350
|
+
# this is a bit of an assumption but it is probably worth not to have to pass this around:
|
351
|
+
self._catch_response = True
|
343
352
|
self.request = error.request # type: ignore
|
344
353
|
|
345
354
|
@classmethod
|
locust/contrib/fasthttp.py
CHANGED
@@ -161,7 +161,7 @@ class FastHttpSession:
|
|
161
161
|
else:
|
162
162
|
return f"{self.base_url}{path}"
|
163
163
|
|
164
|
-
def _send_request_safe_mode(self, method: str, url: str, **kwargs):
|
164
|
+
def _send_request_safe_mode(self, method: str, url: str, **kwargs) -> FastResponse:
|
165
165
|
"""
|
166
166
|
Send an HTTP request, and catch any exception that might occur due to either
|
167
167
|
connection problems, or invalid HTTP status codes
|
@@ -170,7 +170,9 @@ class FastHttpSession:
|
|
170
170
|
return self.client.urlopen(url, method=method, **kwargs)
|
171
171
|
except FAILURE_EXCEPTIONS as e:
|
172
172
|
if hasattr(e, "response"):
|
173
|
-
|
173
|
+
# regular FastResponse object
|
174
|
+
resp = e.response
|
175
|
+
resp.error = e
|
174
176
|
else:
|
175
177
|
req = self.client._make_request(
|
176
178
|
url,
|
@@ -179,9 +181,9 @@ class FastHttpSession:
|
|
179
181
|
payload=kwargs.get("payload"),
|
180
182
|
params=kwargs.get("params"),
|
181
183
|
)
|
182
|
-
|
183
|
-
|
184
|
-
return
|
184
|
+
# fake FastResponse object
|
185
|
+
resp = ErrorResponse(req, e)
|
186
|
+
return resp
|
185
187
|
|
186
188
|
def request(
|
187
189
|
self,
|
@@ -197,10 +199,9 @@ class FastHttpSession:
|
|
197
199
|
allow_redirects: bool = True,
|
198
200
|
context: dict = {},
|
199
201
|
**kwargs,
|
200
|
-
) -> ResponseContextManager
|
202
|
+
) -> ResponseContextManager:
|
201
203
|
"""
|
202
|
-
Send
|
203
|
-
Returns :py:class:`locust.contrib.fasthttp.FastResponse` object.
|
204
|
+
Send an HTTP request
|
204
205
|
|
205
206
|
:param method: method for the new :class:`Request` object.
|
206
207
|
:param url: path that will be concatenated with the base host URL that has been specified.
|
@@ -282,30 +283,31 @@ class FastHttpSession:
|
|
282
283
|
except HTTPParseError as e:
|
283
284
|
request_meta["response_time"] = (time.perf_counter() - start_perf_counter) * 1000
|
284
285
|
request_meta["exception"] = e
|
285
|
-
self.request_event
|
286
|
-
|
286
|
+
rcm = ResponseContextManager(response, self.request_event, request_meta, catch_response)
|
287
|
+
if not catch_response:
|
288
|
+
rcm.__exit__(None, None, None)
|
289
|
+
return rcm
|
287
290
|
|
291
|
+
rcm = ResponseContextManager(response, self.request_event, request_meta, catch_response)
|
288
292
|
# Record the consumed time
|
289
293
|
# Note: This is intentionally placed after we record the content_size above, since
|
290
294
|
# we'll then trigger fetching of the body (unless stream=True)
|
291
295
|
request_meta["response_time"] = (time.perf_counter() - start_perf_counter) * 1000
|
292
296
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
response.raise_for_status()
|
298
|
-
except FAILURE_EXCEPTIONS as e:
|
299
|
-
request_meta["exception"] = e
|
297
|
+
try:
|
298
|
+
response.raise_for_status()
|
299
|
+
except FAILURE_EXCEPTIONS as e:
|
300
|
+
request_meta["exception"] = e # type: ignore[assignment] # mypy, why are you so dumb..
|
300
301
|
|
301
|
-
|
302
|
-
|
302
|
+
if not catch_response: # if not using with-block, report the request immediately
|
303
|
+
rcm.__exit__(None, None, None)
|
304
|
+
return rcm
|
303
305
|
|
304
|
-
def delete(self, url: str, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager
|
306
|
+
def delete(self, url: str, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager:
|
305
307
|
"""Sends a DELETE request"""
|
306
308
|
return self.request("DELETE", url, **kwargs)
|
307
309
|
|
308
|
-
def get(self, url: str, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager
|
310
|
+
def get(self, url: str, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager:
|
309
311
|
"""Sends a GET request"""
|
310
312
|
return self.request("GET", url, **kwargs)
|
311
313
|
|
@@ -320,29 +322,25 @@ class FastHttpSession:
|
|
320
322
|
line, buffer = buffer.split("\n", 1)
|
321
323
|
yield line
|
322
324
|
|
323
|
-
def head(self, url: str, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager
|
325
|
+
def head(self, url: str, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager:
|
324
326
|
"""Sends a HEAD request"""
|
325
327
|
return self.request("HEAD", url, **kwargs)
|
326
328
|
|
327
|
-
def options(self, url: str, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager
|
329
|
+
def options(self, url: str, **kwargs: Unpack[RESTKwargs]) -> ResponseContextManager:
|
328
330
|
"""Sends a OPTIONS request"""
|
329
331
|
return self.request("OPTIONS", url, **kwargs)
|
330
332
|
|
331
|
-
def patch(
|
332
|
-
self, url: str, data: str | dict | None = None, **kwargs: Unpack[PatchKwargs]
|
333
|
-
) -> ResponseContextManager | FastResponse:
|
333
|
+
def patch(self, url: str, data: str | dict | None = None, **kwargs: Unpack[PatchKwargs]) -> ResponseContextManager:
|
334
334
|
"""Sends a PATCH request"""
|
335
335
|
return self.request("PATCH", url, data=data, **kwargs)
|
336
336
|
|
337
337
|
def post(
|
338
338
|
self, url: str, data: str | dict | None = None, json: Any = None, **kwargs: Unpack[PostKwargs]
|
339
|
-
) -> ResponseContextManager
|
339
|
+
) -> ResponseContextManager:
|
340
340
|
"""Sends a POST request"""
|
341
341
|
return self.request("POST", url, data=data, json=json, **kwargs)
|
342
342
|
|
343
|
-
def put(
|
344
|
-
self, url: str, data: str | dict | None = None, **kwargs: Unpack[PutKwargs]
|
345
|
-
) -> ResponseContextManager | FastResponse:
|
343
|
+
def put(self, url: str, data: str | dict | None = None, **kwargs: Unpack[PutKwargs]) -> ResponseContextManager:
|
346
344
|
"""Sends a PUT request"""
|
347
345
|
return self.request("PUT", url, data=data, **kwargs)
|
348
346
|
|
@@ -563,8 +561,8 @@ class FastResponse(CompatResponse):
|
|
563
561
|
|
564
562
|
def raise_for_status(self):
|
565
563
|
"""Raise any connection errors that occurred during the request"""
|
566
|
-
if
|
567
|
-
raise
|
564
|
+
if error := getattr(self, "error", None):
|
565
|
+
raise error
|
568
566
|
|
569
567
|
@property
|
570
568
|
def status_code(self) -> int:
|
@@ -595,7 +593,7 @@ class FastResponse(CompatResponse):
|
|
595
593
|
)
|
596
594
|
|
597
595
|
|
598
|
-
class ErrorResponse:
|
596
|
+
class ErrorResponse(FastResponse): # we're really just pretending to be a FastResponse
|
599
597
|
"""
|
600
598
|
This is used as a dummy response object when geventhttpclient raises an error
|
601
599
|
that doesn't have a real Response object attached. E.g. a socket error or similar
|
@@ -608,9 +606,9 @@ class ErrorResponse:
|
|
608
606
|
text: str | None = None
|
609
607
|
request: CompatRequest
|
610
608
|
|
611
|
-
def __init__(self,
|
612
|
-
self.url = url
|
609
|
+
def __init__(self, request: CompatRequest, error: Exception):
|
613
610
|
self.request = request
|
611
|
+
self.error = error
|
614
612
|
|
615
613
|
def raise_for_status(self):
|
616
614
|
raise self.error
|
@@ -662,8 +660,10 @@ class ResponseContextManager(FastResponse):
|
|
662
660
|
_manual_result = None
|
663
661
|
_entered = False
|
664
662
|
|
665
|
-
def __init__(self, response, request_event, request_meta):
|
663
|
+
def __init__(self, response, request_event, request_meta, catch_response: bool):
|
666
664
|
# copy data from response to this object
|
665
|
+
# I wanted to change this to the approach from the HttpUser's ResponseContentManager.from_request,
|
666
|
+
# but it was such a mess
|
667
667
|
self.__dict__ = response.__dict__
|
668
668
|
try:
|
669
669
|
self._cached_content = response._cached_content
|
@@ -671,16 +671,20 @@ class ResponseContextManager(FastResponse):
|
|
671
671
|
pass
|
672
672
|
self._request_event = request_event
|
673
673
|
self.request_meta = request_meta
|
674
|
+
self._catch_response = catch_response
|
674
675
|
|
675
676
|
def __enter__(self):
|
677
|
+
if not self._catch_response:
|
678
|
+
raise LocustError("In order to use a with-block for requests, you must also pass catch_response=True")
|
676
679
|
self._entered = True
|
677
680
|
return self
|
678
681
|
|
679
682
|
def __exit__(self, exc, value, traceback):
|
680
683
|
# if the user has already manually marked this response as failure or success
|
681
|
-
# we
|
684
|
+
# we ignore the default behaviour of letting the response code determine the outcome
|
682
685
|
if self._manual_result is not None:
|
683
686
|
if self._manual_result is True:
|
687
|
+
self.request_meta["exception"] = None
|
684
688
|
self._report_request()
|
685
689
|
elif isinstance(self._manual_result, Exception):
|
686
690
|
self.request_meta["exception"] = self._manual_result
|
@@ -743,6 +747,13 @@ class ResponseContextManager(FastResponse):
|
|
743
747
|
exc = CatchResponseError(exc)
|
744
748
|
self._manual_result = exc
|
745
749
|
|
750
|
+
def raise_for_status(self):
|
751
|
+
"""Raise any connection errors that occurred during the request"""
|
752
|
+
if not self._manual_result:
|
753
|
+
super().raise_for_status()
|
754
|
+
elif isinstance(self._manual_result, Exception):
|
755
|
+
raise self._manual_result
|
756
|
+
|
746
757
|
|
747
758
|
class RestResponseContextManager(ResponseContextManager):
|
748
759
|
js: dict # This is technically an Optional, but I dont want to force everyone to check it
|
locust/exception.py
CHANGED
locust/user/users.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from locust.clients import HttpSession
|
4
|
-
from locust.exception import LocustError, StopUser
|
4
|
+
from locust.exception import CatchResponseError, LocustError, StopUser
|
5
5
|
from locust.user.task import (
|
6
6
|
LOCUST_STATE_RUNNING,
|
7
7
|
LOCUST_STATE_STOPPING,
|
@@ -302,5 +302,7 @@ class PytestUser(User):
|
|
302
302
|
if isinstance(e, ValueError): # things like MissingSchema etc, lets not catch that
|
303
303
|
raise
|
304
304
|
logger.debug("%s\n%s", e, traceback.format_exc())
|
305
|
+
except CatchResponseError as e:
|
306
|
+
logger.debug("%s\n%s", e, traceback.format_exc())
|
305
307
|
except ConnectionError as e:
|
306
308
|
logger.debug("%s\n%s", e, traceback.format_exc())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: locust
|
3
|
-
Version: 2.40.
|
3
|
+
Version: 2.40.3
|
4
4
|
Summary: Developer-friendly load testing framework
|
5
5
|
Project-URL: homepage, https://locust.io/
|
6
6
|
Project-URL: repository, https://github.com/locustio/locust
|
@@ -38,8 +38,7 @@ Requires-Dist: python-engineio>=4.12.2
|
|
38
38
|
Requires-Dist: python-socketio[client]>=5.13.0
|
39
39
|
Requires-Dist: pywin32; sys_platform == 'win32'
|
40
40
|
Requires-Dist: pyzmq>=25.0.0
|
41
|
-
Requires-Dist: requests>=2.
|
42
|
-
Requires-Dist: requests>=2.32.2; python_version > '3.11'
|
41
|
+
Requires-Dist: requests>=2.32.2
|
43
42
|
Requires-Dist: setuptools>=70.0.0
|
44
43
|
Requires-Dist: tomli>=1.1.0; python_version < '3.11'
|
45
44
|
Requires-Dist: typing-extensions>=4.6.0; python_version < '3.12'
|
@@ -1,13 +1,13 @@
|
|
1
1
|
locust/__init__.py,sha256=HadpgGidiyCDPSKwkxrk1Qw6eB7dTmftNJVftuJzAiw,1876
|
2
2
|
locust/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
|
3
|
-
locust/_version.py,sha256=
|
3
|
+
locust/_version.py,sha256=9cUTLUlZ2HRDSemp7LUnZcGlLE6k-NI6R1w4zxPE-bY,706
|
4
4
|
locust/argument_parser.py,sha256=t6mAoK9u13DxC9UH-alVqS6fFABFTyNWSJG89yQ4QQQ,33056
|
5
|
-
locust/clients.py,sha256=
|
5
|
+
locust/clients.py,sha256=OfMTOT3LwijczdL3u6bpDwDNM2TDcPCIwXBelizPOyw,20776
|
6
6
|
locust/debug.py,sha256=7CCm8bIg44uGH2wqBlo1rXBzV2VzwPicLxLewz8r5CQ,5099
|
7
7
|
locust/dispatch.py,sha256=prdwtb9EoN4A9klgiKgWuwQmvFB8hEuFHOK6ot62AJI,16202
|
8
8
|
locust/env.py,sha256=PypNHmEFBhGBk9dU6pZ2cL5L0TThYejaGWW16RO3ZNQ,13203
|
9
9
|
locust/event.py,sha256=AWoeG2q11Vpupv1eW9Rf2N0EI4dDDAldKe6zY1ajC7I,8717
|
10
|
-
locust/exception.py,sha256=
|
10
|
+
locust/exception.py,sha256=XCQyJj5XHvJPgTWGhzgjjavoqVdyF0tqG2wj6WzmXvo,1954
|
11
11
|
locust/html.py,sha256=18LlaL-NEQdthXVdS16gRAd4SOpW_bowOJKyuLvWrCY,4043
|
12
12
|
locust/input_events.py,sha256=lqLDB2Ax-OQ7-vtYNkGjySjfaWVobBzqf0GxRwjcLcQ,3323
|
13
13
|
locust/log.py,sha256=5E2ZUOa3V4sfCqa-470Gle1Ik9L5nxYitsjEB9tRwE0,3455
|
@@ -18,7 +18,7 @@ locust/shape.py,sha256=t-lwBS8LOjWcKXNL7j2U3zroIXJ1b0fazUwpRYQOKXw,1973
|
|
18
18
|
locust/stats.py,sha256=qyoSKT0i7RunLDj5pMGqizK1Sp8bcqUsXwh2m4_DpR8,47203
|
19
19
|
locust/web.py,sha256=pLYuocmx9gifJ4vsVDgGDYIPkQhQxI7kKjxoXcUajqM,31865
|
20
20
|
locust/contrib/__init__.py,sha256=LtZN7MczpIAbZkN7PT2h8W2wgb9nBl6cDXbFCVsV4fo,290
|
21
|
-
locust/contrib/fasthttp.py,sha256=
|
21
|
+
locust/contrib/fasthttp.py,sha256=Hz-DqTW2qxVWv-OWPIvEgX9CbAlcyPBlp0lAKcOjpbg,30721
|
22
22
|
locust/contrib/milvus.py,sha256=YabgLd0lImzWupJFCm0OZAW-Nxeibwn91ldWpZ2irDo,12811
|
23
23
|
locust/contrib/mongodb.py,sha256=1seUYgJOaNKwybYOP9PUEVhgl8hGy-G33f8lFj3R8W8,1246
|
24
24
|
locust/contrib/oai.py,sha256=Ot3T8lp31ThckGbNps86oVvq6Vn845Eec0mxhDmONDE,2684
|
@@ -32,7 +32,7 @@ locust/user/inspectuser.py,sha256=KgrWHyE5jhK6or58R7soLRf-_st42AaQrR72qbiXw9E,26
|
|
32
32
|
locust/user/markov_taskset.py,sha256=eESre6OacbP7nTzZFwxUe7TO4X4l7WqOAEETtDzsIfU,11784
|
33
33
|
locust/user/sequential_taskset.py,sha256=SbrrGU9HV2nEWe6zQVtjymn8NgPISP7QSNoVdyoXjYg,2687
|
34
34
|
locust/user/task.py,sha256=QnNDs8lEZGgMqBe8l_O4FKgn4WUL9r5WTRHWw88nO-w,17242
|
35
|
-
locust/user/users.py,sha256=
|
35
|
+
locust/user/users.py,sha256=7Au0F6MnqQYAiuHYcXaVsm-BEhyigBq8PiON6RJDfKM,11342
|
36
36
|
locust/user/wait_time.py,sha256=bGRKMVx4lom75sX3POYJUa1CPeME2bEAXG6CEgxSO5U,2675
|
37
37
|
locust/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
38
|
locust/util/cache.py,sha256=IxbpGawl0-hoWKvCrtksxjSLf2GbBBTVns06F7mFBkM,1062
|
@@ -56,8 +56,8 @@ locust/webui/dist/assets/terminal.gif,sha256=iw80LO2u0dnf4wpGfFJZauBeKTcSpw9iUfI
|
|
56
56
|
locust/webui/dist/assets/testruns-dark.png,sha256=G4p2VZSBuuqF4neqUaPSshIp5OKQJ_Bvb69Luj6XuVs,125231
|
57
57
|
locust/webui/dist/assets/testruns-light.png,sha256=JinGDiiBPOkhpfF-XCbmQqhRInqItrjrBTLKt5MlqVI,130301
|
58
58
|
pytest_locust/plugin.py,sha256=WAyiRHLynXegbbX2DxIutPKO4PQRT6JdBFA7zbbaJgM,1469
|
59
|
-
locust-2.40.
|
60
|
-
locust-2.40.
|
61
|
-
locust-2.40.
|
62
|
-
locust-2.40.
|
63
|
-
locust-2.40.
|
59
|
+
locust-2.40.3.dist-info/METADATA,sha256=0TZx5QftsszuyRoq4P061KfB-h0Txp2sFmFKMx5xe6A,9510
|
60
|
+
locust-2.40.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
61
|
+
locust-2.40.3.dist-info/entry_points.txt,sha256=0uIHcQ71R1qaWhM_sd8uBUCCJgp7gJfGHUVMnJeZfcY,86
|
62
|
+
locust-2.40.3.dist-info/licenses/LICENSE,sha256=5hnz-Vpj0Z3kSCQl0LzV2hT1TLc4LHcbpBp3Cy-EuyM,1110
|
63
|
+
locust-2.40.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|