locust 2.37.15.dev9__py3-none-any.whl → 2.37.15.dev26__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/__init__.py +4 -0
- locust/_version.py +2 -2
- locust/user/markov_taskset.py +322 -0
- locust/user/task.py +13 -2
- locust/web.py +6 -0
- {locust-2.37.15.dev9.dist-info → locust-2.37.15.dev26.dist-info}/METADATA +1 -1
- {locust-2.37.15.dev9.dist-info → locust-2.37.15.dev26.dist-info}/RECORD +10 -9
- {locust-2.37.15.dev9.dist-info → locust-2.37.15.dev26.dist-info}/WHEEL +0 -0
- {locust-2.37.15.dev9.dist-info → locust-2.37.15.dev26.dist-info}/entry_points.txt +0 -0
- {locust-2.37.15.dev9.dist-info → locust-2.37.15.dev26.dist-info}/licenses/LICENSE +0 -0
locust/__init__.py
CHANGED
@@ -27,6 +27,7 @@ from .debug import run_single_user
|
|
27
27
|
from .event import Events
|
28
28
|
from .shape import LoadTestShape
|
29
29
|
from .user import wait_time
|
30
|
+
from .user.markov_taskset import MarkovTaskSet, transition, transitions
|
30
31
|
from .user.sequential_taskset import SequentialTaskSet
|
31
32
|
from .user.task import TaskSet, tag, task
|
32
33
|
from .user.users import HttpUser, User
|
@@ -36,6 +37,9 @@ events = Events()
|
|
36
37
|
|
37
38
|
__all__ = (
|
38
39
|
"SequentialTaskSet",
|
40
|
+
"MarkovTaskSet",
|
41
|
+
"transition",
|
42
|
+
"transitions",
|
39
43
|
"wait_time",
|
40
44
|
"task",
|
41
45
|
"tag",
|
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.37.15.
|
21
|
-
__version_tuple__ = version_tuple = (2, 37, 15, '
|
20
|
+
__version__ = version = '2.37.15.dev26'
|
21
|
+
__version_tuple__ = version_tuple = (2, 37, 15, 'dev26')
|
@@ -0,0 +1,322 @@
|
|
1
|
+
from locust.exception import LocustError
|
2
|
+
from locust.user.task import TaskSetMeta
|
3
|
+
from locust.user.users import TaskSet
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import random
|
7
|
+
from collections.abc import Callable
|
8
|
+
|
9
|
+
MarkovTaskT = Callable[..., None]
|
10
|
+
|
11
|
+
|
12
|
+
class NoMarkovTasksError(LocustError):
|
13
|
+
"""Raised when a MarkovTaskSet class doesn't define any Markov tasks."""
|
14
|
+
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
class InvalidTransitionError(LocustError):
|
19
|
+
"""Raised when a transition in a MarkovTaskSet points to a non-existent task."""
|
20
|
+
|
21
|
+
pass
|
22
|
+
|
23
|
+
|
24
|
+
class NonMarkovTaskTransitionError(LocustError):
|
25
|
+
"""Raised when a transition in a MarkovTaskSet points to a task that doesn't define transitions."""
|
26
|
+
|
27
|
+
pass
|
28
|
+
|
29
|
+
|
30
|
+
class MarkovTaskTagError(LocustError):
|
31
|
+
"""Raised when tags are used with Markov tasks, which is unsupported."""
|
32
|
+
|
33
|
+
pass
|
34
|
+
|
35
|
+
|
36
|
+
def is_markov_task(task: MarkovTaskT):
|
37
|
+
"""
|
38
|
+
Determines if a task is a Markov task by checking if it has transitions defined.
|
39
|
+
|
40
|
+
:param task: The task to check
|
41
|
+
:return: True if the task is a Markov task, False otherwise
|
42
|
+
"""
|
43
|
+
return "transitions" in dir(task)
|
44
|
+
|
45
|
+
|
46
|
+
def transition(func_name: str, weight: int = 1) -> Callable[[MarkovTaskT], MarkovTaskT]:
|
47
|
+
"""
|
48
|
+
Decorator for adding a single transition to a Markov task.
|
49
|
+
|
50
|
+
This decorator allows you to define a transition from one task to another in a MarkovTaskSet,
|
51
|
+
with an associated weight that determines the probability of taking this transition.
|
52
|
+
|
53
|
+
:param func_name: The name of the target task function
|
54
|
+
:param weight: The weight of this transition (default: 1)
|
55
|
+
:return: The decorated function with the transition added
|
56
|
+
|
57
|
+
Example::
|
58
|
+
|
59
|
+
class UserBehavior(MarkovTaskSet):
|
60
|
+
@transition('browse_products')
|
61
|
+
def index(self):
|
62
|
+
self.client.get("/")
|
63
|
+
|
64
|
+
@transition('index', weight=3)
|
65
|
+
@transition('product_page', weight=1)
|
66
|
+
def browse_products(self):
|
67
|
+
self.client.get("/products/")
|
68
|
+
"""
|
69
|
+
|
70
|
+
def decorator_func(decorated):
|
71
|
+
if not hasattr(decorated, "transitions"):
|
72
|
+
decorated.transitions = {}
|
73
|
+
|
74
|
+
decorated.transitions[func_name] = weight
|
75
|
+
return decorated
|
76
|
+
|
77
|
+
return decorator_func
|
78
|
+
|
79
|
+
|
80
|
+
def transitions(weights: dict[str, int] | list[tuple[str, int] | str]) -> Callable[[MarkovTaskT], MarkovTaskT]:
|
81
|
+
"""
|
82
|
+
Decorator for adding multiple transitions to a Markov task at once.
|
83
|
+
|
84
|
+
This decorator allows you to define multiple transitions from one task to others in a MarkovTaskSet,
|
85
|
+
with associated weights that determine the probability of taking each transition.
|
86
|
+
|
87
|
+
:param weights: Either a dictionary mapping function names to weights, or a list of function names
|
88
|
+
(with default weight 1) or (function_name, weight) tuples
|
89
|
+
:return: The decorated function with the transitions added
|
90
|
+
|
91
|
+
Example::
|
92
|
+
|
93
|
+
class UserBehavior(MarkovTaskSet):
|
94
|
+
@transitions({'checkout': 1, 'browse_products': 3, 'index': 2})
|
95
|
+
def view_cart(self):
|
96
|
+
self.client.get("/cart/")
|
97
|
+
|
98
|
+
@transitions([
|
99
|
+
('index', 2), # with weight 2
|
100
|
+
'browse_products' # with default weight 1
|
101
|
+
])
|
102
|
+
def checkout(self):
|
103
|
+
self.client.get("/checkout/")
|
104
|
+
"""
|
105
|
+
|
106
|
+
def parse_list_item(item: tuple[str, int] | str) -> tuple[str, int]:
|
107
|
+
return item if isinstance(item, tuple) else (item, 1)
|
108
|
+
|
109
|
+
def decorator_func(decorated):
|
110
|
+
if not hasattr(decorated, "transitions"):
|
111
|
+
decorated.transitions = {}
|
112
|
+
|
113
|
+
decorated.transitions.update(
|
114
|
+
weights
|
115
|
+
if isinstance(weights, dict)
|
116
|
+
else {func_name: weight for func_name, weight in map(parse_list_item, weights)}
|
117
|
+
)
|
118
|
+
|
119
|
+
return decorated
|
120
|
+
|
121
|
+
return decorator_func
|
122
|
+
|
123
|
+
|
124
|
+
def get_markov_tasks(class_dict: dict) -> list:
|
125
|
+
"""
|
126
|
+
Extracts all Markov tasks from a class dictionary.
|
127
|
+
|
128
|
+
This function is used internally by MarkovTaskSetMeta to find all methods
|
129
|
+
that have been decorated with @transition or @transitions.
|
130
|
+
|
131
|
+
:param class_dict: Dictionary containing class attributes and methods
|
132
|
+
:return: List of functions that are Markov tasks
|
133
|
+
"""
|
134
|
+
return [fn for fn in class_dict.values() if is_markov_task(fn)]
|
135
|
+
|
136
|
+
|
137
|
+
def to_weighted_list(transitions: dict):
|
138
|
+
return [name for name in transitions.keys() for _ in range(transitions[name])]
|
139
|
+
|
140
|
+
|
141
|
+
def validate_has_markov_tasks(tasks: list, classname: str):
|
142
|
+
"""
|
143
|
+
Validates that a MarkovTaskSet has at least one Markov task.
|
144
|
+
|
145
|
+
This function is used internally during MarkovTaskSet validation to ensure
|
146
|
+
that the class has at least one method decorated with @transition or @transitions.
|
147
|
+
|
148
|
+
:param tasks: List of tasks to validate
|
149
|
+
:param classname: Name of the class being validated (for error messages)
|
150
|
+
:raises NoMarkovTasksError: If no Markov tasks are found
|
151
|
+
"""
|
152
|
+
if not tasks:
|
153
|
+
raise NoMarkovTasksError(
|
154
|
+
f"No Markov tasks defined in class {classname}. Use the @transition(s) decorators to define some."
|
155
|
+
)
|
156
|
+
|
157
|
+
|
158
|
+
def validate_transitions(tasks: list, class_dict: dict, classname: str):
|
159
|
+
"""
|
160
|
+
Validates that all transitions in Markov tasks point to existing Markov tasks.
|
161
|
+
|
162
|
+
This function checks two conditions for each transition:
|
163
|
+
1. The target task exists in the class
|
164
|
+
2. The target task is also a Markov task (has transitions defined)
|
165
|
+
|
166
|
+
:param tasks: List of Markov tasks to validate
|
167
|
+
:param class_dict: Dictionary containing class attributes and methods
|
168
|
+
:param classname: Name of the class being validated (for error messages)
|
169
|
+
:raises InvalidTransitionError: If a transition points to a non-existent task
|
170
|
+
:raises NonMarkovTaskTransitionError: If a transition points to a task that isn't a Markov task
|
171
|
+
"""
|
172
|
+
for task in tasks:
|
173
|
+
for dest in task.transitions.keys():
|
174
|
+
dest_task = class_dict.get(dest)
|
175
|
+
if not dest_task:
|
176
|
+
raise InvalidTransitionError(
|
177
|
+
f"Transition to {dest} from {task.__name__} is invalid since no such element exists on class {classname}"
|
178
|
+
)
|
179
|
+
if not is_markov_task(dest_task):
|
180
|
+
raise NonMarkovTaskTransitionError(
|
181
|
+
f"{classname}.{dest} cannot be used as a target for a transition since it does not define any transitions of its own."
|
182
|
+
+ f"Used as a transition from {task.__name__}."
|
183
|
+
)
|
184
|
+
|
185
|
+
|
186
|
+
def validate_no_unreachable_tasks(tasks: list, class_dict: dict, classname: str):
|
187
|
+
"""
|
188
|
+
Checks for and warns about unreachable Markov tasks in a MarkovTaskSet.
|
189
|
+
|
190
|
+
This function uses depth-first search (DFS) starting from the first task to identify
|
191
|
+
all reachable tasks. It then warns about any tasks that cannot be reached from the
|
192
|
+
starting task through the defined transitions.
|
193
|
+
|
194
|
+
:param tasks: List of Markov tasks to validate
|
195
|
+
:param class_dict: Dictionary containing class attributes and methods
|
196
|
+
:param classname: Name of the class being validated (for warning messages)
|
197
|
+
:return: The original list of tasks
|
198
|
+
"""
|
199
|
+
visited = set()
|
200
|
+
|
201
|
+
def dfs(task_name):
|
202
|
+
visited.add(task_name)
|
203
|
+
# Convert to a weighted list first to handle bad weights
|
204
|
+
for dest in set(to_weighted_list(class_dict.get(task_name).transitions)):
|
205
|
+
if dest not in visited:
|
206
|
+
dfs(dest)
|
207
|
+
|
208
|
+
dfs(tasks[0].__name__)
|
209
|
+
unreachable = set([task.__name__ for task in tasks]) - visited
|
210
|
+
|
211
|
+
if len(unreachable) > 0:
|
212
|
+
logging.warning(f"The following markov tasks are unreachable in class {classname}: {unreachable}")
|
213
|
+
|
214
|
+
return tasks
|
215
|
+
|
216
|
+
|
217
|
+
def validate_no_tags(task, classname: str):
|
218
|
+
"""
|
219
|
+
Validates that Markov tasks don't have tags, which are unsupported.
|
220
|
+
|
221
|
+
Tags are not supported for MarkovTaskSet because they can make the Markov chain invalid
|
222
|
+
by potentially filtering out tasks that are part of the chain.
|
223
|
+
|
224
|
+
:param task: The task to validate
|
225
|
+
:param classname: Name of the class being validated (for error messages)
|
226
|
+
:raises MarkovTaskTagError: If the task has tags
|
227
|
+
"""
|
228
|
+
if "locust_tag_set" in dir(task):
|
229
|
+
raise MarkovTaskTagError(
|
230
|
+
"Tags are unsupported for MarkovTaskSet since they can make the markov chain invalid. "
|
231
|
+
+ f"Tags detected on {classname}.{task.__name__}: {task.locust_tag_set}"
|
232
|
+
)
|
233
|
+
|
234
|
+
|
235
|
+
def validate_task_name(decorated_func):
|
236
|
+
"""
|
237
|
+
Validates that certain method names aren't used as Markov tasks.
|
238
|
+
|
239
|
+
This function checks for special method names that shouldn't be used as Markov tasks:
|
240
|
+
- "on_stop" and "on_start": Using these as Markov tasks will cause them to be called
|
241
|
+
both as tasks AND on stop/start, which is usually not what the user intended.
|
242
|
+
- "run": This method is used internally by Locust and must not be overridden or
|
243
|
+
annotated with transitions.
|
244
|
+
|
245
|
+
:param decorated_func: The function to validate
|
246
|
+
:raises Exception: If the function name is "run"
|
247
|
+
"""
|
248
|
+
if decorated_func.__name__ in ["on_stop", "on_start"]:
|
249
|
+
logging.warning(
|
250
|
+
"You have tagged your on_stop/start function with @transition. This will make the method get called both as a step AND on stop/start."
|
251
|
+
) # this is usually not what the user intended
|
252
|
+
if decorated_func.__name__ == "run":
|
253
|
+
raise Exception(
|
254
|
+
"TaskSet.run() is a method used internally by Locust, and you must not override it or annotate it with transitions"
|
255
|
+
)
|
256
|
+
|
257
|
+
|
258
|
+
def validate_markov_chain(tasks: list, class_dict: dict, classname: str):
|
259
|
+
"""
|
260
|
+
Runs all validation functions on a Markov chain.
|
261
|
+
|
262
|
+
:param tasks: List of Markov tasks to validate
|
263
|
+
:param class_dict: Dictionary containing class attributes and methods
|
264
|
+
:param classname: Name of the class being validated (for error/warning messages)
|
265
|
+
:raises: Various exceptions if validation fails
|
266
|
+
"""
|
267
|
+
validate_has_markov_tasks(tasks, classname)
|
268
|
+
validate_transitions(tasks, class_dict, classname)
|
269
|
+
validate_no_unreachable_tasks(tasks, class_dict, classname)
|
270
|
+
for task in tasks:
|
271
|
+
validate_task_name(task)
|
272
|
+
validate_no_tags(task, classname)
|
273
|
+
|
274
|
+
|
275
|
+
class MarkovTaskSetMeta(TaskSetMeta):
|
276
|
+
"""
|
277
|
+
Meta class for MarkovTaskSet. It's used to allow MarkovTaskSet classes to specify
|
278
|
+
task execution using the @transition(s) decorators
|
279
|
+
"""
|
280
|
+
|
281
|
+
def __new__(mcs, classname, bases, class_dict):
|
282
|
+
if not class_dict.get("abstract"):
|
283
|
+
class_dict["abstract"] = False
|
284
|
+
|
285
|
+
tasks = get_markov_tasks(class_dict)
|
286
|
+
validate_markov_chain(tasks, class_dict, classname)
|
287
|
+
class_dict["current"] = tasks[0]
|
288
|
+
for task in tasks:
|
289
|
+
task.transitions = to_weighted_list(task.transitions)
|
290
|
+
|
291
|
+
return type.__new__(mcs, classname, bases, class_dict)
|
292
|
+
|
293
|
+
|
294
|
+
class MarkovTaskSet(TaskSet, metaclass=MarkovTaskSetMeta):
|
295
|
+
"""
|
296
|
+
Class defining a probabilistic sequence of functions that a User will execute.
|
297
|
+
The sequence is defined by a Markov Chain to describe a user's load.
|
298
|
+
It holds a current state and a set of possible transitions for each state.
|
299
|
+
Every transition as an associated weight that defines how likely it is to be taken.
|
300
|
+
"""
|
301
|
+
|
302
|
+
current: Callable | TaskSet
|
303
|
+
|
304
|
+
abstract: bool = True
|
305
|
+
"""If abstract is True, the class is meant to be subclassed, and the markov chain won't be validated"""
|
306
|
+
|
307
|
+
def __init__(self, *args, **kwargs):
|
308
|
+
super().__init__(*args, **kwargs)
|
309
|
+
|
310
|
+
def get_next_task(self):
|
311
|
+
"""
|
312
|
+
Gets the next task to execute based on the current state and transitions.
|
313
|
+
|
314
|
+
:return: The current task to execute
|
315
|
+
"""
|
316
|
+
fn = self.current
|
317
|
+
|
318
|
+
transitions = getattr(fn, "transitions")
|
319
|
+
next = random.choice(transitions)
|
320
|
+
self.current = getattr(self, next)
|
321
|
+
|
322
|
+
return fn
|
locust/user/task.py
CHANGED
@@ -165,6 +165,17 @@ def get_tasks_from_base_classes(bases, class_dict):
|
|
165
165
|
return new_tasks
|
166
166
|
|
167
167
|
|
168
|
+
def is_markov_taskset(task: type):
|
169
|
+
"""
|
170
|
+
Determines if a task is a MarkovTaskSet by checking its meta class
|
171
|
+
Defined here to avoid circular imports.
|
172
|
+
|
173
|
+
:param task: The task to check
|
174
|
+
:return: True if the task is a MarkovTaskSet, False otherwise
|
175
|
+
"""
|
176
|
+
return task.__class__.__name__ == "MarkovTaskSetMeta"
|
177
|
+
|
178
|
+
|
168
179
|
def filter_tasks_by_tags(
|
169
180
|
task_holder: type[TaskHolder],
|
170
181
|
tags: set[str] | None = None,
|
@@ -188,7 +199,7 @@ def filter_tasks_by_tags(
|
|
188
199
|
passing = True
|
189
200
|
if hasattr(task, "tasks"):
|
190
201
|
filter_tasks_by_tags(task, tags, exclude_tags, checked)
|
191
|
-
passing = len(task.tasks) > 0
|
202
|
+
passing = len(task.tasks) > 0 or is_markov_taskset(task)
|
192
203
|
else:
|
193
204
|
if tags is not None:
|
194
205
|
passing &= "locust_tag_set" in dir(task) and len(task.locust_tag_set & tags) > 0
|
@@ -200,7 +211,7 @@ def filter_tasks_by_tags(
|
|
200
211
|
checked[task] = passing
|
201
212
|
|
202
213
|
task_holder.tasks = new_tasks
|
203
|
-
if not new_tasks:
|
214
|
+
if not new_tasks and not is_markov_taskset(task_holder):
|
204
215
|
logging.warning(f"{task_holder.__name__} had no tasks left after filtering, instantiating it will fail!")
|
205
216
|
|
206
217
|
|
locust/web.py
CHANGED
@@ -290,6 +290,12 @@ class WebUI:
|
|
290
290
|
parsed_options_dict[key] = value == "true"
|
291
291
|
elif parsed_options_value is None:
|
292
292
|
parsed_options_dict[key] = value
|
293
|
+
elif isinstance(parsed_options_value, list):
|
294
|
+
value_as_list = value.split(",")
|
295
|
+
if all(isinstance(x, int) for x in parsed_options_value):
|
296
|
+
parsed_options_dict[key] = list(map(int, value_as_list))
|
297
|
+
else:
|
298
|
+
parsed_options_dict[key] = value_as_list
|
293
299
|
else:
|
294
300
|
parsed_options_dict[key] = type(parsed_options_value)(value)
|
295
301
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
locust/__init__.py,sha256=
|
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=DkB4ScrxmqVBUFNYdeyBj2Ul9CzpfLwmPNmJYF3JQe0,530
|
4
4
|
locust/argument_parser.py,sha256=oO2Hi13tp_vRqOCQoA44l9qwP1Zeg-0KcnkynvwDqBM,33083
|
5
5
|
locust/clients.py,sha256=o-277lWQdpmPnoRTdf3IQVNPQT8LMFDtPtuxbLHQIIs,19286
|
6
6
|
locust/debug.py,sha256=7CCm8bIg44uGH2wqBlo1rXBzV2VzwPicLxLewz8r5CQ,5099
|
@@ -16,7 +16,7 @@ locust/py.typed,sha256=gkWLl8yD4mIZnNYYAIRM8g9VarLvWmTAFeUfEbxJLBw,65
|
|
16
16
|
locust/runners.py,sha256=niYmGsfOpxMfVmTXGod4MYTefpaZ2wirFlhqxRw5mq4,70617
|
17
17
|
locust/shape.py,sha256=t-lwBS8LOjWcKXNL7j2U3zroIXJ1b0fazUwpRYQOKXw,1973
|
18
18
|
locust/stats.py,sha256=qyoSKT0i7RunLDj5pMGqizK1Sp8bcqUsXwh2m4_DpR8,47203
|
19
|
-
locust/web.py,sha256=
|
19
|
+
locust/web.py,sha256=HLFN9jUtKG3sMIKu_Xw9wtvTAFxXvzDHdtLtfb_JxUQ,31849
|
20
20
|
locust/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
21
|
locust/contrib/fasthttp.py,sha256=04rnf4kxRxVbzOyqDlb-io43dWIyMPMUwmQ7DOFo2Lo,29656
|
22
22
|
locust/contrib/mongodb.py,sha256=1seUYgJOaNKwybYOP9PUEVhgl8hGy-G33f8lFj3R8W8,1246
|
@@ -27,8 +27,9 @@ locust/rpc/protocol.py,sha256=n-rb3GZQcAlldYDj4E4GuFGylYj_26GSS5U29meft5Y,1282
|
|
27
27
|
locust/rpc/zmqrpc.py,sha256=tMeLQiLII8QP29lAHGZsj5Pf5FsTL-X4wM0DrtR3ALw,3214
|
28
28
|
locust/user/__init__.py,sha256=RgdRCflP2dIDcvwVMdhPQHAMhWVwQarQ9wWjF9HKk0w,151
|
29
29
|
locust/user/inspectuser.py,sha256=KgrWHyE5jhK6or58R7soLRf-_st42AaQrR72qbiXw9E,2641
|
30
|
+
locust/user/markov_taskset.py,sha256=eESre6OacbP7nTzZFwxUe7TO4X4l7WqOAEETtDzsIfU,11784
|
30
31
|
locust/user/sequential_taskset.py,sha256=SbrrGU9HV2nEWe6zQVtjymn8NgPISP7QSNoVdyoXjYg,2687
|
31
|
-
locust/user/task.py,sha256=
|
32
|
+
locust/user/task.py,sha256=k7g86WYm1I-tuNm2nVI-3TZegGhmo-pIk7pPFILk3yc,17147
|
32
33
|
locust/user/users.py,sha256=c3Dtldg5ypA0rAE4eBn1mCzFtJ-Nq9BPblqM67wEjSY,10016
|
33
34
|
locust/user/wait_time.py,sha256=bGRKMVx4lom75sX3POYJUa1CPeME2bEAXG6CEgxSO5U,2675
|
34
35
|
locust/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -52,8 +53,8 @@ locust/webui/dist/assets/index-WplK3h0d.js,sha256=DbHI-zvMirYSuqu826NB8euV0N9dWQ
|
|
52
53
|
locust/webui/dist/assets/terminal.gif,sha256=iw80LO2u0dnf4wpGfFJZauBeKTcSpw9iUfISXT2nEF4,75302
|
53
54
|
locust/webui/dist/assets/testruns-dark.png,sha256=G4p2VZSBuuqF4neqUaPSshIp5OKQJ_Bvb69Luj6XuVs,125231
|
54
55
|
locust/webui/dist/assets/testruns-light.png,sha256=JinGDiiBPOkhpfF-XCbmQqhRInqItrjrBTLKt5MlqVI,130301
|
55
|
-
locust-2.37.15.
|
56
|
-
locust-2.37.15.
|
57
|
-
locust-2.37.15.
|
58
|
-
locust-2.37.15.
|
59
|
-
locust-2.37.15.
|
56
|
+
locust-2.37.15.dev26.dist-info/METADATA,sha256=-TDAJIb1shcinEGrDmm9UnGGQcG4lfodngx10GWCeAc,9405
|
57
|
+
locust-2.37.15.dev26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
58
|
+
locust-2.37.15.dev26.dist-info/entry_points.txt,sha256=RAdt8Ku-56m7bFjmdj-MBhbF6h4NX7tVODR9QNnOg0E,44
|
59
|
+
locust-2.37.15.dev26.dist-info/licenses/LICENSE,sha256=5hnz-Vpj0Z3kSCQl0LzV2hT1TLc4LHcbpBp3Cy-EuyM,1110
|
60
|
+
locust-2.37.15.dev26.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|