artefacts-cli 0.7.3__py3-none-any.whl → 0.9.1__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/app.py CHANGED
@@ -1,13 +1,12 @@
1
- import configparser
2
1
  import getpass
3
2
  import json
4
3
  import os
5
4
  import platform
6
5
  import random
7
- import subprocess
8
6
  import tarfile
9
7
  import tempfile
10
8
  import time
9
+ from typing import Optional
11
10
  from urllib.parse import urlparse
12
11
  import webbrowser
13
12
 
@@ -17,94 +16,29 @@ import requests
17
16
  from pathlib import Path
18
17
  from gitignore_parser import parse_gitignore
19
18
 
20
- from artefacts.cli import init_job, generate_scenarios, AuthenticationError, __version__
19
+ from artefacts.cli import (
20
+ init_job,
21
+ generate_scenarios,
22
+ localise,
23
+ AuthenticationError,
24
+ __version__,
25
+ )
21
26
  from artefacts.cli import app_containers as containers
22
- from artefacts.cli.constants import DEPRECATED_FRAMEWORKS, SUPPORTED_FRAMEWORKS
27
+ from artefacts.cli.config import APIConf
28
+ from artefacts.cli.constants import (
29
+ DEPRECATED_FRAMEWORKS,
30
+ SUPPORTED_FRAMEWORKS,
31
+ CONFIG_PATH,
32
+ )
33
+ from artefacts.cli.helpers import (
34
+ add_key_to_conf,
35
+ get_conf_from_file,
36
+ get_artefacts_api_url,
37
+ get_git_revision_branch,
38
+ get_git_revision_hash,
39
+ )
23
40
  from artefacts.cli.utils import add_output_from_default, config_validation, read_config
24
41
 
25
- HOME = os.path.expanduser("~")
26
- CONFIG_DIR = f"{HOME}/.artefacts"
27
- CONFIG_PATH = f"{CONFIG_DIR}/config"
28
-
29
-
30
- def get_git_revision_hash() -> str:
31
- try:
32
- return (
33
- subprocess.check_output(["git", "rev-parse", "HEAD"])
34
- .decode("ascii")
35
- .strip()
36
- )
37
- except subprocess.CalledProcessError:
38
- return ""
39
-
40
-
41
- def get_git_revision_branch() -> str:
42
- try:
43
- return (
44
- subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
45
- .decode("ascii")
46
- .strip()
47
- )
48
- except subprocess.CalledProcessError:
49
- return ""
50
-
51
-
52
- def get_conf_from_file():
53
- config = configparser.ConfigParser()
54
- if not os.path.isfile(CONFIG_PATH):
55
- os.makedirs(CONFIG_DIR, exist_ok=True)
56
- config["DEFAULT"] = {}
57
- with open(CONFIG_PATH, "w") as f:
58
- config.write(f)
59
- config.read(CONFIG_PATH)
60
- return config
61
-
62
-
63
- def get_artefacts_api_url(project_profile):
64
- return os.environ.get(
65
- "ARTEFACTS_API_URL",
66
- project_profile.get(
67
- "ApiUrl",
68
- "https://app.artefacts.com/api",
69
- ),
70
- )
71
-
72
-
73
- class APIConf:
74
- def __init__(self, project_name: str, job_name: str = None) -> None:
75
- config = get_conf_from_file()
76
- if project_name in config:
77
- profile = config[project_name]
78
- else:
79
- profile = {}
80
- self.api_url = get_artefacts_api_url(profile)
81
- self.api_key = os.environ.get("ARTEFACTS_KEY", profile.get("ApiKey", None))
82
- if self.api_key is None:
83
- batch_id = os.environ.get("AWS_BATCH_JOB_ID", None)
84
- job_id = os.environ.get("ARTEFACTS_JOB_ID", None)
85
- if batch_id is None or job_id is None:
86
- raise click.ClickException(
87
- f"No API KEY set. Please run 'artefacts config add {project_name}'"
88
- )
89
- auth_type = "Internal"
90
- # Batch id for array jobs contains array index
91
- batch_id = batch_id.split(":")[0]
92
- self.headers = {"Authorization": f"{auth_type} {job_id}:{batch_id}"}
93
- else:
94
- auth_type = "ApiKey"
95
- self.headers = {"Authorization": f"{auth_type} {self.api_key}"}
96
- self.headers["User-Agent"] = (
97
- f"ArtefactsClient/{__version__} ({platform.platform()}/{platform.python_version()})"
98
- )
99
- if job_name:
100
- click.echo(f"[{job_name}] Connecting to {self.api_url} using {auth_type}")
101
- else:
102
- click.echo(f"Connecting to {self.api_url} using {auth_type}")
103
-
104
-
105
- def validate_artefacts_config(config_file: str) -> dict:
106
- pass
107
-
108
42
 
109
43
  @click.group()
110
44
  def config():
@@ -119,15 +53,8 @@ def path():
119
53
  click.echo(CONFIG_PATH)
120
54
 
121
55
 
122
- def add_key_to_conf(project_name, api_key):
123
- config = get_conf_from_file()
124
- config[project_name] = {"ApiKey": api_key}
125
- with open(CONFIG_PATH, "w") as f:
126
- config.write(f)
127
-
128
-
129
56
  @config.command()
130
- @click.argument("project_name")
57
+ @click.argument("project_name", metavar=localise("PROJECT_NAME"))
131
58
  def add(project_name):
132
59
  """
133
60
  Set configuration for PROJECT_NAME
@@ -145,16 +72,26 @@ def add(project_name):
145
72
  os.system(f'cmd.exe /C start "" {settings_page_url} 2>/dev/null')
146
73
  else:
147
74
  webbrowser.open(settings_page_url)
148
- click.echo(f"Opening the project settings page: {settings_page_url}")
75
+ click.echo(
76
+ localise("Opening the project settings page: {url}").format(
77
+ url=settings_page_url
78
+ )
79
+ )
149
80
  api_key = click.prompt(
150
- f"Please enter your API KEY for {project_name}", type=str, hide_input=True
81
+ localise("Please enter your API KEY for {project}").format(
82
+ project=project_name
83
+ ),
84
+ type=str,
85
+ hide_input=True,
151
86
  )
152
87
  add_key_to_conf(project_name, api_key)
153
- click.echo(f"API KEY saved for {project_name}")
88
+ click.echo(localise("API KEY saved for {project}").format(project=project_name))
154
89
  if click.confirm(
155
- "Would you like to download the generated artefacts.yaml file? This will overwrite any existing config file in the current directory."
90
+ localise(
91
+ "Would you like to download the generated artefacts.yaml file? This will overwrite any existing config file in the current directory."
92
+ )
156
93
  ):
157
- api_conf = APIConf(project_name)
94
+ api_conf = APIConf(project_name, __version__)
158
95
  config_file_name = "artefacts.yaml"
159
96
  config_file_url = f"{api_url}/{project_name}/{config_file_name}"
160
97
  r = requests.get(config_file_url, headers=api_conf.headers)
@@ -164,7 +101,7 @@ def add(project_name):
164
101
 
165
102
 
166
103
  @config.command()
167
- @click.argument("project_name")
104
+ @click.argument("project_name", metavar=localise("PROJECT_NAME"))
168
105
  def delete(project_name):
169
106
  """
170
107
  Delete configuration for PROJECT_NAME
@@ -173,14 +110,14 @@ def delete(project_name):
173
110
  config.remove_section(project_name)
174
111
  with open(CONFIG_PATH, "w") as f:
175
112
  config.write(f)
176
- click.echo(f"{project_name} config removed")
113
+ click.echo(localise("{project} config removed").format(project=project_name))
177
114
 
178
115
 
179
116
  @click.command()
180
- @click.argument("project_name")
117
+ @click.argument("project_name", metavar=localise("PROJECT_NAME"))
181
118
  def hello(project_name):
182
119
  """Show message to confirm credentials allow access to PROJECT_NAME"""
183
- api_conf = APIConf(project_name)
120
+ api_conf = APIConf(project_name, __version__)
184
121
  response = requests.get(
185
122
  f"{api_conf.api_url}/{project_name}/info",
186
123
  headers=api_conf.headers,
@@ -192,76 +129,106 @@ def hello(project_name):
192
129
  )
193
130
  else:
194
131
  result = response.json()
195
- raise click.ClickException(f"Error getting project info: {result['message']}")
132
+ raise click.ClickException(
133
+ localise("Error getting project info: {message}").format(
134
+ message=result["message"]
135
+ )
136
+ )
196
137
 
197
138
 
198
- @click.command()
139
+ @click.command(
140
+ context_settings=dict(
141
+ ignore_unknown_options=True,
142
+ )
143
+ )
144
+ @click.argument("jobname", metavar=localise("JOBNAME"))
199
145
  @click.option(
200
146
  "--config",
201
147
  callback=config_validation,
202
148
  default="artefacts.yaml",
203
- help="Artefacts config file.",
149
+ help=localise("Artefacts configuration file."),
204
150
  )
205
151
  @click.option(
206
152
  "--dryrun",
207
153
  is_flag=True,
208
154
  default=False,
209
- help="Dryrun: no tracking or test execution",
155
+ help=localise("Run with no tracking nor test execution"),
210
156
  )
211
157
  @click.option(
212
158
  "--nosim",
213
159
  is_flag=True,
214
160
  default=False,
215
- help="nosim: no simulator resource provided by Artefacts",
161
+ help=localise("Skip configuring a simulator resource provided by Artefacts"),
216
162
  )
217
163
  @click.option(
218
164
  "--noupload",
219
165
  is_flag=True,
220
166
  default=False,
221
- help="noupload: rosbags are not uploaded to cloud",
167
+ help=localise(
168
+ "Do not upload to Artefacts files generated during a run (e.g. rosbags)"
169
+ ),
222
170
  )
223
171
  @click.option(
224
172
  "--noisolation",
225
173
  is_flag=True,
226
174
  default=False,
227
- help="noisolation: for debugging, break the 'middleware network' isolation between the test suite and the host (in ROS1: --reuse-master flag / in ROS2: --disable-isolation flag)",
175
+ help=localise(
176
+ "Break the 'middleware network' isolation between the test suite and the host (in ROS1: --reuse-master flag; in ROS2: --disable-isolation flag). Primarily for debugging"
177
+ ),
228
178
  )
229
179
  @click.option(
230
180
  "--description",
231
181
  default=None,
232
- help="Optional description for this run",
182
+ help=localise("Optional description for this run"),
233
183
  )
234
184
  @click.option(
235
185
  "--skip-validation",
236
186
  is_flag=True,
237
187
  default=False,
238
188
  is_eager=True, # Necessary for callbacks to see it.
239
- help="Skip configuration validation, so that unsupported settings can be tried out, e.g. non-ROS settings or simulators like SAPIEN.",
189
+ help=localise(
190
+ "Skip configuration validation, so that unsupported settings can be tried out, e.g. non-ROS settings or simulators like SAPIEN."
191
+ ),
240
192
  )
241
193
  @click.option(
242
194
  "--in-container",
243
195
  is_flag=True,
244
196
  default=False,
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.',
197
+ help=localise(
198
+ '[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.'
199
+ ),
200
+ )
201
+ @click.option(
202
+ "--dockerfile",
203
+ default="Dockerfile",
204
+ help=localise(
205
+ "[Experimental] Path to a custom Dockerfile. Defaults to Dockerfile in the run directory. This flag is only used together with `--in-container`"
206
+ ),
246
207
  )
247
208
  @click.option(
248
209
  "--with-image",
249
210
  default=None,
250
- help="[Deprecated and unused from 0.8.0; Image names are now internally managed] Run the job using the image name passed here. Only used when running with --in-container set.",
211
+ help=localise(
212
+ "[Deprecated and unused from 0.8.0; Image names are now internally managed] Run the job using the image name passed here. Only used when running with --in-container set."
213
+ ),
251
214
  )
252
215
  @click.option(
253
216
  "--no-rebuild",
254
217
  is_flag=True,
255
218
  default=False,
256
- help="[Experimental] Override the default behaviour to always rebuild the container image (as we assume incremental testing).",
219
+ help=localise(
220
+ "[Experimental] Override the default behaviour to always rebuild the container image (as we assume incremental testing)."
221
+ ),
257
222
  )
258
223
  @click.option(
259
224
  "--with-gui",
260
225
  is_flag=True,
261
226
  default=False,
262
- 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.",
227
+ help=localise(
228
+ "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."
229
+ ),
263
230
  )
264
- @click.argument("jobname")
231
+ @click.argument("container-engine-args", nargs=-1, type=click.UNPROCESSED)
265
232
  @click.pass_context
266
233
  def run(
267
234
  ctx: click.Context,
@@ -274,16 +241,32 @@ def run(
274
241
  description="",
275
242
  skip_validation=False,
276
243
  in_container: bool = False,
244
+ dockerfile: str = "Dockerfile",
277
245
  with_image: str = "artefacts",
278
246
  no_rebuild: bool = False,
279
247
  with_gui: bool = False,
248
+ container_engine_args: Optional[tuple] = None,
280
249
  ):
281
250
  """
282
251
  Run JOBNAME locally
283
252
 
284
253
  * Directly in the shell by default.
285
254
  * Inside a packaged container when using the --in-container option.
255
+
256
+ In container mode:
257
+ * Images are built automatically if missing.
258
+ * Currently 1 image per job found in artefacts.yaml.
259
+ * Images are rebuilt at each run (relatively fast when no change).
260
+ * `dockerfile` allows to specify an alternative Dockerfile.
286
261
  """
262
+ # Workaround for job names coming after engine arguments
263
+ # Idea: Job names do not start with hyphens.
264
+ if jobname.startswith("-") and container_engine_args is not None:
265
+ _fix = list(container_engine_args)
266
+ _fix.insert(0, jobname)
267
+ jobname = _fix.pop()
268
+ container_engine_args = tuple(_fix)
269
+
287
270
  warpconfig = read_config(config)
288
271
  project_id = warpconfig["project"]
289
272
 
@@ -291,30 +274,40 @@ def run(
291
274
  click.echo("#" * 80)
292
275
  click.echo(f"# Job {jobname}".ljust(79, " ") + "#")
293
276
  click.echo("#" * 80)
294
- click.echo(f"[{jobname}] Checking container image")
277
+ click.echo(f"[{jobname}] " + localise("Checking container image"))
295
278
  if not no_rebuild:
296
279
  ctx.invoke(
297
280
  containers.build,
298
281
  root=".",
282
+ dockerfile=dockerfile,
283
+ only=[jobname],
299
284
  )
300
- click.echo(f"[{jobname}] Container image ready")
301
- click.echo(f"[{jobname}] Run in container")
285
+ click.echo(f"[{jobname}] " + localise("Container image ready"))
286
+ click.echo(f"[{jobname}] " + localise("Run in container"))
302
287
  return ctx.invoke(
303
288
  containers.run,
304
289
  jobname=jobname,
305
290
  config=config,
306
291
  with_gui=with_gui,
292
+ engine_args=container_engine_args,
307
293
  )
308
294
 
309
- api_conf = APIConf(project_id, jobname)
310
- click.echo(f"[{jobname}] Starting tests")
295
+ api_conf = APIConf(project_id, __version__, jobname)
296
+ click.echo(f"[{jobname}] " + localise("Starting tests"))
311
297
  if jobname not in warpconfig["jobs"]:
312
- click.secho(f"[{jobname}] Error: Job name not defined", err=True, bold=True)
298
+ click.secho(
299
+ f"[{jobname}] " + localise("Error: Job name not defined"),
300
+ err=True,
301
+ bold=True,
302
+ )
313
303
  raise click.Abort()
314
304
  jobconf = warpconfig["jobs"][jobname]
315
305
  job_type = jobconf.get("type", "test")
316
306
  if job_type not in ["test"]:
317
- click.echo(f"[{jobname}] Job type not supported: {job_type}")
307
+ click.echo(
308
+ f"[{jobname}] "
309
+ + localise("Job type not supported: {jt}").format(jt=job_type)
310
+ )
318
311
  return
319
312
 
320
313
  framework = jobconf["runtime"].get("framework", None)
@@ -323,19 +316,30 @@ def run(
323
316
  if framework in DEPRECATED_FRAMEWORKS.keys():
324
317
  migrated_framework = DEPRECATED_FRAMEWORKS[framework]
325
318
  click.echo(
326
- f"[{jobname}] The selected framework '{framework}' is deprecated. Using '{migrated_framework}' instead."
319
+ f"[{jobname}] "
320
+ + localise(
321
+ "The selected framework '{framework}' is deprecated. Using '{alt}' instead."
322
+ ).format(framework=framework, alt=migrated_framework)
327
323
  )
328
324
  framework = migrated_framework
329
325
 
330
326
  if framework not in SUPPORTED_FRAMEWORKS:
331
327
  click.echo(
332
- f"[{jobname}] WARNING: framework: '{framework}' is not officially supported. Attempting run."
328
+ f"[{jobname}] "
329
+ + localise(
330
+ "WARNING: framework: '{framework}' is not officially supported. Attempting run."
331
+ ).format(framework=framework)
333
332
  )
334
333
 
335
334
  batch_index = os.environ.get("AWS_BATCH_JOB_ARRAY_INDEX", None)
336
335
  if batch_index is not None:
337
336
  batch_index = int(batch_index)
338
- click.echo(f"[{jobname}] AWS BATCH ARRAY DETECTED, batch_index={batch_index}")
337
+ click.echo(
338
+ f"[{jobname}] "
339
+ + localise("AWS BATCH ARRAY DETECTED, batch_index={index}").format(
340
+ index=batch_index
341
+ )
342
+ )
339
343
  scenarios, first = generate_scenarios(jobconf, batch_index)
340
344
  context = None
341
345
  execution_context = getpass.getuser() + "@" + platform.node()
@@ -359,7 +363,10 @@ def run(
359
363
  )
360
364
  except AuthenticationError:
361
365
  click.secho(
362
- f"[{jobname}] Unable to authenticate (Stage: Job initialisation), please check your project name and API key",
366
+ f"[{jobname}] "
367
+ + localise(
368
+ "Unable to authenticate (Stage: Job initialisation), please check your project name and API key"
369
+ ),
363
370
  err=True,
364
371
  bold=True,
365
372
  )
@@ -368,13 +375,19 @@ def run(
368
375
  job_success = True
369
376
  for scenario_n, scenario in enumerate(scenarios):
370
377
  click.echo(
371
- f"[{jobname}] Starting scenario {scenario_n + 1}/{len(scenarios)}: {scenario['name']}"
378
+ f"[{jobname}] "
379
+ + localise("Starting scenario {sid}/{num}: {name}").format(
380
+ sid=scenario_n + 1, num=len(scenarios), name=scenario["name"]
381
+ )
372
382
  )
373
383
  try:
374
384
  run = warpjob.new_run(scenario)
375
385
  except AuthenticationError:
376
386
  click.secho(
377
- f"[{jobname}] Unable to authenticate (Stage: Job run), please check your project name and API key",
387
+ f"[{jobname}] "
388
+ + localise(
389
+ "Unable to authenticate (Stage: Job run), please check your project name and API key"
390
+ ),
378
391
  err=True,
379
392
  bold=True,
380
393
  )
@@ -385,19 +398,22 @@ def run(
385
398
 
386
399
  if "ros_testfile" not in run.params:
387
400
  click.secho(
388
- f"[{jobname}] Test launch file not specified for ros2 project",
401
+ f"[{jobname}] "
402
+ + localise("Test launch file not specified for ros2 project"),
389
403
  err=True,
390
404
  bold=True,
391
405
  )
392
406
  result = get_TestSuite_error_result(
393
407
  scenario["name"],
394
- "launch_test file not specified error",
395
- f"Please specify a `ros_testfile` in the artefacts.yaml scenario configuration.",
408
+ localise("launch_test file not specified error"),
409
+ localise(
410
+ "Please specify a `ros_testfile` in the artefacts.yaml scenario configuration."
411
+ ),
396
412
  )
397
413
  run.log_tests_results([result], False)
398
414
  run.stop()
399
415
  if dryrun:
400
- click.echo(f"[{jobname}] Performing dry run")
416
+ click.echo(f"[{jobname}] " + localise("Performing dry run"))
401
417
  results, success = {}, True
402
418
  else:
403
419
  try:
@@ -407,7 +423,8 @@ def run(
407
423
  warpjob.log_tests_result(False)
408
424
  click.secho(e, bold=True, err=True)
409
425
  click.secho(
410
- f"[{jobname}] artefacts failed to execute the tests",
426
+ f"[{jobname}] "
427
+ + localise("Artefacts failed to execute the tests"),
411
428
  err=True,
412
429
  bold=True,
413
430
  )
@@ -417,7 +434,10 @@ def run(
417
434
  warpjob.stop()
418
435
  warpjob.log_tests_result(job_success)
419
436
  click.secho(
420
- f"[{jobname}] Not able to execute tests. Make sure that ROS2 is sourced and that your launch file syntax is correct.",
437
+ f"[{jobname}] "
438
+ + localise(
439
+ "Not able to execute tests. Make sure that ROS2 is sourced and that your launch file syntax is correct."
440
+ ),
421
441
  err=True,
422
442
  bold=True,
423
443
  )
@@ -429,13 +449,14 @@ def run(
429
449
 
430
450
  if "ros_testfile" not in run.params:
431
451
  click.secho(
432
- f"[{jobname}] Test launch file not specified for ros1 project",
452
+ f"[{jobname}] "
453
+ + localise("Test launch file not specified for ros1 project"),
433
454
  err=True,
434
455
  bold=True,
435
456
  )
436
457
  raise click.Abort()
437
458
  if dryrun:
438
- click.echo(f"[{jobname}] Performing dry run")
459
+ click.echo(f"[{jobname}] " + localise("Performing dry run"))
439
460
  results, success = {}, True
440
461
  else:
441
462
  results, success = run_ros1_tests(run)
@@ -446,13 +467,14 @@ def run(
446
467
 
447
468
  if "run" not in run.params:
448
469
  click.secho(
449
- f"[{jobname}] run command not specified for scenario",
470
+ f"[{jobname}] "
471
+ + localise("run command not specified for scenario"),
450
472
  err=True,
451
473
  bold=True,
452
474
  )
453
475
  raise click.Abort()
454
476
  if dryrun:
455
- click.echo(f"[{jobname}] Performing dry run")
477
+ click.echo(f"[{jobname}] " + localise("Performing dry run"))
456
478
  results, success = {}, True
457
479
  else:
458
480
  results, success = run_other_tests(run)
@@ -466,7 +488,7 @@ def run(
466
488
 
467
489
  run.stop()
468
490
  warpjob.log_tests_result(job_success)
469
- click.echo(f"[{jobname}] Done")
491
+ click.echo(f"[{jobname}] " + localise("Done"))
470
492
  time.sleep(random.random() * 1)
471
493
 
472
494
  warpjob.stop()
@@ -477,21 +499,23 @@ def run(
477
499
  "--config",
478
500
  callback=config_validation,
479
501
  default="artefacts.yaml",
480
- help="Artefacts config file.",
502
+ help=localise("Artefacts configuration file."),
481
503
  )
482
504
  @click.option(
483
505
  "--description",
484
506
  default=None,
485
- help="Optional description for this run",
507
+ help=localise("Optional description for this run"),
486
508
  )
487
509
  @click.option(
488
510
  "--skip-validation",
489
511
  is_flag=True,
490
512
  default=False,
491
513
  is_eager=True, # Necessary for callbacks to see it.
492
- help="Skip configuration validation, so that unsupported settings can be tried out, e.g. non-ROS settings or simulators like SAPIEN.",
514
+ help=localise(
515
+ "Skip configuration validation, so that unsupported settings can be tried out, e.g. non-ROS settings or simulators like SAPIEN."
516
+ ),
493
517
  )
494
- @click.argument("jobname")
518
+ @click.argument("jobname", metavar=localise("JOBNAME"))
495
519
  def run_remote(config, description, jobname, skip_validation=False):
496
520
  """
497
521
  Run JOBNAME in the cloud by packaging local sources.
@@ -502,9 +526,11 @@ def run_remote(config, description, jobname, skip_validation=False):
502
526
  try:
503
527
  warpconfig = read_config(config)
504
528
  except FileNotFoundError:
505
- raise click.ClickException(f"Project config file not found: {config}")
529
+ raise click.ClickException(
530
+ localise("Project config file not found: {config}").format(config=config)
531
+ )
506
532
  project_id = warpconfig["project"]
507
- api_conf = APIConf(project_id)
533
+ api_conf = APIConf(project_id, __version__)
508
534
  project_folder = os.path.dirname(os.path.abspath(config))
509
535
  dashboard_url = urlparse(api_conf.api_url)
510
536
  dashboard_url = f"{dashboard_url.scheme}://{dashboard_url.netloc}/{project_id}"
@@ -513,7 +539,9 @@ def run_remote(config, description, jobname, skip_validation=False):
513
539
  warpconfig["jobs"][jobname]
514
540
  except KeyError:
515
541
  raise click.ClickException(
516
- f"Can't find a job named '{jobname}' in config '{config}'"
542
+ localise("Can't find a job named '{jobname}' in config '{config}'").format(
543
+ jobname=jobname, config=config
544
+ )
517
545
  )
518
546
 
519
547
  # Mutate job and then keep only the selected job in the config
@@ -531,7 +559,7 @@ def run_remote(config, description, jobname, skip_validation=False):
531
559
  if "on" in run_config:
532
560
  del run_config["on"]
533
561
 
534
- click.echo("Packaging source...")
562
+ click.echo(localise("Packaging source..."))
535
563
 
536
564
  with tempfile.NamedTemporaryFile(
537
565
  prefix=project_id.split("/")[-1], suffix=".tgz", delete=True
@@ -576,7 +604,13 @@ def run_remote(config, description, jobname, skip_validation=False):
576
604
  result = upload_urls_response.json()
577
605
  except requests.exceptions.JSONDecodeError:
578
606
  raise click.ClickException(
579
- f"Apologies, problem in interacting with the Artefacts backend: {upload_urls_response.status_code} {upload_urls_response.reason}. Response text: {upload_urls_response.text}."
607
+ localise(
608
+ "Apologies, problem in interacting with the Artefacts backend: {status_code} {reason}. Response text: {detail}."
609
+ ).format(
610
+ status_code=upload_urls_response.status_code,
611
+ reason=upload_urls_response.reason,
612
+ detail=upload_urls_response.text,
613
+ )
580
614
  )
581
615
 
582
616
  if (
@@ -584,25 +618,39 @@ def run_remote(config, description, jobname, skip_validation=False):
584
618
  and result["message"] == "Not allowed"
585
619
  ):
586
620
  raise click.ClickException(
587
- f"Missing access! Please make sure your API key is added at {dashboard_url}/settings"
621
+ localise(
622
+ "Missing access! Please make sure your API key is added at {url}/settings"
623
+ ).format(url=dashboard_url)
588
624
  )
589
625
 
590
626
  if upload_urls_response.status_code == 402:
591
627
  raise click.ClickException(
592
- f"Billing issue, please go to {dashboard_url}/settings to correct: {result['error']}"
628
+ localise(
629
+ "Billing issue, please go to {url}/settings to correct: {error}"
630
+ ).format(url=dashboard_url, error=result["error"])
593
631
  )
594
632
 
595
633
  if "message" in result:
596
634
  raise click.ClickException(
597
- f"Error getting project info: {result['message']}"
635
+ localise("Error getting project info: {message}").format(
636
+ message=result["message"]
637
+ )
598
638
  )
599
639
  elif "error" in result:
600
640
  raise click.ClickException(
601
- f"Error getting project info: {result['error']}"
641
+ localise("Error getting project info: {message}").format(
642
+ message=result["error"]
643
+ )
602
644
  )
603
645
  else:
604
646
  raise click.ClickException(
605
- f"Error getting project info: {upload_urls_response.status_code} {upload_urls_response.reason}. Response text: {upload_urls_response.text}."
647
+ localise(
648
+ "Error getting project info: {status_code} {reason}. Response text: {detail}."
649
+ ).format(
650
+ status_code=upload_urls_response.status_code,
651
+ reason=upload_urls_response.reason,
652
+ detail=upload_urls_response.text,
653
+ )
606
654
  )
607
655
 
608
656
  upload_urls = upload_urls_response.json()["upload_urls"]
@@ -613,7 +661,7 @@ def run_remote(config, description, jobname, skip_validation=False):
613
661
  description = os.environ.get("GITHUB_WORKFLOW")
614
662
  url = f"{os.environ.get('GITHUB_SERVER_URL')}/{os.environ.get('GITHUB_REPOSITORY')}/actions/runs/{os.environ.get('GITHUB_RUN_ID')}"
615
663
  else:
616
- description = "Testing local source"
664
+ description = localise("Testing local source")
617
665
  # Mock the necessary parts of the GitHub event
618
666
  execution_context = getpass.getuser() + "@" + platform.node()
619
667
  integration_payload = {
@@ -661,7 +709,9 @@ def run_remote(config, description, jobname, skip_validation=False):
661
709
  )
662
710
 
663
711
  click.echo(
664
- f"Uploading complete! The new job will show up shortly at {dashboard_url}"
712
+ localise(
713
+ "Uploading complete! The new job will show up shortly at {url}"
714
+ ).format(url=dashboard_url)
665
715
  )
666
716
 
667
717
 
@@ -669,14 +719,7 @@ def run_remote(config, description, jobname, skip_validation=False):
669
719
  @click.version_option(version=__version__)
670
720
  def artefacts():
671
721
  """A command line tool to interface with ARTEFACTS"""
672
- compute_env = os.getenv("AWS_BATCH_CE_NAME", "")
673
- if compute_env != "":
674
- click.echo(f"running version {__version__}")
675
- if (
676
- "development" in compute_env
677
- and os.getenv("ARTEFACTS_API_URL", None) is None
678
- ):
679
- os.environ["ARTEFACTS_API_URL"] = "https://ui.artefacts.com/api"
722
+ pass
680
723
 
681
724
 
682
725
  artefacts.add_command(config)