locust-plugins 4.2.0__tar.gz → 4.3.1__tar.gz

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.
Files changed (94) hide show
  1. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.pylintrc +1 -1
  2. {locust-plugins-4.2.0/locust_plugins.egg-info → locust-plugins-4.3.1}/PKG-INFO +26 -2
  3. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/README.md +2 -2
  4. locust-plugins-4.3.1/examples/distributor_ex.py +41 -0
  5. locust-plugins-4.3.1/examples/products.csv +2 -0
  6. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/_version.py +2 -2
  7. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/csvreader.py +4 -3
  8. locust-plugins-4.3.1/locust_plugins/distributor.py +59 -0
  9. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/mongoreader.py +52 -3
  10. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/socketio.py +2 -2
  11. {locust-plugins-4.2.0 → locust-plugins-4.3.1/locust_plugins.egg-info}/PKG-INFO +26 -2
  12. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/SOURCES.txt +3 -0
  13. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/requires.txt +1 -1
  14. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/setup.py +2 -1
  15. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/tox.ini +4 -0
  16. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.github/workflows/tests.yml +0 -0
  17. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.gitignore +0 -0
  18. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.vscode/launch.json +0 -0
  19. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.vscode/settings.json +0 -0
  20. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/CONTRIBUTING.md +0 -0
  21. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/LICENSE +0 -0
  22. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/MANIFEST.in +0 -0
  23. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/Makefile +0 -0
  24. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/bin/locust-compose +0 -0
  25. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/appinsights_listener_ex.py +0 -0
  26. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/cloudwatch_listener_ex.py +0 -0
  27. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/cmd_line_examples.sh +0 -0
  28. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/connection_pool_ex.py +0 -0
  29. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/constant_total_ips_ex.py +0 -0
  30. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/csvreader_ex.py +0 -0
  31. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/embedded_resource_manager_ex.py +0 -0
  32. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/jmeter_listener_example.py +0 -0
  33. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/kafka_ex.py +0 -0
  34. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/mongoreader_ex.py +0 -0
  35. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/mqtt_ex.py +0 -0
  36. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/playwright-recording.py +0 -0
  37. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/playwright_ex.py +0 -0
  38. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/reschedule_on_fail_ex.py +0 -0
  39. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/rest_ex.py +0 -0
  40. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/socketio_ex.py +0 -0
  41. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/ssn.csv +0 -0
  42. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/ssn.tsv +0 -0
  43. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example.py +0 -0
  44. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_local.py +0 -0
  45. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_master.py +0 -0
  46. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_worker.py +0 -0
  47. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/tsvreader_ex.py +0 -0
  48. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/webdriver_ex.py +0 -0
  49. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/__init__.py +0 -0
  50. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/connection_pools.py +0 -0
  51. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/Makefile +0 -0
  52. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/README.md +0 -0
  53. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/docker-compose.yml +0 -0
  54. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/Dockerfile +0 -0
  55. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/Makefile +0 -0
  56. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/create_datasource.sh +0 -0
  57. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/grafana_setup.sh +0 -0
  58. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/import_dashboards.sh +0 -0
  59. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/Dockerfile +0 -0
  60. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/Makefile +0 -0
  61. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/timescale_schema.sql +0 -0
  62. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/zz_hypertable.sql +0 -0
  63. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/main_dashboard.png +0 -0
  64. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/main_dashboard_by_request_graphs.png +0 -0
  65. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/requests_table.png +0 -0
  66. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/scatter_plot.png +0 -0
  67. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/testruns.png +0 -0
  68. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/__init__.py +0 -0
  69. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/appinsights.py +0 -0
  70. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/cloudwatch.py +0 -0
  71. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/jmeter.py +0 -0
  72. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/timescale.py +0 -0
  73. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/py.typed +0 -0
  74. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/transaction_manager.py +0 -0
  75. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/__init__.py +0 -0
  76. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/kafka.py +0 -0
  77. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/mqtt.py +0 -0
  78. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/playwright.py +0 -0
  79. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/resource.py +0 -0
  80. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/rest.py +0 -0
  81. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/webdriver.py +0 -0
  82. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/utils.py +0 -0
  83. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/wait_time.py +0 -0
  84. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/dependency_links.txt +0 -0
  85. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/not-zip-safe +0 -0
  86. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/top_level.txt +0 -0
  87. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/mypy.ini +0 -0
  88. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/pyproject.toml +0 -0
  89. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/setup.cfg +0 -0
  90. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/test/test.csv +0 -0
  91. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/test/test_all.py +0 -0
  92. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/test/test_cloudwatch_plugin.py +0 -0
  93. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/test/test_missing_extras.py +0 -0
  94. {locust-plugins-4.2.0 → locust-plugins-4.3.1}/tusk.yaml +0 -0
@@ -11,7 +11,7 @@ attr-rgx=[a-z0-9_]{1,30}$
11
11
  ignored-argument-names=_.*|^ignored_|^unused_|^kwargs|^environment
12
12
 
13
13
  [MESSAGES CONTROL]
14
- disable=logging-not-lazy,logging-fstring-interpolation,missing-docstring,wrong-import-position,wrong-import-order,too-few-public-methods,invalid-name,protected-access,logging-format-interpolation,dangerous-default-value,global-statement,too-many-locals,too-many-arguments,too-many-instance-attributes,blacklisted-name,attribute-defined-outside-init,broad-except,bare-except,consider-using-with,too-many-branches,unspecified-encoding,arguments-differ,broad-exception-raised
14
+ disable=logging-not-lazy,logging-fstring-interpolation,missing-docstring,wrong-import-position,wrong-import-order,too-few-public-methods,invalid-name,protected-access,logging-format-interpolation,dangerous-default-value,global-statement,too-many-locals,too-many-arguments,too-many-instance-attributes,blacklisted-name,attribute-defined-outside-init,broad-except,bare-except,consider-using-with,too-many-branches,unspecified-encoding,arguments-differ,broad-exception-raised,wildcard-import,keyword-arg-before-vararg
15
15
 
16
16
  [MASTER]
17
17
  ignore=locustio,examples
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: locust-plugins
3
- Version: 4.2.0
3
+ Version: 4.3.1
4
4
  Summary: Useful plugins/extensions for Locust
5
5
  Home-page: https://github.com/SvenskaSpel/locust-plugins
6
6
  Author: Lars Holmberg
@@ -16,20 +16,44 @@ Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
19
20
  Classifier: Intended Audience :: Developers
20
21
  Classifier: Intended Audience :: System Administrators
21
22
  Requires-Python: >=3.8, <4
23
+ License-File: LICENSE
24
+ Requires-Dist: locust>=2.20.0
25
+ Requires-Dist: typing-extensions
22
26
  Provides-Extra: websocket
27
+ Requires-Dist: websocket-client; extra == "websocket"
23
28
  Provides-Extra: playwright
29
+ Requires-Dist: playwright>=1.34.0; extra == "playwright"
24
30
  Provides-Extra: dashboards
31
+ Requires-Dist: psycogreen; extra == "dashboards"
32
+ Requires-Dist: psycopg2-binary; extra == "dashboards"
25
33
  Provides-Extra: kafka
34
+ Requires-Dist: confluent-kafka; extra == "kafka"
26
35
  Provides-Extra: mongo
36
+ Requires-Dist: pymongo; extra == "mongo"
27
37
  Provides-Extra: mqtt
38
+ Requires-Dist: paho-mqtt>=1.5.0; extra == "mqtt"
28
39
  Provides-Extra: appinsights
40
+ Requires-Dist: opencensus-ext-azure; extra == "appinsights"
29
41
  Provides-Extra: resource
42
+ Requires-Dist: lxml; extra == "resource"
30
43
  Provides-Extra: boto3
44
+ Requires-Dist: boto3; extra == "boto3"
31
45
  Provides-Extra: all
46
+ Requires-Dist: websocket-client; extra == "all"
47
+ Requires-Dist: playwright>=1.34.0; extra == "all"
48
+ Requires-Dist: psycogreen; extra == "all"
49
+ Requires-Dist: psycopg2-binary; extra == "all"
50
+ Requires-Dist: confluent-kafka; extra == "all"
51
+ Requires-Dist: pymongo; extra == "all"
52
+ Requires-Dist: paho-mqtt>=1.5.0; extra == "all"
53
+ Requires-Dist: opencensus-ext-azure; extra == "all"
54
+ Requires-Dist: lxml; extra == "all"
55
+ Requires-Dist: boto3; extra == "all"
32
56
  Provides-Extra: webdriver
33
- License-File: LICENSE
57
+ Requires-Dist: selenium>=4.0.0; extra == "webdriver"
34
58
 
35
59
  https://github.com/SvenskaSpel/locust-plugins
@@ -55,8 +55,8 @@ locust --help
55
55
  ## Wait time
56
56
  - Custom wait time functions ([example](examples/constant_total_ips_ex.py), [source](locust_plugins/wait_time.py))
57
57
 
58
- ## Debug
59
- - Support for running a single User in the debugger (moved to [locust core](https://docs.locust.io/en/latest/running-in-debugger.html)!)
58
+ ## Distributing test data
59
+ - Support for distributing test data from master to workers while maintaining test data order ([example](examples/distributor_ex.py), [source](locust_plugins/distributor.py))
60
60
 
61
61
  ## Transaction manager
62
62
  - Support for logging transactions (aggregating multiple requests or other actions) ([example](examples/transaction_example.py), [source](locust_plugins/transaction_manager.py))
@@ -0,0 +1,41 @@
1
+ # this example is a little more complex than it needs to be, but I wanted to highlight
2
+ # that it is entirely possible to have multiple distributors at the same time
3
+
4
+ from typing import Dict, List
5
+ from locust_plugins.mongoreader import MongoLRUReader
6
+ from locust_plugins.csvreader import CSVDictReader, CSVReader
7
+ from locust_plugins.distributor import Distributor
8
+ from locust import HttpUser, task, run_single_user, events
9
+ from locust.runners import WorkerRunner
10
+
11
+ distributors = {}
12
+
13
+
14
+ @events.init.add_listener
15
+ def on_locust_init(environment, **_kwargs):
16
+ ssn_reader = None
17
+ product_reader = None
18
+ if not isinstance(environment.runner, WorkerRunner):
19
+ product_reader = CSVReader("products.csv")
20
+ csv = True
21
+ if csv:
22
+ ssn_reader = CSVDictReader("ssn.tsv", delimiter="\t")
23
+ else:
24
+ ssn_reader = MongoLRUReader({"foo": "bar"}, "last_login")
25
+ product_reader = CSVReader("products.csv")
26
+ distributors["customers"] = Distributor(environment, ssn_reader, "customers")
27
+ distributors["products"] = Distributor(environment, product_reader, "products")
28
+
29
+
30
+ class MyUser(HttpUser):
31
+ host = "http://www.example.com"
32
+
33
+ @task
34
+ def my_task(self) -> None:
35
+ customer: Dict = next(distributors["customers"])
36
+ product: List[str] = next(distributors["products"])
37
+ self.client.get(f"/?customer={customer['ssn']}&product={product[0]}")
38
+
39
+
40
+ if __name__ == "__main__":
41
+ run_single_user(MyUser)
@@ -0,0 +1,2 @@
1
+ asdf1
2
+ qwerty2
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '4.2.0'
16
- __version_tuple__ = version_tuple = (4, 2, 0)
15
+ __version__ = version = '4.3.1'
16
+ __version_tuple__ = version_tuple = (4, 3, 1)
@@ -1,7 +1,8 @@
1
1
  import csv
2
+ from typing import Iterator, Dict
2
3
 
3
4
 
4
- class CSVReader:
5
+ class CSVReader(Iterator):
5
6
  "Read test data from csv file using an iterator"
6
7
 
7
8
  def __init__(self, file, **kwargs):
@@ -21,8 +22,8 @@ class CSVReader:
21
22
  return next(self.reader)
22
23
 
23
24
 
24
- class CSVDictReader:
25
- "Read test data from csv file using an iterator"
25
+ class CSVDictReader(Iterator[Dict]):
26
+ "Read test data from csv file using an iterator, returns rows as dicts"
26
27
 
27
28
  def __init__(self, file, **kwargs):
28
29
  try:
@@ -0,0 +1,59 @@
1
+ from typing import Dict, Iterator, Optional
2
+ import logging
3
+ from gevent.event import AsyncResult
4
+ import greenlet
5
+ from locust.env import Environment
6
+ from locust.runners import WorkerRunner
7
+
8
+ _results: Dict[int, AsyncResult] = {}
9
+
10
+
11
+ class Distributor:
12
+ def __init__(self, environment: Environment, iterator: Optional[Iterator], name="distributor"):
13
+ """Register distributor method handlers and tie them to use the iterator that you pass.
14
+
15
+ iterator is not used on workers, so you can leave it as None there.
16
+ """
17
+ self.iterator = iterator
18
+ self.name = name
19
+ self.runner = environment.runner
20
+ assert iterator or isinstance(
21
+ self.runner, WorkerRunner
22
+ ), "iterator is a mandatory parameter when not on a worker runner"
23
+ if self.runner:
24
+ # received on master
25
+ def _distributor_request(environment: Environment, msg, **kwargs):
26
+ item = next(self.iterator)
27
+ self.runner.send_message(
28
+ f"_{name}_response",
29
+ {"item": item, "gid": msg.data["gid"]},
30
+ client_id=msg.data["client_id"],
31
+ )
32
+
33
+ # received on worker
34
+ def _distributor_response(environment: Environment, msg, **kwargs):
35
+ _results[msg.data["gid"]].set(msg.data)
36
+
37
+ self.runner.register_message(f"_{name}_request", _distributor_request)
38
+ self.runner.register_message(f"_{name}_response", _distributor_response)
39
+
40
+ def __next__(self):
41
+ """Get the next data dict from iterator
42
+
43
+ Args:
44
+ user (User): current user object (we use the object id of the User to keep track of who's waiting for which data)
45
+ """
46
+ if not self.runner: # no need to do anything clever if there is no runner
47
+ assert self.iterator
48
+ return next(self.iterator)
49
+
50
+ gid = greenlet.getcurrent().minimal_ident # type: ignore
51
+
52
+ if gid in _results:
53
+ logging.warning("This user was already waiting for data. Strange.")
54
+
55
+ _results[gid] = AsyncResult()
56
+ self.runner.send_message(f"_{self.name}_request", {"gid": gid, "client_id": self.runner.client_id})
57
+ item = _results[gid].get()["item"] # this waits for the reply
58
+ del _results[gid]
59
+ return item
@@ -1,23 +1,71 @@
1
- from typing import Iterator
1
+ from typing import Iterator, Optional, Dict
2
2
  from pymongo import MongoClient
3
- import pymongo.collection
3
+ from pymongo.collection import Collection
4
+ from pymongo.cursor import Cursor
4
5
  from datetime import datetime, timezone
5
6
  import logging
6
7
  import time
7
8
  from contextlib import contextmanager
8
9
  import os
10
+ from os import environ as env
9
11
  from abc import ABC, abstractmethod
10
12
  from gevent.lock import Semaphore
11
13
 
12
14
  dblock = Semaphore()
13
15
 
14
16
 
17
+ class MongoLRUReader(Iterator[Dict]):
18
+ def __init__(
19
+ self,
20
+ filter: Dict, # pylint: disable=redefined-builtin
21
+ timestamp_field: str,
22
+ coll: Optional[Collection] = None,
23
+ ):
24
+ """A thread safe iterator to read test data from a mongo collection sorted by least-recently-used.
25
+
26
+ Order is ensured even if Locust is restarted, because the timestamp_field is updated on every iteration step.
27
+
28
+ Iteration is quite fast, but the first query can be slow if you dont have an index that covers the filter and sort fields.
29
+
30
+ Args:
31
+ filter (Dict): Query filter statement
32
+ sort_field (str): Time stamp field, e.g. "last_used"
33
+ collection (pymongo.collection.Collection, optional): By default, we use LOCUST_MONGO, LOCUST_MONGO_DATABASE and LOCUST_MONGO_COLLECTION env vars to get the collection, but you can also pass a pre-existing Collection.
34
+
35
+ """
36
+ self.timestamp_field = timestamp_field
37
+ self.coll: Collection = (
38
+ coll or MongoClient(env["LOCUST_MONGO"])[env["LOCUST_MONGO_DATABASE"]][env["LOCUST_MONGO_COLLECTION"]]
39
+ )
40
+ self.cursor: Cursor = self.coll.find(filter, sort=[(self.timestamp_field, 1)])
41
+ records_in_buffer = self.cursor._refresh() # trigger fetch immediately instead of waiting for the first next()
42
+ if not records_in_buffer:
43
+ logging.warning(f"No records returned from query {filter}")
44
+
45
+ def __next__(self) -> dict:
46
+ try:
47
+ with dblock:
48
+ doc: dict = next(self.cursor)
49
+ self.coll.find_one_and_update(
50
+ {"_id": doc["_id"]},
51
+ {"$set": {self.timestamp_field: datetime.now(tz=timezone.utc)}},
52
+ )
53
+ return doc
54
+ except StopIteration:
55
+ with dblock:
56
+ self.cursor.rewind()
57
+ return next(self.cursor)
58
+
59
+
60
+ ### Legacy
61
+
62
+
15
63
  class NoUserException(Exception):
16
64
  pass
17
65
 
18
66
 
19
67
  class User(dict):
20
- def __init__(self, coll: pymongo.collection.Collection, query: dict):
68
+ def __init__(self, coll: Collection, query: dict):
21
69
  self.coll = coll
22
70
  with dblock:
23
71
  data = self.coll.find_one_and_update(
@@ -55,6 +103,7 @@ class MongoReader(AbstractReader):
55
103
  self.reduced_filters = []
56
104
  self.delay_warning = 0
57
105
  self.query = {"$and": filters + [{"logged_in": False}]}
106
+ logging.warning("MongoReader is deprecated, please switch to MongoReaderLRU")
58
107
 
59
108
  @contextmanager
60
109
  def user(self, query: dict = None):
@@ -88,7 +88,7 @@ class SocketIOUser(User):
88
88
  logging.debug(f"WSR: {message}")
89
89
  self.on_message(message)
90
90
 
91
- def send(self, body, name=None, context={}):
91
+ def send(self, body, name=None, context={}, opcode=websocket.ABNF.OPCODE_TEXT):
92
92
  if not name:
93
93
  if body == "2":
94
94
  name = "2 heartbeat"
@@ -112,7 +112,7 @@ class SocketIOUser(User):
112
112
  context={**self.context(), **context},
113
113
  )
114
114
  logging.debug(f"WSS: {body}")
115
- self.ws.send(body)
115
+ self.ws.send(body, opcode)
116
116
 
117
117
  def sleep_with_heartbeat(self, seconds):
118
118
  while seconds >= 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: locust-plugins
3
- Version: 4.2.0
3
+ Version: 4.3.1
4
4
  Summary: Useful plugins/extensions for Locust
5
5
  Home-page: https://github.com/SvenskaSpel/locust-plugins
6
6
  Author: Lars Holmberg
@@ -16,20 +16,44 @@ Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
19
20
  Classifier: Intended Audience :: Developers
20
21
  Classifier: Intended Audience :: System Administrators
21
22
  Requires-Python: >=3.8, <4
23
+ License-File: LICENSE
24
+ Requires-Dist: locust>=2.20.0
25
+ Requires-Dist: typing-extensions
22
26
  Provides-Extra: websocket
27
+ Requires-Dist: websocket-client; extra == "websocket"
23
28
  Provides-Extra: playwright
29
+ Requires-Dist: playwright>=1.34.0; extra == "playwright"
24
30
  Provides-Extra: dashboards
31
+ Requires-Dist: psycogreen; extra == "dashboards"
32
+ Requires-Dist: psycopg2-binary; extra == "dashboards"
25
33
  Provides-Extra: kafka
34
+ Requires-Dist: confluent-kafka; extra == "kafka"
26
35
  Provides-Extra: mongo
36
+ Requires-Dist: pymongo; extra == "mongo"
27
37
  Provides-Extra: mqtt
38
+ Requires-Dist: paho-mqtt>=1.5.0; extra == "mqtt"
28
39
  Provides-Extra: appinsights
40
+ Requires-Dist: opencensus-ext-azure; extra == "appinsights"
29
41
  Provides-Extra: resource
42
+ Requires-Dist: lxml; extra == "resource"
30
43
  Provides-Extra: boto3
44
+ Requires-Dist: boto3; extra == "boto3"
31
45
  Provides-Extra: all
46
+ Requires-Dist: websocket-client; extra == "all"
47
+ Requires-Dist: playwright>=1.34.0; extra == "all"
48
+ Requires-Dist: psycogreen; extra == "all"
49
+ Requires-Dist: psycopg2-binary; extra == "all"
50
+ Requires-Dist: confluent-kafka; extra == "all"
51
+ Requires-Dist: pymongo; extra == "all"
52
+ Requires-Dist: paho-mqtt>=1.5.0; extra == "all"
53
+ Requires-Dist: opencensus-ext-azure; extra == "all"
54
+ Requires-Dist: lxml; extra == "all"
55
+ Requires-Dist: boto3; extra == "all"
32
56
  Provides-Extra: webdriver
33
- License-File: LICENSE
57
+ Requires-Dist: selenium>=4.0.0; extra == "webdriver"
34
58
 
35
59
  https://github.com/SvenskaSpel/locust-plugins
@@ -20,6 +20,7 @@ examples/cmd_line_examples.sh
20
20
  examples/connection_pool_ex.py
21
21
  examples/constant_total_ips_ex.py
22
22
  examples/csvreader_ex.py
23
+ examples/distributor_ex.py
23
24
  examples/embedded_resource_manager_ex.py
24
25
  examples/jmeter_listener_example.py
25
26
  examples/kafka_ex.py
@@ -27,6 +28,7 @@ examples/mongoreader_ex.py
27
28
  examples/mqtt_ex.py
28
29
  examples/playwright-recording.py
29
30
  examples/playwright_ex.py
31
+ examples/products.csv
30
32
  examples/reschedule_on_fail_ex.py
31
33
  examples/rest_ex.py
32
34
  examples/socketio_ex.py
@@ -42,6 +44,7 @@ locust_plugins/__init__.py
42
44
  locust_plugins/_version.py
43
45
  locust_plugins/connection_pools.py
44
46
  locust_plugins/csvreader.py
47
+ locust_plugins/distributor.py
45
48
  locust_plugins/mongoreader.py
46
49
  locust_plugins/py.typed
47
50
  locust_plugins/transaction_manager.py
@@ -1,4 +1,4 @@
1
- locust>=2.16.1
1
+ locust>=2.20.0
2
2
  typing-extensions
3
3
 
4
4
  [all]
@@ -32,6 +32,7 @@ setup(
32
32
  "Programming Language :: Python :: 3.9",
33
33
  "Programming Language :: Python :: 3.10",
34
34
  "Programming Language :: Python :: 3.11",
35
+ "Programming Language :: Python :: 3.12",
35
36
  "Intended Audience :: Developers",
36
37
  "Intended Audience :: System Administrators",
37
38
  ],
@@ -45,7 +46,7 @@ setup(
45
46
  package_data={"locust_plugins": ["py.typed"]},
46
47
  zip_safe=False,
47
48
  install_requires=[
48
- "locust>=2.16.1",
49
+ "locust>=2.20.0",
49
50
  "typing-extensions",
50
51
  ],
51
52
  extras_require={
@@ -32,6 +32,10 @@ commands =
32
32
  grep -m 1 'Manual' output.txt
33
33
  grep -m 1 'LCP' output.txt
34
34
  bash -ec "! grep 'object has no attribute' output.txt"
35
+ bash -ec "(cd examples && PYTHONUNBUFFERED=1 locust -f distributor_ex.py --headless -t 5 -u 4 --processes 4) |& tee output.txt || true"
36
+ grep -m 1 'customer=2099010101-1111' output.txt
37
+ grep -m 1 'product=asdf1' output.txt
38
+ bash -ec "! grep -q Traceback output.txt"
35
39
  locust -f examples/jmeter_listener_example.py --headless -t 1
36
40
  locust-compose up -d
37
41
  sleep 15
File without changes
File without changes
File without changes
File without changes
File without changes