remotivelabs-cli 0.0.41__py3-none-any.whl → 0.1.0a1__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/.DS_Store +0 -0
  2. cli/api/cloud/tokens.py +62 -0
  3. cli/broker/brokers.py +0 -1
  4. cli/broker/export.py +4 -4
  5. cli/broker/lib/broker.py +9 -13
  6. cli/broker/license_flows.py +1 -1
  7. cli/broker/scripting.py +2 -1
  8. cli/broker/signals.py +9 -10
  9. cli/cloud/auth/cmd.py +25 -6
  10. cli/cloud/auth/login.py +279 -24
  11. cli/cloud/auth_tokens.py +295 -12
  12. cli/cloud/brokers.py +3 -4
  13. cli/cloud/cloud_cli.py +2 -2
  14. cli/cloud/configs.py +1 -2
  15. cli/cloud/organisations.py +92 -20
  16. cli/cloud/projects.py +1 -2
  17. cli/cloud/recordings.py +9 -16
  18. cli/cloud/recordings_playback.py +6 -8
  19. cli/cloud/sample_recordings.py +2 -3
  20. cli/cloud/service_account_tokens.py +21 -5
  21. cli/cloud/service_accounts.py +32 -4
  22. cli/cloud/storage/cmd.py +1 -1
  23. cli/cloud/storage/copy.py +3 -4
  24. cli/connect/connect.py +1 -1
  25. cli/connect/protopie/protopie.py +12 -14
  26. cli/remotive.py +30 -6
  27. cli/settings/__init__.py +1 -2
  28. cli/settings/config_file.py +85 -0
  29. cli/settings/core.py +195 -46
  30. cli/settings/migrate_all_token_files.py +74 -0
  31. cli/settings/migrate_token_file.py +52 -0
  32. cli/settings/token_file.py +69 -4
  33. cli/tools/can/can.py +2 -2
  34. cli/typer/typer_utils.py +18 -1
  35. cli/utils/__init__.py +0 -0
  36. cli/{cloud → utils}/rest_helper.py +109 -38
  37. {remotivelabs_cli-0.0.41.dist-info → remotivelabs_cli-0.1.0a1.dist-info}/METADATA +6 -4
  38. remotivelabs_cli-0.1.0a1.dist-info/RECORD +59 -0
  39. {remotivelabs_cli-0.0.41.dist-info → remotivelabs_cli-0.1.0a1.dist-info}/WHEEL +1 -1
  40. cli/settings/cmd.py +0 -72
  41. remotivelabs_cli-0.0.41.dist-info/RECORD +0 -54
  42. {remotivelabs_cli-0.0.41.dist-info → remotivelabs_cli-0.1.0a1.dist-info}/LICENSE +0 -0
  43. {remotivelabs_cli-0.0.41.dist-info → remotivelabs_cli-0.1.0a1.dist-info}/entry_points.txt +0 -0
cli/cloud/auth_tokens.py CHANGED
@@ -1,33 +1,316 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Optional, Union
4
+
1
5
  import typer
6
+ from rich.console import Console
7
+ from rich.table import Table
2
8
 
3
- from cli.settings import settings
9
+ from cli.api.cloud import tokens
10
+ from cli.cloud.organisations import do_select_default_org
11
+ from cli.errors import ErrorPrinter
12
+ from cli.settings import TokenFile, TokenNotFoundError, settings
4
13
  from cli.typer import typer_utils
14
+ from cli.utils.rest_helper import RestHelper as Rest
5
15
 
6
- from .rest_helper import RestHelper as Rest
16
+ console = Console(stderr=False)
17
+ err_console = Console(stderr=True)
7
18
 
8
19
  app = typer_utils.create_typer()
9
20
 
10
21
 
11
- # TODO: add add interactive flag to set target directory # pylint: disable=fixme
12
- @app.command(name="create", help="Create and download a new personal access token")
13
- def create(activate: bool = typer.Option(False, help="Activate the token for use after download")) -> None: # pylint: disable=W0621
14
- response = Rest.handle_post(url="/api/me/keys", return_response=True)
15
- pat = settings.add_personal_token(response.text)
22
+ def _prompt_choice( # noqa: C901, PLR0912
23
+ choices: List[TokenFile],
24
+ skip_prompt: bool = False,
25
+ info_message: Optional[str] = None,
26
+ ) -> Optional[Union[TokenFile, None]]:
27
+ accounts = settings.get_cli_config().accounts
28
+
29
+ try:
30
+ active_account = settings.get_cli_config().get_active()
31
+ except TokenNotFoundError:
32
+ active_account = None
33
+
34
+ table = Table("#", "Active", "Type", "Token", "Account", "Created", "Expires")
35
+
36
+ included_tokens: list[TokenFile] = []
37
+ excluded_tokens: list[TokenFile] = []
38
+
39
+ for token in choices:
40
+ account = accounts.get(token.account.email)
41
+ if account and account.credentials_name and account.credentials_name in (token.name or ""):
42
+ included_tokens.append(token)
43
+ else:
44
+ excluded_tokens.append(token)
45
+ if len(excluded_tokens) > 0:
46
+ err_console.print("The following credentials were not included in account config and cannot be activated")
47
+ for token in excluded_tokens:
48
+ err_console.print(f" * {token.name} - {token.account.email}")
49
+
50
+ if len(included_tokens) == 0:
51
+ return None
52
+
53
+ if info_message is not None:
54
+ console.print(info_message)
55
+
56
+ included_tokens.sort(key=lambda token: token.created, reverse=True)
57
+
58
+ for idx, choice in enumerate(included_tokens, start=1):
59
+ table.add_row(
60
+ f"[yellow]{idx}",
61
+ ":white_check_mark:" if active_account is not None and active_account.credentials_name == choice.name else "",
62
+ "user" if choice.type == "authorized_user" else "sa",
63
+ choice.name,
64
+ f"[bold]{choice.account.email if choice.account else 'unknown'}[/bold]",
65
+ str(choice.created),
66
+ str(choice.expires),
67
+ )
68
+
69
+ # console.print("It seems like you have access tokens from previous login, you can select one of these instead of logging in")
70
+ console.print(table)
71
+
72
+ if skip_prompt:
73
+ return None
74
+
75
+ typer.echo("")
76
+ selection = typer.prompt(f"Enter the number(# 1-{len(included_tokens)}) of the token to select (or q to skip)")
77
+
78
+ if selection == "q":
79
+ return None
80
+ try:
81
+ index = int(selection) - 1
82
+ if 0 <= index < len(included_tokens):
83
+ return included_tokens[index]
84
+ raise ValueError
85
+ except ValueError:
86
+ typer.echo("Invalid choice, please try again")
87
+ return _prompt_choice(included_tokens, skip_prompt, info_message)
88
+
89
+
90
+ # @app.command(name="create")
91
+ def create(
92
+ activate: bool = typer.Option(False, help="Activate the token for use after download"),
93
+ ) -> None:
94
+ """
95
+ Create a new personal access token in [bold]cloud[/bold] and download locally
96
+ """
97
+ response = tokens.create()
98
+ pat = settings.add_personal_token(response.text())
16
99
  print(f"Personal access token added: {pat.name}")
17
100
 
18
101
  if not activate:
19
102
  print(f"Use 'remotive cloud auth tokens activate {pat.name}' to use this access token from cli")
20
103
  else:
21
- settings.activate_token(pat.name)
104
+ settings.activate_token(pat)
22
105
  print("Token file activated and ready for use")
23
106
  print("\033[93m This file contains secrets and must be kept safe")
24
107
 
25
108
 
26
- @app.command(name="list", help="List personal access tokens")
109
+ # @app.command(name="list", help="List personal credentials in [bold]cloud[/bold]")
27
110
  def list_tokens() -> None:
28
111
  Rest.handle_get("/api/me/keys")
29
112
 
30
113
 
31
- @app.command(name="revoke", help="Revoke personal access token")
32
- def revoke(name: str = typer.Argument(help="Access token name")) -> None:
33
- Rest.handle_delete(f"/api/me/keys/{name}")
114
+ # @app.command(name="revoke")
115
+ def revoke(
116
+ name: str = typer.Argument(help="Access token name"),
117
+ delete: bool = typer.Option(True, help="Also delete token"),
118
+ ) -> None:
119
+ """
120
+ Revoke personal credentials in cloud and removes the file from filesystem
121
+
122
+ If cloud token is not found but token is found on file system it will delete it and
123
+ vice versa.
124
+ """
125
+ _revoke_and_delete_personal_token(name, delete)
126
+
127
+
128
+ # @app.command(name="activate")
129
+ def activate(
130
+ token_name: str = typer.Argument(..., help="Token path, filename or name to activate"),
131
+ ) -> None:
132
+ """
133
+ Activate a credential file to be used for authentication using filename, path or name.
134
+
135
+ This will be used as the current access token in all requests.
136
+ """
137
+ try:
138
+ token_file = settings.get_token_file(token_name)
139
+ settings.activate_token(token_file)
140
+ except TokenNotFoundError:
141
+ err_console.print(f":boom: [bold red] Error: [/bold red] Token with filename or name {token_name} could not be found")
142
+
143
+
144
+ @app.command("activate")
145
+ def select_personal_token(
146
+ token_name: str = typer.Argument(None, help="Name, filename or path to a credentials file"),
147
+ ) -> None:
148
+ """
149
+ Prompts user to select and activate one of the available credential files
150
+ """
151
+ if token_name is not None:
152
+ token_selected = True
153
+ try:
154
+ token_file = settings.get_token_file(token_name)
155
+ settings.activate_token(token_file)
156
+ except TokenNotFoundError:
157
+ token_selected = False
158
+ err_console.print(f":boom: [bold red] Error: [/bold red] Token with filename or name {token_name} could not be found")
159
+ else:
160
+ token_files = settings.list_personal_tokens()
161
+ token_files.extend(settings.list_service_account_tokens())
162
+ if len(token_files) > 0:
163
+ token_selected = list_and_select_personal_token(include_service_accounts=True)
164
+
165
+ if token_selected:
166
+ is_logged_in = Rest.has_access("/api/whoami")
167
+ if not is_logged_in:
168
+ ErrorPrinter.print_generic_error("Could not access RemotiveCloud with selected token")
169
+ else:
170
+ console.print("[green]Success![/green] Access to RemotiveCloud granted")
171
+ else:
172
+ ErrorPrinter.print_hint("No credentials available, login to activate credentials")
173
+
174
+ if settings.get_cli_config().get_active_default_organisation() is None:
175
+ set_default_organisation = typer.confirm(
176
+ "You have not set a default organisation\nWould you like to choose one now?",
177
+ abort=False,
178
+ default=True,
179
+ )
180
+ if set_default_organisation:
181
+ do_select_default_org(get=False)
182
+
183
+
184
+ def list_and_select_personal_token(
185
+ skip_prompt: bool = False,
186
+ include_service_accounts: bool = False,
187
+ info_message: Optional[str] = None,
188
+ ) -> bool:
189
+ personal_tokens = settings.list_personal_tokens()
190
+
191
+ if include_service_accounts:
192
+ sa_tokens = settings.list_service_account_tokens()
193
+ personal_tokens.extend(sa_tokens)
194
+
195
+ # merged = _merge_local_tokens_with_cloud(personal_tokens)
196
+
197
+ selected_token = _prompt_choice(personal_tokens, skip_prompt=skip_prompt, info_message=info_message)
198
+ if selected_token is not None:
199
+ settings.activate_token(selected_token)
200
+ return True
201
+ return False
202
+
203
+
204
+ # @app.command("select-revoke")
205
+ def select_revoke_personal_token() -> None:
206
+ """
207
+ Prompts a user to select one of the credential files to revoke and delete
208
+ """
209
+ personal_tokens = settings.list_personal_tokens()
210
+ sa_tokens = settings.list_service_account_tokens()
211
+ personal_tokens.extend(sa_tokens)
212
+
213
+ is_logged_in = Rest.has_access("/api/whoami")
214
+ if not is_logged_in:
215
+ ErrorPrinter.print_hint("You must be logged in")
216
+ raise typer.Exit(0)
217
+
218
+ # merged = _merge_local_tokens_with_cloud(personal_tokens)
219
+
220
+ selected_token = _prompt_choice(personal_tokens)
221
+
222
+ if selected_token is not None:
223
+ _revoke_and_delete_personal_token(selected_token.name, True)
224
+ # Rest.handle_patch(f"/api/me/keys/{selected_token.name}/revoke", quiet=True, access_token=selected_token.token)
225
+ # Rest.handle_delete(f"/api/me/keys/{selected_token.name}", quiet=True, access_token=selected_token.token)
226
+ # settings.remove_token_file(selected_token.name)
227
+ # active_token = settings.get_active_token_file()
228
+ # if active_token.name == selected_token.name:
229
+ # settings.clear_active_token()
230
+ # select_revoke_personal_token()
231
+
232
+
233
+ # @app.command("test-all")
234
+ def test_all_personal_tokens() -> None:
235
+ """
236
+ Tests each credential file to see if it is valid
237
+ """
238
+ personal_tokens = settings.list_personal_tokens()
239
+ personal_tokens.extend(settings.list_service_account_tokens())
240
+ if len(personal_tokens) == 0:
241
+ console.print("No personal tokens found on disk")
242
+ return
243
+
244
+ for token in personal_tokens:
245
+ r = Rest.handle_get(
246
+ "/api/whoami",
247
+ allow_status_codes=[401],
248
+ access_token=token.token,
249
+ use_progress_indicator=True,
250
+ return_response=True,
251
+ )
252
+ if r.status_code == 200:
253
+ if token.account is not None:
254
+ console.print(f"{token.account.email} ({token.name}) :white_check_mark:")
255
+ else:
256
+ console.print(f"{token.name} :white_check_mark:")
257
+ elif token.account is not None:
258
+ console.print(f"{token.account.email} ({token.name}) :x: Failed")
259
+ else:
260
+ console.print(f"{token.name} :x: Failed")
261
+
262
+
263
+ # @app.command(name="list-service-account-tokens-files")
264
+ def list_sats_files() -> None:
265
+ """
266
+ List service account access token files in remotivelabs config directory
267
+ """
268
+ service_account_files = settings.list_service_account_token_files()
269
+ for file in service_account_files:
270
+ print(file)
271
+
272
+
273
+ @app.command(name="list")
274
+ def list_pats_files() -> None:
275
+ """
276
+ Lists available credential files on filesystem
277
+ """
278
+ personal_files = settings.list_personal_token_files()
279
+ service_account_files = settings.list_service_account_token_files()
280
+ personal_files.extend(service_account_files)
281
+ for file in personal_files:
282
+ print(file)
283
+
284
+
285
+ def _revoke_and_delete_personal_token(name: str, delete: bool) -> None:
286
+ token_file = None
287
+
288
+ # First we try to find the file and make sure its not the currently active
289
+ try:
290
+ token_file = settings.get_token_file(name)
291
+ active_token = settings.get_active_token_file()
292
+ if token_file.name == active_token.name:
293
+ ErrorPrinter.print_hint("You cannot revoke the current active token")
294
+ return
295
+ except TokenNotFoundError:
296
+ pass
297
+
298
+ # The lets try to revoke from cloud
299
+ res_revoke = tokens.revoke(name)
300
+ if delete:
301
+ res_delete = tokens.delete(name)
302
+ if res_delete.is_success:
303
+ ErrorPrinter.print_generic_message("Token successfully revoked and deleted")
304
+ else:
305
+ ErrorPrinter.print_hint(f"Failed to revoke and delete token in cloud: {res_delete.status_code}")
306
+ elif res_revoke.is_success:
307
+ ErrorPrinter.print_generic_message("Token successfully revoked")
308
+ else:
309
+ ErrorPrinter.print_hint("Failed to revoke and delete token in cloud")
310
+
311
+ # Finally try to remove the file if exists
312
+ if token_file is not None:
313
+ settings.remove_token_file(token_file.name)
314
+ console.print("Successfully deleted token on filesystem")
315
+ else:
316
+ ErrorPrinter.print_hint("Token not found on filesystem")
cli/cloud/brokers.py CHANGED
@@ -10,14 +10,13 @@ import typer
10
10
  import websocket
11
11
 
12
12
  from cli.typer import typer_utils
13
-
14
- from .rest_helper import RestHelper as Rest
13
+ from cli.utils.rest_helper import RestHelper as Rest
15
14
 
16
15
  app = typer_utils.create_typer()
17
16
 
18
17
 
19
18
  @app.command("list", help="Lists brokers in project")
20
- def list(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None: # pylint: disable=W0622
19
+ def list(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
21
20
  Rest.handle_get(f"/api/project/{project}/brokers")
22
21
 
23
22
 
@@ -94,7 +93,7 @@ def logs(
94
93
  def on_message(_wsapp: Any, message: str) -> None:
95
94
  print(message)
96
95
 
97
- def on_error(_wsapp: Any, err: str) -> None: # pylint: disable=W0613
96
+ def on_error(_wsapp: Any, err: str) -> None:
98
97
  print("EXAMPLE error encountered: ", err)
99
98
 
100
99
  Rest.ensure_auth_token()
cli/cloud/cloud_cli.py CHANGED
@@ -1,8 +1,8 @@
1
1
  import typer
2
2
 
3
3
  from cli.cloud import auth, brokers, configs, organisations, projects, recordings, sample_recordings, service_accounts, storage
4
- from cli.cloud.rest_helper import RestHelper
5
4
  from cli.typer import typer_utils
5
+ from cli.utils.rest_helper import RestHelper
6
6
 
7
7
  app = typer_utils.create_typer()
8
8
 
@@ -15,7 +15,7 @@ def licenses(
15
15
  RestHelper.handle_get(f"/api/bu/{organisation}/licenses", {"filter": filter_option})
16
16
 
17
17
 
18
- app.add_typer(organisations.app, name="organisations", help="Manage organisations")
18
+ app.add_typer(organisations.app, name="organisations", help="Manage organizations")
19
19
  app.add_typer(projects.app, name="projects", help="Manage projects")
20
20
  app.add_typer(auth.app, name="auth")
21
21
  app.add_typer(brokers.app, name="brokers", help="Manage cloud broker lifecycle")
cli/cloud/configs.py CHANGED
@@ -8,8 +8,7 @@ import typer
8
8
  from rich.progress import Progress, SpinnerColumn, TextColumn
9
9
 
10
10
  from cli.typer import typer_utils
11
-
12
- from .rest_helper import RestHelper as Rest
11
+ from cli.utils.rest_helper import RestHelper as Rest
13
12
 
14
13
  app = typer_utils.create_typer()
15
14
 
@@ -1,29 +1,101 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
- import sys
4
+ from dataclasses import dataclass
5
+ from typing import List, Optional
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
3
10
 
4
- from cli.cloud.rest_helper import RestHelper
11
+ from cli.settings import settings
5
12
  from cli.typer import typer_utils
13
+ from cli.utils.rest_helper import RestHelper
6
14
 
15
+ console = Console(stderr=False)
7
16
  app = typer_utils.create_typer()
8
17
 
9
18
 
10
- @app.command(name="list", help="List your available organisations")
11
- def list_orgs() -> None:
12
- r = RestHelper.handle_get("/api/home", return_response=True)
13
- if r is None:
14
- return
15
- if r.status_code == 200:
16
- j = list(
17
- map(
18
- lambda x: {
19
- "uid": x["billableUnitUser"]["billableUnit"]["uid"],
20
- "displayName": x["billableUnitUser"]["billableUnit"]["displayName"],
21
- },
22
- r.json(),
23
- )
19
+ @dataclass
20
+ class Organisation:
21
+ display_name: str
22
+ uid: str
23
+
24
+
25
+ def _prompt_choice(choices: List[Organisation]) -> Optional[Organisation]:
26
+ table = Table("#", "Name", "Uid", "Default")
27
+
28
+ token = settings.get_active_token_file()
29
+ config = settings.get_cli_config()
30
+
31
+ current_default_org = config.accounts[token.account.email].default_organization if token is not None else None
32
+
33
+ for idx, choice in enumerate(choices, start=1):
34
+ table.add_row(
35
+ f"[yellow]{idx}",
36
+ f"[bold]{choice.display_name}[/bold]",
37
+ choice.uid,
38
+ ":thumbsup:" if current_default_org is not None and current_default_org == choice.uid else "",
24
39
  )
25
- print(json.dumps(j))
40
+ console.print(table)
41
+
42
+ typer.echo("")
43
+ selection = typer.prompt(f"Enter the number(# 1-{len(choices)}) of the organisation to select (or q to quit)")
44
+
45
+ if selection == "q":
46
+ return None
47
+ try:
48
+ index = int(selection) - 1
49
+ if 0 <= index < len(choices):
50
+ return choices[index]
51
+ raise ValueError
52
+ except ValueError:
53
+ typer.echo("Invalid choice, please try again")
54
+ return _prompt_choice(choices)
55
+
56
+
57
+ @app.command("default")
58
+ def select_default_org(
59
+ organisation_uid: str = typer.Argument(None, help="Organisation uid or empty to select one"),
60
+ get: bool = typer.Option(False, help="Print current default organisation"),
61
+ ) -> None:
62
+ do_select_default_org(organisation_uid, get)
63
+
64
+
65
+ def do_select_default_org(organisation_uid: Optional[str] = None, get: bool = False) -> None:
66
+ r"""
67
+ Set default organisation for the currently activated user, empty to choose from available organizations or organization uid as argument
68
+
69
+ remotive cloud organizations default my_org \[set specific org uid]
70
+ remotive cloud organizations default \[select one from prompt]
71
+ remotive cloud organizations default --get \[print current default]
72
+
73
+ Note that service-accounts does Not have permission to list organizations and will get a 403 Forbidden response so you must
74
+ select the organization uid as argument
75
+ """
76
+ if get:
77
+ default_organisation = settings.get_cli_config().get_active_default_organisation()
78
+ if default_organisation is not None:
79
+ console.print(default_organisation)
80
+ else:
81
+ console.print("No default organisation set")
82
+ elif organisation_uid is not None:
83
+ settings.set_default_organisation(organisation_uid)
26
84
  else:
27
- print(f"Got status code: {r.status_code}")
28
- print(r.text)
29
- sys.exit(1)
85
+ r = RestHelper.handle_get("/api/bu", return_response=True)
86
+ orgs = r.json()
87
+ orgs = [Organisation(display_name=o["organisation"]["displayName"], uid=o["organisation"]["uid"]) for o in orgs]
88
+
89
+ selected = _prompt_choice(orgs)
90
+
91
+ if selected is not None:
92
+ settings.get_cli_config()
93
+ typer.echo(f"Default organisation: {selected.display_name} (uid: {selected.uid})")
94
+ settings.set_default_organisation(selected.uid)
95
+
96
+
97
+ @app.command(name="list", help="List your available organizations")
98
+ def list_orgs() -> None:
99
+ r = RestHelper.handle_get("/api/bu", return_response=True)
100
+ orgs = [{"uid": org["organisation"]["uid"], "displayName": org["organisation"]["displayName"]} for org in r.json()]
101
+ print(json.dumps(orgs))
cli/cloud/projects.py CHANGED
@@ -3,8 +3,7 @@ import json
3
3
  import typer
4
4
 
5
5
  from cli.typer import typer_utils
6
-
7
- from .rest_helper import RestHelper as Rest
6
+ from cli.utils.rest_helper import RestHelper as Rest
8
7
 
9
8
  app = typer_utils.create_typer()
10
9
 
cli/cloud/recordings.py CHANGED
@@ -19,11 +19,11 @@ from typing_extensions import Annotated
19
19
  from cli.cloud.uri import URI
20
20
  from cli.errors import ErrorPrinter
21
21
  from cli.typer import typer_utils
22
+ from cli.utils.rest_helper import RestHelper as Rest
23
+ from cli.utils.rest_helper import err_console
22
24
 
23
25
  from ..broker.lib.broker import Broker
24
26
  from .recordings_playback import app as playback_app
25
- from .rest_helper import RestHelper as Rest
26
- from .rest_helper import err_console
27
27
 
28
28
  app = typer_utils.create_typer()
29
29
  app.add_typer(playback_app, name="playback")
@@ -129,7 +129,6 @@ def mount( # noqa: C901
129
129
  transformation_name: str = typer.Option("default", help="Specify a custom signal transformation to use"),
130
130
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
131
131
  ) -> None:
132
- # pylint: disable=R0912
133
132
  Rest.ensure_auth_token()
134
133
 
135
134
  Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
@@ -230,7 +229,7 @@ def delete_recording_file(
230
229
 
231
230
 
232
231
  @app.command()
233
- def upload( # noqa: C901
232
+ def upload( # noqa: C901, PLR0912, PLR0915
234
233
  path: Path = typer.Argument(
235
234
  ...,
236
235
  exists=True,
@@ -244,7 +243,6 @@ def upload( # noqa: C901
244
243
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
245
244
  recording_session: Optional[str] = typer.Option(default=None, help="Optional existing recording to upload file to"),
246
245
  ) -> None:
247
- # pylint: disable=R0912,R0914,R0915
248
246
  """
249
247
  Uploads a recording to RemotiveCloud.
250
248
  Except for recordings from RemotiveBroker you can also upload Can ASC (.asc), Can BLF(.blf) and Can LOG (.log, .txt)
@@ -281,8 +279,7 @@ def upload( # noqa: C901
281
279
  return
282
280
 
283
281
  # Exact same as in cloud console
284
- def get_processing_message(step: str) -> str:
285
- # pylint: disable=R0911
282
+ def get_processing_message(step: str) -> str: # noqa: PLR0911
286
283
  if step == "REQUESTED":
287
284
  return "Preparing file..."
288
285
  if step == "VALIDATING":
@@ -333,9 +330,9 @@ def upload( # noqa: C901
333
330
  err_console.print(f":boom: [bold red]Got status code[/bold red]: {upload_response.status_code} {upload_response.text}")
334
331
 
335
332
 
336
- # TODO - Change to use Path for directory # pylint: disable=W0511
333
+ # TODO - Change to use Path for directory
337
334
  @app.command()
338
- def upload_broker_configuration( # noqa: C901
335
+ def upload_broker_configuration(
339
336
  directory: Path = typer.Argument(
340
337
  ...,
341
338
  exists=True,
@@ -353,8 +350,6 @@ def upload_broker_configuration( # noqa: C901
353
350
  """
354
351
  Uploads a broker configuration directory
355
352
  """
356
- # pylint: disable=R0914,R0915
357
-
358
353
  # Must end with /
359
354
 
360
355
  #
@@ -431,7 +426,7 @@ def upload_broker_configuration( # noqa: C901
431
426
  path = file["local_path"]
432
427
  url = upload_urls[key]
433
428
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
434
- r = requests.put(url, open(path, "rb"), headers=headers, timeout=60) # pylint: disable=R1732
429
+ r = requests.put(url, open(path, "rb"), headers=headers, timeout=60)
435
430
  if r.status_code != 200:
436
431
  print("Failed to upload broker configuration")
437
432
  print(r.status_code)
@@ -529,11 +524,9 @@ def stop(
529
524
  _do_change_playback_mode("stop", recording_session, broker, project)
530
525
 
531
526
 
532
- # pylint: disable-next=C0301
533
- def _do_change_playback_mode(
527
+ def _do_change_playback_mode( # noqa: PLR0912
534
528
  mode: str, recording_session: str, broker_name: Optional[str], project: str, seconds: Optional[int] = None
535
- ) -> None: # noqa: C901
536
- # pylint: disable=R0912,R0914,R0915
529
+ ) -> None:
537
530
  response = Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
538
531
  if response is None:
539
532
  return
@@ -15,10 +15,10 @@ from rich import print as rich_print
15
15
  from rich.progress import Progress, SpinnerColumn, TextColumn
16
16
 
17
17
  from cli.errors import ErrorPrinter
18
+ from cli.utils.rest_helper import RestHelper as Rest
18
19
 
19
20
  from ..broker.lib.broker import Broker, SubscribableSignal
20
21
  from ..typer import typer_utils
21
- from .rest_helper import RestHelper as Rest
22
22
 
23
23
  app = typer_utils.create_typer(
24
24
  help="""
@@ -101,9 +101,8 @@ def read_scripted_code_file(file_path: Path) -> bytes:
101
101
  return file.read()
102
102
 
103
103
 
104
- # pylint: disable=R0913
105
104
  @app.command()
106
- def subscribe(
105
+ def subscribe( # noqa: PLR0913
107
106
  recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
108
107
  broker: str = typer.Option(None, help="Broker to use"),
109
108
  signal: List[str] = typer.Option(None, help="Signal names to subscribe to, mandatory when not using script"),
@@ -156,12 +155,12 @@ def subscribe(
156
155
  except grpc.RpcError as rpc_error:
157
156
  ErrorPrinter.print_grpc_error(rpc_error)
158
157
 
159
- except Exception as e: # pylint: disable=W0718
158
+ except Exception as e:
160
159
  ErrorPrinter.print_generic_error(str(e))
161
160
  sys.exit(1)
162
161
 
163
162
 
164
- def _do_change_playback_mode( # noqa: C901
163
+ def _do_change_playback_mode( # noqa: C901, PLR0913, PLR0912
165
164
  mode: str,
166
165
  recording_session: str,
167
166
  brokerstr: str,
@@ -169,8 +168,7 @@ def _do_change_playback_mode( # noqa: C901
169
168
  seconds: int | None = None,
170
169
  progress_on_play: bool = False,
171
170
  repeat: bool = False,
172
- ) -> None: # noqa: C901
173
- # pylint: disable=R0913,R0912
171
+ ) -> None:
174
172
  response = Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
175
173
  if response is None:
176
174
  return
@@ -247,7 +245,7 @@ def _verify_recording_on_broker(broker: Broker, recording_session: str, mode: st
247
245
  ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
248
246
  sys.exit(1)
249
247
  except grpc.RpcError as rpc_error:
250
- if rpc_error.code() == grpc.StatusCode.NOT_FOUND: # pylint: disable=no-member
248
+ if rpc_error.code() == grpc.StatusCode.NOT_FOUND:
251
249
  ErrorPrinter.print_generic_error(f"You must use mount to prepare a recording before you can use {mode}")
252
250
  ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
253
251
  else:
@@ -3,8 +3,7 @@ import json
3
3
  import typer
4
4
 
5
5
  from cli.typer import typer_utils
6
-
7
- from .rest_helper import RestHelper as Rest
6
+ from cli.utils.rest_helper import RestHelper as Rest
8
7
 
9
8
  app = typer_utils.create_typer()
10
9
 
@@ -18,7 +17,7 @@ def do_import(
18
17
 
19
18
 
20
19
  @app.command("list")
21
- def list() -> None: # pylint: disable=W0622
20
+ def list() -> None:
22
21
  """
23
22
  List available sample recordings
24
23
  """