artefacts-cli 0.9.3__tar.gz → 0.9.5__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 (92) hide show
  1. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/CHANGELOG.md +18 -3
  2. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/PKG-INFO +5 -1
  3. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/__init__.py +31 -36
  4. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/app.py +98 -43
  5. artefacts_cli-0.9.5/artefacts/cli/config.py +210 -0
  6. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/helpers.py +12 -0
  7. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/locales/art.pot +97 -64
  8. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/locales/base.pot +97 -64
  9. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/locales/click.pot +2 -2
  10. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/locales/ja_JP/LC_MESSAGES/artefacts.po +101 -63
  11. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/logger.py +5 -1
  12. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/ros2.py +1 -1
  13. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/version.py +2 -2
  14. artefacts_cli-0.9.5/artefacts/copava/__init__.py +1 -0
  15. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts_cli.egg-info/PKG-INFO +5 -1
  16. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts_cli.egg-info/SOURCES.txt +2 -3
  17. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts_cli.egg-info/requires.txt +5 -0
  18. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/bin/release +5 -0
  19. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/pyproject.toml +6 -0
  20. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/pytest.ini +4 -0
  21. artefacts_cli-0.9.5/scripts/whats_latest.rb +11 -0
  22. artefacts_cli-0.9.5/tests/cli/test_cli.py +219 -0
  23. artefacts_cli-0.9.5/tests/cli/test_config_validation.py +24 -0
  24. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/test_utils.py +1 -1
  25. artefacts_cli-0.9.5/tests/cli/test_warp.py +287 -0
  26. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/test_warp_run.py +79 -18
  27. artefacts_cli-0.9.5/tests/conftest.py +195 -0
  28. artefacts_cli-0.9.3/tests/fixtures/warp.yaml → artefacts_cli-0.9.5/tests/fixtures/artefacts.yaml.template +1 -1
  29. artefacts_cli-0.9.3/artefacts/cli/config.py +0 -62
  30. artefacts_cli-0.9.3/artefacts/copava/__init__.py +0 -1
  31. artefacts_cli-0.9.3/tests/cli/test_cli.py +0 -144
  32. artefacts_cli-0.9.3/tests/cli/test_config_validation.py +0 -70
  33. artefacts_cli-0.9.3/tests/cli/test_warp.py +0 -117
  34. artefacts_cli-0.9.3/tests/conftest.py +0 -93
  35. artefacts_cli-0.9.3/tests/fixtures/artefacts_ros1.yaml +0 -32
  36. artefacts_cli-0.9.3/tests/test_config_validation.py +0 -70
  37. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/.pyre_configuration +0 -0
  38. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/README.md +0 -0
  39. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/README_INTERNAL.md +0 -0
  40. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/__init__.py +0 -0
  41. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/app_containers.py +0 -0
  42. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/app_containers.pyi +0 -0
  43. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/bagparser.py +0 -0
  44. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/constants.py +0 -0
  45. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/containers/__init__.py +0 -0
  46. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/containers/docker_cm.py +0 -0
  47. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/containers/docker_utils.py +0 -0
  48. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/containers/utils.py +0 -0
  49. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/errors.py +0 -0
  50. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/i18n.py +0 -0
  51. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/other.py +0 -0
  52. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/parameters.py +0 -0
  53. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/ros1.py +0 -0
  54. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/utils.py +0 -0
  55. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/cli/utils_ros.py +0 -0
  56. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts/wrappers/artefacts_ros1_meta.launch +0 -0
  57. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts.yaml +0 -0
  58. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts_cli.egg-info/dependency_links.txt +0 -0
  59. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts_cli.egg-info/entry_points.txt +0 -0
  60. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/artefacts_cli.egg-info/top_level.txt +0 -0
  61. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/test_run_remote.yaml +0 -0
  62. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/CMakeLists.txt +0 -0
  63. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/test_meta.launch +0 -0
  64. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/test_turtle.launch +0 -0
  65. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/turtle_odometry.launch +0 -0
  66. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/package.xml +0 -0
  67. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/setup.py +0 -0
  68. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/TestTurtle.py +0 -0
  69. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/__init__.py +0 -0
  70. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_odom.py +0 -0
  71. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_post_process.py +0 -0
  72. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_trajectory.py +0 -0
  73. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/test/viz_turtle_odom.xml +0 -0
  74. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim2/launch_turtle.py +0 -0
  75. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/infra-tests/turtlesim2/sample_node.py +0 -0
  76. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/setup.cfg +0 -0
  77. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/__init__.py +0 -0
  78. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/__init__.py +0 -0
  79. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/containers/__init__.py +0 -0
  80. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/containers/test_docker.py +0 -0
  81. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/containers/test_utils.py +0 -0
  82. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/test_app_containers.py +0 -0
  83. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/test_other.py +0 -0
  84. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/test_parameters.py +0 -0
  85. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/test_ros1.py +0 -0
  86. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/test_ros2.py +0 -0
  87. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/cli/test_utils_ros.py +0 -0
  88. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/fixtures/artefacts_deprecated.yaml +0 -0
  89. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/fixtures/bad_launch_test.py +0 -0
  90. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/fixtures/test_junit.xml +0 -0
  91. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/fixtures/warp-env-param.yaml +0 -0
  92. {artefacts_cli-0.9.3 → artefacts_cli-0.9.5}/tests/utils/docker_mock.py +0 -0
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ TODO and upcoming
11
+
12
+ ## [0.9.5] - 2025-06-30
13
+
14
+ ### Added
15
+
16
+ - API calls are automatically retried up to 3 times on server-side 502, 503, 504 errors (#264)
17
+ - API calls get short timeout to detect network issues and report ot the user nicely. (#264)
18
+
19
+ ### Fixed
20
+
21
+ - Wrong error handling on `artefacts config add`, resulting in HTML `artefacts.yaml` (#220)
22
+ - Correct number of runs in a given job now correctly submitted to the dashboard api (#281)
23
+ - Jobs moves onto next run on certain errors which previously terminated the job in run local (#281)
24
+
10
25
  ## [0.9.3] - 2025-06-03
11
26
 
12
27
  ### Removed
@@ -105,9 +120,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
105
120
  - Local metrics errors do not block publication of results.
106
121
  - Introduction of Black formatting.
107
122
 
108
- [unreleased]: https://github.com/art-e-fact/artefacts-client/compare/0.9.2...HEAD
109
- [0.9.2]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.9.2
110
- [0.9.1]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.9.1
123
+ [unreleased]: https://github.com/art-e-fact/artefacts-client/compare/0.9.5...HEAD
124
+ [0.9.5]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.9.5
125
+ [0.9.3]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.9.3
111
126
  [0.8.0]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.8.0
112
127
  [0.7.0]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.7.0
113
128
  [0.5.8]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.5.8
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: artefacts_cli
3
- Version: 0.9.3
3
+ Version: 0.9.5
4
4
  Author-email: FD <fabian@artefacts.com>, AGC <alejandro@artefacts.com>, TN <tomo@artefacts.com>, EP <eric@artefacts.com>
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/art-e-fact/artefacts-client
@@ -20,6 +20,7 @@ Requires-Dist: mcap-ros2-support
20
20
  Requires-Dist: PyYAML>=6.0
21
21
  Requires-Dist: requests>=2.27.1
22
22
  Requires-Dist: rospkg
23
+ Requires-Dist: urllib3>=2.4.0
23
24
  Provides-Extra: dev
24
25
  Requires-Dist: awscli; extra == "dev"
25
26
  Requires-Dist: babel==2.17.0; extra == "dev"
@@ -41,6 +42,9 @@ Requires-Dist: python-markdown-math; extra == "dev"
41
42
  Requires-Dist: ruff>=0.9.2; extra == "dev"
42
43
  Requires-Dist: setuptools_scm>=8; extra == "dev"
43
44
  Requires-Dist: twine; extra == "dev"
45
+ Provides-Extra: dev-extended
46
+ Requires-Dist: artefacts_cli[dev]; extra == "dev-extended"
47
+ Requires-Dist: pytest-watch; extra == "dev-extended"
44
48
 
45
49
  # Artefacts CLI
46
50
 
@@ -6,7 +6,6 @@ import glob
6
6
  import json
7
7
  import math
8
8
  import os
9
- import requests
10
9
 
11
10
  from .config import APIConf
12
11
  from .i18n import localise
@@ -33,7 +32,15 @@ except PackageNotFoundError:
33
32
  __version__ = "0.0.0"
34
33
 
35
34
 
36
- class AuthenticationError(Exception):
35
+ class ArtefactsAPIError(Exception):
36
+ """
37
+ Tentative base error class for Artefacts API interactions
38
+ """
39
+
40
+ pass
41
+
42
+
43
+ class AuthenticationError(ArtefactsAPIError):
37
44
  """Raised when artefacts authentication failed"""
38
45
 
39
46
  pass
@@ -52,6 +59,7 @@ class WarpJob:
52
59
  noisolation=False,
53
60
  context=None,
54
61
  run_offset=0,
62
+ n_subjobs=1, # Total Number of Runs
55
63
  ):
56
64
  self.project_id = project_id
57
65
  self.job_id = os.environ.get("ARTEFACTS_JOB_ID", None)
@@ -67,28 +75,27 @@ class WarpJob:
67
75
  self.noupload = noupload
68
76
  self.noisolation = noisolation
69
77
  self.context = context
78
+ self.n_subjobs = n_subjobs
70
79
 
71
80
  if dryrun:
72
81
  self.job_id = "dryrun"
73
82
  if self.job_id is None:
74
83
  # Only create a new job if job_id is not specified
75
84
  data = {
85
+ "project_id": self.project_id,
76
86
  "start": round(self.start),
77
87
  "status": "in progress",
78
88
  "params": json.dumps(self.params),
79
89
  "project": self.project_id,
80
90
  "jobname": self.jobname,
81
91
  "timeout": self.params.get("timeout", 5) * 60,
92
+ "n_subjobs": self.n_subjobs,
82
93
  }
83
94
  if context is not None:
84
95
  data["message"] = context["description"]
85
96
  data["commit"] = context["commit"]
86
97
  data["ref"] = context["ref"]
87
- response = requests.post(
88
- f"{api_conf.api_url}/{self.project_id}/job",
89
- json=data,
90
- headers=api_conf.headers,
91
- )
98
+ response = self.api_conf.create("job", data)
92
99
  if response.status_code != 200:
93
100
  if response.status_code == 403:
94
101
  msg = response.json()["message"]
@@ -102,7 +109,7 @@ class WarpJob:
102
109
  )
103
110
  )
104
111
  logger.warning(response.text)
105
- raise AuthenticationError(str(response.status_code))
112
+ raise ArtefactsAPIError(str(response.status_code))
106
113
  self.job_id = response.json()["job_id"]
107
114
  self.output_path = self.params.get("output_path", f"/tmp/{self.job_id}")
108
115
  os.makedirs(self.output_path, exist_ok=True)
@@ -111,24 +118,21 @@ class WarpJob:
111
118
  def log_tests_result(self, success):
112
119
  self.success = success
113
120
 
114
- def stop(self):
121
+ def update(self, last_run_success: bool) -> bool:
115
122
  end = datetime.now(timezone.utc).timestamp()
116
123
  if self.dryrun:
117
- return
124
+ return True
118
125
  # Log metadata
119
126
  data = {
127
+ "project_id": self.project_id,
120
128
  "end": round(end),
121
129
  "duration": round(end - self.start),
122
- "success": self.success, # need to be determined based on all runs, can be an AND in the API
130
+ "success": last_run_success,
123
131
  "status": "finished", # need to be determined based on all runs
124
132
  }
125
- requests.put(
126
- f"{self.api_conf.api_url}/{self.project_id}/job/{self.job_id}",
127
- json=data,
128
- headers=self.api_conf.headers,
129
- )
133
+ response = self.api_conf.update("job", self.job_id, data)
130
134
 
131
- return
135
+ return response.status_code == 200
132
136
 
133
137
  def new_run(self, scenario):
134
138
  run = WarpRun(self, scenario["name"], scenario, self.n_runs)
@@ -153,6 +157,7 @@ class WarpRun:
153
157
  self.logger = logger
154
158
  os.makedirs(self.output_path, exist_ok=True)
155
159
  data = {
160
+ "project_id": self.job.project_id,
156
161
  "job_id": job.job_id,
157
162
  "run_n": self.run_n,
158
163
  "start": round(self.start),
@@ -163,14 +168,8 @@ class WarpRun:
163
168
 
164
169
  if self.job.dryrun:
165
170
  return
166
- query_url = (
167
- f"{self.job.api_conf.api_url}/{self.job.project_id}/job/{job.job_id}/run"
168
- )
169
- response = requests.post(
170
- query_url,
171
- json=data,
172
- headers=self.job.api_conf.headers,
173
- )
171
+
172
+ response = self.job.api_conf.create("run", data)
174
173
  if response.status_code != 200:
175
174
  if response.status_code == 403:
176
175
  msg = response.json()["message"]
@@ -184,7 +183,7 @@ class WarpRun:
184
183
  )
185
184
  )
186
185
  self.logger.warning(response.text)
187
- raise AuthenticationError(str(response.status_code))
186
+ raise ArtefactsAPIError(str(response.status_code))
188
187
  return
189
188
 
190
189
  def log_params(self, params):
@@ -266,6 +265,7 @@ class WarpRun:
266
265
 
267
266
  # Log metadata
268
267
  data = {
268
+ "project_id": self.job.project_id,
269
269
  "job_id": self.job.job_id,
270
270
  "run_n": self.run_n,
271
271
  "start": math.floor(self.start),
@@ -280,11 +280,7 @@ class WarpRun:
280
280
  if not self.job.noupload:
281
281
  data["uploads"] = self.uploads
282
282
 
283
- response = requests.put(
284
- f"{self.job.api_conf.api_url}/{self.job.project_id}/job/{self.job.job_id}/run/{self.run_n}",
285
- json=data,
286
- headers=self.job.api_conf.headers,
287
- )
283
+ response = self.job.api_conf.update("run", self.run_n, data)
288
284
 
289
285
  # use s3 presigned urls to upload the artifacts
290
286
  if self.job.noupload:
@@ -307,11 +303,8 @@ class WarpRun:
307
303
  )
308
304
  )
309
305
  )
310
- # TODO: add a retry policy
311
- requests.post(
312
- upload_info["url"],
313
- data=upload_info["fields"],
314
- files=files,
306
+ self.job.api_conf.upload(
307
+ upload_info["url"], upload_info["fields"], files
315
308
  )
316
309
  except OverflowError:
317
310
  self.logger.warning(
@@ -342,6 +335,7 @@ def init_job(
342
335
  noisolation: bool = False,
343
336
  context: Optional[dict] = None,
344
337
  run_offset=0,
338
+ n_subjobs: int = 1,
345
339
  ):
346
340
  return WarpJob(
347
341
  project_id,
@@ -354,6 +348,7 @@ def init_job(
354
348
  noisolation,
355
349
  context,
356
350
  run_offset,
351
+ n_subjobs,
357
352
  )
358
353
 
359
354
 
@@ -20,6 +20,7 @@ from artefacts.cli import (
20
20
  init_job,
21
21
  generate_scenarios,
22
22
  localise,
23
+ logger,
23
24
  AuthenticationError,
24
25
  __version__,
25
26
  )
@@ -32,6 +33,7 @@ from artefacts.cli.constants import (
32
33
  )
33
34
  from artefacts.cli.helpers import (
34
35
  add_key_to_conf,
36
+ endpoint_exists,
35
37
  get_conf_from_file,
36
38
  get_artefacts_api_url,
37
39
  get_git_revision_branch,
@@ -64,39 +66,70 @@ def add(project_name):
64
66
  profile = config[project_name]
65
67
  else:
66
68
  profile = {}
69
+
67
70
  api_url = get_artefacts_api_url(profile)
68
71
  dashboard_url = api_url.split("/api")[0]
72
+
69
73
  settings_page_url = f"{dashboard_url}/{project_name}/settings"
70
- # Check if running on WSL
71
- if "WSLENV" in os.environ:
72
- os.system(f'cmd.exe /C start "" {settings_page_url} 2>/dev/null')
73
- else:
74
- webbrowser.open(settings_page_url)
75
74
  click.echo(
76
75
  localise("Opening the project settings page: {url}").format(
77
76
  url=settings_page_url
78
77
  )
79
78
  )
80
- api_key = click.prompt(
81
- localise("Please enter your API KEY for {project}").format(
82
- project=project_name
83
- ),
84
- type=str,
85
- hide_input=True,
86
- )
87
- add_key_to_conf(project_name, api_key)
88
- click.echo(localise("API KEY saved for {project}").format(project=project_name))
89
- if click.confirm(
90
- localise(
91
- "Would you like to download the generated artefacts.yaml file? This will overwrite any existing config file in the current directory."
79
+
80
+ if endpoint_exists(settings_page_url):
81
+ # Check if running on WSL
82
+ if "WSLENV" in os.environ:
83
+ os.system(f'cmd.exe /C start "" {settings_page_url} 2>/dev/null')
84
+ else:
85
+ webbrowser.open(settings_page_url)
86
+
87
+ api_key = click.prompt(
88
+ localise("Please enter your API KEY for {project}").format(
89
+ project=project_name
90
+ ),
91
+ type=str,
92
+ hide_input=True,
93
+ )
94
+ add_key_to_conf(project_name, api_key)
95
+ click.echo(localise("API KEY saved for {project}").format(project=project_name))
96
+ if click.confirm(
97
+ localise(
98
+ "Would you like to download a pregenerated artefacts.yaml file? This will overwrite any existing config file in the current directory."
99
+ )
100
+ ):
101
+ config_file_name = "artefacts.yaml"
102
+ config_file_url = f"{api_url}/{project_name}/{config_file_name}"
103
+ # Get credentials only now, as API key set just before.
104
+ api_conf = APIConf(project_name, __version__)
105
+ config_response = api_conf.read("url", config_file_url)
106
+ if config_response.status_code == 200:
107
+ with open(config_file_name, "wb") as f:
108
+ f.write(config_response.content)
109
+ else:
110
+ click.echo(
111
+ localise(
112
+ "We encountered a problem in getting the generated configuration file. Please consider downloading it from the project page on the dashboard at {url}. Sorry for the inconvenience."
113
+ ).format(url=settings_page_url)
114
+ )
115
+ logger.debug(
116
+ localise(
117
+ "If you are using an alternative server, please also consider checking the value of ARTEFACTS_API_URL in your environment."
118
+ )
119
+ )
120
+ else:
121
+ click.echo(
122
+ localise(
123
+ "Our apologies: The project page does not seem available at the moment. If `{project_name}` is correct, please try again later.".format(
124
+ project_name=project_name
125
+ )
126
+ )
127
+ )
128
+ click.echo(
129
+ localise(
130
+ "If you are using an alternative server, please also consider checking the value of ARTEFACTS_API_URL in your environment."
131
+ )
92
132
  )
93
- ):
94
- api_conf = APIConf(project_name, __version__)
95
- config_file_name = "artefacts.yaml"
96
- config_file_url = f"{api_url}/{project_name}/{config_file_name}"
97
- r = requests.get(config_file_url, headers=api_conf.headers)
98
- with open(config_file_name, "wb") as f:
99
- f.write(r.content)
100
133
  return
101
134
 
102
135
 
@@ -118,10 +151,7 @@ def delete(project_name):
118
151
  def hello(project_name):
119
152
  """Show message to confirm credentials allow access to PROJECT_NAME"""
120
153
  api_conf = APIConf(project_name, __version__)
121
- response = requests.get(
122
- f"{api_conf.api_url}/{project_name}/info",
123
- headers=api_conf.headers,
124
- )
154
+ response = api_conf.read("url", f"{api_conf.api_url}/{project_name}/info")
125
155
  if response.status_code == 200:
126
156
  result = response.json()
127
157
  click.echo(
@@ -360,6 +390,7 @@ def run(
360
390
  noisolation,
361
391
  context,
362
392
  first,
393
+ len(scenarios),
363
394
  )
364
395
  except AuthenticationError:
365
396
  click.secho(
@@ -412,6 +443,8 @@ def run(
412
443
  )
413
444
  run.log_tests_results([result], False)
414
445
  run.stop()
446
+ warpjob.update(last_run_success=False)
447
+ continue
415
448
  if dryrun:
416
449
  click.echo(f"[{jobname}] " + localise("Performing dry run"))
417
450
  results, success = {}, True
@@ -419,7 +452,8 @@ def run(
419
452
  try:
420
453
  results, success = run_ros2_tests(run)
421
454
  except Exception as e:
422
- warpjob.stop()
455
+ run.stop()
456
+ warpjob.update(last_run_success=False)
423
457
  warpjob.log_tests_result(False)
424
458
  click.secho(e, bold=True, err=True)
425
459
  click.secho(
@@ -428,10 +462,18 @@ def run(
428
462
  err=True,
429
463
  bold=True,
430
464
  )
431
- raise click.Abort()
465
+ continue
432
466
  if success is None:
467
+ result = get_TestSuite_error_result(
468
+ scenario["name"],
469
+ localise("ROS2 environment error"),
470
+ localise(
471
+ "Not able to execute tests. Make sure that ROS2 is sourced and that your launch file syntax is correct."
472
+ ),
473
+ )
474
+ run.log_tests_results([result], False)
433
475
  run.stop()
434
- warpjob.stop()
476
+ warpjob.update(last_run_success=False)
435
477
  warpjob.log_tests_result(job_success)
436
478
  click.secho(
437
479
  f"[{jobname}] "
@@ -441,7 +483,7 @@ def run(
441
483
  err=True,
442
484
  bold=True,
443
485
  )
444
- raise click.Abort()
486
+ continue
445
487
  if not success:
446
488
  job_success = False
447
489
  elif framework is not None and framework.startswith("ros1:"):
@@ -454,7 +496,17 @@ def run(
454
496
  err=True,
455
497
  bold=True,
456
498
  )
457
- raise click.Abort()
499
+ result = get_TestSuite_error_result(
500
+ scenario["name"],
501
+ localise("launch_test file not specified error"),
502
+ localise(
503
+ "Please specify a `ros_testfile` in the artefacts.yaml scenario configuration."
504
+ ),
505
+ )
506
+ run.log_tests_results([result], False)
507
+ run.stop()
508
+ warpjob.update(last_run_success=False)
509
+ continue
458
510
  if dryrun:
459
511
  click.echo(f"[{jobname}] " + localise("Performing dry run"))
460
512
  results, success = {}, True
@@ -472,7 +524,10 @@ def run(
472
524
  err=True,
473
525
  bold=True,
474
526
  )
475
- raise click.Abort()
527
+ run.stop()
528
+ warpjob.update(last_run_success=False)
529
+ warpjob.log_tests_result(False)
530
+ continue
476
531
  if dryrun:
477
532
  click.echo(f"[{jobname}] " + localise("Performing dry run"))
478
533
  results, success = {}, True
@@ -487,12 +542,11 @@ def run(
487
542
  add_output_from_default(run)
488
543
 
489
544
  run.stop()
490
- warpjob.log_tests_result(job_success)
545
+ warpjob.log_tests_result(job_success)
546
+ warpjob.update(last_run_success=run.success)
491
547
  click.echo(f"[{jobname}] " + localise("Done"))
492
548
  time.sleep(random.random() * 1)
493
549
 
494
- warpjob.stop()
495
-
496
550
 
497
551
  @click.command()
498
552
  @click.option(
@@ -590,9 +644,10 @@ def run_remote(config, description, jobname, skip_validation=False):
590
644
  temp_file.seek(0)
591
645
 
592
646
  # Request signed upload URLs
593
- upload_urls_response = requests.put(
594
- f"{api_conf.api_url}/{project_id}/upload_source",
595
- headers=api_conf.headers,
647
+ # TODO Horrible approach, but the endpoint is neither CRUD nor REST, etc.
648
+ # So the idea to remain horrible and change this soon.
649
+ upload_urls_response = api_conf.direct("put")(
650
+ f"{api_conf.api_url}/{project_id}/upload_source"
596
651
  )
597
652
 
598
653
  if not upload_urls_response.ok:
@@ -694,10 +749,10 @@ def run_remote(config, description, jobname, skip_validation=False):
694
749
  item_show_func=lambda x: x and x[0],
695
750
  ) as bar:
696
751
  for filename, file in bar:
697
- response = requests.post(
752
+ response = api_conf.upload(
698
753
  upload_urls[filename]["url"],
699
- data=upload_urls[filename]["fields"],
700
- files={"file": file},
754
+ upload_urls[filename]["fields"],
755
+ {"file": file},
701
756
  )
702
757
  if not response.ok:
703
758
  raise click.ClickException(