artefacts-cli 0.6.15__py3-none-any.whl → 0.6.17__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
@@ -15,7 +15,7 @@ logging.basicConfig(level=logging.INFO)
15
15
 
16
16
 
17
17
  try:
18
- __version__ = version("package-name")
18
+ __version__ = version("artefacts-cli")
19
19
  except PackageNotFoundError:
20
20
  try:
21
21
  # Package is not installed, most likely dev/test mode
artefacts/cli/app.py CHANGED
@@ -231,11 +231,64 @@ def hello(project_name):
231
231
  is_eager=True, # Necessary for callbacks to see it.
232
232
  help="Skip configuration validation, so that unsupported settings can be tried out, e.g. non-ROS settings or simulators like SAPIEN.",
233
233
  )
234
+ @click.option(
235
+ "--in-container",
236
+ is_flag=True,
237
+ default=False,
238
+ 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.',
239
+ )
240
+ @click.option(
241
+ "--with-image",
242
+ default="artefacts",
243
+ help="[Experimental] Run the job using the image name passed here. Only used when running with --in-container set.",
244
+ )
245
+ @click.option(
246
+ "--rebuild-container",
247
+ is_flag=True,
248
+ default=False,
249
+ help="[Experimental] Rebuild the container image before running. This flag guarantees that the run uses the latest code available when building the image, and usually takes more time before the run can start (time to compile and generate the image).",
250
+ )
251
+ @click.option(
252
+ "--with-gui",
253
+ is_flag=True,
254
+ default=False,
255
+ help="Show any GUI if any is created by the test runs. By default, UI elements are run but hidden---only test logs are returned. Please note GUI often assume X11 (e.g. ROS), typically with Qt, so this may not work without a appropriate environment.",
256
+ )
234
257
  @click.argument("jobname")
258
+ @click.pass_context
235
259
  def run(
236
- config, jobname, dryrun, nosim, noisolation, description="", skip_validation=False
260
+ ctx: click.Context,
261
+ config,
262
+ jobname,
263
+ dryrun,
264
+ nosim,
265
+ noisolation,
266
+ description="",
267
+ skip_validation=False,
268
+ in_container: bool = False,
269
+ with_image: str = "artefacts",
270
+ rebuild_container: bool = False,
271
+ with_gui: bool = False,
237
272
  ):
238
- """Run JOBNAME locally"""
273
+ """
274
+ Run JOBNAME locally
275
+
276
+ * Directly in the shell by default.
277
+ * Inside a packaged container when using the --in-container option.
278
+ """
279
+ if in_container:
280
+ if rebuild_container or not ctx.invoke(containers.check, name=with_image):
281
+ ctx.invoke(
282
+ containers.build, path=".", dockerfile="Dockerfile", name=with_image
283
+ )
284
+ return ctx.invoke(
285
+ containers.run,
286
+ image=with_image,
287
+ jobname=jobname,
288
+ config=config,
289
+ with_gui=with_gui,
290
+ )
291
+
239
292
  warpconfig = read_config(config)
240
293
 
241
294
  project_id = warpconfig["project"]
@@ -430,7 +483,7 @@ def run_remote(config, description, jobname, skip_validation=False):
430
483
  click.echo(f"Packaging source...")
431
484
 
432
485
  with tempfile.NamedTemporaryFile(
433
- prefix=project_id, suffix=".tgz", delete=True
486
+ prefix=project_id.split("/")[-1], suffix=".tgz", delete=True
434
487
  ) as temp_file:
435
488
  # get list of patterns to be ignored in .artefactsignore
436
489
  ignore_file = Path(project_folder) / Path(".artefactsignore")
@@ -573,7 +626,7 @@ artefacts.add_command(config)
573
626
  artefacts.add_command(hello)
574
627
  artefacts.add_command(run)
575
628
  artefacts.add_command(run_remote)
576
- artefacts.add_command(containers.package)
629
+ artefacts.add_command(containers.containers)
577
630
 
578
631
 
579
632
  if __name__ == "__main__":
@@ -2,6 +2,7 @@ import os
2
2
 
3
3
  import click
4
4
 
5
+ from artefacts.cli.constants import DEFAULT_API_URL
5
6
  from artefacts.cli.utils import config_validation, read_config
6
7
  from artefacts.cli.containers.utils import ContainerMgr
7
8
 
@@ -9,12 +10,12 @@ from artefacts.cli.containers.utils import ContainerMgr
9
10
  @click.group()
10
11
  @click.option("--debug/--no-debug", default=False)
11
12
  @click.pass_context
12
- def package(ctx: click.Context, debug: bool):
13
+ def containers(ctx: click.Context, debug: bool):
13
14
  ctx.ensure_object(dict)
14
15
  ctx.obj["debug"] = debug
15
16
 
16
17
 
17
- @package.command()
18
+ @containers.command()
18
19
  @click.option(
19
20
  "--path",
20
21
  default=".",
@@ -28,13 +29,13 @@ def package(ctx: click.Context, debug: bool):
28
29
  @click.option(
29
30
  "--name",
30
31
  required=False,
31
- help="Name for the generated package",
32
+ help="Name for the generated container",
32
33
  )
33
34
  @click.pass_context
34
35
  def build(ctx: click.Context, path: str, dockerfile: str, name: str):
35
36
  if not os.path.exists(os.path.join(path, dockerfile)):
36
37
  raise click.ClickException(
37
- f"No {dockerfile} found here. I cannot build the package."
38
+ f"No {dockerfile} found here. I cannot build the container."
38
39
  )
39
40
  if name is None:
40
41
  name = "artefacts"
@@ -43,7 +44,21 @@ def build(ctx: click.Context, path: str, dockerfile: str, name: str):
43
44
  print(f"Package complete in image: {image}")
44
45
 
45
46
 
46
- @package.command()
47
+ @containers.command()
48
+ @click.argument("name")
49
+ @click.pass_context
50
+ def check(ctx: click.Context, name: str):
51
+ if name is None:
52
+ name = "artefacts"
53
+ handler = ContainerMgr()
54
+ result = handler.check(name)
55
+ if ctx.parent is None:
56
+ # Print only if the command is called directly.
57
+ print(f"Package {name} exists and ready to use.")
58
+ return result
59
+
60
+
61
+ @containers.command()
47
62
  @click.argument("image")
48
63
  @click.argument("jobname")
49
64
  @click.option(
@@ -52,15 +67,29 @@ def build(ctx: click.Context, path: str, dockerfile: str, name: str):
52
67
  default="artefacts.yaml",
53
68
  help="Artefacts config file.",
54
69
  )
70
+ @click.option(
71
+ "--with-gui",
72
+ "with_gui",
73
+ default=False,
74
+ help="Show any GUI if any is created by the test runs. By default, UI elements are run but hidden---only test logs are returned. Please note GUI often assume an X11 environment, typically with Qt, so this may not work without a appropriate environment.",
75
+ )
55
76
  @click.pass_context
56
- def run(ctx: click.Context, image: str, jobname: str, config: str):
77
+ def run(ctx: click.Context, image: str, jobname: str, config: str, with_gui: bool):
57
78
  try:
58
79
  artefacts_config = read_config(config)
59
80
  except FileNotFoundError:
60
81
  raise click.ClickException(f"Project config file not found: {config}")
61
82
  project = artefacts_config["project"]
62
83
  handler = ContainerMgr()
63
- container, logs = handler.run(image, project, jobname)
84
+ params = dict(
85
+ image=image,
86
+ project=project,
87
+ jobname=jobname,
88
+ # Hidden setting primarily useful to Artefacts developers
89
+ api_url=os.environ.get("ARTEFACTS_API_URL", DEFAULT_API_URL),
90
+ with_gui=with_gui,
91
+ )
92
+ container, logs = handler.run(**params)
64
93
  if container:
65
94
  print(f"Package run complete: Container Id for inspection: {container['Id']}")
66
95
  else:
@@ -1,3 +1,5 @@
1
+ DEFAULT_API_URL = "https://app.artefacts.com/api"
2
+
1
3
  SUPPORTED_FRAMEWORKS = [
2
4
  "ros2:iron",
3
5
  "ros2:humble",
@@ -4,6 +4,8 @@ import os
4
4
  from pathlib import Path
5
5
  from typing import Any, Tuple, Union
6
6
 
7
+ from artefacts.cli.constants import DEFAULT_API_URL
8
+
7
9
 
8
10
  class CMgr:
9
11
  def build(self, **kwargs) -> Tuple[str, Generator]:
@@ -13,8 +15,20 @@ class CMgr:
13
15
  """
14
16
  raise NotImplemented()
15
17
 
18
+ def check(self, image: str) -> bool:
19
+ """
20
+ Checks whether a target image exists locally.
21
+ """
22
+ raise NotImplemented()
23
+
16
24
  def run(
17
- self, image: str, project: str, jobname: str = None
25
+ self,
26
+ image: str,
27
+ project: str,
28
+ jobname: str = None,
29
+ artefacts_dir: str = Path("~/.artefacts").expanduser(),
30
+ api_url: str = DEFAULT_API_URL,
31
+ with_gui: bool = False,
18
32
  ) -> Tuple[Any, Generator]:
19
33
  """
20
34
  Returns a container (Any type as depends on the framework)
@@ -38,7 +52,7 @@ class CMgr:
38
52
  """
39
53
  if not path:
40
54
  raise Exception(
41
- "`path` must be a string, a Path object, or exclusded from the kwargs"
55
+ "`path` must be a string, a Path object, or excluded from the kwargs"
42
56
  )
43
57
  if os.environ.get("ARTEFACTS_KEY", None):
44
58
  return True
@@ -1,9 +1,12 @@
1
1
  from collections.abc import Generator
2
2
  import json
3
3
  import os
4
+ from pathlib import Path
5
+ import platform
4
6
  from typing import Any, Tuple
5
7
  from uuid import uuid4
6
8
 
9
+ from artefacts.cli.constants import DEFAULT_API_URL
7
10
  from artefacts.cli.containers import CMgr
8
11
  from artefacts.cli.utils import ensure_available
9
12
 
@@ -37,12 +40,20 @@ class DockerManager(CMgr):
37
40
  img_id = self.client.inspect_image(kwargs["tag"])["Id"]
38
41
  return img_id, iter(logs)
39
42
 
43
+ def check(
44
+ self,
45
+ image: str,
46
+ ) -> bool:
47
+ return len(self.client.images(name=image)) > 0
48
+
40
49
  def run(
41
50
  self,
42
51
  image: str,
43
52
  project: str,
44
53
  jobname: str = None,
45
- artefacts_dir: str = os.path.join(os.path.expanduser("~/.artefacts")),
54
+ artefacts_dir: str = Path("~/.artefacts").expanduser(),
55
+ api_url: str = DEFAULT_API_URL,
56
+ with_gui: bool = False,
46
57
  ) -> Tuple[Any, Generator]:
47
58
  if not self._valid_artefacts_api_key(project, artefacts_dir):
48
59
  return None, iter(
@@ -51,12 +62,24 @@ class DockerManager(CMgr):
51
62
  ]
52
63
  )
53
64
  try:
65
+ env = {
66
+ "JOB_ID": str(uuid4()),
67
+ "ARTEFACTS_JOB_NAME": jobname,
68
+ "ARTEFACTS_API_URL": api_url,
69
+ }
70
+
71
+ if platform.system() in ["Darwin", "Windows"]:
72
+ # Assume we run in Docker Desktop
73
+ env["DISPLAY"] = "host.docker.internal:0"
74
+ else:
75
+ env["DISPLAY"] = os.environ.get("DISPLAY", ":0")
76
+
77
+ if not with_gui:
78
+ env["QT_QPA_PLATFORM"] = "offscreen"
79
+
54
80
  container = self.client.create_container(
55
81
  image,
56
- environment={
57
- "JOB_ID": str(uuid4()),
58
- "ARTEFACTS_JOB_NAME": jobname,
59
- },
82
+ environment=env,
60
83
  detach=False,
61
84
  volumes=["/root/.artefacts"],
62
85
  host_config=self.client.create_host_config(
@@ -66,6 +89,7 @@ class DockerManager(CMgr):
66
89
  "mode": "ro",
67
90
  },
68
91
  },
92
+ network_mode="host",
69
93
  ),
70
94
  )
71
95
  self.client.start(container=container.get("Id"))
@@ -50,7 +50,8 @@ class ContainerMgr:
50
50
  def build(self, **kwargs) -> Tuple[str, Generator]:
51
51
  return self.mgr.build(**kwargs)
52
52
 
53
- def run(
54
- self, image: str, project: str, jobname: str = None
55
- ) -> Tuple[Any, Generator]:
56
- return self.mgr.run(image, project, jobname)
53
+ def check(self, image: str) -> bool:
54
+ return self.mgr.check(image)
55
+
56
+ def run(self, **kwargs) -> Tuple[Any, Generator]:
57
+ return self.mgr.run(**kwargs)
artefacts/cli/version.py CHANGED
@@ -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.15'
16
- __version_tuple__ = version_tuple = (0, 6, 15)
15
+ __version__ = version = '0.6.17'
16
+ __version_tuple__ = version_tuple = (0, 6, 17)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: artefacts_cli
3
- Version: 0.6.15
3
+ Version: 0.6.17
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
@@ -9,33 +9,34 @@ Classifier: License :: OSI Approved :: Apache Software License
9
9
  Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.8
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: artefacts-copava >=0.1.11
13
- Requires-Dist: click >=8.0.4
14
- Requires-Dist: gitignore-parser >=0.1.11
15
- Requires-Dist: junitparser >=2.5
12
+ Requires-Dist: artefacts-copava>=0.1.11
13
+ Requires-Dist: click>=8.0.4
14
+ Requires-Dist: gitignore_parser>=0.1.11
15
+ Requires-Dist: junitparser>=2.5
16
16
  Requires-Dist: mcap
17
17
  Requires-Dist: mcap-ros2-support
18
- Requires-Dist: PyYAML >=6.0
19
- Requires-Dist: requests >=2.27.1
18
+ Requires-Dist: PyYAML>=6.0
19
+ Requires-Dist: requests>=2.27.1
20
20
  Requires-Dist: setuptools-scm
21
+ Requires-Dist: setuptools>=74
21
22
  Provides-Extra: dev
22
- Requires-Dist: awscli ; extra == 'dev'
23
- Requires-Dist: build ; extra == 'dev'
24
- Requires-Dist: docker ; extra == 'dev'
25
- Requires-Dist: lark ; extra == 'dev'
26
- Requires-Dist: pyre-check ; extra == 'dev'
27
- Requires-Dist: pytest ; extra == 'dev'
28
- Requires-Dist: pytest-cov ; extra == 'dev'
29
- Requires-Dist: pytest-env ; extra == 'dev'
30
- Requires-Dist: pytest-mock ; extra == 'dev'
31
- Requires-Dist: ruff ; extra == 'dev'
32
- Requires-Dist: twine ; extra == 'dev'
33
- Requires-Dist: mkdocs-click ==0.8.0 ; extra == 'dev'
34
- Requires-Dist: mkdocs-material ==8.5.6 ; extra == 'dev'
35
- Requires-Dist: mkdocs-mermaid2-plugin ==0.6.0 ; extra == 'dev'
36
- Requires-Dist: mkdocs ==1.4.2 ; extra == 'dev'
37
- Requires-Dist: pre-commit ; extra == 'dev'
38
- Requires-Dist: python-markdown-math ; extra == 'dev'
23
+ Requires-Dist: awscli; extra == "dev"
24
+ Requires-Dist: build; extra == "dev"
25
+ Requires-Dist: docker; extra == "dev"
26
+ 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
+ Requires-Dist: mkdocs-click==0.8.0; extra == "dev"
35
+ Requires-Dist: mkdocs-material==8.5.6; extra == "dev"
36
+ Requires-Dist: mkdocs-mermaid2-plugin==0.6.0; extra == "dev"
37
+ Requires-Dist: mkdocs==1.4.2; extra == "dev"
38
+ Requires-Dist: pre-commit; extra == "dev"
39
+ Requires-Dist: python-markdown-math; extra == "dev"
39
40
 
40
41
  # Artefacts CLI
41
42
 
@@ -0,0 +1,21 @@
1
+ artefacts/cli/__init__.py,sha256=--1soc8cqq1JWhz4DuRUvJWgSeDCPGqm8mEmdqtclsI,11644
2
+ artefacts/cli/app.py,sha256=cxVVHD89_eVCHm5fqPq3ANuy6jTPfw3se-FST_7wILY,22093
3
+ artefacts/cli/app_containers.py,sha256=tHdGkxtzjexLhgcx5YXy_OOJsPcBi1jmwKZN01OAcmU,3044
4
+ artefacts/cli/bagparser.py,sha256=FE_QaztC9pg4hQzTjGSdyve6mzZbHJbyqa3wqvZSbxE,3702
5
+ artefacts/cli/constants.py,sha256=bvsVDwqkAc49IZN7j6k6IL6EG87bECHd_VINtKJqbv8,320
6
+ artefacts/cli/other.py,sha256=7NvzlspvG0zF7sryR-QznwdLupXLln1BKWxHB9VuEcc,1160
7
+ artefacts/cli/parameters.py,sha256=MDhrM7ur95wKTLDteqz2f-sLdCPcepi5wk0XjeLo6TU,788
8
+ artefacts/cli/ros1.py,sha256=RbtirCGarD9a0ikfuGK-pdpWYSXfqJhEt4rpA0uFsyU,9625
9
+ artefacts/cli/ros2.py,sha256=YaCi3HRuCdvCTjM0Ftnm-SLgMOy1OneIr0aU7KVTiAM,4476
10
+ artefacts/cli/utils.py,sha256=6yQJRzv-xaLbtcA73Tty9C9QZeV06n5pjXMYl3xsLPE,3156
11
+ artefacts/cli/utils_ros.py,sha256=pYbhAU9fK2VbnWm3nSLBwUMVBzsZuCn10SFZmVW3-Zo,2090
12
+ artefacts/cli/version.py,sha256=q7Xb4Wt4eH2VCIhiAa6149XDeUKQFfphqlwqiTFiurk,413
13
+ artefacts/cli/containers/__init__.py,sha256=y6NVB0efBfRQBlHb71xhRu1hEh7t2bkKNs2vtsLJaO8,2239
14
+ artefacts/cli/containers/docker.py,sha256=A9jqs0WU-g8umJzPpEACi0_DU5N8rglR-V3dmwxDuBA,3507
15
+ artefacts/cli/containers/utils.py,sha256=6Bw6s1ceEnTbWSdE5_1CReNW6RB29VeHsq-BAwcJ_fY,1895
16
+ artefacts/wrappers/artefacts_ros1_meta.launch,sha256=9tN7_0xLH8jW27KYFerhF3NuWDx2dED3ks_qoGVZAPw,1412
17
+ artefacts_cli-0.6.17.dist-info/METADATA,sha256=aiDZPGqyOGAS_pS3XnjIk4IAStv_vS3fUOW3qPyEH-s,3051
18
+ artefacts_cli-0.6.17.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
19
+ artefacts_cli-0.6.17.dist-info/entry_points.txt,sha256=nlTXRzilNjccbi53FgaRWCQPkG-pv61HRkaCkrKjlec,58
20
+ artefacts_cli-0.6.17.dist-info/top_level.txt,sha256=FdaMV1C9m36MWa-2Stm5xVODv7hss_nRYNwR83j_7ow,10
21
+ artefacts_cli-0.6.17.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,21 +0,0 @@
1
- artefacts/cli/__init__.py,sha256=9rTAfBoS9ALJxKSqaUo32ThbDshbvNN4-EmoyUsPhRI,11643
2
- artefacts/cli/app.py,sha256=J9lBtYDrBx9vPam1hJWDXKwkTEP4IXIfNEGCrGBzEE8,20125
3
- artefacts/cli/app_containers.py,sha256=hHJvqjxirbLUxgAT8fsOAMjkPmN0XGIX73krr5TEm7I,2011
4
- artefacts/cli/bagparser.py,sha256=FE_QaztC9pg4hQzTjGSdyve6mzZbHJbyqa3wqvZSbxE,3702
5
- artefacts/cli/constants.py,sha256=vZxtUprVqqZx1k4-LFFDU2tI0AnENV2cBt655uR17R8,269
6
- artefacts/cli/other.py,sha256=7NvzlspvG0zF7sryR-QznwdLupXLln1BKWxHB9VuEcc,1160
7
- artefacts/cli/parameters.py,sha256=MDhrM7ur95wKTLDteqz2f-sLdCPcepi5wk0XjeLo6TU,788
8
- artefacts/cli/ros1.py,sha256=RbtirCGarD9a0ikfuGK-pdpWYSXfqJhEt4rpA0uFsyU,9625
9
- artefacts/cli/ros2.py,sha256=YaCi3HRuCdvCTjM0Ftnm-SLgMOy1OneIr0aU7KVTiAM,4476
10
- artefacts/cli/utils.py,sha256=6yQJRzv-xaLbtcA73Tty9C9QZeV06n5pjXMYl3xsLPE,3156
11
- artefacts/cli/utils_ros.py,sha256=pYbhAU9fK2VbnWm3nSLBwUMVBzsZuCn10SFZmVW3-Zo,2090
12
- artefacts/cli/version.py,sha256=mm-8uYlR9TBoYFbfyOehz69r2OAONIb8KypEpPRKVqU,413
13
- artefacts/cli/containers/__init__.py,sha256=xOVmjLw3um8joQmjK_se-D1S9juufA-1s-RqXYJ5EoQ,1875
14
- artefacts/cli/containers/docker.py,sha256=nHVZ4ZSge7oUCbwvIT1vI-GAxuiSdJ-MVvLHJ2Un_OY,2805
15
- artefacts/cli/containers/utils.py,sha256=yahjJr9Oh7NuvJvy5X_nREMupQZ8A6hrhss3mscyqio,1882
16
- artefacts/wrappers/artefacts_ros1_meta.launch,sha256=9tN7_0xLH8jW27KYFerhF3NuWDx2dED3ks_qoGVZAPw,1412
17
- artefacts_cli-0.6.15.dist-info/METADATA,sha256=JXVv2Yc_Q859rR3F1qxSUl-uVaHa_1PV5sZEUAQZHPQ,3048
18
- artefacts_cli-0.6.15.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
19
- artefacts_cli-0.6.15.dist-info/entry_points.txt,sha256=nlTXRzilNjccbi53FgaRWCQPkG-pv61HRkaCkrKjlec,58
20
- artefacts_cli-0.6.15.dist-info/top_level.txt,sha256=FdaMV1C9m36MWa-2Stm5xVODv7hss_nRYNwR83j_7ow,10
21
- artefacts_cli-0.6.15.dist-info/RECORD,,