locust 2.32.2.dev19__py3-none-any.whl → 2.32.2.dev36__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
@@ -14,7 +14,7 @@ __version_tuple__: VERSION_TUPLE
14
14
  version_tuple: VERSION_TUPLE
15
15
 
16
16
 
17
- __version__ = "2.32.2.dev19"
17
+ __version__ = "2.32.2.dev36"
18
18
  version = __version__
19
- __version_tuple__ = (2, 32, 2, "dev19")
19
+ __version_tuple__ = (2, 32, 2, "dev36")
20
20
  version_tuple = __version_tuple__
locust/argument_parser.py CHANGED
@@ -612,6 +612,14 @@ Typically ONLY these options (and --locustfile) need to be specified on workers,
612
612
  env_var="LOCUST_MASTER_NODE_PORT",
613
613
  )
614
614
 
615
+ web_ui_group.add_argument(
616
+ "--web-base-path",
617
+ type=str,
618
+ default="",
619
+ help="Base path for the web interface (e.g., '/locust'). Default is empty (root path).",
620
+ env_var="LOCUST_WEB_BASE_PATH",
621
+ )
622
+
615
623
  tag_group = parser.add_argument_group(
616
624
  "Tag options",
617
625
  "Locust tasks can be tagged using the @tag decorator. These options let specify which tasks to include or exclude during a test.",
locust/env.py CHANGED
@@ -165,6 +165,7 @@ class Environment:
165
165
  self,
166
166
  host="",
167
167
  port=8089,
168
+ web_base_path: str | None = None,
168
169
  web_login: bool = False,
169
170
  tls_cert: str | None = None,
170
171
  tls_key: str | None = None,
@@ -199,6 +200,7 @@ class Environment:
199
200
  delayed_start=delayed_start,
200
201
  userclass_picker_is_active=userclass_picker_is_active,
201
202
  build_path=build_path,
203
+ web_base_path=web_base_path,
202
204
  )
203
205
  return self.web_ui
204
206
 
locust/main.py CHANGED
@@ -454,7 +454,9 @@ See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-numb
454
454
  elif options.worker:
455
455
  try:
456
456
  runner = environment.create_worker_runner(options.master_host, options.master_port)
457
- logger.debug("Connected to locust master: %s:%s", options.master_host, options.master_port)
457
+ logger.debug(
458
+ "Connected to locust master: %s:%s%s", options.master_host, options.master_port, options.web_base_path
459
+ )
458
460
  except OSError as e:
459
461
  logger.error("Failed to connect to the Locust master: %s", e)
460
462
  sys.exit(-1)
@@ -490,26 +492,32 @@ See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-numb
490
492
  if not options.headless and not options.worker:
491
493
  protocol = "https" if options.tls_cert and options.tls_key else "http"
492
494
 
495
+ if options.web_base_path and options.web_base_path[0] != "/":
496
+ logger.error(
497
+ f"Invalid format for --web-base-path argument ({options.web_base_path}): the url path must start with a slash."
498
+ )
499
+ sys.exit(1)
493
500
  if options.web_host == "*":
494
501
  # special check for "*" so that we're consistent with --master-bind-host
495
502
  web_host = ""
496
503
  else:
497
504
  web_host = options.web_host
498
505
  if web_host:
499
- logger.info(f"Starting web interface at {protocol}://{web_host}:{options.web_port}")
506
+ logger.info(f"Starting web interface at {protocol}://{web_host}:{options.web_port}{options.web_base_path}")
500
507
  if options.web_host_display_name:
501
508
  logger.info(f"Starting web interface at {options.web_host_display_name}")
502
509
  else:
503
510
  if os.name == "nt":
504
511
  logger.info(
505
- f"Starting web interface at {protocol}://localhost:{options.web_port} (accepting connections from all network interfaces)"
512
+ f"Starting web interface at {protocol}://localhost:{options.web_port}{options.web_base_path} (accepting connections from all network interfaces)"
506
513
  )
507
514
  else:
508
- logger.info(f"Starting web interface at {protocol}://0.0.0.0:{options.web_port}")
515
+ logger.info(f"Starting web interface at {protocol}://0.0.0.0:{options.web_port}{options.web_base_path}")
509
516
 
510
517
  web_ui = environment.create_web_ui(
511
518
  host=web_host,
512
519
  port=options.web_port,
520
+ web_base_path=options.web_base_path,
513
521
  web_login=options.web_login,
514
522
  tls_cert=options.tls_cert,
515
523
  tls_key=options.tls_key,
locust/runners.py CHANGED
@@ -1216,6 +1216,7 @@ class WorkerRunner(DistributedRunner):
1216
1216
  self.client_id = socket.gethostname() + "_" + uuid4().hex
1217
1217
  self.master_host = master_host
1218
1218
  self.master_port = master_port
1219
+ self.web_base_path = environment.parsed_options.web_base_path if environment.parsed_options else ""
1219
1220
  self.logs: list[str] = []
1220
1221
  self.worker_cpu_warning_emitted = False
1221
1222
  self._users_dispatcher: UsersDispatcher | None = None
@@ -1475,11 +1476,11 @@ class WorkerRunner(DistributedRunner):
1475
1476
  if not success:
1476
1477
  if self.retry < 3:
1477
1478
  logger.debug(
1478
- f"Failed to connect to master {self.master_host}:{self.master_port}, retry {self.retry}/{CONNECT_RETRY_COUNT}."
1479
+ f"Failed to connect to master {self.master_host}:{self.master_port}{self.web_base_path}, retry {self.retry}/{CONNECT_RETRY_COUNT}."
1479
1480
  )
1480
1481
  else:
1481
1482
  logger.warning(
1482
- f"Failed to connect to master {self.master_host}:{self.master_port}, retry {self.retry}/{CONNECT_RETRY_COUNT}."
1483
+ f"Failed to connect to master {self.master_host}:{self.master_port}{self.web_base_path}, retry {self.retry}/{CONNECT_RETRY_COUNT}."
1483
1484
  )
1484
1485
  if self.retry > CONNECT_RETRY_COUNT:
1485
1486
  raise ConnectionError()
locust/web.py CHANGED
@@ -15,6 +15,7 @@ from typing import TYPE_CHECKING, Any, TypedDict
15
15
 
16
16
  import gevent
17
17
  from flask import (
18
+ Blueprint,
18
19
  Flask,
19
20
  Response,
20
21
  jsonify,
@@ -120,6 +121,7 @@ class WebUI:
120
121
  environment: Environment,
121
122
  host: str,
122
123
  port: int,
124
+ web_base_path: str | None = None,
123
125
  web_login: bool = False,
124
126
  tls_cert: str | None = None,
125
127
  tls_key: str | None = None,
@@ -161,20 +163,21 @@ class WebUI:
161
163
  self.auth_args = {}
162
164
  self.app.template_folder = build_path or DEFAULT_BUILD_PATH
163
165
  self.app.static_url_path = "/assets/"
166
+
167
+ app_blueprint = Blueprint("locust", __name__, url_prefix=web_base_path)
164
168
  # ensures static js files work on Windows
165
169
  mimetypes.add_type("application/javascript", ".js")
166
-
167
170
  if self.web_login:
168
171
  self._login_manager = LoginManager()
169
172
  self._login_manager.init_app(self.app)
170
- self._login_manager.login_view = "login"
173
+ self._login_manager.login_view = "locust.login"
171
174
 
172
175
  if environment.runner:
173
176
  self.update_template_args()
174
177
  if not delayed_start:
175
178
  self.start()
176
179
 
177
- @app.errorhandler(Exception)
180
+ @app_blueprint.errorhandler(Exception)
178
181
  def handle_exception(error):
179
182
  error_message = str(error)
180
183
  error_code = getattr(error, "code", 500)
@@ -184,7 +187,7 @@ class WebUI:
184
187
  )
185
188
  return make_response(error_message, error_code)
186
189
 
187
- @app.route("/assets/<path:path>")
190
+ @app_blueprint.route("/assets/<path:path>")
188
191
  def send_assets(path):
189
192
  directory = (
190
193
  os.path.join(self.app.template_folder, "assets")
@@ -194,7 +197,7 @@ class WebUI:
194
197
 
195
198
  return send_from_directory(directory, path)
196
199
 
197
- @app.route("/")
200
+ @app_blueprint.route("/")
198
201
  @self.auth_required_if_enabled
199
202
  def index() -> str | Response:
200
203
  if not environment.runner:
@@ -203,7 +206,7 @@ class WebUI:
203
206
 
204
207
  return render_template("index.html", template_args=self.template_args)
205
208
 
206
- @app.route("/swarm", methods=["POST"])
209
+ @app_blueprint.route("/swarm", methods=["POST"])
207
210
  @self.auth_required_if_enabled
208
211
  def swarm() -> Response:
209
212
  assert request.method == "POST"
@@ -317,7 +320,7 @@ class WebUI:
317
320
  else:
318
321
  return jsonify({"success": False, "message": "No runner", "host": environment.host})
319
322
 
320
- @app.route("/stop")
323
+ @app_blueprint.route("/stop")
321
324
  @self.auth_required_if_enabled
322
325
  def stop() -> Response:
323
326
  if self._swarm_greenlet is not None:
@@ -327,7 +330,7 @@ class WebUI:
327
330
  environment.runner.stop()
328
331
  return jsonify({"success": True, "message": "Test stopped"})
329
332
 
330
- @app.route("/stats/reset")
333
+ @app_blueprint.route("/stats/reset")
331
334
  @self.auth_required_if_enabled
332
335
  def reset_stats() -> str:
333
336
  environment.events.reset_stats.fire()
@@ -336,7 +339,7 @@ class WebUI:
336
339
  environment.runner.exceptions = {}
337
340
  return "ok"
338
341
 
339
- @app.route("/stats/report")
342
+ @app_blueprint.route("/stats/report")
340
343
  @self.auth_required_if_enabled
341
344
  def stats_report() -> Response:
342
345
  theme = request.args.get("theme", "")
@@ -382,7 +385,7 @@ class WebUI:
382
385
  )
383
386
  return response
384
387
 
385
- @app.route("/stats/requests/csv")
388
+ @app_blueprint.route("/stats/requests/csv")
386
389
  @self.auth_required_if_enabled
387
390
  def request_stats_csv() -> Response:
388
391
  data = StringIO()
@@ -390,7 +393,7 @@ class WebUI:
390
393
  self.stats_csv_writer.requests_csv(writer)
391
394
  return _download_csv_response(data.getvalue(), "requests")
392
395
 
393
- @app.route("/stats/requests_full_history/csv")
396
+ @app_blueprint.route("/stats/requests_full_history/csv")
394
397
  @self.auth_required_if_enabled
395
398
  def request_stats_full_history_csv() -> Response:
396
399
  options = self.environment.parsed_options
@@ -408,7 +411,7 @@ class WebUI:
408
411
 
409
412
  return make_response("Error: Server was not started with option to generate full history.", 404)
410
413
 
411
- @app.route("/stats/failures/csv")
414
+ @app_blueprint.route("/stats/failures/csv")
412
415
  @self.auth_required_if_enabled
413
416
  def failures_stats_csv() -> Response:
414
417
  data = StringIO()
@@ -416,7 +419,7 @@ class WebUI:
416
419
  self.stats_csv_writer.failures_csv(writer)
417
420
  return _download_csv_response(data.getvalue(), "failures")
418
421
 
419
- @app.route("/stats/requests")
422
+ @app_blueprint.route("/stats/requests")
420
423
  @self.auth_required_if_enabled
421
424
  @memoize(timeout=DEFAULT_CACHE_TIME, dynamic_timeout=True)
422
425
  def request_stats() -> Response:
@@ -486,7 +489,7 @@ class WebUI:
486
489
 
487
490
  return jsonify(report)
488
491
 
489
- @app.route("/exceptions")
492
+ @app_blueprint.route("/exceptions")
490
493
  @self.auth_required_if_enabled
491
494
  def exceptions() -> Response:
492
495
  return jsonify(
@@ -503,7 +506,7 @@ class WebUI:
503
506
  }
504
507
  )
505
508
 
506
- @app.route("/exceptions/csv")
509
+ @app_blueprint.route("/exceptions/csv")
507
510
  @self.auth_required_if_enabled
508
511
  def exceptions_csv() -> Response:
509
512
  data = StringIO()
@@ -511,7 +514,7 @@ class WebUI:
511
514
  self.stats_csv_writer.exceptions_csv(writer)
512
515
  return _download_csv_response(data.getvalue(), "exceptions")
513
516
 
514
- @app.route("/tasks")
517
+ @app_blueprint.route("/tasks")
515
518
  @self.auth_required_if_enabled
516
519
  def tasks() -> dict[str, dict[str, dict[str, float]]]:
517
520
  runner = self.environment.runner
@@ -531,15 +534,15 @@ class WebUI:
531
534
  }
532
535
  return task_data
533
536
 
534
- @app.route("/logs")
537
+ @app_blueprint.route("/logs")
535
538
  @self.auth_required_if_enabled
536
539
  def logs():
537
540
  return jsonify({"master": get_logs(), "workers": self.environment.worker_logs})
538
541
 
539
- @app.route("/login")
542
+ @app_blueprint.route("/login")
540
543
  def login():
541
544
  if not self.web_login:
542
- return redirect(url_for("index"))
545
+ return redirect(url_for("locust.index"))
543
546
 
544
547
  self.auth_args["error"] = session.get("auth_error", None)
545
548
  self.auth_args["info"] = session.get("auth_info", None)
@@ -549,7 +552,7 @@ class WebUI:
549
552
  auth_args=self.auth_args,
550
553
  )
551
554
 
552
- @app.route("/user", methods=["POST"])
555
+ @app_blueprint.route("/user", methods=["POST"])
553
556
  def update_user():
554
557
  assert request.method == "POST"
555
558
 
@@ -558,6 +561,8 @@ class WebUI:
558
561
 
559
562
  return {}, 201
560
563
 
564
+ app.register_blueprint(app_blueprint)
565
+
561
566
  @property
562
567
  def login_manager(self):
563
568
  if self.web_login:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: locust
3
- Version: 2.32.2.dev19
3
+ Version: 2.32.2.dev36
4
4
  Summary: Developer-friendly load testing framework
5
5
  Home-page: https://locust.io/
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  locust/__init__.py,sha256=Jit8eNUrwuMLqavyFvMZr69e61DILq_KB4yT4MciScw,1681
2
2
  locust/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
3
- locust/_version.py,sha256=dAoefD2JjRrFZS7zkL9p4T6yjIMEHwk4JeynkD6_F5g,460
4
- locust/argument_parser.py,sha256=Q_P7Yt0KdtOiNAgTZcEVuxX9eGzJoyibI3mPrf0vzvA,29184
3
+ locust/_version.py,sha256=i7Sq9rHyPekA0ITFLF-YnS37XLkPpy3coTR-cqoSpdk,460
4
+ locust/argument_parser.py,sha256=LuGP0vppUIYzbXI3PTIbB1USajbwwTGC_FqagkyG3Dg,29424
5
5
  locust/clients.py,sha256=XK-xabq2_5GZKMEjebDobvEjeBTtCs8h2EelL7s68Qs,19346
6
6
  locust/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  locust/contrib/fasthttp.py,sha256=Fs83qq8zqT4_aTnA8JUQaIWo2IBj-LNi2AKflx8yGtg,28858
@@ -9,18 +9,18 @@ locust/contrib/mongodb.py,sha256=1seUYgJOaNKwybYOP9PUEVhgl8hGy-G33f8lFj3R8W8,124
9
9
  locust/contrib/postgres.py,sha256=OuMWnGYN10K65Tq2axVESEW25Y0g5gJb0rK90jkcCJg,1230
10
10
  locust/debug.py,sha256=7CCm8bIg44uGH2wqBlo1rXBzV2VzwPicLxLewz8r5CQ,5099
11
11
  locust/dispatch.py,sha256=prdwtb9EoN4A9klgiKgWuwQmvFB8hEuFHOK6ot62AJI,16202
12
- locust/env.py,sha256=e5FNcpoftv8G0D86cR1t1cXatumBmCIDA7DbR1JjOSw,13000
12
+ locust/env.py,sha256=URPRltUBdOdCCO1yb2WmE9JeUlI_SAm9l7cvR9j1c_s,13083
13
13
  locust/event.py,sha256=AWoeG2q11Vpupv1eW9Rf2N0EI4dDDAldKe6zY1ajC7I,8717
14
14
  locust/exception.py,sha256=jGgJ32ubuf4pWdlaVOkbh2Y0LlG0_DHi-lv3ib8ppOE,1791
15
15
  locust/html.py,sha256=TCwCa93t7l-M6SEAxDA2hIw18JztQw46eDkXxgbs254,3614
16
16
  locust/input_events.py,sha256=ZIyePyAMuA_YFYWg18g_pE4kwuQV3RbEB250MzXRwjY,3314
17
17
  locust/log.py,sha256=Wrkn0Ibugh5Sqjm4hGQ2-jUsy1tNMBdTctp4FyXQI24,3457
18
- locust/main.py,sha256=HvE5EBnfkEEJWggb4KGxBJQr4ndvCb62-nKwKNpV9_A,29498
18
+ locust/main.py,sha256=LEr4WaPCraEOF8mt0-FY8r1MveF6G9y6lsnRxm66PNc,29933
19
19
  locust/py.typed,sha256=gkWLl8yD4mIZnNYYAIRM8g9VarLvWmTAFeUfEbxJLBw,65
20
20
  locust/rpc/__init__.py,sha256=nVGoHWFQxZjnhCDWjbgXIbmFbN9sizAjkhvSs9_642c,58
21
21
  locust/rpc/protocol.py,sha256=n-rb3GZQcAlldYDj4E4GuFGylYj_26GSS5U29meft5Y,1282
22
22
  locust/rpc/zmqrpc.py,sha256=_zl0DKazQLD2YyqxvyuskVApsDCaUnhloHvV8SkJxjw,3157
23
- locust/runners.py,sha256=YwUFagjLcNEXj4_64g047xWncL5m4Hd99Lg6PzYkGOQ,70366
23
+ locust/runners.py,sha256=vO_2Nv7WHcHnUguIpNCBstSAln4Zg1S4ExfFoHQpo4A,70514
24
24
  locust/shape.py,sha256=t-lwBS8LOjWcKXNL7j2U3zroIXJ1b0fazUwpRYQOKXw,1973
25
25
  locust/stats.py,sha256=z_s98ClnSfryvRi-VS0c7u5g87B_o4qL8nkunkPuhPI,45989
26
26
  locust/user/__init__.py,sha256=S2yvmI_AU9kXirtTIVqiV_Hs7yXzqXvaSgkNo9ig-fk,71
@@ -39,7 +39,7 @@ locust/util/load_locustfile.py,sha256=hn70KcIG8jHmZyuKv2pcEmwgWtOEu24Efeji1KRYNU
39
39
  locust/util/rounding.py,sha256=5haxR8mKhATqag6WvPby-MSRRgIw5Ob6thbyvMYZM7o,92
40
40
  locust/util/timespan.py,sha256=Y0LtnhUq2Mq19p04u0XtBlYQ_-S2cRvwRdgru8W9WhA,986
41
41
  locust/util/url.py,sha256=s_W2PCxvxTWxWX0yUvp-8VBuQm881KwI5X9iifogZG4,321
42
- locust/web.py,sha256=AZpd8HaQ-VaMiaZz9tTWJL4uLCfHViargrnvdG-LtYk,29363
42
+ locust/web.py,sha256=iV0VM6nIhals1seZSkDoKMOdAQciv8cbz_Uaq2OF2hI,29731
43
43
  locust/webui/dist/assets/favicon-dark.png,sha256=6zVkRtiRfU45qQGvEhf1cq2nNauFs_JW5SI79wT0YkM,2437
44
44
  locust/webui/dist/assets/favicon-light.png,sha256=VdG2GZyeTCOML7xfievupBP4EhmHoUqlVCw-tXqkvxU,2468
45
45
  locust/webui/dist/assets/index-CV_-ndKF.js,sha256=YNaB15qUPWDD41yMWEB-9ZuyQIPVVW2bzACpKmowu-c,1674517
@@ -47,8 +47,8 @@ locust/webui/dist/auth.html,sha256=Na04RsfJNXyscodaY3CjreTxUmeYnXaOONhSxPB-59Y,6
47
47
  locust/webui/dist/index.html,sha256=ozpUdS047Z-rFgrKq1J-40FNHZlkR36tbhzipbxkS5o,654
48
48
  locust/webui/dist/report.html,sha256=Ok3WzJmly4E0pyBuHmzIVJx7e36zdyqgJ0VwmwVQnuE,1470993
49
49
  poetry.lock,sha256=NUOC8hV7YCL5kHXj3GF2Kb16DLRCXoZUjiWXPghYEyg,213450
50
- locust-2.32.2.dev19.dist-info/LICENSE,sha256=78XGpIn3fHVBfaxlPNUfjVufSN7QsdhpJMRJHv2AFpo,1095
51
- locust-2.32.2.dev19.dist-info/METADATA,sha256=7bnBB-gNKZyvDB6-qW2pH1BE3PyiVoGtzRJ67LNgE6I,7877
52
- locust-2.32.2.dev19.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
53
- locust-2.32.2.dev19.dist-info/entry_points.txt,sha256=RhIxlLwU_Ae_WjimS5COUDLagdCh6IOMyLtgaQxNmlM,43
54
- locust-2.32.2.dev19.dist-info/RECORD,,
50
+ locust-2.32.2.dev36.dist-info/LICENSE,sha256=78XGpIn3fHVBfaxlPNUfjVufSN7QsdhpJMRJHv2AFpo,1095
51
+ locust-2.32.2.dev36.dist-info/METADATA,sha256=GfKRSEiT_UBfhg6kkY_HM75tm6lBqkydZ9gMMRf5yRo,7877
52
+ locust-2.32.2.dev36.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
53
+ locust-2.32.2.dev36.dist-info/entry_points.txt,sha256=RhIxlLwU_Ae_WjimS5COUDLagdCh6IOMyLtgaQxNmlM,43
54
+ locust-2.32.2.dev36.dist-info/RECORD,,