locust-cloud 1.25.2.dev3__tar.gz → 1.25.3.dev5__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.

Potentially problematic release.


This version of locust-cloud might be problematic. Click here for more details.

Files changed (40) hide show
  1. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/PKG-INFO +1 -1
  2. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/args.py +1 -2
  3. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/cloud.py +11 -4
  4. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/common.py +2 -0
  5. locust_cloud-1.25.3.dev5/locust_cloud/import_finder.py +63 -0
  6. locust_cloud-1.25.3.dev5/tests/import_finder_test.py +145 -0
  7. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/.github/workflows/daily-alb-teardown.yml +0 -0
  8. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/.github/workflows/daily-check.yml +0 -0
  9. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/.github/workflows/tests.yml +0 -0
  10. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/.gitignore +0 -0
  11. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/.pre-commit-config.yaml +0 -0
  12. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/.vscode/extensions.json +0 -0
  13. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/.vscode/launch.json +0 -0
  14. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/.vscode/settings.json +0 -0
  15. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/LICENSE +0 -0
  16. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/README.md +0 -0
  17. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/alb-teardown.py +0 -0
  18. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/__init__.py +0 -0
  19. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/actions.py +0 -0
  20. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/apisession.py +0 -0
  21. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/docs/.gitignore +0 -0
  22. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/docs/1-first-run.rst +0 -0
  23. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/docs/2-examples.rst +0 -0
  24. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/docs/images/locust-cloud-screenshot.png +0 -0
  25. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/docs/locust-cloud.rst +0 -0
  26. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/input_events.py +0 -0
  27. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/web_login.py +0 -0
  28. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locust_cloud/websocket.py +0 -0
  29. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/locustfile.py +0 -0
  30. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/pyproject.toml +0 -0
  31. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/testdata/extra-files/extra.txt +0 -0
  32. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/testdata/extra-package/example/__init__.py +0 -0
  33. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/testdata/extra-package/setup.py +0 -0
  34. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/testdata/requirements.txt +0 -0
  35. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/tests/args_test.py +0 -0
  36. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/tests/browser_tests.py +0 -0
  37. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/tests/cloud_test.py +0 -0
  38. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/tests/web_login_test.py +0 -0
  39. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/tests/websocket_test.py +0 -0
  40. {locust_cloud-1.25.2.dev3 → locust_cloud-1.25.3.dev5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: locust-cloud
3
- Version: 1.25.2.dev3
3
+ Version: 1.25.3.dev5
4
4
  Summary: Locust Cloud
5
5
  Project-URL: homepage, https://locust.cloud
6
6
  Project-URL: repository, https://github.com/locustcloud/locust-cloud
@@ -10,6 +10,7 @@ import tempfile
10
10
 
11
11
  from locust_cloud.actions import delete
12
12
  from locust_cloud.apisession import ApiSession
13
+ from locust_cloud.common import CWD
13
14
  from locust_cloud.web_login import logout, web_login
14
15
 
15
16
  if sys.version_info >= (3, 11):
@@ -25,8 +26,6 @@ from zipfile import ZipFile
25
26
 
26
27
  import configargparse
27
28
 
28
- CWD = pathlib.Path.cwd()
29
-
30
29
 
31
30
  class LocustTomlConfigParser(configargparse.TomlConfigParser):
32
31
  def parse(self, stream: IO[str]) -> OrderedDict[str, Any]:
@@ -15,6 +15,7 @@ from locust_cloud.args import (
15
15
  zip_project_paths,
16
16
  )
17
17
  from locust_cloud.common import __version__
18
+ from locust_cloud.import_finder import get_imported_files
18
19
  from locust_cloud.input_events import input_listener
19
20
  from locust_cloud.websocket import SessionMismatchError, Websocket, WebsocketTimeout
20
21
 
@@ -55,11 +56,18 @@ def main(locustfiles: list[str] | None = None):
55
56
  logger.error(e)
56
57
  return
57
58
 
58
- project_data = zip_project_paths(set(relative_locustfiles + (options.extra_files or [])))
59
-
60
59
  session = ApiSession(options.non_interactive)
61
60
  websocket = Websocket()
62
61
 
62
+ auto_extra_files = set()
63
+ for lf in relative_locustfiles:
64
+ auto_extra_files.update(get_imported_files(lf))
65
+
66
+ project_files = set(relative_locustfiles + (options.extra_files or []) + list(auto_extra_files))
67
+ logger.debug(f"Project files: {project_files}")
68
+
69
+ project_data = zip_project_paths(project_files)
70
+
63
71
  try:
64
72
  logger.info(f"Deploying ({session.region}, locust-cloud {__version__})")
65
73
  locust_env_variables = [
@@ -147,10 +155,9 @@ def main(locustfiles: list[str] | None = None):
147
155
 
148
156
  log_ws_url = js["log_ws_url"]
149
157
  session_id = js["session_id"]
150
- webui_url = log_ws_url.replace("/socket-logs", "")
151
158
 
152
159
  def open_ui():
153
- webbrowser.open_new_tab(webui_url)
160
+ webbrowser.open_new_tab("https://auth.locust.cloud/load-test")
154
161
 
155
162
  Thread(target=input_listener({"\r": open_ui, "\n": open_ui}), daemon=True).start()
156
163
 
@@ -8,6 +8,8 @@ import platformdirs
8
8
 
9
9
  __version__ = importlib.metadata.version("locust-cloud")
10
10
 
11
+ CWD = pathlib.Path.cwd()
12
+
11
13
 
12
14
  VALID_REGIONS = ["us-east-1", "eu-north-1"]
13
15
  CLOUD_CONF_FILE = pathlib.Path(platformdirs.user_config_dir(appname="locust-cloud")) / "config"
@@ -0,0 +1,63 @@
1
+ import ast
2
+ import importlib.util
3
+ import logging
4
+ import site
5
+ from pathlib import Path
6
+
7
+ from locust_cloud.common import CWD
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ SITE_PACKAGES_PATHS = [Path(p) for p in [site.getusersitepackages(), *site.getsitepackages()]]
12
+
13
+
14
+ def imported_modules(tree):
15
+ for node in ast.walk(tree):
16
+ if isinstance(node, ast.Import):
17
+ for alias in node.names:
18
+ yield alias.name.split(".")[0]
19
+ if isinstance(node, ast.ImportFrom):
20
+ if node.level: # relative import
21
+ continue
22
+ if node.module:
23
+ yield node.module.split(".")[0]
24
+
25
+
26
+ def get_imported_files(file_path: Path) -> set[Path]:
27
+ """
28
+ Get a list of path that are imported from the given python script
29
+ They are returned as relative paths to CWD
30
+ """
31
+ paths_queue: list[Path] = [Path(file_path).resolve()]
32
+ paths_seen: set[Path] = set()
33
+ imports: set[Path] = set()
34
+
35
+ while paths_queue:
36
+ current = paths_queue.pop()
37
+ if current in paths_seen:
38
+ continue
39
+
40
+ tree = ast.parse(current.read_text())
41
+ for mod in imported_modules(tree):
42
+ spec = importlib.util.find_spec(mod)
43
+ if spec and spec.origin:
44
+ p = Path(spec.origin).resolve()
45
+ if (
46
+ p != current
47
+ and p.is_relative_to(CWD)
48
+ and all(parent not in SITE_PACKAGES_PATHS for parent in p.parents)
49
+ and all(parent not in imports for parent in p.parents)
50
+ and not "site-packages" in str(p)
51
+ ):
52
+ # add the whole package directory if __init__.py, else the file
53
+ if p.name == "__init__.py":
54
+ pkg_dir = p.parent
55
+ paths_queue.extend([p for p in pkg_dir.rglob("*.py") if p.is_file()])
56
+ imports.add(p.parent)
57
+ else:
58
+ paths_queue.append(p)
59
+ imports.add(p)
60
+ else:
61
+ logger.debug(f"Unable to find spec for module: {mod}")
62
+
63
+ return set([i.relative_to(CWD) for i in imports])
@@ -0,0 +1,145 @@
1
+ import shutil
2
+ import tempfile
3
+ import textwrap
4
+ from contextlib import contextmanager
5
+ from pathlib import Path
6
+
7
+ from locust_cloud.import_finder import CWD, get_imported_files
8
+
9
+
10
+ @contextmanager
11
+ def temporary_file(content, dir=CWD, suffix=".py"):
12
+ with tempfile.NamedTemporaryFile(dir=dir, suffix=suffix) as f:
13
+ f.write(content.encode())
14
+ f.seek(0)
15
+ yield f.name
16
+
17
+
18
+ def import_name(path):
19
+ return str(Path(path).relative_to(CWD)).removesuffix(".py")
20
+
21
+
22
+ def test_ignore_external_packages_imports():
23
+ with temporary_file("import requests") as f:
24
+ assert len(get_imported_files(Path(f))) == 0
25
+
26
+
27
+ def test_import_file():
28
+ with temporary_file(
29
+ textwrap.dedent(
30
+ """
31
+ def foo():
32
+ return "bar"
33
+ """
34
+ )
35
+ ) as to_import:
36
+ with temporary_file(f"import {import_name(to_import)}\n") as f:
37
+ imports = get_imported_files(Path(f))
38
+ assert imports == {Path(to_import).relative_to(CWD)}
39
+
40
+
41
+ def test_from_import_file():
42
+ with temporary_file(
43
+ textwrap.dedent(
44
+ """
45
+ def foo():
46
+ return "bar"
47
+ """
48
+ )
49
+ ) as to_import:
50
+ with temporary_file(f"from {import_name(to_import)} import foo") as f:
51
+ assert get_imported_files(Path(f)) == {Path(to_import).relative_to(CWD)}
52
+
53
+
54
+ def test_all_imports():
55
+ with temporary_file(
56
+ textwrap.dedent(
57
+ """
58
+ def foo():
59
+ return "bar"
60
+ """
61
+ )
62
+ ) as to_import:
63
+ with temporary_file(
64
+ textwrap.dedent(
65
+ f"""
66
+ import requests
67
+ import {import_name(to_import)}
68
+ """
69
+ )
70
+ ) as f:
71
+ assert get_imported_files(Path(f)) == {Path(to_import).relative_to(CWD)}
72
+
73
+
74
+ def test_second_level_imports():
75
+ with temporary_file(
76
+ textwrap.dedent(
77
+ """
78
+ def foo():
79
+ return "bar"
80
+ """
81
+ )
82
+ ) as to_import:
83
+ with temporary_file(
84
+ textwrap.dedent(
85
+ f"""
86
+ def bar():
87
+ import {import_name(to_import)}
88
+ """
89
+ )
90
+ ) as f:
91
+ assert get_imported_files(Path(f)) == {Path(to_import).relative_to(CWD)}
92
+
93
+
94
+ def test_recursive_imports():
95
+ with temporary_file(
96
+ textwrap.dedent(
97
+ """
98
+ def foo():
99
+ return "bar"
100
+ """
101
+ )
102
+ ) as to_import_1:
103
+ with temporary_file(f"import {import_name(to_import_1)}") as to_import:
104
+ with temporary_file(f"import {import_name(to_import)}") as f:
105
+ assert get_imported_files(Path(f)) == {
106
+ Path(to_import).relative_to(CWD),
107
+ Path(to_import_1).relative_to(CWD),
108
+ }
109
+
110
+
111
+ def test_package_imports():
112
+ test_package = CWD / "test_package"
113
+ test_package.mkdir()
114
+
115
+ (test_package / "__init__.py").write_text(
116
+ textwrap.dedent(
117
+ """
118
+ from test import bar
119
+ def foo():
120
+ return "bar"
121
+ """
122
+ )
123
+ )
124
+ (test_package / "test.py").write_text(
125
+ textwrap.dedent(
126
+ """
127
+ def bar():
128
+ return "baz"
129
+ """
130
+ )
131
+ )
132
+ try:
133
+ with temporary_file("import test_package") as f:
134
+ assert get_imported_files(Path(f)) == {test_package.relative_to(CWD)}
135
+
136
+ with temporary_file("import test_package.test") as f:
137
+ assert get_imported_files(Path(f)) == {test_package.relative_to(CWD)}
138
+
139
+ with temporary_file("from test_package import bar") as f:
140
+ assert get_imported_files(Path(f)) == {test_package.relative_to(CWD)}
141
+
142
+ with temporary_file("from test_package.test import bar") as f:
143
+ assert get_imported_files(Path(f)) == {test_package.relative_to(CWD)}
144
+ finally:
145
+ shutil.rmtree(test_package, ignore_errors=True)