locust 2.35.1.dev11__py3-none-any.whl → 2.35.1.dev22__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 CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.35.1.dev11'
21
- __version_tuple__ = version_tuple = (2, 35, 1, 'dev11')
20
+ __version__ = version = '2.35.1.dev22'
21
+ __version_tuple__ = version_tuple = (2, 35, 1, 'dev22')
locust/main.py CHANGED
@@ -15,6 +15,7 @@ import sys
15
15
  import time
16
16
  import traceback
17
17
  import webbrowser
18
+ from typing import TYPE_CHECKING
18
19
 
19
20
  import gevent
20
21
 
@@ -42,6 +43,9 @@ except ModuleNotFoundError as e:
42
43
  if e.msg != "No module named 'locust_cloud'":
43
44
  raise
44
45
 
46
+ if TYPE_CHECKING:
47
+ from collections.abc import Callable
48
+
45
49
  version = locust.__version__
46
50
 
47
51
  # Options to ignore when using a custom shape class without `use_common_options=True`
@@ -83,53 +87,73 @@ def create_environment(
83
87
  )
84
88
 
85
89
 
86
- def main():
87
- # find specified locustfile(s) and make sure it exists, using a very simplified
88
- # command line parser that is only used to parse the -f option.
89
- locustfiles = parse_locustfile_option()
90
- locustfiles_length = len(locustfiles)
90
+ def merge_locustfiles_content(
91
+ locustfiles: list[str],
92
+ ) -> tuple[
93
+ dict[str, type[locust.User]],
94
+ dict[str, locust.LoadTestShape],
95
+ dict[str, list[locust.TaskSet | Callable]],
96
+ locust.LoadTestShape | None,
97
+ ]:
98
+ """
99
+ Validate content of each locustfile in locustfiles and merge data to single objects output.
91
100
 
92
- # Grabbing the Locustfile if only one was provided. Otherwise, allowing users to select the locustfile in the UI
93
- # If --headless or --autostart and multiple locustfiles, all provided UserClasses will be ran
94
- locustfile = locustfiles[0] if locustfiles_length == 1 else None
101
+ Can stop locust execution on errors.
102
+ """
103
+ available_user_classes: dict[str, type[locust.User]] = {}
104
+ available_shape_classes: dict[str, locust.LoadTestShape] = {}
105
+ # TODO: list[locust.TaskSet | Callable] should be replaced with correct type,
106
+ # supported by User class task attribute. This require additional rewrite,
107
+ # out of main refactoring.
108
+ # Check docs for real supported task attribute signature for User\TaskSet class.
109
+ available_user_tasks: dict[str, list[locust.TaskSet | Callable]] = {}
95
110
 
96
- # Importing Locustfile(s) - setting available UserClasses and ShapeClasses to choose from in UI
97
- user_classes: dict[str, locust.User] = {}
98
- available_user_classes = {}
99
- available_shape_classes = {}
100
- available_user_tasks = {}
101
- shape_class = None
102
111
  for _locustfile in locustfiles:
103
- docstring, _user_classes, shape_classes = load_locustfile(_locustfile)
112
+ user_classes, shape_classes = load_locustfile(_locustfile)
104
113
 
105
114
  # Setting Available Shape Classes
106
- if shape_classes:
107
- shape_class = shape_classes[0]
108
- for shape_class in shape_classes:
109
- shape_class_name = type(shape_class).__name__
110
- if shape_class_name in available_shape_classes.keys():
111
- sys.stderr.write(f"Duplicate shape classes: {shape_class_name}\n")
112
- sys.exit(1)
115
+ for _shape_class in shape_classes:
116
+ shape_class_name = type(_shape_class).__name__
117
+ if shape_class_name in available_shape_classes.keys():
118
+ sys.stderr.write(f"Duplicate shape classes: {shape_class_name}\n")
119
+ sys.exit(1)
113
120
 
114
- available_shape_classes[shape_class_name] = shape_class
121
+ available_shape_classes[shape_class_name] = _shape_class
115
122
 
116
123
  # Setting Available User Classes
117
- for key, value in _user_classes.items():
118
- if key in available_user_classes.keys():
119
- previous_path = inspect.getfile(user_classes[key])
120
- new_path = inspect.getfile(value)
124
+ for class_name, class_definition in user_classes.items():
125
+ if class_name in available_user_classes.keys():
126
+ previous_path = inspect.getfile(available_user_classes[class_name])
127
+ new_path = inspect.getfile(class_definition)
121
128
  if previous_path == new_path:
122
129
  # The same User class was defined in two locustfiles but one probably imported the other, so we just ignore it
123
130
  continue
124
131
  else:
125
132
  sys.stderr.write(
126
- f"Duplicate user class names: {key} is defined in both {previous_path} and {new_path}\n"
133
+ f"Duplicate user class names: {class_name} is defined in both {previous_path} and {new_path}\n"
127
134
  )
128
135
  sys.exit(1)
129
136
 
130
- user_classes[key] = value
131
- available_user_classes[key] = value
132
- available_user_tasks[key] = value.tasks or {}
137
+ available_user_classes[class_name] = class_definition
138
+ available_user_tasks[class_name] = class_definition.tasks
139
+
140
+ shape_class = list(available_shape_classes.values())[0] if available_shape_classes else None
141
+
142
+ return available_user_classes, available_shape_classes, available_user_tasks, shape_class
143
+
144
+
145
+ def main():
146
+ # find specified locustfile(s) and make sure it exists, using a very simplified
147
+ # command line parser that is only used to parse the -f option.
148
+ locustfiles = parse_locustfile_option()
149
+
150
+ # Importing Locustfile(s) - setting available UserClasses and ShapeClasses to choose from in UI
151
+ (
152
+ available_user_classes,
153
+ available_shape_classes,
154
+ available_user_tasks,
155
+ shape_class,
156
+ ) = merge_locustfiles_content(locustfiles)
133
157
 
134
158
  stats.validate_stats_configuration()
135
159
 
@@ -256,25 +280,25 @@ def main():
256
280
 
257
281
  if options.list_commands:
258
282
  print("Available Users:")
259
- for name in user_classes:
283
+ for name in available_user_classes:
260
284
  print(" " + name)
261
285
  sys.exit(0)
262
286
 
263
- if not user_classes:
287
+ if not available_user_classes:
264
288
  logger.error("No User class found!")
265
289
  sys.exit(1)
266
290
 
267
291
  # make sure specified User exists
268
292
  if options.user_classes:
269
- if missing := set(options.user_classes) - set(user_classes.keys()):
293
+ if missing := set(options.user_classes) - set(available_user_classes.keys()):
270
294
  logger.error(f"Unknown User(s): {', '.join(missing)}\n")
271
295
  sys.exit(1)
272
296
  else:
273
- names = set(options.user_classes) & set(user_classes.keys())
274
- user_classes = [user_classes[n] for n in names]
297
+ names = set(options.user_classes) & set(available_user_classes.keys())
298
+ user_classes = [available_user_classes[n] for n in names]
275
299
  else:
276
300
  # list() call is needed to consume the dict_view object in Python 3
277
- user_classes = list(user_classes.values())
301
+ user_classes = list(available_user_classes.values())
278
302
 
279
303
  if not shape_class and options.num_users:
280
304
  fixed_count_total = sum([user_class.fixed_count for user_class in user_classes])
@@ -293,7 +317,8 @@ def main():
293
317
  if soft_limit < minimum_open_file_limit:
294
318
  # Increasing the limit to 10000 within a running process should work on at least MacOS.
295
319
  # It does not work on all OS:es, but we should be no worse off for trying.
296
- resource.setrlimit(resource.RLIMIT_NOFILE, [minimum_open_file_limit, hard_limit])
320
+ limits = minimum_open_file_limit, hard_limit
321
+ resource.setrlimit(resource.RLIMIT_NOFILE, limits)
297
322
  except BaseException:
298
323
  logger.warning(
299
324
  f"""System open file limit '{soft_limit} is below minimum setting '{minimum_open_file_limit}'.
@@ -301,9 +326,10 @@ It's not high enough for load testing, and the OS didn't allow locust to increas
301
326
  See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-number-of-open-files-limit for more info."""
302
327
  )
303
328
 
304
- # create locust Environment
305
- locustfile_path = None if not locustfile else os.path.basename(locustfile)
329
+ # At least one locust file exists, or system will exit earlier
330
+ locustfile_path = os.path.basename(locustfiles[0])
306
331
 
332
+ # create locust Environment
307
333
  environment = create_environment(
308
334
  user_classes,
309
335
  options,
@@ -10,21 +10,21 @@ from ..shape import LoadTestShape
10
10
  from ..user import User
11
11
 
12
12
 
13
- def is_user_class(item):
13
+ def is_user_class(item) -> bool:
14
14
  """
15
15
  Check if a variable is a runnable (non-abstract) User class
16
16
  """
17
17
  return bool(inspect.isclass(item) and issubclass(item, User) and item.abstract is False)
18
18
 
19
19
 
20
- def is_shape_class(item):
20
+ def is_shape_class(item) -> bool:
21
21
  """
22
22
  Check if a class is a LoadTestShape
23
23
  """
24
24
  return bool(inspect.isclass(item) and issubclass(item, LoadTestShape) and not getattr(item, "abstract", True))
25
25
 
26
26
 
27
- def load_locustfile(path) -> tuple[str | None, dict[str, User], list[LoadTestShape]]:
27
+ def load_locustfile(path) -> tuple[dict[str, type[User]], list[LoadTestShape]]:
28
28
  """
29
29
  Import given locustfile path and return (docstring, callables).
30
30
 
@@ -82,4 +82,4 @@ def load_locustfile(path) -> tuple[str | None, dict[str, User], list[LoadTestSha
82
82
  # Find shape class, if any, return it
83
83
  shape_classes = [value() for value in vars(imported).values() if is_shape_class(value)]
84
84
 
85
- return imported.__doc__, user_classes, shape_classes
85
+ return user_classes, shape_classes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: locust
3
- Version: 2.35.1.dev11
3
+ Version: 2.35.1.dev22
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
@@ -1,6 +1,6 @@
1
1
  locust/__init__.py,sha256=aWeuBPUxONjwNm1xp4v8L4BO14SuYLjscIiwJVX1Ui4,1746
2
2
  locust/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
3
- locust/_version.py,sha256=NsBT_jt7I0dYb-aexQDL_lnMYnG0qvP2W7UOWuPNrX8,528
3
+ locust/_version.py,sha256=aB7giOvdJVeZH1I1TCtKoUJc3fAF7cQ_RUCxXFpRpmI,528
4
4
  locust/argument_parser.py,sha256=D8DzhpwLN-K53IAeTtyimv5DeBdWYyp4gobnAe5T42k,32942
5
5
  locust/clients.py,sha256=o-277lWQdpmPnoRTdf3IQVNPQT8LMFDtPtuxbLHQIIs,19286
6
6
  locust/debug.py,sha256=7CCm8bIg44uGH2wqBlo1rXBzV2VzwPicLxLewz8r5CQ,5099
@@ -11,7 +11,7 @@ locust/exception.py,sha256=jGgJ32ubuf4pWdlaVOkbh2Y0LlG0_DHi-lv3ib8ppOE,1791
11
11
  locust/html.py,sha256=VjoAEZEolJ8il4HLACBlVA81Qk36fBwlDj_7ojAEFss,4132
12
12
  locust/input_events.py,sha256=lqLDB2Ax-OQ7-vtYNkGjySjfaWVobBzqf0GxRwjcLcQ,3323
13
13
  locust/log.py,sha256=5E2ZUOa3V4sfCqa-470Gle1Ik9L5nxYitsjEB9tRwE0,3455
14
- locust/main.py,sha256=va7Jrdw6MeyyRVnux2uEdwTAIcMfY5dmzTFncCI3ouo,26779
14
+ locust/main.py,sha256=fT99Qm8PfOtwqzyyr-Smv00-hkkKRTNSesTY5njceRk,27690
15
15
  locust/py.typed,sha256=gkWLl8yD4mIZnNYYAIRM8g9VarLvWmTAFeUfEbxJLBw,65
16
16
  locust/runners.py,sha256=niYmGsfOpxMfVmTXGod4MYTefpaZ2wirFlhqxRw5mq4,70617
17
17
  locust/shape.py,sha256=t-lwBS8LOjWcKXNL7j2U3zroIXJ1b0fazUwpRYQOKXw,1973
@@ -37,7 +37,7 @@ locust/util/date.py,sha256=2uZAY-fkJq7llUcVywTDTbe-_2IYumCv18n3Vrc75gw,833
37
37
  locust/util/deprecation.py,sha256=4my4IcFpbM6yEKr569GMUKMsS0ywp0N4JPhhwm3J1-w,2026
38
38
  locust/util/directory.py,sha256=2EeuVIIFEErm0OpNXdsEgQLx49jAXq-PvMj2uY0Mr8o,326
39
39
  locust/util/exception_handler.py,sha256=jTMyBq2a0O07fRjmqGkheyaPj58tUgnbbcjoesKGPws,797
40
- locust/util/load_locustfile.py,sha256=hn70KcIG8jHmZyuKv2pcEmwgWtOEu24Efeji1KRYNUE,2964
40
+ locust/util/load_locustfile.py,sha256=0ncZh2GQvkiItZB0ePy6JQ_2ayZNP9jXOTfUhdvlQB4,2956
41
41
  locust/util/rounding.py,sha256=5haxR8mKhATqag6WvPby-MSRRgIw5Ob6thbyvMYZM7o,92
42
42
  locust/util/timespan.py,sha256=Y0LtnhUq2Mq19p04u0XtBlYQ_-S2cRvwRdgru8W9WhA,986
43
43
  locust/util/url.py,sha256=s_W2PCxvxTWxWX0yUvp-8VBuQm881KwI5X9iifogZG4,321
@@ -51,8 +51,8 @@ locust/webui/dist/assets/graphs-light.png,sha256=7L6pOehXqCojQclzeP91l-LskFQAw_n
51
51
  locust/webui/dist/assets/index-DQLt1q6M.js,sha256=RfwivgOXkowxUEGaAaUQEWabTNLSrCFER0p1e09K_ao,1687689
52
52
  locust/webui/dist/assets/testruns-dark.png,sha256=np6MvpgJ2gkKQ66SOmukLtjsMtHqTSr5dNfza-2XtCo,267621
53
53
  locust/webui/dist/assets/testruns-light.png,sha256=iLAxBZh3kRsfGkcB1-1KSAbFgGji43IqiUrYuJlUoPk,276839
54
- locust-2.35.1.dev11.dist-info/METADATA,sha256=rQKTfXrmIs0vg8PPip5hCV_sYFujNz7z-XYKRp_Edk0,9442
55
- locust-2.35.1.dev11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
- locust-2.35.1.dev11.dist-info/entry_points.txt,sha256=RAdt8Ku-56m7bFjmdj-MBhbF6h4NX7tVODR9QNnOg0E,44
57
- locust-2.35.1.dev11.dist-info/licenses/LICENSE,sha256=5hnz-Vpj0Z3kSCQl0LzV2hT1TLc4LHcbpBp3Cy-EuyM,1110
58
- locust-2.35.1.dev11.dist-info/RECORD,,
54
+ locust-2.35.1.dev22.dist-info/METADATA,sha256=McWzZwgwMBHS8Yu_z0RwoR3nc1GaJpMYNn9EQnHR9OA,9442
55
+ locust-2.35.1.dev22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
+ locust-2.35.1.dev22.dist-info/entry_points.txt,sha256=RAdt8Ku-56m7bFjmdj-MBhbF6h4NX7tVODR9QNnOg0E,44
57
+ locust-2.35.1.dev22.dist-info/licenses/LICENSE,sha256=5hnz-Vpj0Z3kSCQl0LzV2hT1TLc4LHcbpBp3Cy-EuyM,1110
58
+ locust-2.35.1.dev22.dist-info/RECORD,,