remotivelabs-cli 0.0.25__py3-none-any.whl → 0.0.27__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.
Files changed (43) hide show
  1. cli/broker/brokers.py +2 -2
  2. cli/broker/export.py +5 -5
  3. cli/broker/files.py +5 -5
  4. cli/broker/lib/broker.py +82 -51
  5. cli/broker/license_flows.py +11 -9
  6. cli/broker/licenses.py +2 -2
  7. cli/broker/playback.py +14 -34
  8. cli/broker/record.py +3 -3
  9. cli/broker/scripting.py +4 -4
  10. cli/broker/signals.py +20 -12
  11. cli/cloud/__init__.py +0 -1
  12. cli/cloud/auth.py +40 -35
  13. cli/cloud/auth_tokens.py +73 -37
  14. cli/cloud/brokers.py +24 -33
  15. cli/cloud/cloud_cli.py +7 -18
  16. cli/cloud/configs.py +28 -11
  17. cli/cloud/filestorage.py +63 -51
  18. cli/cloud/organisations.py +30 -0
  19. cli/cloud/projects.py +11 -8
  20. cli/cloud/recordings.py +148 -117
  21. cli/cloud/recordings_playback.py +52 -39
  22. cli/cloud/rest_helper.py +247 -196
  23. cli/cloud/resumable_upload.py +9 -8
  24. cli/cloud/sample_recordings.py +5 -5
  25. cli/cloud/service_account_tokens.py +18 -16
  26. cli/cloud/service_accounts.py +9 -9
  27. cli/connect/__init__.py +0 -1
  28. cli/connect/connect.py +7 -6
  29. cli/connect/protopie/protopie.py +32 -16
  30. cli/errors.py +6 -5
  31. cli/remotive.py +13 -9
  32. cli/requirements.txt +4 -1
  33. cli/settings.py +9 -9
  34. cli/tools/__init__.py +0 -1
  35. cli/tools/can/__init__.py +0 -1
  36. cli/tools/can/can.py +8 -8
  37. cli/tools/tools.py +2 -2
  38. {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/METADATA +5 -3
  39. remotivelabs_cli-0.0.27.dist-info/RECORD +45 -0
  40. remotivelabs_cli-0.0.25.dist-info/RECORD +0 -44
  41. {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/LICENSE +0 -0
  42. {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/WHEEL +0 -0
  43. {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/entry_points.txt +0 -0
cli/cloud/recordings.py CHANGED
@@ -9,32 +9,34 @@ import tempfile
9
9
  import time
10
10
  import urllib.parse
11
11
  from pathlib import Path
12
- from typing import Dict, List, Union
12
+ from typing import Any, Dict, List, Tuple, Union
13
+ from urllib.parse import quote
13
14
 
14
15
  import grpc
15
16
  import requests
16
17
  import typer
17
- from rich.progress import Progress, SpinnerColumn, TextColumn, track
18
+ from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, track
18
19
 
19
20
  from cli.errors import ErrorPrinter
20
21
 
21
22
  from ..broker.lib.broker import Broker
22
- from . import rest_helper as rest
23
23
  from .recordings_playback import app as playback_app
24
+ from .rest_helper import RestHelper as Rest
25
+ from .rest_helper import err_console
24
26
 
25
27
  app = typer.Typer()
26
28
  app.add_typer(playback_app, name="playback")
27
29
 
28
30
 
29
- def uid(p):
31
+ def uid(p: Any) -> Any:
30
32
  print(p)
31
33
  return p["uid"]
32
34
 
33
35
 
34
36
  # to be used in options
35
37
  # autocompletion=project_names)
36
- def project_names():
37
- r = requests.get(f"{rest.base_url}/api/bu/{rest.org}/project", headers=rest.headers)
38
+ def project_names() -> Any:
39
+ r = requests.get(f"{Rest.get_base_url()}/api/bu/{Rest.get_org()}/project", headers=Rest.get_headers(), timeout=60)
38
40
  # sys.stderr.write(r.text)
39
41
  if r.status_code == 200:
40
42
  projects = r.json()
@@ -48,7 +50,7 @@ def project_names():
48
50
  def list_recordings(
49
51
  is_processing: bool = typer.Option(default=False, help="Use this option to see only those that are beeing processed or are invalid"),
50
52
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
51
- ):
53
+ ) -> None:
52
54
  """
53
55
  List all recording sessions in a project. You can choose to see all valid recordings (default) or use
54
56
  --is-processing and you will get those that are currently beeing processed or that failed to be validated.
@@ -56,35 +58,35 @@ def list_recordings(
56
58
  """
57
59
 
58
60
  if is_processing:
59
- rest.handle_get(f"/api/project/{project}/files/recording/processing")
61
+ Rest.handle_get(f"/api/project/{project}/files/recording/processing")
60
62
  else:
61
- rest.handle_get(f"/api/project/{project}/files/recording")
63
+ Rest.handle_get(f"/api/project/{project}/files/recording")
62
64
 
63
65
 
64
66
  @app.command(help="Shows details about a specific recording in project")
65
67
  def describe(
66
68
  recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
67
69
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
68
- ):
69
- rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}")
70
+ ) -> None:
71
+ Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}")
70
72
 
71
73
 
72
74
  # @app.command(help="Shows details about a specific recording in project")
73
75
  # def copy(recording_session: str = typer.Argument(..., help="Recording session id"),
74
76
  # target_project: str = typer.Option(..., help="Which project to copy the recording to"),
75
77
  # project: str = typer.Option(..., help="Project ID", envvar='REMOTIVE_CLOUD_PROJECT')):
76
- # rest.handle_post(url=f"/api/project/{project}/files/recording/{recording_session}/copy",
78
+ # Rest.handle_post(url=f"/api/project/{project}/files/recording/{recording_session}/copy",
77
79
  # body=json.dumps({'projectUid': target_project}))
78
80
 
79
81
 
80
- def do_start(name: str, project: str, api_key: str, return_response: bool = False):
82
+ def do_start(name: str, project: str, api_key: str, return_response: bool = False) -> requests.Response | None:
81
83
  if api_key == "":
82
84
  body = {"size": "S"}
83
85
  else:
84
86
  body = {"size": "S", "apiKey": api_key}
85
87
 
86
88
  name = name if name is not None else "personal"
87
- return rest.handle_post(
89
+ return Rest.handle_post(
88
90
  f"/api/project/{project}/brokers/{name}",
89
91
  body=json.dumps(body),
90
92
  return_response=return_response,
@@ -93,19 +95,20 @@ def do_start(name: str, project: str, api_key: str, return_response: bool = Fals
93
95
 
94
96
 
95
97
  @app.command(help="Prepares all recording files and transformations to be available for playback")
96
- def mount(
98
+ def mount( # noqa: C901
97
99
  recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
98
100
  broker: str = typer.Option(None, help="Broker to use"),
99
101
  ensure_broker_started: bool = typer.Option(default=False, help="Ensure broker exists, start otherwise"),
100
102
  transformation_name: str = typer.Option("default", help="Specify a custom signal transformation to use"),
101
103
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
102
- ):
103
- rest.ensure_auth_token()
104
+ ) -> None:
105
+ # pylint: disable=R0912
106
+ Rest.ensure_auth_token()
104
107
 
105
- rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
108
+ Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
106
109
 
107
110
  if broker is None:
108
- r = rest.handle_get(url=f"/api/project/{project}/brokers/personal", return_response=True, allow_status_codes=[404])
111
+ r = Rest.handle_get(url=f"/api/project/{project}/brokers/personal", return_response=True, allow_status_codes=[404])
109
112
 
110
113
  if r.status_code == 200:
111
114
  broker = r.json()["shortName"]
@@ -114,32 +117,35 @@ def mount(
114
117
  r = do_start(None, project, "", return_response=True)
115
118
  if r.status_code != 200:
116
119
  print(r.text)
117
- exit(0)
120
+ sys.exit(0)
118
121
  broker = r.json()["shortName"]
119
122
  else:
120
123
  sys.stderr.write(f"Got http status code {r.status_code}")
121
- typer.Exit(0)
124
+ raise typer.Exit(0)
122
125
  else:
123
- r = rest.handle_get(url=f"/api/project/{project}/brokers/{broker}", return_response=True, allow_status_codes=[404])
126
+ r = Rest.handle_get(url=f"/api/project/{project}/brokers/{broker}", return_response=True, allow_status_codes=[404])
124
127
 
128
+ if r is None:
129
+ sys.exit(1)
125
130
  if r.status_code == 404:
126
131
  if ensure_broker_started:
127
132
  r = do_start(broker, project, "", return_response=True)
128
-
133
+ if r is None:
134
+ sys.exit(1)
129
135
  if r.status_code != 200:
130
136
  print(r.text)
131
- exit(1)
137
+ sys.exit(1)
132
138
  else:
133
139
  ErrorPrinter.print_generic_error(f"Broker {broker} not running")
134
- exit(1)
140
+ sys.exit(1)
135
141
  elif r.status_code != 200:
136
142
  sys.stderr.write(f"Got http status code {r.status_code}")
137
- typer.Exit(1)
143
+ raise typer.Exit(1)
138
144
  broker_config_query = ""
139
145
  if transformation_name != "default":
140
146
  broker_config_query = f"?brokerConfigName={transformation_name}"
141
147
 
142
- rest.handle_get(
148
+ Rest.handle_get(
143
149
  f"/api/project/{project}/files/recording/{recording_session}/upload{broker_config_query}",
144
150
  params={"brokerName": broker},
145
151
  return_response=True,
@@ -155,29 +161,33 @@ def download_recording_file(
155
161
  ..., help="Recording session id that this file belongs to", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"
156
162
  ),
157
163
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
158
- ):
159
- rest.ensure_auth_token()
164
+ ) -> None:
165
+ Rest.ensure_auth_token()
166
+ recording_file_name_qouted = quote(recording_file_name, safe="")
160
167
  get_signed_url_resp = requests.get(
161
- f"{rest.base_url}/api/project/{project}/files/recording/{recording_session}/recording-file/{recording_file_name}",
162
- headers=rest.headers,
168
+ f"{Rest.get_base_url()}/api/project/{project}/files/recording/{recording_session}/recording-file/{recording_file_name_qouted}",
169
+ headers=Rest.get_headers(),
163
170
  allow_redirects=True,
171
+ timeout=60,
164
172
  )
165
173
  if get_signed_url_resp.status_code == 200:
166
174
  # Next download the actual file
167
- rest.download_file(recording_file_name, get_signed_url_resp.json()["downloadUrl"])
175
+ Rest.download_file(recording_file_name, get_signed_url_resp.json()["downloadUrl"])
168
176
  print(f"Downloaded {recording_file_name}")
177
+ else:
178
+ print(get_signed_url_resp)
169
179
 
170
180
 
171
181
  @app.command(name="delete")
172
182
  def delete(
173
183
  recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
174
184
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
175
- ):
185
+ ) -> None:
176
186
  """
177
187
  Deletes the specified recording session including all media files and configurations.
178
188
 
179
189
  """
180
- rest.handle_delete(f"/api/project/{project}/files/recording/{recording_session}")
190
+ Rest.handle_delete(f"/api/project/{project}/files/recording/{recording_session}")
181
191
 
182
192
 
183
193
  @app.command(name="delete-recording-file")
@@ -187,12 +197,12 @@ def delete_recording_file(
187
197
  ..., help="Recording session id that this file belongs to", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"
188
198
  ),
189
199
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
190
- ):
200
+ ) -> None:
191
201
  """
192
202
  Deletes the specified recording file
193
203
 
194
204
  """
195
- rest.handle_delete(f"/api/project/{project}/files/recording/{recording_session}/recording-file/{recording_file_name}")
205
+ Rest.handle_delete(f"/api/project/{project}/files/recording/{recording_session}/recording-file/{recording_file_name}")
196
206
 
197
207
 
198
208
  @app.command()
@@ -209,19 +219,22 @@ def upload( # noqa: C901
209
219
  ),
210
220
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
211
221
  recording_session: str = typer.Option(default=None, help="Optional existing recording to upload file to"),
212
- ):
222
+ ) -> None:
223
+ # pylint: disable=R0912,R0914,R0915
213
224
  """
214
225
  Uploads a recording to RemotiveCloud.
215
226
  Except for recordings from RemotiveBroker you can also upload Can ASC (.asc), Can BLF(.blf) and Can LOG (.log, .txt)
216
227
  """
217
228
 
218
229
  filename = os.path.basename(path.name)
219
- rest.ensure_auth_token()
230
+ Rest.ensure_auth_token()
220
231
 
221
232
  if recording_session is None:
222
- r = rest.handle_post(f"/api/project/{project}/files/recording/{filename}", return_response=True)
233
+ r = Rest.handle_post(f"/api/project/{project}/files/recording/{filename}", return_response=True)
223
234
  else:
224
- r = rest.handle_post(f"/api/project/{project}/files/recording/{recording_session}/recording-file/{filename}", return_response=True)
235
+ r = Rest.handle_post(f"/api/project/{project}/files/recording/{recording_session}/recording-file/{filename}", return_response=True)
236
+ if r is None:
237
+ return
225
238
 
226
239
  upload_url = r.text
227
240
  url_path = urllib.parse.urlparse(upload_url).path
@@ -234,14 +247,18 @@ def upload( # noqa: C901
234
247
  "Something went wrong, please try again. Please contact RemotiveLabs support if this problem remains"
235
248
  )
236
249
  ErrorPrinter.print_hint("Please make sure to use the latest version of RemotiveCLI")
237
- exit(1)
250
+ sys.exit(1)
238
251
 
239
- upload_response = rest.upload_file_with_signed_url(
252
+ upload_response = Rest.upload_file_with_signed_url(
240
253
  path=path, url=upload_url, upload_headers={"Content-Type": "application/x-www-form-urlencoded"}, return_response=True
241
254
  )
242
255
 
256
+ if upload_response is None:
257
+ return
258
+
243
259
  # Exact same as in cloud console
244
260
  def get_processing_message(step: str) -> str:
261
+ # pylint: disable=R0911
245
262
  if step == "REQUESTED":
246
263
  return "Preparing file..."
247
264
  if step == "VALIDATING":
@@ -264,10 +281,12 @@ def upload( # noqa: C901
264
281
  t = p.add_task("Processing...", total=1)
265
282
  while True:
266
283
  time.sleep(1)
267
- r = rest.handle_get(
284
+ r = Rest.handle_get(
268
285
  f"/api/project/{project}/files/recording/processing", return_response=True, use_progress_indicator=False
269
286
  )
270
- status_list: List[Dict[str, any]] = r.json()
287
+ if r is None:
288
+ return
289
+ status_list: List[Dict[str, Any]] = r.json()
271
290
  res = list(filter(lambda s: s["uploadId"] == upload_id, status_list))
272
291
  if len(res) == 1:
273
292
  tracking_state = res[0]
@@ -284,28 +303,41 @@ def upload( # noqa: C901
284
303
  break
285
304
  if error_message is not None:
286
305
  ErrorPrinter.print_generic_error(error_message)
287
- exit(1)
306
+ sys.exit(1)
288
307
 
289
308
  else:
290
- rest.err_console.print(f":boom: [bold red]Got status code[/bold red]: {upload_response.status_code} {upload_response.text}")
309
+ err_console.print(f":boom: [bold red]Got status code[/bold red]: {upload_response.status_code} {upload_response.text}")
291
310
 
292
311
 
312
+ # TODO - Change to use Path for directory # pylint: disable=W0511
293
313
  @app.command()
294
- def upload_broker_configuration(
295
- directory: str = typer.Argument(..., help="Configuration directory"),
314
+ def upload_broker_configuration( # noqa: C901
315
+ directory: Path = typer.Argument(
316
+ ...,
317
+ exists=True,
318
+ file_okay=False,
319
+ dir_okay=True,
320
+ writable=False,
321
+ readable=True,
322
+ resolve_path=True,
323
+ help="Directory to upload",
324
+ ),
296
325
  recording_session: str = typer.Option(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
297
326
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
298
327
  overwrite: bool = typer.Option(False, help="Overwrite existing configuration if it exists"),
299
- ):
328
+ ) -> None:
329
+ """
330
+ Uploads a broker configuration directory
331
+ """
332
+ # pylint: disable=R0914,R0915
333
+
300
334
  # Must end with /
301
- if not directory.endswith("/"):
302
- directory = f"{directory}/"
303
335
 
304
336
  #
305
337
  # List files in specified directory. Look for interfaces.json and use that directory where this is located
306
338
  # as configuration home directory
307
339
  #
308
- files = list(filter(lambda item: "interfaces.json" in item, glob.iglob(directory + "**/**", recursive=True)))
340
+ files = list(filter(lambda item: "interfaces.json" in item, glob.iglob(str(directory) + "/**/**", recursive=True)))
309
341
  if len(files) == 0:
310
342
  sys.stderr.write("No interfaces.json found in directory, this file is required")
311
343
  raise typer.Exit(1)
@@ -319,14 +351,16 @@ def upload_broker_configuration(
319
351
  # name already exists
320
352
  #
321
353
  # task = progress.add_task(description=f"Preparing upload of {broker_config_dir_name}", total=1)
322
- details_resp = rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
354
+ details_resp = Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
355
+ if details_resp is None:
356
+ return
323
357
  details = details_resp.json()
324
358
  existing_configs = details["brokerConfigurations"]
325
359
  if len(existing_configs) > 0:
326
360
  data = list(filter(lambda x: x["name"] == broker_config_dir_name, existing_configs))
327
361
  if len(data) > 0:
328
362
  if overwrite:
329
- rest.handle_delete(
363
+ Rest.handle_delete(
330
364
  f"/api/project/{project}/files/recording/{recording_session}/configuration/{broker_config_dir_name}", quiet=True
331
365
  )
332
366
  else:
@@ -340,7 +374,7 @@ def upload_broker_configuration(
340
374
  file_infos = list(
341
375
  map(
342
376
  lambda item: {"local_path": item, "remote_path": f"/{broker_config_dir_name}{item.rsplit(broker_config_dir_name, 1)[-1]}"},
343
- glob.iglob(directory + "**/*.*", recursive=True),
377
+ glob.iglob(str(directory) + "/**/*.*", recursive=True),
344
378
  )
345
379
  )
346
380
 
@@ -349,11 +383,13 @@ def upload_broker_configuration(
349
383
  #
350
384
  json_request_upload_urls_req = {"name": "not_used", "paths": list(map(lambda x: x["remote_path"], file_infos))}
351
385
 
352
- response = rest.handle_put(
386
+ response = Rest.handle_put(
353
387
  url=f"/api/project/{project}/files/recording/{recording_session}/configuration",
354
388
  return_response=True,
355
389
  body=json.dumps(json_request_upload_urls_req),
356
390
  )
391
+ if response is None:
392
+ return
357
393
  if response.status_code != 200:
358
394
  print("Failed to prepare configuration upload")
359
395
  print(f"{response.text} - {response.status_code}")
@@ -371,48 +407,52 @@ def upload_broker_configuration(
371
407
  path = file["local_path"]
372
408
  url = upload_urls[key]
373
409
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
374
- r = requests.put(url, open(path, "rb"), headers=headers)
410
+ r = requests.put(url, open(path, "rb"), headers=headers, timeout=60) # pylint: disable=R1732
375
411
  if r.status_code != 200:
376
412
  print("Failed to upload broker configuration")
377
413
  print(r.status_code)
378
414
  print(r.text)
379
- typer.Exit(1)
415
+ raise typer.Exit(1)
380
416
 
381
417
  print(f"Successfully uploaded broker configuration {broker_config_dir_name}")
382
418
 
383
419
 
384
420
  @app.command(help="Downloads the specified broker configuration directory as zip file")
385
- def download_configuration(
421
+ def download_broker_configuration(
386
422
  broker_config_name: str = typer.Argument(..., help="Broker config name"),
387
423
  recording_session: str = typer.Option(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
388
424
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
389
- ):
390
- rest.ensure_auth_token()
391
- r = rest.handle_get(
425
+ ) -> None:
426
+ Rest.ensure_auth_token()
427
+ r = Rest.handle_get(
392
428
  url=f"/api/project/{project}/files/recording/{recording_session}/configuration/{broker_config_name}", return_response=True
393
429
  )
430
+ if r is None:
431
+ return
394
432
  filename = get_filename_from_cd(r.headers.get("content-disposition"))
395
- open(filename, "wb").write(r.content)
396
- print(f"Downloaded file {filename}")
433
+ if filename is not None:
434
+ with open(filename, "wb") as f:
435
+ f.write(r.content)
436
+ print(f"Downloaded file {filename}")
397
437
 
398
438
 
399
- @app.command(help="Downloads the specified broker configuration directory as zip file")
400
- def delete_configuration(
439
+ @app.command(help="Delete the specified broker configuration")
440
+ def delete_broker_configuration(
401
441
  broker_config_name: str = typer.Argument(..., help="Broker config name"),
402
442
  recording_session: str = typer.Option(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
403
443
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
404
- ):
405
- rest.handle_delete(url=f"/api/project/{project}/files/recording/{recording_session}/configuration/{broker_config_name}")
444
+ ) -> None:
445
+ Rest.handle_delete(url=f"/api/project/{project}/files/recording/{recording_session}/configuration/{broker_config_name}")
406
446
 
407
447
 
408
448
  @app.command(help="Copy recording to another project")
409
449
  def copy(
410
450
  recording_session: str = typer.Argument(..., help="Recording session id"),
411
- project: str = typer.Option(..., help="Project to import sample recording into", envvar="REMOTIVE_CLOUD_PROJECT"),
412
- destination_project: str = typer.Option(..., help="Destination project"),
413
- ):
414
- rest.handle_post(
415
- url=f"/api/project/{project}/files/recording/{recording_session}/copy", body=json.dumps({"projectUid": destination_project})
451
+ from_project: str = typer.Option(..., help="Source project"),
452
+ to_project: str = typer.Option(..., help="Destination project"),
453
+ ) -> None:
454
+ Rest.handle_post(
455
+ url=f"/api/project/{from_project}/files/recording/{recording_session}/copy", body=json.dumps({"projectUid": to_project})
416
456
  )
417
457
 
418
458
 
@@ -421,7 +461,7 @@ def play(
421
461
  recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
422
462
  broker: str = typer.Option(None, help="Broker to use"),
423
463
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
424
- ):
464
+ ) -> None:
425
465
  """
426
466
  Plays a recording (Deprecated - Use recordings playback play)"
427
467
  """
@@ -433,32 +473,20 @@ def pause(
433
473
  recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
434
474
  broker: str = typer.Option(None, help="Broker to use"),
435
475
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
436
- ):
476
+ ) -> None:
437
477
  """
438
478
  Pause recording (Deprecated - Use recordings playback pause")
439
479
  """
440
480
  _do_change_playback_mode("pause", recording_session, broker, project)
441
481
 
442
482
 
443
- @app.command(name="playback-status", deprecated=True)
444
- def playback_status(
445
- recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
446
- broker: str = typer.Option(None, help="Broker to use"),
447
- project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
448
- ):
449
- """
450
- Playback progress (Deprecated - Use recordings playback progress)
451
- """
452
- _do_change_playback_mode("status", recording_session, broker, project)
453
-
454
-
455
483
  @app.command(deprecated=True)
456
484
  def seek(
457
485
  recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
458
486
  seconds: int = typer.Option(..., min=0, help="Target offset in seconds"),
459
487
  broker: str = typer.Option(None, help="Broker to use"),
460
488
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
461
- ):
489
+ ) -> None:
462
490
  """
463
491
  Seek into recording (Deprecated - Use recordings playback seek)
464
492
  """
@@ -470,30 +498,36 @@ def stop(
470
498
  recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
471
499
  broker: str = typer.Option(None, help="Broker to use"),
472
500
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
473
- ):
501
+ ) -> None:
474
502
  """
475
503
  Stop recording (Deprecated - Use recordings playback stop)
476
504
  """
477
505
  _do_change_playback_mode("stop", recording_session, broker, project)
478
506
 
479
507
 
480
- def _do_change_playback_mode(mode: str, recording_session: str, broker: str, project: str, seconds: int | None = None): # noqa: C901
481
- response = rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
508
+ # pylint: disable-next=C0301
509
+ def _do_change_playback_mode(mode: str, recording_session: str, broker_name: str, project: str, seconds: int | None = None) -> None: # noqa: C901
510
+ # pylint: disable=R0912,R0914,R0915
511
+ response = Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
512
+ if response is None:
513
+ return
482
514
  r = json.loads(response.text)
483
- recordings: list = r["recordings"]
515
+ recordings: List[Any] = r["recordings"]
484
516
  files = list(map(lambda rec: {"recording": rec["fileName"], "namespace": rec["metadata"]["namespace"]}, recordings))
485
517
 
486
- if broker is not None:
487
- response = rest.handle_get(f"/api/project/{project}/brokers/{broker}", return_response=True, allow_status_codes=[404])
518
+ if broker_name is not None:
519
+ response = Rest.handle_get(f"/api/project/{project}/brokers/{broker_name}", return_response=True, allow_status_codes=[404])
488
520
  else:
489
- response = rest.handle_get(f"/api/project/{project}/brokers/personal", return_response=True, allow_status_codes=[404])
521
+ response = Rest.handle_get(f"/api/project/{project}/brokers/personal", return_response=True, allow_status_codes=[404])
522
+ if response is None:
523
+ return
490
524
  if response.status_code == 404:
491
525
  broker_arg = ""
492
- if broker is not None:
493
- broker_arg = f" --broker {broker} --ensure-broker-started"
526
+ if broker_name is not None:
527
+ broker_arg = f" --broker {broker_name} --ensure-broker-started"
494
528
  ErrorPrinter.print_generic_error("You need to mount the recording before you play")
495
529
  ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session}{broker_arg} --project {project}")
496
- exit(1)
530
+ sys.exit(1)
497
531
 
498
532
  broker_info = json.loads(response.text)
499
533
  broker = Broker(broker_info["url"], None)
@@ -503,7 +537,7 @@ def _do_change_playback_mode(mode: str, recording_session: str, broker: str, pro
503
537
  # error messages
504
538
  tmp = os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
505
539
  broker.download(".cloud.context", tmp, True)
506
- with open(tmp, "r") as f:
540
+ with open(tmp, "r", encoding="utf8") as f:
507
541
  json_context = json.loads(f.read())
508
542
  if json_context["recordingSessionId"] != recording_session:
509
543
  ErrorPrinter.print_generic_error(
@@ -511,33 +545,30 @@ def _do_change_playback_mode(mode: str, recording_session: str, broker: str, pro
511
545
  f"which not the same as you are trying to {mode}, use cmd below to mount this recording"
512
546
  )
513
547
  ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
514
- exit(1)
548
+ sys.exit(1)
515
549
  except grpc.RpcError as rpc_error:
516
- if rpc_error.code() == grpc.StatusCode.NOT_FOUND:
517
- ErrorPrinter.print_generic_error(f"You must use mount to prepare a recording before you can use {mode}")
518
- ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
519
- else:
520
- ErrorPrinter.print_grpc_error(rpc_error)
521
- exit(1)
550
+ # if rpc_error.code() == grpc.StatusCode.NOT_FOUND:
551
+ # ErrorPrinter.print_generic_error(f"You must use mount to prepare a recording before you can use {mode}")
552
+ # ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
553
+ # else:
554
+ ErrorPrinter.print_grpc_error(rpc_error)
555
+ sys.exit(1)
522
556
  if mode == "pause":
523
557
  broker.pause_play(files, True)
524
558
  elif mode == "play":
525
559
  r = broker.play(files, True)
526
560
  elif mode == "seek":
527
- broker.seek(files, int(seconds * 1000000), True)
561
+ if seconds is not None:
562
+ broker.seek(files, int(seconds * 1000000), True)
563
+ else:
564
+ broker.seek(files, 0, True)
528
565
  elif mode == "stop":
529
566
  broker.seek(files, 0, True)
530
- elif mode == "status":
531
- p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True)
532
- t = p.add_task("label", total=1)
533
-
534
- with p:
535
- broker.listen_on_playback(lambda c: p.update(t, description=str(c)))
536
567
  else:
537
- raise Exception(f"Illegal command {mode}")
568
+ raise ValueError(f"Illegal command {mode}")
538
569
 
539
570
 
540
- def get_filename_from_cd(cd):
571
+ def get_filename_from_cd(cd: Union[str, None]) -> Union[str, None]:
541
572
  """
542
573
  Get filename from content-disposition
543
574
  """
@@ -546,10 +577,10 @@ def get_filename_from_cd(cd):
546
577
  fname = re.findall("filename=(.+)", cd)
547
578
  if len(fname) == 0:
548
579
  return None
549
- return fname[0]
580
+ return str(fname[0])
550
581
 
551
582
 
552
- def use_progress(label: str):
583
+ def use_progress(label: str) -> Tuple[Progress, TaskID]:
553
584
  p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True)
554
585
  t = p.add_task(label, total=1)
555
586
  return (p, t)