artefacts-cli 0.7.3__py3-none-any.whl → 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
artefacts/cli/__init__.py CHANGED
@@ -133,6 +133,7 @@ class WarpRun:
133
133
  self.output_path = self.params.get(
134
134
  "output_path", f"{self.job.output_path}/{self.run_n}"
135
135
  )
136
+ self.logger = logger
136
137
  os.makedirs(self.output_path, exist_ok=True)
137
138
  data = {
138
139
  "job_id": job.job_id,
@@ -155,10 +156,10 @@ class WarpRun:
155
156
  if response.status_code != 200:
156
157
  if response.status_code == 403:
157
158
  msg = response.json()["message"]
158
- logger.warning(msg)
159
+ self.logger.warning(msg)
159
160
  raise AuthenticationError(msg)
160
- logger.warning(f"Error on scenario creation: {response.status_code}")
161
- logger.warning(response.text)
161
+ self.logger.warning(f"Error on scenario creation: {response.status_code}")
162
+ self.logger.warning(response.text)
162
163
  raise AuthenticationError(str(response.status_code))
163
164
  return
164
165
 
@@ -275,9 +276,11 @@ class WarpRun:
275
276
  files=files,
276
277
  )
277
278
  except OverflowError:
278
- logger.warning(f"File too large: {file_name} could not be uploaded")
279
+ self.logger.warning(
280
+ f"File too large: {file_name} could not be uploaded"
281
+ )
279
282
  except Exception as e:
280
- logger.warning(f"Error uploading {file_name}: {e}, skipping")
283
+ self.logger.warning(f"Error uploading {file_name}: {e}, skipping")
281
284
 
282
285
 
283
286
  def init_job(
artefacts/cli/app.py CHANGED
@@ -244,6 +244,11 @@ def hello(project_name):
244
244
  default=False,
245
245
  help='[Experimental] Run the job inside a package container. The container image is build if it does not exist yet, with default name as "artefacts" (please use --with-image to override the image name). This option overrides (for now) --dryrun, --nosim, --noisolation and --description.',
246
246
  )
247
+ @click.option(
248
+ "--dockerfile",
249
+ default="Dockerfile",
250
+ help="[Experimental] Path to a custom Dockerfile. Defaults to Dockerfile in the run directory. This flag is only used together with `--in-container`",
251
+ )
247
252
  @click.option(
248
253
  "--with-image",
249
254
  default=None,
@@ -274,6 +279,7 @@ def run(
274
279
  description="",
275
280
  skip_validation=False,
276
281
  in_container: bool = False,
282
+ dockerfile: str = "Dockerfile",
277
283
  with_image: str = "artefacts",
278
284
  no_rebuild: bool = False,
279
285
  with_gui: bool = False,
@@ -283,6 +289,12 @@ def run(
283
289
 
284
290
  * Directly in the shell by default.
285
291
  * Inside a packaged container when using the --in-container option.
292
+
293
+ In container mode:
294
+ * Images are built automatically if missing.
295
+ * Currently 1 image per job found in artefacts.yaml.
296
+ * Images are rebuilt at each run (relatively fast when no change).
297
+ * `dockerfile` allows to specify an alternative Dockerfile.
286
298
  """
287
299
  warpconfig = read_config(config)
288
300
  project_id = warpconfig["project"]
@@ -296,6 +308,8 @@ def run(
296
308
  ctx.invoke(
297
309
  containers.build,
298
310
  root=".",
311
+ dockerfile=dockerfile,
312
+ only=[jobname],
299
313
  )
300
314
  click.echo(f"[{jobname}] Container image ready")
301
315
  click.echo(f"[{jobname}] Run in container")
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from pathlib import Path
3
+ from typing import Optional
3
4
 
4
5
  import click
5
6
 
@@ -45,6 +46,13 @@ def containers(ctx: click.Context, debug: bool):
45
46
  default="artefacts.yaml",
46
47
  help="Path to the Artefacts configuration file. It defaults to `./artefacts.yaml`",
47
48
  )
49
+ @click.option(
50
+ "--only",
51
+ required=False,
52
+ type=Optional[list],
53
+ default=None,
54
+ help="Optional list of job names to process. The default is to process all jobs.",
55
+ )
48
56
  @click.pass_context
49
57
  def build(
50
58
  ctx: click.Context,
@@ -53,6 +61,7 @@ def build(
53
61
  dockerfile: str,
54
62
  name: str,
55
63
  config: str,
64
+ only: Optional[list] = None,
56
65
  ):
57
66
  try:
58
67
  artefacts_config = read_config(config)
@@ -63,7 +72,11 @@ def build(
63
72
  prefix = artefacts_config["project"].strip().lower()
64
73
  dockerfiles = []
65
74
  if os.path.exists(dockerfile):
66
- for job_name in artefacts_config["jobs"]:
75
+ if only:
76
+ jobs = only
77
+ else:
78
+ jobs = artefacts_config["jobs"]
79
+ for job_name in jobs:
67
80
  dockerfiles.append(
68
81
  dict(
69
82
  path=root,
@@ -71,6 +84,11 @@ def build(
71
84
  name=f"{prefix}/{job_name.strip().lower()}",
72
85
  )
73
86
  )
87
+ elif dockerfile != "Dockerfile" and not os.path.exists(dockerfile):
88
+ # The user asks explicitly for using a specific Dockerfile, so fast fail if we cannot find it
89
+ raise click.ClickException(
90
+ f"Dockerfile `{dockerfile}` not found. Please ensure the file exits. Automatic Dockerfile generation may also work by dropping the --dockerfile option."
91
+ )
74
92
  else:
75
93
  # The split on `prefix` is to ensure there is no slash (project names are org/project) confusing the path across supported OS.
76
94
  dest_root = (
@@ -90,6 +108,8 @@ def build(
90
108
  scenarios = Converter().process(config, as_text=False)
91
109
  for idx, df in enumerate(scenarios.values()):
92
110
  job_name = df.job_name.strip().lower()
111
+ if only and job_name not in only:
112
+ continue
93
113
  dest = dest_root / Path(job_name)
94
114
  dest.mkdir(parents=True, exist_ok=True)
95
115
  _dockerfile = os.path.join(dest, "Dockerfile")
artefacts/cli/logger.py CHANGED
@@ -1,10 +1,26 @@
1
1
  import logging
2
2
  import sys
3
3
 
4
- logger = logging.getLogger()
4
+ logger = logging.getLogger("artefacts")
5
+ logger.setLevel(logging.INFO) # Allow INFO and above
5
6
 
6
- default_handler = logging.StreamHandler(stream=sys.stderr)
7
- default_handler.setLevel(logging.ERROR)
8
- default_handler.setFormatter(logging.Formatter())
9
7
 
10
- logger.addHandler(default_handler)
8
+ # INFO and WARNING messages go to stdout
9
+ class InfoWarningFilter(logging.Filter):
10
+ def filter(self, record):
11
+ return record.levelno in [logging.INFO, logging.WARNING]
12
+
13
+
14
+ info_handler = logging.StreamHandler(stream=sys.stdout)
15
+ info_handler.setLevel(logging.INFO)
16
+ info_handler.addFilter(InfoWarningFilter())
17
+ info_handler.setFormatter(logging.Formatter("%(message)s"))
18
+
19
+ # ERROR and above go to stderr
20
+ error_handler = logging.StreamHandler(stream=sys.stderr)
21
+ error_handler.setLevel(logging.ERROR)
22
+ error_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
23
+
24
+ logger.addHandler(info_handler)
25
+ logger.addHandler(error_handler)
26
+ logger.propagate = False
artefacts/cli/other.py CHANGED
@@ -16,6 +16,9 @@ def generate_parameter_output(params: dict):
16
16
 
17
17
 
18
18
  def run_other_tests(run):
19
+ """Note: parameter names will be set as environment variables
20
+ (must be letters, numbers and underscores), and saved into yaml and json files
21
+ """
19
22
  scenario = run.params
20
23
  if "params" in scenario:
21
24
  generate_parameter_output(scenario["params"])
artefacts/cli/ros2.py CHANGED
@@ -2,7 +2,6 @@ import yaml
2
2
  from glob import glob
3
3
  import os
4
4
  import shutil
5
-
6
5
  from .utils import run_and_save_logs
7
6
  from .utils_ros import parse_tests_results, get_TestSuite_error_result
8
7
  from .parameters import TMP_SCENARIO_PARAMS_YAML
@@ -93,8 +92,11 @@ def run_ros2_tests(run):
93
92
  generate_scenario_parameter_output(
94
93
  run.params["params"], TMP_SCENARIO_PARAMS_YAML
95
94
  )
96
-
97
- preexisting_rosbags = glob("rosbag2*")
95
+ # We look for the directory as drilling down will find both
96
+ # the directory as well as the rosbag itself.
97
+ preexisting_rosbags = [
98
+ path for path in glob("**/rosbag2*", recursive=True) if os.path.isdir(path)
99
+ ]
98
100
  test_result_file_path = f"{run.output_path}/tests_junit.xml"
99
101
  launch_arguments = [
100
102
  f"{k}:={v}" for k, v in run.params.get("launch_arguments", {}).items()
@@ -182,33 +184,40 @@ def run_ros2_tests(run):
182
184
  for output in scenario.get("output_dirs", []):
183
185
  run.log_artifacts(output)
184
186
 
185
- # check if any rosbag was created
186
- rosbags = glob("rosbag2*")
187
+ # Checks for a new rosbag after test completions. We look for the directory
188
+ # as drilling down will find both the directory as well as the rosbag itself.
189
+ rosbags = [
190
+ path for path in glob("**/rosbag2*", recursive=True) if os.path.isdir(path)
191
+ ]
187
192
  new_rosbags = set(rosbags).difference(set(preexisting_rosbags))
188
193
  from artefacts.cli.bagparser import BagFileParser
189
194
 
195
+ run.logger.info(f"Found new rosbags: {new_rosbags}")
190
196
  if len(new_rosbags) > 0:
191
- rosbag_path = new_rosbags.pop()
192
- run.log_artifacts(rosbag_path, "rosbag")
197
+ rosbag_dir = new_rosbags.pop()
198
+ run.log_artifacts(rosbag_dir, "rosbag")
193
199
  if "metrics" in run.params:
194
- # TODO should go inside BagFileParser?
195
- db_files = glob(f"{rosbag_path}/*.mcap") # Ros2 Default
200
+ # Search for database files in the directory
201
+ # TODO: should go inside BagFileParser?
202
+ db_files = glob(f"{rosbag_dir}/*.mcap") # Ros2 Default
196
203
  if not db_files:
197
- db_files = glob(f"{rosbag_path}/*.db3") # Legacy
204
+ db_files = glob(f"{rosbag_dir}/*.db3") # Legacy
198
205
  if not db_files:
199
206
  raise FileNotFoundError(
200
- "No .mcap or .db3 files found in the specified path."
207
+ "No .mcap or .db3 files found in the specified path. Attempted to find in "
208
+ f"{rosbag_dir}/*.mcap and {rosbag_dir}/*.db3"
201
209
  )
202
210
  db_file = db_files[0]
211
+
203
212
  bag = BagFileParser(db_file)
204
213
  for metric in run.params["metrics"]:
205
214
  try:
206
215
  last_value = bag.get_last_message(metric)[1].data
207
216
  run.log_metric(metric, last_value)
208
217
  except KeyError:
209
- print(f"Metric {metric} not found in rosbag, skipping.")
218
+ run.logger.error(f"Metric {metric} not found in rosbag, skipping.")
210
219
  except TypeError or IndexError:
211
- print(
220
+ run.logger.error(
212
221
  f"Metric {metric} not found. Is it being published?. Skipping."
213
222
  )
214
223
 
@@ -66,9 +66,9 @@ def parse_tests_results(file):
66
66
  except Exception as e:
67
67
  print(f"[Exception in parse_tests_results] {e}")
68
68
  print("Test result xml could not be loaded, marking success as False")
69
- results = get_TestSuite_error_result(
69
+ result = get_TestSuite_error_result(
70
70
  "unittest.suite.TestSuite",
71
71
  "Error parsing XML test results",
72
72
  f"The test may have timed out. Exception: {e}",
73
73
  )
74
- return results, None
74
+ return [result], None
artefacts/cli/version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.7.3'
21
- __version_tuple__ = version_tuple = (0, 7, 3)
20
+ __version__ = version = '0.8.0'
21
+ __version_tuple__ = version_tuple = (0, 8, 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: artefacts_cli
3
- Version: 0.7.3
3
+ Version: 0.8.0
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
@@ -1,24 +1,24 @@
1
1
  artefacts/__init__.py,sha256=VLmogtpRQeJjQjAORV8ClSJ5qF-57Hxx3apvgy9H1zk,76
2
- artefacts/cli/__init__.py,sha256=pt8OK66hMeQUxT9iLcvzYIIjFGrPS63ecWo8hS0T2qQ,11980
3
- artefacts/cli/app.py,sha256=lTdfJsNCdUh8A0V-MvvYUNoB1rgHAMpXxInqPgPeC4g,24241
4
- artefacts/cli/app_containers.py,sha256=AOTiXjyJzYZn6JqfW26N3ehGQWcEVQ5mEmKDdp9a6tQ,5636
2
+ artefacts/cli/__init__.py,sha256=qIHUUf51UoviMtChkO5Qn-9ZzC5nH7vPE-QlqC30cRE,12080
3
+ artefacts/cli/app.py,sha256=2ogpU_zZuynCu3lz5Ai2PqFiVFiGVdBZojeD7qNZqyk,24831
4
+ artefacts/cli/app_containers.py,sha256=sRjm-z1Bbp81UE-GFrXv-0XiB6bCDsiMcsEOuT24dww,6413
5
5
  artefacts/cli/bagparser.py,sha256=FE_QaztC9pg4hQzTjGSdyve6mzZbHJbyqa3wqvZSbxE,3702
6
6
  artefacts/cli/constants.py,sha256=bvsVDwqkAc49IZN7j6k6IL6EG87bECHd_VINtKJqbv8,320
7
7
  artefacts/cli/errors.py,sha256=BiCRo3IwVjtEotaFtmwsGTZiX-TRE69KqLrEQItLsag,34
8
- artefacts/cli/logger.py,sha256=MP8WDImHA3BKVsn55BMWtGP5-aCmXl5ViVPtIo3jKk4,242
9
- artefacts/cli/other.py,sha256=7NvzlspvG0zF7sryR-QznwdLupXLln1BKWxHB9VuEcc,1160
8
+ artefacts/cli/logger.py,sha256=PklhdEs-T8p9eogX7O_i6Gmw3v707y1Z0vgVwxBu96Q,792
9
+ artefacts/cli/other.py,sha256=Jp15jLjttMk0JW15EAU5hgQNAkbX7vMGyjdWectehlw,1317
10
10
  artefacts/cli/parameters.py,sha256=msf2aG-tmw0ahxwrPpB2W6KqdMj5A-nw9DPG9flkHTg,788
11
11
  artefacts/cli/ros1.py,sha256=rKepZckAuy5O_qraF2CW5GiTmTZHar7LRD4pvESy6T0,9622
12
- artefacts/cli/ros2.py,sha256=mXBURNHosG7WtqxjmydfQiPoGCNXOHe9reM-FRLwaHU,7887
12
+ artefacts/cli/ros2.py,sha256=UMC1-6h0LhUhvXZist134WG18Tgm0y1DZeHqFNDOEKk,8495
13
13
  artefacts/cli/utils.py,sha256=oy56o3N361srwhIvbMxwSPg8I_-tC7xcWtTSINTF2rE,4125
14
- artefacts/cli/utils_ros.py,sha256=ucJrIMLcTh26ioduj3xiozgxqXZghkyTMHWI9BsHNjI,2156
15
- artefacts/cli/version.py,sha256=HuvheJ09dqzSRYh_uHb_Af7PEi7XiVW5Tk0cjtnAa6I,511
14
+ artefacts/cli/utils_ros.py,sha256=D6NSsF-5EfkQjCa6KAL7pFNRpu3JyH3nb0UEEz2SEqQ,2156
15
+ artefacts/cli/version.py,sha256=fSm5pLlwHxfTD7vBTVEqChJUua9ilUsdQYNN_V3u3iE,511
16
16
  artefacts/cli/containers/__init__.py,sha256=K0efkJXNCqXH-qYBqhCE_8zVUCHbVmeuKH-y_fE8s4M,2254
17
17
  artefacts/cli/containers/docker.py,sha256=R0yA-aIZCyYWN7gzim_Dhn1owpKI9ekMu6qbz5URYbQ,4311
18
18
  artefacts/cli/containers/utils.py,sha256=bILX0uvazUJq7hoqKk4ztRzI_ZerYs04XQdKdx1ltjk,2002
19
19
  artefacts/wrappers/artefacts_ros1_meta.launch,sha256=9tN7_0xLH8jW27KYFerhF3NuWDx2dED3ks_qoGVZAPw,1412
20
- artefacts_cli-0.7.3.dist-info/METADATA,sha256=ByqmiKWu6jggqXcAsF_7wa1kl2iVJl5Dq_x9sD-I4Xw,3183
21
- artefacts_cli-0.7.3.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
22
- artefacts_cli-0.7.3.dist-info/entry_points.txt,sha256=nlTXRzilNjccbi53FgaRWCQPkG-pv61HRkaCkrKjlec,58
23
- artefacts_cli-0.7.3.dist-info/top_level.txt,sha256=FdaMV1C9m36MWa-2Stm5xVODv7hss_nRYNwR83j_7ow,10
24
- artefacts_cli-0.7.3.dist-info/RECORD,,
20
+ artefacts_cli-0.8.0.dist-info/METADATA,sha256=D2FpCO_zKGpEeeoRiUnaMLV5mcZS14Wu-rc4MpVq7zs,3183
21
+ artefacts_cli-0.8.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
22
+ artefacts_cli-0.8.0.dist-info/entry_points.txt,sha256=nlTXRzilNjccbi53FgaRWCQPkG-pv61HRkaCkrKjlec,58
23
+ artefacts_cli-0.8.0.dist-info/top_level.txt,sha256=FdaMV1C9m36MWa-2Stm5xVODv7hss_nRYNwR83j_7ow,10
24
+ artefacts_cli-0.8.0.dist-info/RECORD,,