locust-cloud 1.20.7.dev3__tar.gz → 1.20.8.dev3__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 (33) hide show
  1. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/.github/workflows/daily-check.yml +3 -1
  2. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/PKG-INFO +1 -1
  3. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/args.py +63 -3
  4. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/cloud.py +3 -0
  5. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locustfile.py +6 -1
  6. locust_cloud-1.20.8.dev3/testdata/extra-package/example/__init__.py +2 -0
  7. locust_cloud-1.20.8.dev3/testdata/extra-package/setup.py +8 -0
  8. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/tests/args_test.py +10 -10
  9. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/uv.lock +352 -352
  10. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/.github/workflows/tests.yml +0 -0
  11. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/.gitignore +0 -0
  12. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/.pre-commit-config.yaml +0 -0
  13. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/.vscode/extensions.json +0 -0
  14. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/.vscode/launch.json +0 -0
  15. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/.vscode/settings.json +0 -0
  16. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/LICENSE +0 -0
  17. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/README.md +0 -0
  18. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/__init__.py +0 -0
  19. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/apisession.py +0 -0
  20. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/common.py +0 -0
  21. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/docs/.gitignore +0 -0
  22. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/docs/1-first-run.rst +0 -0
  23. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/docs/2-examples.rst +0 -0
  24. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/docs/images/locust-cloud-screenshot.png +0 -0
  25. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/docs/locust-cloud.rst +0 -0
  26. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/input_events.py +0 -0
  27. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/web_login.py +0 -0
  28. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/locust_cloud/websocket.py +0 -0
  29. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/pyproject.toml +0 -0
  30. {locust_cloud-1.20.7.dev3/testdata → locust_cloud-1.20.8.dev3/testdata/extra-files}/extra.txt +0 -0
  31. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/tests/cloud_test.py +0 -0
  32. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/tests/web_login_test.py +0 -0
  33. {locust_cloud-1.20.7.dev3 → locust_cloud-1.20.8.dev3}/tests/websocket_test.py +0 -0
@@ -35,11 +35,13 @@ jobs:
35
35
  # any local changes would make hatch-vcs set a "local version" (+dev0...), so we ignore any uv.lock updates:
36
36
  - run: git update-index --assume-unchanged uv.lock
37
37
  - run: uv run locust-cloud --help
38
- - run: uv run locust-cloud --image-tag master --profile status-checker --mock-server --autostart --autoquit 0 --run-time 1m --loglevel DEBUG --extra-files testdata |& tee output.txt
38
+ - run: uv run locust-cloud --image-tag master --profile status-checker --mock-server --autostart --autoquit 0 --run-time 1m --loglevel DEBUG --extra-files testdata/extra-files --extra-packages testdata/extra-package |& tee output.txt
39
39
  # check ok exit
40
40
  - run: grep -m 1 '(exit code 0)' output.txt
41
41
  # check extra files specified were available
42
42
  - run: "grep -m 1 -- '--extra-files verification: pineapple' output.txt"
43
+ # check extra package were successfully installed
44
+ - run: "grep -m 1 -- 'Hello from the example package!' output.txt"
43
45
  # check for errors
44
46
  - run: bash -ec "! grep Traceback output.txt"
45
47
  - run: bash -ec "! grep ERROR output.txt"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: locust-cloud
3
- Version: 1.20.7.dev3
3
+ Version: 1.20.8.dev3
4
4
  Summary: Locust Cloud
5
5
  Project-URL: homepage, https://locust.cloud
6
6
  Project-URL: repository, https://github.com/locustcloud/locust-cloud
@@ -4,7 +4,9 @@ import gzip
4
4
  import io
5
5
  import os
6
6
  import pathlib
7
+ import shutil
7
8
  import sys
9
+ import tempfile
8
10
 
9
11
  if sys.version_info >= (3, 11):
10
12
  import tomllib
@@ -61,6 +63,17 @@ def valid_extra_files_path(file_path: str) -> pathlib.Path:
61
63
  return p
62
64
 
63
65
 
66
+ def valid_extra_packages_path(file_path: str) -> pathlib.Path:
67
+ p = pathlib.Path(file_path).resolve()
68
+
69
+ if not p.exists():
70
+ raise ArgumentTypeError(f"Path not found: {file_path}")
71
+ if p.is_file() and not (p.suffix == ".whl" or p.suffixes == [".tar", ".gz"]):
72
+ raise ArgumentTypeError(f"Invalid file suffix (must be '.whl' or '.tar.gz'): {file_path}")
73
+
74
+ return p
75
+
76
+
64
77
  def transfer_encode(file_name: str, stream: IO[bytes]) -> dict[str, str]:
65
78
  return {
66
79
  "filename": file_name,
@@ -91,7 +104,7 @@ def expanded(paths: list[pathlib.Path]) -> Generator[pathlib.Path, None, None]:
91
104
  yield path
92
105
 
93
106
 
94
- def transfer_encoded_extra_files(paths: list[pathlib.Path]) -> dict[str, str]:
107
+ def transfer_encoded_args_files(paths: list[pathlib.Path], to_file: str | None) -> dict[str, str]:
95
108
  buffer = io.BytesIO()
96
109
 
97
110
  with ZipFile(buffer, "w") as zf:
@@ -99,13 +112,53 @@ def transfer_encoded_extra_files(paths: list[pathlib.Path]) -> dict[str, str]:
99
112
  zf.write(path.relative_to(CWD))
100
113
 
101
114
  buffer.seek(0)
102
- return transfer_encode("extra-files.zip", buffer)
115
+ return transfer_encode(f"{to_file}.zip", buffer)
116
+
117
+
118
+ def flat_transfer_encoded_args_files(paths: list[pathlib.Path], to_file: str | None) -> dict[str, str]:
119
+ buffer = io.BytesIO()
120
+
121
+ with tempfile.TemporaryDirectory() as tmpdir:
122
+ tmp_path = pathlib.Path(tmpdir)
123
+
124
+ for src in paths:
125
+ src_path = pathlib.Path(src)
126
+ dest_path = tmp_path / src_path.name
127
+
128
+ if src_path.is_file():
129
+ shutil.copy(src_path, dest_path)
130
+ elif src_path.is_dir():
131
+ shutil.copytree(src_path, dest_path)
132
+ else:
133
+ print(f"Warning: {src} is not a valid file or directory")
134
+
135
+ # Create the zip archive
136
+ with ZipFile(buffer, "w") as zf:
137
+ for item in tmp_path.iterdir():
138
+ if item.is_file():
139
+ zf.write(item, arcname=item.name)
140
+ elif item.is_dir():
141
+ for root, _, files in os.walk(item):
142
+ for file in files:
143
+ file_path = pathlib.Path(root) / file
144
+ arcname = file_path.relative_to(tmp_path)
145
+ zf.write(file_path, arcname)
146
+
147
+ buffer.seek(0)
148
+ return transfer_encode(f"{to_file}.zip", buffer)
103
149
 
104
150
 
105
151
  class MergeToTransferEncodedZip(argparse.Action):
106
152
  def __call__(self, parser, namespace, values, option_string=None):
107
153
  paths = cast(list[pathlib.Path], values)
108
- value = transfer_encoded_extra_files(paths)
154
+ value = transfer_encoded_args_files(paths, option_string.lstrip("-"))
155
+ setattr(namespace, self.dest, value)
156
+
157
+
158
+ class MergeToTransferEncodedZipFlat(MergeToTransferEncodedZip):
159
+ def __call__(self, parser, namespace, values, option_string=None):
160
+ paths = cast(list[pathlib.Path], values)
161
+ value = flat_transfer_encoded_args_files(paths, option_string.lstrip("-"))
109
162
  setattr(namespace, self.dest, value)
110
163
 
111
164
 
@@ -163,6 +216,13 @@ cloud_parser.add_argument(
163
216
  type=valid_extra_files_path,
164
217
  help="A list of extra files or directories to upload. Space-separated, e.g. `--extra-files testdata.csv *.py my-directory/`.",
165
218
  )
219
+ cloud_parser.add_argument(
220
+ "--extra-packages",
221
+ action=MergeToTransferEncodedZipFlat,
222
+ nargs="*",
223
+ type=valid_extra_packages_path,
224
+ help="A list of extra packages to upload. Space-separated whl/tar.gz files or directory packages to be installed when running locust.",
225
+ )
166
226
  cloud_parser.add_argument(
167
227
  "--testrun-tags",
168
228
  nargs="*",
@@ -101,6 +101,9 @@ def main():
101
101
  if options.extra_files:
102
102
  payload["extra_files"] = options.extra_files
103
103
 
104
+ if options.extra_packages:
105
+ payload["extra_packages"] = options.extra_packages
106
+
104
107
  for attempt in range(1, 16):
105
108
  try:
106
109
  response = session.post("/deploy", json=payload)
@@ -23,10 +23,15 @@ class MyUser(FastHttpUser):
23
23
  resp.failure("orderId missing")
24
24
 
25
25
 
26
- extra = pathlib.Path("testdata/extra.txt")
26
+ extra = pathlib.Path("testdata/extra-files/extra.txt")
27
27
  if extra.exists():
28
28
  print("--extra-files verification:", extra.read_text())
29
29
 
30
30
 
31
+ import example # type: ignore
32
+
33
+ example.hello()
34
+
35
+
31
36
  if __name__ == "__main__":
32
37
  run_single_user(MyUser)
@@ -0,0 +1,2 @@
1
+ def hello():
2
+ print("Hello from the example package!")
@@ -0,0 +1,8 @@
1
+ from setuptools import find_packages, setup
2
+
3
+ setup(
4
+ name="example",
5
+ version="0.1",
6
+ packages=find_packages(),
7
+ install_requires=[],
8
+ )
@@ -12,7 +12,7 @@ from locust_cloud.args import (
12
12
  expanded,
13
13
  pipe,
14
14
  transfer_encode,
15
- transfer_encoded_extra_files,
15
+ transfer_encoded_args_files,
16
16
  transfer_encoded_file,
17
17
  valid_extra_files_path,
18
18
  )
@@ -59,12 +59,12 @@ def test_transfer_encoded_file():
59
59
 
60
60
 
61
61
  def test_expanded():
62
- result = list(expanded([pathlib.Path("locustfile.py"), pathlib.Path("testdata")]))
63
- assert result == [pathlib.Path("locustfile.py"), pathlib.Path("testdata/extra.txt")]
62
+ result = list(expanded([pathlib.Path("locustfile.py"), pathlib.Path("testdata/extra-files")]))
63
+ assert result == [pathlib.Path("locustfile.py"), pathlib.Path("testdata/extra-files/extra.txt")]
64
64
 
65
65
 
66
- def test_transfer_encoded_extra_files():
67
- result = transfer_encoded_extra_files([pathlib.Path("testdata").resolve()])
66
+ def test_transfer_encoded_args_files():
67
+ result = transfer_encoded_args_files([pathlib.Path("testdata/extra-files").resolve()], "extra-files")
68
68
  assert result["filename"] == "extra-files.zip"
69
69
 
70
70
  buffer = pipe(
@@ -76,7 +76,7 @@ def test_transfer_encoded_extra_files():
76
76
  )
77
77
 
78
78
  with ZipFile(buffer) as zf:
79
- assert zf.namelist() == ["testdata/extra.txt"]
79
+ assert zf.namelist() == ["testdata/extra-files/extra.txt"]
80
80
 
81
81
 
82
82
  def test_parser_locustfile(capsys):
@@ -86,8 +86,8 @@ def test_parser_locustfile(capsys):
86
86
  expected = "error: argument -f/--locustfile: File not found: does-not-exist"
87
87
  assert expected in capsys.readouterr().err
88
88
 
89
- options, _ = combined_cloud_parser.parse_known_args("locust-cloud --locustfile testdata/extra.txt")
90
- assert options.locustfile == transfer_encoded_file("testdata/extra.txt")
89
+ options, _ = combined_cloud_parser.parse_known_args("locust-cloud --locustfile testdata/extra-files/extra.txt")
90
+ assert options.locustfile == transfer_encoded_file("testdata/extra-files/extra.txt")
91
91
 
92
92
 
93
93
  def test_parser_extra_files(capsys):
@@ -103,7 +103,7 @@ def test_parser_extra_files(capsys):
103
103
  expected = "error: argument --extra-files: File not found: does-not-exist"
104
104
  assert expected in capsys.readouterr().err
105
105
 
106
- options, _ = combined_cloud_parser.parse_known_args("locust-cloud --extra-files testdata")
106
+ options, _ = combined_cloud_parser.parse_known_args("locust-cloud --extra-files testdata/extra-files")
107
107
  assert options.extra_files["filename"] == "extra-files.zip"
108
108
  buffer = pipe(
109
109
  options.extra_files["data"],
@@ -114,7 +114,7 @@ def test_parser_extra_files(capsys):
114
114
  )
115
115
 
116
116
  with ZipFile(buffer) as zf:
117
- assert zf.namelist() == ["testdata/extra.txt"]
117
+ assert zf.namelist() == ["testdata/extra-files/extra.txt"]
118
118
 
119
119
 
120
120
  def test_parser_loglevel(capsys):