artefacts-cli 0.6.17__tar.gz → 0.6.19__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 (66) hide show
  1. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/PKG-INFO +10 -11
  2. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/__init__.py +39 -31
  3. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/app.py +13 -7
  4. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/app_containers.py +4 -3
  5. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/containers/__init__.py +14 -14
  6. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/containers/docker.py +30 -19
  7. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/containers/utils.py +8 -4
  8. artefacts_cli-0.6.19/artefacts/cli/errors.py +1 -0
  9. artefacts_cli-0.6.19/artefacts/cli/logger.py +10 -0
  10. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/parameters.py +1 -1
  11. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/ros1.py +7 -7
  12. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/ros2.py +2 -2
  13. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/utils.py +1 -1
  14. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/utils_ros.py +1 -1
  15. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/version.py +2 -2
  16. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts.yaml +5 -4
  17. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts_cli.egg-info/PKG-INFO +10 -11
  18. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts_cli.egg-info/SOURCES.txt +5 -0
  19. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts_cli.egg-info/requires.txt +8 -9
  20. artefacts_cli-0.6.19/infra-tests/test_run_remote.yaml +25 -0
  21. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/TestTurtle.py +3 -4
  22. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_odom.py +1 -1
  23. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_post_process.py +10 -9
  24. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim2/launch_turtle.py +1 -1
  25. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim2/sample_node.py +2 -2
  26. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/pyproject.toml +9 -9
  27. artefacts_cli-0.6.19/tests/cli/containers/__init__.py +0 -0
  28. artefacts_cli-0.6.19/tests/cli/containers/test_utils.py +13 -0
  29. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/test_app_containers.py +0 -1
  30. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/test_ros1.py +0 -1
  31. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/test_ros2.py +3 -2
  32. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/conftest.py +0 -1
  33. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/utils/docker_mock.py +0 -2
  34. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/README.md +0 -0
  35. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/README_INTERNAL.md +0 -0
  36. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/bagparser.py +0 -0
  37. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/constants.py +0 -0
  38. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/cli/other.py +0 -0
  39. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts/wrappers/artefacts_ros1_meta.launch +0 -0
  40. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts_cli.egg-info/dependency_links.txt +0 -0
  41. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts_cli.egg-info/entry_points.txt +0 -0
  42. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/artefacts_cli.egg-info/top_level.txt +0 -0
  43. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/bin/release +0 -0
  44. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/CMakeLists.txt +0 -0
  45. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/test_meta.launch +0 -0
  46. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/test_turtle.launch +0 -0
  47. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/turtle_odometry.launch +0 -0
  48. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/package.xml +0 -0
  49. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/setup.py +0 -0
  50. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/__init__.py +0 -0
  51. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_trajectory.py +0 -0
  52. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/test/viz_turtle_odom.xml +0 -0
  53. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/pytest.ini +0 -0
  54. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/setup.cfg +0 -0
  55. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/__init__.py +0 -0
  56. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/__init__.py +0 -0
  57. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/test_cli.py +0 -0
  58. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/test_config_validation.py +0 -0
  59. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/test_other.py +0 -0
  60. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/test_parameters.py +0 -0
  61. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/cli/test_warp.py +0 -0
  62. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/fixtures/artefacts_deprecated.yaml +0 -0
  63. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/fixtures/artefacts_ros1.yaml +0 -0
  64. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/fixtures/warp-env-param.yaml +0 -0
  65. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/fixtures/warp.yaml +0 -0
  66. {artefacts_cli-0.6.17 → artefacts_cli-0.6.19}/tests/test_config_validation.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: artefacts_cli
3
- Version: 0.6.17
3
+ Version: 0.6.19
4
4
  Author-email: FD <fabian@artefacts.com>, AGC <alejandro@artefacts.com>, TN <tomo@artefacts.com>, EP <eric@artefacts.com>
5
5
  Project-URL: Homepage, https://github.com/art-e-fact/artefacts-client
6
6
  Project-URL: Bug Tracker, https://github.com/art-e-fact/artefacts-client/issues
@@ -17,26 +17,25 @@ Requires-Dist: mcap
17
17
  Requires-Dist: mcap-ros2-support
18
18
  Requires-Dist: PyYAML>=6.0
19
19
  Requires-Dist: requests>=2.27.1
20
- Requires-Dist: setuptools-scm
21
- Requires-Dist: setuptools>=74
22
20
  Provides-Extra: dev
23
21
  Requires-Dist: awscli; extra == "dev"
24
22
  Requires-Dist: build; extra == "dev"
25
23
  Requires-Dist: docker; extra == "dev"
26
24
  Requires-Dist: lark; extra == "dev"
27
- Requires-Dist: pyre-check; extra == "dev"
28
- Requires-Dist: pytest; extra == "dev"
29
- Requires-Dist: pytest-cov; extra == "dev"
30
- Requires-Dist: pytest-env; extra == "dev"
31
- Requires-Dist: pytest-mock; extra == "dev"
32
- Requires-Dist: ruff; extra == "dev"
33
- Requires-Dist: twine; extra == "dev"
34
25
  Requires-Dist: mkdocs-click==0.8.0; extra == "dev"
35
26
  Requires-Dist: mkdocs-material==8.5.6; extra == "dev"
36
27
  Requires-Dist: mkdocs-mermaid2-plugin==0.6.0; extra == "dev"
37
28
  Requires-Dist: mkdocs==1.4.2; extra == "dev"
29
+ Requires-Dist: numpy; extra == "dev"
38
30
  Requires-Dist: pre-commit; extra == "dev"
31
+ Requires-Dist: pyre-check; extra == "dev"
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: pytest-env; extra == "dev"
35
+ Requires-Dist: pytest-mock; extra == "dev"
39
36
  Requires-Dist: python-markdown-math; extra == "dev"
37
+ Requires-Dist: ruff>=0.9.2; extra == "dev"
38
+ Requires-Dist: twine; extra == "dev"
40
39
 
41
40
  # Artefacts CLI
42
41
 
@@ -2,7 +2,6 @@ from importlib.metadata import version, PackageNotFoundError
2
2
  import json
3
3
  import glob
4
4
  from datetime import datetime, timezone
5
- import logging
6
5
  import os
7
6
  import math
8
7
  import requests
@@ -10,8 +9,7 @@ import copy
10
9
  from typing import Optional
11
10
 
12
11
  from .parameters import iter_grid
13
-
14
- logging.basicConfig(level=logging.INFO)
12
+ from .logger import logger
15
13
 
16
14
 
17
15
  try:
@@ -23,7 +21,7 @@ except PackageNotFoundError:
23
21
 
24
22
  __version__ = get_version()
25
23
  except Exception as e:
26
- logging.warning(f"Could not determine package version: {e}. Default to 0.0.0")
24
+ logger.warning(f"Could not determine package version: {e}. Default to 0.0.0")
27
25
  __version__ = "0.0.0"
28
26
 
29
27
 
@@ -42,6 +40,7 @@ class WarpJob:
42
40
  jobconf,
43
41
  dryrun=False,
44
42
  nosim=False,
43
+ noupload=False,
45
44
  noisolation=False,
46
45
  context=None,
47
46
  run_offset=0,
@@ -57,6 +56,7 @@ class WarpJob:
57
56
  self.n_runs = run_offset
58
57
  self.dryrun = dryrun
59
58
  self.nosim = nosim
59
+ self.noupload = noupload
60
60
  self.noisolation = noisolation
61
61
  self.context = context
62
62
 
@@ -84,10 +84,10 @@ class WarpJob:
84
84
  if response.status_code != 200:
85
85
  if response.status_code == 403:
86
86
  msg = response.json()["message"]
87
- logging.warning(msg)
87
+ logger.warning(msg)
88
88
  raise AuthenticationError(msg)
89
- logging.warning(f"Error on job creation: {response.status_code}")
90
- logging.warning(response.text)
89
+ logger.warning(f"Error on job creation: {response.status_code}")
90
+ logger.warning(response.text)
91
91
  raise AuthenticationError(str(response.status_code))
92
92
  self.job_id = response.json()["job_id"]
93
93
  self.output_path = self.params.get("output_path", f"/tmp/{self.job_id}")
@@ -108,7 +108,7 @@ class WarpJob:
108
108
  "success": self.success, # need to be determined based on all runs, can be an AND in the API
109
109
  "status": "finished", # need to be determined based on all runs
110
110
  }
111
- response = requests.put(
111
+ requests.put(
112
112
  f"{self.api_conf.api_url}/{self.project_id}/job/{self.job_id}",
113
113
  json=data,
114
114
  headers=self.api_conf.headers,
@@ -155,10 +155,10 @@ class WarpRun:
155
155
  if response.status_code != 200:
156
156
  if response.status_code == 403:
157
157
  msg = response.json()["message"]
158
- logging.warning(msg)
158
+ logger.warning(msg)
159
159
  raise AuthenticationError(msg)
160
- logging.warning(f"Error on scenario creation: {response.status_code}")
161
- logging.warning(response.text)
160
+ logger.warning(f"Error on scenario creation: {response.status_code}")
161
+ logger.warning(response.text)
162
162
  raise AuthenticationError(str(response.status_code))
163
163
  return
164
164
 
@@ -170,7 +170,7 @@ class WarpRun:
170
170
 
171
171
  def log_metrics(self):
172
172
  metrics = self.params.get("metrics", None)
173
- if type(metrics) == str:
173
+ if type(metrics) is str:
174
174
  with open(f"{self.output_path}/{metrics}") as f:
175
175
  metric_values = json.load(f)
176
176
  for k, v in metric_values.items():
@@ -182,7 +182,8 @@ class WarpRun:
182
182
  metric_values = json.load(f)
183
183
  for k, v in metric_values.items():
184
184
  self.log_metric(k, v)
185
- except: # if the metrics.json file does not exist, do nothing
185
+ except FileNotFoundError:
186
+ # if the metrics.json file does not exist, do nothing
186
187
  pass
187
188
 
188
189
  def log_tests_results(self, test_results, success):
@@ -225,7 +226,7 @@ class WarpRun:
225
226
  """log a single file filename"""
226
227
 
227
228
  def _get_filename(path):
228
- return path.split(f"/")[-1]
229
+ return path.split("/")[-1]
229
230
 
230
231
  if prefix is not None:
231
232
  self.uploads.update({f"{prefix}/{_get_filename(filename)}": filename})
@@ -255,23 +256,28 @@ class WarpRun:
255
256
  headers=self.job.api_conf.headers,
256
257
  )
257
258
  # use s3 presigned urls to upload the artifacts
258
- upload_urls = response.json()["upload_urls"]
259
- for key, file_name in self.uploads.items():
260
- files = {"file": open(file_name, "rb")}
261
- upload_info = upload_urls[key]
262
- file_size_mb = os.path.getsize(file_name) / 1024 / 1024
263
- try:
264
- print(f"Uploading {file_name} ({file_size_mb:.2f} MB)")
265
- # TODO: add a retry policy
266
- r = requests.post(
267
- upload_info["url"],
268
- data=upload_info["fields"],
269
- files=files,
270
- )
271
- except OverflowError:
272
- logging.warning(f"File too large: {file_name} could not be uploaded")
273
- except Exception as e:
274
- logging.warning(f"Error uploading {file_name}: {e}, skipping")
259
+ if self.job.noupload:
260
+ print(
261
+ "noupload: job artifacts are not uploaded to cloud, including the ones specified in output_dirs"
262
+ )
263
+ else:
264
+ upload_urls = response.json()["upload_urls"]
265
+ for key, file_name in self.uploads.items():
266
+ files = {"file": open(file_name, "rb")}
267
+ upload_info = upload_urls[key]
268
+ file_size_mb = os.path.getsize(file_name) / 1024 / 1024
269
+ try:
270
+ print(f"Uploading {file_name} ({file_size_mb:.2f} MB)")
271
+ # TODO: add a retry policy
272
+ requests.post(
273
+ upload_info["url"],
274
+ data=upload_info["fields"],
275
+ files=files,
276
+ )
277
+ except OverflowError:
278
+ logger.warning(f"File too large: {file_name} could not be uploaded")
279
+ except Exception as e:
280
+ logger.warning(f"Error uploading {file_name}: {e}, skipping")
275
281
 
276
282
 
277
283
  def init_job(
@@ -281,6 +287,7 @@ def init_job(
281
287
  jobconf: dict,
282
288
  dryrun: bool = False,
283
289
  nosim: bool = False,
290
+ noupload: bool = False,
284
291
  noisolation: bool = False,
285
292
  context: Optional[dict] = None,
286
293
  run_offset=0,
@@ -292,6 +299,7 @@ def init_job(
292
299
  jobconf,
293
300
  dryrun,
294
301
  nosim,
302
+ noupload,
295
303
  noisolation,
296
304
  context,
297
305
  run_offset,
@@ -5,7 +5,6 @@ import os
5
5
  import platform
6
6
  import random
7
7
  import subprocess
8
- import sys
9
8
  import tarfile
10
9
  import tempfile
11
10
  import time
@@ -22,7 +21,6 @@ from artefacts.cli import init_job, generate_scenarios, AuthenticationError, __v
22
21
  from artefacts.cli import app_containers as containers
23
22
  from artefacts.cli.constants import DEPRECATED_FRAMEWORKS, SUPPORTED_FRAMEWORKS
24
23
  from artefacts.cli.utils import read_config, config_validation
25
- import artefacts_copava as copava
26
24
 
27
25
  HOME = os.path.expanduser("~")
28
26
  CONFIG_DIR = f"{HOME}/.artefacts"
@@ -213,6 +211,12 @@ def hello(project_name):
213
211
  default=False,
214
212
  help="nosim: no simulator resource provided by Artefacts",
215
213
  )
214
+ @click.option(
215
+ "--noupload",
216
+ is_flag=True,
217
+ default=False,
218
+ help="noupload: rosbags are not uploaded to cloud",
219
+ )
216
220
  @click.option(
217
221
  "--noisolation",
218
222
  is_flag=True,
@@ -262,6 +266,7 @@ def run(
262
266
  jobname,
263
267
  dryrun,
264
268
  nosim,
269
+ noupload,
265
270
  noisolation,
266
271
  description="",
267
272
  skip_validation=False,
@@ -337,6 +342,7 @@ def run(
337
342
  jobconf,
338
343
  dryrun,
339
344
  nosim,
345
+ noupload,
340
346
  noisolation,
341
347
  context,
342
348
  first,
@@ -349,7 +355,7 @@ def run(
349
355
  job_success = True
350
356
  for scenario_n, scenario in enumerate(scenarios):
351
357
  click.echo(
352
- f"Starting scenario {scenario_n+1}/{len(scenarios)}: {scenario['name']}"
358
+ f"Starting scenario {scenario_n + 1}/{len(scenarios)}: {scenario['name']}"
353
359
  )
354
360
  try:
355
361
  run = warpjob.new_run(scenario)
@@ -410,7 +416,7 @@ def run(
410
416
  results, success = run_other_tests(run)
411
417
  if not success:
412
418
  job_success = False
413
- if type(run.params.get("metrics", [])) == str:
419
+ if type(run.params.get("metrics", [])) is str:
414
420
  run.log_metrics()
415
421
 
416
422
  run.stop()
@@ -480,7 +486,7 @@ def run_remote(config, description, jobname, skip_validation=False):
480
486
  if "on" in run_config:
481
487
  del run_config["on"]
482
488
 
483
- click.echo(f"Packaging source...")
489
+ click.echo("Packaging source...")
484
490
 
485
491
  with tempfile.NamedTemporaryFile(
486
492
  prefix=project_id.split("/")[-1], suffix=".tgz", delete=True
@@ -490,7 +496,7 @@ def run_remote(config, description, jobname, skip_validation=False):
490
496
  try:
491
497
  ignore_matches = parse_gitignore(ignore_file)
492
498
  except FileNotFoundError:
493
- ignore_matches = lambda x: False
499
+ ignore_matches = lambda x: False # noqa: E731
494
500
  with tarfile.open(fileobj=temp_file, mode="w:gz") as tar_file:
495
501
  for root, dirs, files in os.walk(project_folder):
496
502
  for file in files:
@@ -604,7 +610,7 @@ def run_remote(config, description, jobname, skip_validation=False):
604
610
  )
605
611
 
606
612
  click.echo(
607
- f"Uploading complete! The new job will show up shortly at {dashboard_url}/tests"
613
+ f"Uploading complete! The new job will show up shortly at {dashboard_url}"
608
614
  )
609
615
 
610
616
 
@@ -85,14 +85,15 @@ def run(ctx: click.Context, image: str, jobname: str, config: str, with_gui: boo
85
85
  image=image,
86
86
  project=project,
87
87
  jobname=jobname,
88
- # Hidden setting primarily useful to Artefacts developers
89
- api_url=os.environ.get("ARTEFACTS_API_URL", DEFAULT_API_URL),
90
88
  with_gui=with_gui,
89
+ # Hidden settings primarily useful to Artefacts developers
90
+ api_url=os.environ.get("ARTEFACTS_API_URL", DEFAULT_API_URL),
91
+ api_key=os.environ.get("ARTEFACTS_KEY", None),
91
92
  )
92
93
  container, logs = handler.run(**params)
93
94
  if container:
94
95
  print(f"Package run complete: Container Id for inspection: {container['Id']}")
95
96
  else:
96
- print(f"Package run failed:")
97
+ print("Package run failed:")
97
98
  for entry in logs:
98
99
  print("\t- " + entry)
@@ -2,7 +2,7 @@ from collections.abc import Generator
2
2
  import configparser
3
3
  import os
4
4
  from pathlib import Path
5
- from typing import Any, Tuple, Union
5
+ from typing import Any, Optional, Tuple, Union
6
6
 
7
7
  from artefacts.cli.constants import DEFAULT_API_URL
8
8
 
@@ -13,13 +13,13 @@ class CMgr:
13
13
  Returns the build image ID (e.g. sha256:abcdefghi)
14
14
  and an iterator over the build log entries.
15
15
  """
16
- raise NotImplemented()
16
+ raise NotImplementedError()
17
17
 
18
18
  def check(self, image: str) -> bool:
19
19
  """
20
20
  Checks whether a target image exists locally.
21
21
  """
22
- raise NotImplemented()
22
+ raise NotImplementedError()
23
23
 
24
24
  def run(
25
25
  self,
@@ -34,19 +34,19 @@ class CMgr:
34
34
  Returns a container (Any type as depends on the framework)
35
35
  and an iterator over the container log entries.
36
36
  """
37
- raise NotImplemented()
37
+ raise NotImplementedError()
38
38
 
39
- def _valid_artefacts_api_key(
39
+ def _get_artefacts_api_key(
40
40
  self, project: str, path: Union[str, Path] = Path("~/.artefacts").expanduser()
41
- ) -> bool:
41
+ ) -> Optional[str]:
42
42
  """
43
- Check if a valid API key is available to embed in containers.
43
+ Get any valid API key to embed in containers.
44
44
 
45
- 1. Check overrides with the ARTEFACTS_KEY environment variable.
46
- 2. If `path` is not given, check the default .artefacts folder for the config file.
47
- 3. If `path` is given, check the file directly is a file, or check for a `config` file if a folder.
45
+ 1. Checks first from the ARTEFACTS_KEY environment variable.
46
+ 2. If `path` is not given, check from the default configuraiton file in the .artefacts folder.
47
+ 3. If `path` is given, check the file directly if a file, or check for a `config` file if a folder.
48
48
 
49
- When a config file is found, we check here if the API key for the `project` is available.
49
+ When a config file is found, we get the API key for the `project`.
50
50
 
51
51
  `path` set to None is an error, and aborts execution.
52
52
  """
@@ -55,7 +55,7 @@ class CMgr:
55
55
  "`path` must be a string, a Path object, or excluded from the kwargs"
56
56
  )
57
57
  if os.environ.get("ARTEFACTS_KEY", None):
58
- return True
58
+ return os.environ["ARTEFACTS_KEY"]
59
59
  path = Path(path) # Ensure we have a Path object
60
60
  config = configparser.ConfigParser()
61
61
  if path.is_dir():
@@ -63,6 +63,6 @@ class CMgr:
63
63
  else:
64
64
  config.read(path)
65
65
  try:
66
- return config[project].get("apikey") != None
66
+ return config[project].get("apikey")
67
67
  except KeyError:
68
- return False
68
+ return None
@@ -12,8 +12,8 @@ from artefacts.cli.utils import ensure_available
12
12
 
13
13
  ensure_available("docker")
14
14
 
15
- import docker
16
- from docker import APIClient
15
+ import docker # noqa: E402
16
+ from docker import APIClient # noqa: E402
17
17
 
18
18
 
19
19
  class DockerManager(CMgr):
@@ -53,21 +53,35 @@ class DockerManager(CMgr):
53
53
  jobname: str = None,
54
54
  artefacts_dir: str = Path("~/.artefacts").expanduser(),
55
55
  api_url: str = DEFAULT_API_URL,
56
+ api_key: str = None,
56
57
  with_gui: bool = False,
57
58
  ) -> Tuple[Any, Generator]:
58
- if not self._valid_artefacts_api_key(project, artefacts_dir):
59
+ """
60
+ Run an application as an Artefacts-enabled container in a Docker engine
61
+
62
+ The arguments are considered straightforward, except the different
63
+ priorities between `artefacts_dir` and `api_key`:
64
+ * `api_key` has the highest priority. When specified, `artefacts_dir`
65
+ is ignored. The container will rely on the key as an environment
66
+ variable (ARTEFACTS_KEY).
67
+ * Whenever `api_key` is not provided, the container gets `artefacts_dir`
68
+ mounted as volume. The directory must contain a valid configuration
69
+ with the project's key.
70
+ """
71
+ env = {
72
+ "JOB_ID": str(uuid4()),
73
+ "ARTEFACTS_JOB_NAME": jobname,
74
+ "ARTEFACTS_API_URL": api_url,
75
+ }
76
+
77
+ env["ARTEFACTS_KEY"] = self._get_artefacts_api_key(project, artefacts_dir)
78
+ if env["ARTEFACTS_KEY"] is None:
59
79
  return None, iter(
60
80
  [
61
- "Missing API key for the project. Does `~/.artefacts/config` exist and contain your key?"
81
+ f"Missing API key for the project. Does `{artefacts_dir}/config` exist and contain your key? Alternatively ARTEFACTS_KEY can be set with the key."
62
82
  ]
63
83
  )
64
84
  try:
65
- env = {
66
- "JOB_ID": str(uuid4()),
67
- "ARTEFACTS_JOB_NAME": jobname,
68
- "ARTEFACTS_API_URL": api_url,
69
- }
70
-
71
85
  if platform.system() in ["Darwin", "Windows"]:
72
86
  # Assume we run in Docker Desktop
73
87
  env["DISPLAY"] = "host.docker.internal:0"
@@ -77,24 +91,21 @@ class DockerManager(CMgr):
77
91
  if not with_gui:
78
92
  env["QT_QPA_PLATFORM"] = "offscreen"
79
93
 
80
- container = self.client.create_container(
81
- image,
94
+ container_conf = dict(
95
+ image=image,
82
96
  environment=env,
83
97
  detach=False,
84
- volumes=["/root/.artefacts"],
85
98
  host_config=self.client.create_host_config(
86
- binds={
87
- artefacts_dir: {
88
- "bind": "/root/.artefacts",
89
- "mode": "ro",
90
- },
91
- },
92
99
  network_mode="host",
93
100
  ),
94
101
  )
102
+
103
+ container = self.client.create_container(**container_conf)
95
104
  self.client.start(container=container.get("Id"))
105
+
96
106
  for entry in self.client.logs(container=container.get("Id"), stream=True):
97
107
  print(entry.decode("utf-8").strip())
108
+
98
109
  return container, iter([])
99
110
  except docker.errors.ImageNotFound:
100
111
  return None, iter(
@@ -1,8 +1,11 @@
1
+ import sys
2
+
1
3
  from collections.abc import Generator
2
- import logging
3
4
  from typing import Any, Tuple
4
5
 
6
+ from artefacts.cli import errors
5
7
  from artefacts.cli.containers import CMgr
8
+ from artefacts.cli.logger import logger
6
9
 
7
10
 
8
11
  class ContainerMgr:
@@ -12,12 +15,13 @@ class ContainerMgr:
12
15
  }
13
16
 
14
17
  def __init__(self):
15
- self.logger = logging.getLogger(__name__)
18
+ self.logger = logger
16
19
  self.mgr = self._configure()
17
20
  if self.mgr is None:
18
- raise Exception(
19
- f"Failed to find supported container stack. Please install and start one in {list(self.SUPPORTED_PRIORITISED_ENGINES.values())}, with default settings (custom sockets not supported at this stage)"
21
+ self.logger.error(
22
+ f"Failed to find supported container stack. Please install and start one of {list(self.SUPPORTED_PRIORITISED_ENGINES.values())}, with default settings (custom sockets not supported at this stage)"
20
23
  )
24
+ sys.exit(errors.CONTAINER_ENGINE_NOT_FOUND)
21
25
 
22
26
  def _configure(self) -> CMgr:
23
27
  manager = None
@@ -0,0 +1 @@
1
+ CONTAINER_ENGINE_NOT_FOUND = 1000
@@ -0,0 +1,10 @@
1
+ import logging
2
+ import sys
3
+
4
+ logger = logging.getLogger()
5
+
6
+ default_handler = logging.StreamHandler(stream=sys.stderr)
7
+ default_handler.setLevel(logging.ERROR)
8
+ default_handler.setFormatter(logging.Formatter())
9
+
10
+ logger.addHandler(default_handler)
@@ -17,7 +17,7 @@ def iter_grid(grid_spec: dict) -> Iterable[dict]:
17
17
  items = sorted(grid_spec.items())
18
18
  keys, values = zip(*items)
19
19
  # Make sure single values are converted to lists
20
- values = [x if type(x) == list else [x] for x in values]
20
+ values = [x if type(x) is list else [x] for x in values]
21
21
  for v in product(*values):
22
22
  params = dict(zip(keys, v))
23
23
  yield params
@@ -50,17 +50,17 @@ def generate_rosbag_args(scenario: dict) -> str:
50
50
  return topics
51
51
  else:
52
52
  logging.warning(
53
- f"[warning in generate_rosbag_args] rosbag_record asks for 'subscriptions' but they are not specified. Falling back to default: no rosbag will be recorded"
53
+ "[warning in generate_rosbag_args] rosbag_record asks for 'subscriptions' but they are not specified. Falling back to default: no rosbag will be recorded"
54
54
  )
55
55
  return "none"
56
56
  else:
57
- assert (
58
- type(rosbag_record) == list
59
- ), f"rosbag_record supports 'all', 'none', 'subscriptions' or a list of strings interpreted as a list of ROS topics, regex supported"
57
+ assert type(rosbag_record) is list, (
58
+ "rosbag_record supports 'all', 'none', 'subscriptions' or a list of strings interpreted as a list of ROS topics, regex supported"
59
+ )
60
60
  for e in rosbag_record:
61
- assert (
62
- type(e) == str
63
- ), f"Elements of the rosbag_record list must only be strings. They are interpreted as a list of ROS topics, regex supported"
61
+ assert type(e) is str, (
62
+ "Elements of the rosbag_record list must only be strings. They are interpreted as a list of ROS topics, regex supported"
63
+ )
64
64
  return f"--regex {' '.join(rosbag_record)}"
65
65
 
66
66
 
@@ -114,9 +114,9 @@ def run_ros2_tests(run):
114
114
  try:
115
115
  last_value = bag.get_last_message(metric)[1].data
116
116
  run.log_metric(metric, last_value)
117
- except KeyError as e:
117
+ except KeyError:
118
118
  print(f"Metric {metric} not found in rosbag, skipping.")
119
- except TypeError or IndexError as e:
119
+ except TypeError or IndexError:
120
120
  print(
121
121
  f"Metric {metric} not found. Is it being published?. Skipping."
122
122
  )
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import subprocess
3
3
  import sys
4
- from typing import Any, Union
4
+ from typing import Union
5
5
 
6
6
  import click
7
7
 
@@ -49,7 +49,7 @@ def parse_tests_results(file):
49
49
 
50
50
  except Exception as e:
51
51
  print(f"[Exception in parse_tests_results] {e}")
52
- print(f"Test result xml could not be loaded, marking success as False")
52
+ print("Test result xml could not be loaded, marking success as False")
53
53
  results = [
54
54
  {
55
55
  "suite": "unittest.suite.TestSuite",
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.6.17'
16
- __version_tuple__ = version_tuple = (0, 6, 17)
15
+ __version__ = version = '0.6.19'
16
+ __version_tuple__ = version_tuple = (0, 6, 19)
@@ -1,6 +1,6 @@
1
1
  version: 0.1.0
2
2
 
3
- project: artefacts-tests/cli-run-remote-testing
3
+ project: artefacts/cli-run-remote-testing
4
4
 
5
5
  on:
6
6
  push:
@@ -43,12 +43,13 @@ jobs:
43
43
  type: test
44
44
  package:
45
45
  custom:
46
- os: public.ecr.aws/artefacts/ros2:galactic-turtlesim-noartefacts
46
+ os: public.ecr.aws/artefacts/ros2:humble-turtlesim-noartefacts
47
47
  commands:
48
- - pip3 install --upgrade setuptools pip && pip3 install ./src
48
+ - cd src
49
+ - pip3 install .
49
50
  runtime:
50
51
  simulator: turtlesim
51
- framework: ros2:galactic
52
+ framework: ros2:humble
52
53
  scenarios:
53
54
  defaults:
54
55
  subscriptions:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: artefacts_cli
3
- Version: 0.6.17
3
+ Version: 0.6.19
4
4
  Author-email: FD <fabian@artefacts.com>, AGC <alejandro@artefacts.com>, TN <tomo@artefacts.com>, EP <eric@artefacts.com>
5
5
  Project-URL: Homepage, https://github.com/art-e-fact/artefacts-client
6
6
  Project-URL: Bug Tracker, https://github.com/art-e-fact/artefacts-client/issues
@@ -17,26 +17,25 @@ Requires-Dist: mcap
17
17
  Requires-Dist: mcap-ros2-support
18
18
  Requires-Dist: PyYAML>=6.0
19
19
  Requires-Dist: requests>=2.27.1
20
- Requires-Dist: setuptools-scm
21
- Requires-Dist: setuptools>=74
22
20
  Provides-Extra: dev
23
21
  Requires-Dist: awscli; extra == "dev"
24
22
  Requires-Dist: build; extra == "dev"
25
23
  Requires-Dist: docker; extra == "dev"
26
24
  Requires-Dist: lark; extra == "dev"
27
- Requires-Dist: pyre-check; extra == "dev"
28
- Requires-Dist: pytest; extra == "dev"
29
- Requires-Dist: pytest-cov; extra == "dev"
30
- Requires-Dist: pytest-env; extra == "dev"
31
- Requires-Dist: pytest-mock; extra == "dev"
32
- Requires-Dist: ruff; extra == "dev"
33
- Requires-Dist: twine; extra == "dev"
34
25
  Requires-Dist: mkdocs-click==0.8.0; extra == "dev"
35
26
  Requires-Dist: mkdocs-material==8.5.6; extra == "dev"
36
27
  Requires-Dist: mkdocs-mermaid2-plugin==0.6.0; extra == "dev"
37
28
  Requires-Dist: mkdocs==1.4.2; extra == "dev"
29
+ Requires-Dist: numpy; extra == "dev"
38
30
  Requires-Dist: pre-commit; extra == "dev"
31
+ Requires-Dist: pyre-check; extra == "dev"
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: pytest-env; extra == "dev"
35
+ Requires-Dist: pytest-mock; extra == "dev"
39
36
  Requires-Dist: python-markdown-math; extra == "dev"
37
+ Requires-Dist: ruff>=0.9.2; extra == "dev"
38
+ Requires-Dist: twine; extra == "dev"
40
39
 
41
40
  # Artefacts CLI
42
41
 
@@ -8,6 +8,8 @@ artefacts/cli/app.py
8
8
  artefacts/cli/app_containers.py
9
9
  artefacts/cli/bagparser.py
10
10
  artefacts/cli/constants.py
11
+ artefacts/cli/errors.py
12
+ artefacts/cli/logger.py
11
13
  artefacts/cli/other.py
12
14
  artefacts/cli/parameters.py
13
15
  artefacts/cli/ros1.py
@@ -26,6 +28,7 @@ artefacts_cli.egg-info/entry_points.txt
26
28
  artefacts_cli.egg-info/requires.txt
27
29
  artefacts_cli.egg-info/top_level.txt
28
30
  bin/release
31
+ infra-tests/test_run_remote.yaml
29
32
  infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/CMakeLists.txt
30
33
  infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/package.xml
31
34
  infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/setup.py
@@ -52,6 +55,8 @@ tests/cli/test_parameters.py
52
55
  tests/cli/test_ros1.py
53
56
  tests/cli/test_ros2.py
54
57
  tests/cli/test_warp.py
58
+ tests/cli/containers/__init__.py
59
+ tests/cli/containers/test_utils.py
55
60
  tests/fixtures/artefacts_deprecated.yaml
56
61
  tests/fixtures/artefacts_ros1.yaml
57
62
  tests/fixtures/warp-env-param.yaml
@@ -6,24 +6,23 @@ mcap
6
6
  mcap-ros2-support
7
7
  PyYAML>=6.0
8
8
  requests>=2.27.1
9
- setuptools-scm
10
- setuptools>=74
11
9
 
12
10
  [dev]
13
11
  awscli
14
12
  build
15
13
  docker
16
14
  lark
17
- pyre-check
18
- pytest
19
- pytest-cov
20
- pytest-env
21
- pytest-mock
22
- ruff
23
- twine
24
15
  mkdocs-click==0.8.0
25
16
  mkdocs-material==8.5.6
26
17
  mkdocs-mermaid2-plugin==0.6.0
27
18
  mkdocs==1.4.2
19
+ numpy
28
20
  pre-commit
21
+ pyre-check
22
+ pytest
23
+ pytest-cov
24
+ pytest-env
25
+ pytest-mock
29
26
  python-markdown-math
27
+ ruff>=0.9.2
28
+ twine
@@ -0,0 +1,25 @@
1
+ version: 0.1.0
2
+
3
+ project: artefacts/cli-run-remote-testing
4
+
5
+ on:
6
+ push:
7
+ jobs:
8
+ - turtlesim
9
+
10
+ jobs:
11
+ turtlesim:
12
+ type: test
13
+ package:
14
+ custom:
15
+ os: public.ecr.aws/artefacts/ros2:humble-turtlesim
16
+ runtime:
17
+ simulator: turtlesim
18
+ framework: ros2:humble
19
+ scenarios:
20
+ defaults:
21
+ subscriptions:
22
+ pose: turtle1/pose
23
+ settings:
24
+ - name: turtle
25
+ ros_testfile: turtlesim2/launch_turtle.py
@@ -14,7 +14,6 @@ NAME = "turtle1"
14
14
 
15
15
 
16
16
  class TestTurtle(unittest.TestCase):
17
-
18
17
  # runs before each test_* method
19
18
  def setUp(self):
20
19
  ## Arange
@@ -22,12 +21,12 @@ class TestTurtle(unittest.TestCase):
22
21
  # setup the turtlesim simulator
23
22
  rospy.wait_for_service(f"/{NAME}/teleport_absolute")
24
23
  rospy.wait_for_service(f"/{NAME}/set_pen")
25
- rospy.wait_for_service(f"/clear")
24
+ rospy.wait_for_service("/clear")
26
25
  self.srv_teleport_absolute = rospy.ServiceProxy(
27
26
  f"/{NAME}/teleport_absolute", TeleportAbsolute
28
27
  )
29
28
  self.srv_set_pen = rospy.ServiceProxy(f"/{NAME}/set_pen", SetPen)
30
- self.srv_clear = rospy.ServiceProxy(f"/clear", EmptySrv)
29
+ self.srv_clear = rospy.ServiceProxy("/clear", EmptySrv)
31
30
  self.srv_set_pen(r=255, g=255, b=255, width=5, off=False)
32
31
  while not rospy.has_param("test/start_pose"):
33
32
  rospy.sleep(0.1)
@@ -70,7 +69,7 @@ class TestTurtle(unittest.TestCase):
70
69
  self.assertNotEqual(
71
70
  distance_to_start,
72
71
  0,
73
- msg=f"turtle position at end of test exactly at starting position: did not move?",
72
+ msg="turtle position at end of test exactly at starting position: did not move?",
74
73
  )
75
74
  # check if the turtle finished the loop trajectory
76
75
  self.assertAlmostEqual(
@@ -94,7 +94,7 @@ class TurtleOdom:
94
94
  def main():
95
95
  rospy.init_node("turtle_odometry")
96
96
  try:
97
- to = TurtleOdom("turtle1")
97
+ TurtleOdom("turtle1")
98
98
  rospy.spin()
99
99
  except rospy.ROSInterruptException:
100
100
  pass
@@ -57,6 +57,7 @@ with rosbag.Bag(args["bag_path"], "r") as bag:
57
57
  estimated_messages = [_ for _ in bag.read_messages(topics=estimated_topic)]
58
58
  groundtruth_messages = [_ for _ in bag.read_messages(topics=groundtruth_topic)]
59
59
 
60
+
60
61
  # helper functions
61
62
  def compute_distance_travelled(message_list):
62
63
  """given a list of messages of TurtlePose type,
@@ -88,9 +89,9 @@ def match_lookup(lookup_time, lookup_list, previous_index, max_search=100):
88
89
  within previous_index and previous_index + max_search
89
90
  checks the previous message too. returns whichever is the closest to the lookup_time
90
91
  """
91
- assert (
92
- len(lookup_list) > previous_index
93
- ), f"previous index = {previous_index} is beyond bounds of lookup list of length {len(lookup_list)}"
92
+ assert len(lookup_list) > previous_index, (
93
+ f"previous index = {previous_index} is beyond bounds of lookup list of length {len(lookup_list)}"
94
+ )
94
95
  time_delta = -1 # initialize as negative
95
96
  i = previous_index
96
97
  while (
@@ -141,9 +142,9 @@ for msg in estimated_messages:
141
142
  matched_messages.append({"estimated": msg, "groundtruth": matched_msg})
142
143
  assert len(matched_messages) == len(estimated_messages)
143
144
  N = 50
144
- assert (
145
- len(matched_messages) > N
146
- ), f"[error in turtle_post_process] not enough matched messages found"
145
+ assert len(matched_messages) > N, (
146
+ "[error in turtle_post_process] not enough matched messages found"
147
+ )
147
148
  # remove garbage messages logged during test setup
148
149
  # @TODO replace hard-coded assumption of N first messages with an event trigger
149
150
  matched_messages = matched_messages[N:]
@@ -156,7 +157,7 @@ time_deltas = [
156
157
  ]
157
158
  fig = px.scatter(
158
159
  time_deltas,
159
- title=f"Time delta for each matched pair of messages <br>Max time delta = {max([abs(t) for t in time_deltas])*1000:.0f} ms",
160
+ title=f"Time delta for each matched pair of messages <br>Max time delta = {max([abs(t) for t in time_deltas]) * 1000:.0f} ms",
160
161
  labels={"value": "time delta (s)"},
161
162
  )
162
163
  fig.write_html(args["out_folder"] + "/mtime_deltas.html")
@@ -214,7 +215,7 @@ fig = px.scatter(
214
215
  error_horiz(match["groundtruth"], match["estimated"])
215
216
  for match in matched_messages
216
217
  ],
217
- title=f"Horizontal error over distance travelled <br>Final error = { error_horiz(matched_messages[-1]['groundtruth'], matched_messages[-1]['estimated']):.2f} m",
218
+ title=f"Horizontal error over distance travelled <br>Final error = {error_horiz(matched_messages[-1]['groundtruth'], matched_messages[-1]['estimated']):.2f} m",
218
219
  labels={"x": "distance travelled (m)", "y": "error (m)"},
219
220
  )
220
221
  fig.write_html(args["out_folder"] + "/error_horiz_distance.html")
@@ -250,7 +251,7 @@ fig.add_trace(
250
251
  )
251
252
  )
252
253
  fig.update_layout(
253
- title=f"Turtle trajectory: ground truth vs estimated from odometry",
254
+ title="Turtle trajectory: ground truth vs estimated from odometry",
254
255
  xaxis_title="x",
255
256
  yaxis_title="y",
256
257
  margin_t=30,
@@ -15,7 +15,7 @@ def generate_test_description():
15
15
  proc_env["PYTHONUNBUFFERED"] = "1"
16
16
  # TODO switch to package
17
17
  sample_process = ExecuteProcess(
18
- cmd=[sys.executable, "infra-tests/turtlesim2/sample_node.py"],
18
+ cmd=[sys.executable, "turtlesim2/sample_node.py"],
19
19
  output="log",
20
20
  env=proc_env,
21
21
  )
@@ -20,7 +20,7 @@ class TestListener(Node):
20
20
  vel_msg.linear.x = 1.0
21
21
  self.velocity_publisher.publish(vel_msg)
22
22
  self.gt_pose = msg
23
- if self.gt_pose.x > 8:
23
+ if self.gt_pose.x > 3:
24
24
  self.get_logger().info("Turtle nearing right edge")
25
25
 
26
26
 
@@ -29,7 +29,7 @@ def main():
29
29
  node = TestListener()
30
30
  try:
31
31
  while True:
32
- rclpy.spin_once(node, timeout_sec=0.1)
32
+ rclpy.spin_once(node, timeout_sec=1)
33
33
  rclpy.shutdown()
34
34
  except KeyboardInterrupt:
35
35
  rclpy.shutdown()
@@ -27,8 +27,6 @@ dependencies = [
27
27
  "mcap-ros2-support",
28
28
  "PyYAML>=6.0",
29
29
  "requests>=2.27.1",
30
- "setuptools-scm",
31
- "setuptools>=74", # setuptools-scm requires without version, but does not work with oldish versions, so let's ensure a recent, tested one.
32
30
  ]
33
31
  dynamic = ["version"]
34
32
 
@@ -38,6 +36,7 @@ packages = ["artefacts", "artefacts.cli"]
38
36
  [tool.setuptools_scm]
39
37
  version_file = "artefacts/cli/version.py"
40
38
  local_scheme = "no-local-version"
39
+ fallback_version = "0.0.0"
41
40
 
42
41
  [project.optional-dependencies]
43
42
  dev = [
@@ -45,19 +44,20 @@ dev = [
45
44
  "build",
46
45
  "docker",
47
46
  "lark",
48
- "pyre-check",
49
- "pytest",
50
- "pytest-cov",
51
- "pytest-env",
52
- "pytest-mock",
53
- "ruff",
54
- "twine",
55
47
  "mkdocs-click==0.8.0",
56
48
  "mkdocs-material==8.5.6",
57
49
  "mkdocs-mermaid2-plugin==0.6.0",
58
50
  "mkdocs==1.4.2",
51
+ "numpy",
59
52
  "pre-commit",
53
+ "pyre-check",
54
+ "pytest",
55
+ "pytest-cov",
56
+ "pytest-env",
57
+ "pytest-mock",
60
58
  "python-markdown-math",
59
+ "ruff>=0.9.2",
60
+ "twine",
61
61
  ]
62
62
 
63
63
  [project.urls]
File without changes
@@ -0,0 +1,13 @@
1
+ import pytest
2
+
3
+ from artefacts.cli import errors
4
+ from artefacts.cli.containers.utils import ContainerMgr
5
+
6
+
7
+ def test_exit_when_no_container_engine(mocker):
8
+ # Make believe there is no engine
9
+ mocker.patch.object(ContainerMgr, "_configure", lambda _: None)
10
+ with pytest.raises(SystemExit) as e:
11
+ ContainerMgr()
12
+ assert e.type is SystemExit
13
+ assert e.value.code == errors.CONTAINER_ENGINE_NOT_FOUND
@@ -1,7 +1,6 @@
1
1
  import os
2
2
  from uuid import uuid4
3
3
 
4
- import pytest
5
4
 
6
5
  from artefacts.cli.app_containers import containers
7
6
 
@@ -5,7 +5,6 @@ from artefacts.cli.ros1 import (
5
5
  )
6
6
  import yaml
7
7
  import pytest
8
- import os
9
8
 
10
9
 
11
10
  def test_generate_parameter_output(tmp_path):
@@ -6,7 +6,6 @@ import pytest
6
6
  from artefacts.cli import WarpJob, WarpRun
7
7
  from artefacts.cli.app import APIConf
8
8
  from artefacts.cli.ros2 import generate_scenario_parameter_output, run_ros2_tests
9
- import artefacts.cli.utils
10
9
 
11
10
 
12
11
  def test_generate_parameter_output(tmp_path):
@@ -37,4 +36,6 @@ def test_passing_launch_arguments(mock_run_and_save_logs, _mock_exists):
37
36
  assert (
38
37
  " test.launch.py arg1:=val1 arg2:=val2"
39
38
  in mock_run_and_save_logs.call_args[0][0]
40
- ), "Launch arguments should be passed to the test command after the launch file path"
39
+ ), (
40
+ "Launch arguments should be passed to the test command after the launch file path"
41
+ )
@@ -3,7 +3,6 @@ import pytest
3
3
  import os
4
4
 
5
5
  from click.testing import CliRunner
6
- import docker
7
6
 
8
7
  from tests.utils import docker_mock
9
8
 
@@ -1,10 +1,8 @@
1
1
  # Fake Docker client
2
2
 
3
- import copy
4
3
  from dataclasses import dataclass, asdict
5
4
  import hashlib
6
5
  from random import randint
7
- from unittest import mock
8
6
 
9
7
  from docker import DockerClient, APIClient
10
8
  from docker.constants import DEFAULT_DOCKER_API_VERSION
File without changes
File without changes