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 +8 -5
- artefacts/cli/app.py +14 -0
- artefacts/cli/app_containers.py +21 -1
- artefacts/cli/logger.py +21 -5
- artefacts/cli/other.py +3 -0
- artefacts/cli/ros2.py +22 -13
- artefacts/cli/utils_ros.py +2 -2
- artefacts/cli/version.py +2 -2
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.8.0.dist-info}/METADATA +1 -1
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.8.0.dist-info}/RECORD +13 -13
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.8.0.dist-info}/WHEEL +0 -0
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.8.0.dist-info}/entry_points.txt +0 -0
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.8.0.dist-info}/top_level.txt +0 -0
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(
|
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")
|
artefacts/cli/app_containers.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
186
|
-
|
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
|
-
|
192
|
-
run.log_artifacts(
|
197
|
+
rosbag_dir = new_rosbags.pop()
|
198
|
+
run.log_artifacts(rosbag_dir, "rosbag")
|
193
199
|
if "metrics" in run.params:
|
194
|
-
#
|
195
|
-
|
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"{
|
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
|
-
|
218
|
+
run.logger.error(f"Metric {metric} not found in rosbag, skipping.")
|
210
219
|
except TypeError or IndexError:
|
211
|
-
|
220
|
+
run.logger.error(
|
212
221
|
f"Metric {metric} not found. Is it being published?. Skipping."
|
213
222
|
)
|
214
223
|
|
artefacts/cli/utils_ros.py
CHANGED
@@ -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
|
-
|
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
|
74
|
+
return [result], None
|
artefacts/cli/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: artefacts_cli
|
3
|
-
Version: 0.
|
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=
|
3
|
-
artefacts/cli/app.py,sha256=
|
4
|
-
artefacts/cli/app_containers.py,sha256=
|
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=
|
9
|
-
artefacts/cli/other.py,sha256=
|
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=
|
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=
|
15
|
-
artefacts/cli/version.py,sha256=
|
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.
|
21
|
-
artefacts_cli-0.
|
22
|
-
artefacts_cli-0.
|
23
|
-
artefacts_cli-0.
|
24
|
-
artefacts_cli-0.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|