remotivelabs-cli 0.3.0__py3-none-any.whl → 0.3.2__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.

Potentially problematic release.


This version of remotivelabs-cli might be problematic. Click here for more details.

Files changed (45) hide show
  1. cli/broker/__init__.py +36 -0
  2. cli/broker/discovery.py +43 -0
  3. cli/broker/export.py +6 -36
  4. cli/broker/files.py +12 -12
  5. cli/broker/lib/broker.py +13 -18
  6. cli/broker/lib/helper.py +6 -7
  7. cli/broker/license_flows.py +11 -13
  8. cli/broker/playback.py +10 -10
  9. cli/broker/record.py +4 -4
  10. cli/broker/scripting.py +6 -9
  11. cli/broker/signals.py +17 -19
  12. cli/cloud/auth/cmd.py +17 -14
  13. cli/cloud/auth/login.py +16 -26
  14. cli/cloud/auth_tokens.py +9 -13
  15. cli/cloud/brokers.py +5 -9
  16. cli/cloud/configs.py +4 -17
  17. cli/cloud/organisations.py +8 -10
  18. cli/cloud/projects.py +3 -3
  19. cli/cloud/recordings.py +35 -61
  20. cli/cloud/recordings_playback.py +22 -22
  21. cli/cloud/resumable_upload.py +6 -6
  22. cli/cloud/service_account_tokens.py +3 -2
  23. cli/cloud/storage/cmd.py +2 -3
  24. cli/cloud/storage/copy.py +2 -1
  25. cli/connect/connect.py +4 -4
  26. cli/connect/protopie/protopie.py +21 -29
  27. cli/remotive.py +4 -7
  28. cli/settings/core.py +4 -2
  29. cli/settings/migration/migration_tools.py +2 -1
  30. cli/tools/can/can.py +4 -7
  31. cli/topology/__init__.py +3 -0
  32. cli/topology/cmd.py +61 -83
  33. cli/topology/start_trial.py +105 -0
  34. cli/typer/typer_utils.py +3 -6
  35. cli/utils/console.py +61 -0
  36. cli/utils/rest_helper.py +23 -16
  37. cli/utils/versions.py +2 -4
  38. {remotivelabs_cli-0.3.0.dist-info → remotivelabs_cli-0.3.2.dist-info}/METADATA +1 -1
  39. remotivelabs_cli-0.3.2.dist-info/RECORD +74 -0
  40. cli/broker/brokers.py +0 -93
  41. cli/errors.py +0 -44
  42. remotivelabs_cli-0.3.0.dist-info/RECORD +0 -71
  43. {remotivelabs_cli-0.3.0.dist-info → remotivelabs_cli-0.3.2.dist-info}/LICENSE +0 -0
  44. {remotivelabs_cli-0.3.0.dist-info → remotivelabs_cli-0.3.2.dist-info}/WHEEL +0 -0
  45. {remotivelabs_cli-0.3.0.dist-info → remotivelabs_cli-0.3.2.dist-info}/entry_points.txt +0 -0
cli/remotive.py CHANGED
@@ -3,12 +3,10 @@ from __future__ import annotations
3
3
  import os
4
4
 
5
5
  import typer
6
- from rich import print as rich_print
7
- from rich.console import Console
8
6
  from trogon import Trogon
9
7
  from typer.main import get_group
10
8
 
11
- from cli.broker.brokers import app as broker_app
9
+ from cli.broker import app as broker_app
12
10
  from cli.cloud import app as cloud_app
13
11
  from cli.connect.connect import app as connect_app
14
12
  from cli.settings import Settings, settings
@@ -19,8 +17,7 @@ from cli.tools.tools import app as tools_app
19
17
  from cli.topology.cmd import app as topology_app
20
18
  from cli.typer import typer_utils
21
19
  from cli.utils import versions
22
-
23
- err_console = Console(stderr=True)
20
+ from cli.utils.console import print_generic_error, print_generic_message
24
21
 
25
22
 
26
23
  def is_featue_flag_enabled(env_var: str) -> bool:
@@ -48,7 +45,7 @@ def version_callback(value: bool) -> None:
48
45
 
49
46
  def test_callback(value: int) -> None:
50
47
  if value:
51
- rich_print(value)
48
+ print_generic_message(str(value))
52
49
  raise typer.Exit()
53
50
 
54
51
 
@@ -72,7 +69,7 @@ def run_migrations(settings: Settings) -> None:
72
69
  migrate_config_file(settings.config_file_path, settings)
73
70
 
74
71
  if has_migrated_tokens:
75
- err_console.print("Migrated old credentials and configuration files, you may need to login again or activate correct credentials")
72
+ print_generic_error("Migrated old credentials and configuration files, you may need to login again or activate correct credentials")
76
73
 
77
74
 
78
75
  def set_default_org_as_env(settings: Settings) -> None:
cli/settings/core.py CHANGED
@@ -8,13 +8,13 @@ from typing import Optional
8
8
 
9
9
  from rich.console import Console
10
10
 
11
- from cli.errors import ErrorPrinter
12
11
  from cli.settings import config_file as cf
13
12
  from cli.settings import state_file as sf
14
13
  from cli.settings import token_file as tf
15
14
  from cli.settings.config_file import Account, ConfigFile
16
15
  from cli.settings.state_file import StateFile
17
16
  from cli.settings.token_file import TokenFile
17
+ from cli.utils.console import print_hint
18
18
 
19
19
  err_console = Console(stderr=True)
20
20
 
@@ -63,11 +63,13 @@ class Settings:
63
63
  def set_default_organisation(self, organisation: str) -> None:
64
64
  """
65
65
  Set the default organization for the active account
66
+
67
+ TODO: Raise error, dont sys.exit
66
68
  """
67
69
  config = self._get_cli_config()
68
70
  active_account = config.get_active_account()
69
71
  if not active_account:
70
- ErrorPrinter.print_hint("You must have an account activated in order to set default organization")
72
+ print_hint("You must have an account activated in order to set default organization")
71
73
  sys.exit(1)
72
74
  active_account.default_organization = organisation
73
75
  self._write_config_file(config)
@@ -4,6 +4,7 @@ from itertools import chain
4
4
  from pathlib import Path
5
5
 
6
6
  from cli.settings.token_file import TokenFile
7
+ from cli.utils.console import print_generic_message
7
8
 
8
9
 
9
10
  def list_token_files(config_dir: Path) -> list[TokenFile]:
@@ -20,7 +21,7 @@ def list_token_files(config_dir: Path) -> list[TokenFile]:
20
21
  token_file = TokenFile.from_json_str(file.read_text())
21
22
  token_files.append(token_file)
22
23
  except Exception:
23
- print(f"warning: invalid token file {file}. Consider removing it.")
24
+ print_generic_message(f"warning: invalid token file {file}. Consider removing it.")
24
25
  return token_files
25
26
 
26
27
 
cli/tools/can/can.py CHANGED
@@ -2,12 +2,9 @@ from pathlib import Path
2
2
 
3
3
  import can
4
4
  import typer
5
- from rich.console import Console
6
5
 
7
6
  from cli.typer import typer_utils
8
-
9
- err_console = Console(stderr=True)
10
- console = Console()
7
+ from cli.utils.console import print_generic_error, print_success
11
8
 
12
9
  HELP = """
13
10
  CAN related tools
@@ -49,7 +46,7 @@ def convert(
49
46
  for msg in reader:
50
47
  writer.on_message_received(msg)
51
48
  except Exception as e:
52
- err_console.print(f":boom: [bold red]Failed to convert file[/bold red]: {e}")
49
+ print_generic_error(f"Failed to convert file: {e}")
53
50
 
54
51
 
55
52
  @app.command("validate")
@@ -76,6 +73,6 @@ def validate(
76
73
  for msg in reader:
77
74
  if print_to_terminal:
78
75
  writer.on_message_received(msg)
79
- console.print(f"Successfully verified {in_file}")
76
+ print_success(f"{in_file} verified")
80
77
  except Exception as e:
81
- err_console.print(f":boom: [bold red]Failed to convert file[/bold red]: {e}")
78
+ print_generic_error(f"Failed to convert file: {e}")
@@ -0,0 +1,3 @@
1
+ from cli.topology.cmd import app
2
+
3
+ __all__ = ["app"]
cli/topology/cmd.py CHANGED
@@ -1,101 +1,79 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
- import datetime
5
- from typing import Any
6
-
7
3
  import typer
8
- from rich.console import Console
9
4
 
10
- from cli.errors import ErrorPrinter
11
- from cli.settings import settings
5
+ from cli.topology.start_trial import (
6
+ MissingOrganizationError,
7
+ NoActiveAccountError,
8
+ NotAuthorizedError,
9
+ NotAuthorizedToStartTrialError,
10
+ NotSignedInError,
11
+ SubscriptionExpiredError,
12
+ start_trial,
13
+ )
12
14
  from cli.typer import typer_utils
13
- from cli.utils.rest_helper import RestHelper
15
+ from cli.utils.console import print_generic_error, print_generic_message, print_hint
14
16
 
15
17
  HELP = """
16
- RemotiveTopology commands
18
+ Manage RemotiveTopology resources
17
19
  """
18
- console = Console()
19
- app = typer_utils.create_typer(help=HELP)
20
-
21
-
22
- @dataclasses.dataclass
23
- class Subscription:
24
- type: str
25
- display_name: str
26
- feature: str
27
- start_date: str # TODO: add datetime
28
- end_date: str # TODO: add datetime
29
-
30
20
 
31
- def _print_current_subscription(subscription_info: dict[str, Any]) -> None:
32
- subscription_type = subscription_info.get("subscriptionType")
33
- end_date_str = subscription_info.get("endDate")
34
- now = datetime.datetime.now()
35
-
36
- def parse_date(date_str: str | None) -> datetime.datetime | None:
37
- return datetime.datetime.fromisoformat(date_str) if date_str else None
38
-
39
- expires = parse_date(end_date_str)
40
-
41
- if subscription_type == "trial":
42
- if expires and expires < now:
43
- console.print(f"Your Topology trial expired {end_date_str}, please contact support@remotivelabs.com")
44
- else:
45
- console.print(f"You already have an active topology trial, it expires {end_date_str}")
46
-
47
- elif subscription_type == "paid":
48
- if expires and expires < now:
49
- console.print(f"Topology subscription has ended, expired {end_date_str}")
50
- else:
51
- console.print(f"You already have an active topology subscription, it expires {end_date_str or 'Never'}")
52
-
53
- else:
54
- ErrorPrinter.print_generic_error("Unexpected exception, please contact support@remotivelabs.com")
55
- raise typer.Exit(1)
21
+ app = typer_utils.create_typer(rich_markup_mode="rich", help=HELP)
56
22
 
57
23
 
58
24
  @app.command("start-trial")
59
- def start_trial(
25
+ def start_trial_cmd( # noqa: C901
60
26
  organization: str = typer.Option(None, help="Organization to start trial for", envvar="REMOTIVE_CLOUD_ORGANIZATION"),
61
27
  ) -> None:
62
28
  """
63
- Allows you ta start a 30 day trial subscription for running RemotiveTopology, you can read more at https://docs.remotivelabs.com/docs/remotive-topology.
29
+ Allows you ta start a 30 day trial subscription for running RemotiveTopology.
64
30
 
31
+ You can read more at https://docs.remotivelabs.com/docs/remotive-topology.
65
32
  """
66
- RestHelper.use_progress("Checking access tokens...", transient=True)
67
- active_token = settings.get_active_token_file()
68
- if not active_token:
69
- if len(settings.list_personal_token_files()) == 0:
70
- console.print(
71
- "You must first sign in to RemotiveCloud, please use [bold]remotive cloud auth login[/bold] to sign-in"
72
- "This requires a RemotiveCloud account, if you do not have an account you can sign-up at https://cloud.remotivelabs.com"
73
- )
74
- else:
75
- console.print(
76
- "You have not actived your account, please run [bold]remotive cloud auth activate[/bold] to choose an account"
77
- "or [bold]remotive cloud auth login[/bold] to sign-in"
78
- )
79
- return
80
-
81
- has_access = RestHelper.has_access("/api/whoami")
82
- if not has_access:
83
- ErrorPrinter.print_generic_message("Your current active credentials are not valid")
84
- raise typer.Exit(1)
85
-
86
- active_account = settings.get_active_account()
87
- if active_account and not organization and not active_account.default_organization:
88
- ErrorPrinter.print_hint("You have not specified any organization and no default organization is set")
89
- raise typer.Exit(1)
90
-
91
- sub = RestHelper.handle_get(f"/api/bu/{organization}/features/topology", return_response=True, allow_status_codes=[404, 403])
92
- if sub.status_code == 404:
93
- created = RestHelper.handle_post(f"/api/bu/{organization}/features/topology", return_response=True)
94
- console.print(f"Topology trial started, it expires {created.json()['endDate']}")
95
- elif sub.status_code == 403:
96
- ErrorPrinter.print_generic_error(f"You are not allowed to start-trial topology in organization {organization}")
33
+ try:
34
+ subscription = start_trial(organization)
35
+ if subscription.type == "trial":
36
+ print_generic_message(f"topology trial active, expires {subscription.end_date}")
37
+ elif subscription.type == "paid":
38
+ print_generic_message(f"you already have a topology subscription, expires {subscription.end_date or 'Never'}")
39
+
40
+ except NotSignedInError:
41
+ print_generic_error(
42
+ "You must first sign in to RemotiveCloud, please use [bold]remotive cloud auth login[/bold] to sign-in"
43
+ "This requires a RemotiveCloud account, if you do not have an account you can sign-up at https://cloud.remotivelabs.com"
44
+ )
45
+ raise typer.Exit(2)
46
+
47
+ except NoActiveAccountError:
48
+ print_hint(
49
+ "You have not actived your account, please run [bold]remotive cloud auth activate[/bold] to choose an account"
50
+ "or [bold]remotive cloud auth login[/bold] to sign-in"
51
+ )
52
+ raise typer.Exit(3)
53
+
54
+ except NotAuthorizedError:
55
+ print_hint(
56
+ "Your current active credentials are not valid, please run [bold]remotive cloud auth login[/bold] to sign-in again."
57
+ "This requires a RemotiveCloud account, if you do not have an account you can sign-up at https://cloud.remotivelabs.com"
58
+ )
59
+ raise typer.Exit(4)
60
+
61
+ except MissingOrganizationError:
62
+ print_hint("You have not specified any organization and no default organization is set")
63
+ raise typer.Exit(5)
64
+
65
+ except NotAuthorizedToStartTrialError as e:
66
+ print_generic_error(f"You are not allowed to start-trial topology in organization {e.organization}")
67
+ raise typer.Exit(6)
68
+
69
+ except SubscriptionExpiredError as e:
70
+ if e.subscription.type == "trial":
71
+ print_generic_error(f"Your Topology trial expired {e.subscription.end_date}, please contact support@remotivelabs.com")
72
+ raise typer.Exit(7)
73
+
74
+ print_generic_error(f"Your Topology subscription has expired {e.subscription.end_date}, please contact support@remotivelabs.com")
75
+ raise typer.Exit(7)
76
+
77
+ except Exception as e:
78
+ print_generic_error(f"Unexpected error: {e}")
97
79
  raise typer.Exit(1)
98
- else:
99
- subscription_info = sub.json()
100
- _print_current_subscription(subscription_info)
101
- return
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from dataclasses import dataclass
5
+ from datetime import date
6
+ from typing import Any
7
+
8
+ from cli.settings import settings
9
+ from cli.settings.config_file import Account
10
+ from cli.utils.rest_helper import RestHelper
11
+ from cli.utils.time import parse_date
12
+
13
+
14
+ class NoActiveAccountError(Exception):
15
+ """Raised when the user has no active account, but there are available accounts to activate"""
16
+
17
+
18
+ class NotSignedInError(Exception):
19
+ """Raised when the user has no active account, and no available accounts to activate"""
20
+
21
+
22
+ class NotAuthorizedError(Exception):
23
+ """Raised when the user is not authorized"""
24
+
25
+ def __init__(self, account: Account):
26
+ self.account = account
27
+
28
+
29
+ class MissingOrganizationError(Exception):
30
+ """Raised when the user has not specified an organization and no default organization is set"""
31
+
32
+
33
+ class NotAuthorizedToStartTrialError(Exception):
34
+ """Raised when the user is not authorized to start a topology trial"""
35
+
36
+ def __init__(self, account: Account, organization: str):
37
+ self.account = account
38
+ self.organization = organization
39
+
40
+
41
+ class SubscriptionExpiredError(Exception):
42
+ """Raised when the subscription has expired"""
43
+
44
+ def __init__(self, subscription: Subscription):
45
+ self.subscription = subscription
46
+
47
+
48
+ @dataclass
49
+ class Subscription:
50
+ type: str
51
+ display_name: str
52
+ feature: str
53
+ start_date: date
54
+ end_date: date
55
+
56
+ @staticmethod
57
+ def from_dict(data: Any) -> Subscription:
58
+ if not isinstance(data, dict):
59
+ raise ValueError(f"Invalid subscription data {data}")
60
+
61
+ return Subscription(
62
+ type=data["subscriptionType"],
63
+ display_name=data["displayName"],
64
+ feature=data["feature"],
65
+ start_date=parse_date(data["startDate"]),
66
+ end_date=parse_date(data["endDate"]),
67
+ )
68
+
69
+
70
+ def start_trial(organization: str | None = None) -> Subscription:
71
+ """
72
+ Start a 30 day trial subscription for running RemotiveTopology.
73
+
74
+ # TODO: move authentication (and basic authorization) to a logged_in decorator
75
+ """
76
+ active_account = settings.get_active_account()
77
+ active_token_file = settings.get_active_token_file()
78
+
79
+ if not active_account or not active_token_file:
80
+ if len(settings.list_accounts()) == 0:
81
+ raise NotSignedInError()
82
+ raise NoActiveAccountError()
83
+
84
+ if not RestHelper.has_access("/api/whoami"):
85
+ raise NotAuthorizedError(account=active_account)
86
+
87
+ valid_org = organization or active_account.default_organization
88
+ if not valid_org:
89
+ raise MissingOrganizationError()
90
+
91
+ res = RestHelper.handle_get(f"/api/bu/{valid_org}/features/topology", return_response=True, allow_status_codes=[403, 404])
92
+ if res.status_code == 403:
93
+ raise NotAuthorizedToStartTrialError(account=active_account, organization=valid_org)
94
+ if res.status_code == 404:
95
+ created = RestHelper.handle_post(f"/api/bu/{valid_org}/features/topology", return_response=True)
96
+ subscription = Subscription.from_dict(created.json())
97
+ else:
98
+ # 200 OK means we already have a valid subscription
99
+ subscription = Subscription.from_dict(res.json())
100
+
101
+ # check subscription validity
102
+ if subscription.end_date < datetime.datetime.now().date():
103
+ raise SubscriptionExpiredError(subscription=subscription)
104
+
105
+ return subscription
cli/typer/typer_utils.py CHANGED
@@ -2,24 +2,21 @@ from typing import Any
2
2
 
3
3
  import typer
4
4
  from click import Context
5
- from rich.console import Console
6
5
  from typer.core import TyperGroup
7
6
 
7
+ from cli.utils.console import print_generic_message
8
+
8
9
 
9
10
  class OrderCommands(TyperGroup):
10
11
  def list_commands(self, _ctx: Context): # type: ignore
11
12
  return list(self.commands)
12
13
 
13
14
 
14
- console = Console()
15
-
16
-
17
15
  def create_typer(**kwargs: Any) -> typer.Typer:
18
16
  """Create a Typer instance with default settings."""
19
- # return typer.Typer(no_args_is_help=True, **kwargs)
20
17
  return typer.Typer(cls=OrderCommands, no_args_is_help=True, invoke_without_command=True, **kwargs)
21
18
 
22
19
 
23
20
  def print_padded(label: str, right_text: str, length: int = 30) -> None:
24
21
  padded_label = label.ljust(length) # pad to 30 characters
25
- console.print(f"{padded_label} {right_text}")
22
+ print_generic_message(f"{padded_label} {right_text}")
cli/utils/console.py ADDED
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from typing import Any
6
+
7
+ import grpc
8
+ from rich.console import Console
9
+
10
+ console = Console(soft_wrap=True)
11
+ err_console = Console(stderr=True, soft_wrap=True)
12
+
13
+
14
+ def print_grpc_error(error: grpc.RpcError) -> None:
15
+ if error.code() == grpc.StatusCode.UNAUTHENTICATED:
16
+ is_access_token = os.environ["ACCESS_TOKEN"]
17
+ if is_access_token is not None and is_access_token == "true":
18
+ err_console.print(f":boom: [bold red]Authentication failed[/bold red]: {error.details()}")
19
+ err_console.print("Please login again")
20
+ else:
21
+ err_console.print(":boom: [bold red]Authentication failed[/bold red]")
22
+ err_console.print("Failed to verify api-key")
23
+ else:
24
+ err_console.print(f":boom: [bold red]Unexpected error, status code[/bold red]: {error.code()}")
25
+ err_console.print(error.details())
26
+ sys.exit(1)
27
+
28
+
29
+ def print_hint(message: str) -> None:
30
+ err_console.print(f":point_right: [bold]{message}[/bold]")
31
+
32
+
33
+ def print_generic_error(message: str) -> None:
34
+ err_console.print(f":boom: [bold red]Failed[/bold red]: {message}")
35
+
36
+
37
+ def print_success(message: str) -> None:
38
+ console.print(f"[bold green]Success![/bold green] {message}")
39
+
40
+
41
+ def print_generic_message(message: str) -> None:
42
+ console.print(f"[bold]{message}[/bold]")
43
+
44
+
45
+ def print_newline() -> None:
46
+ """TODO: is this needed?"""
47
+ console.print("\n")
48
+
49
+
50
+ def print_url(url: str) -> None:
51
+ console.print(url, style="bold")
52
+
53
+
54
+ def print_unformatted(message: Any) -> None:
55
+ """TODO: should we allow this?"""
56
+ console.print(message)
57
+
58
+
59
+ def print_unformatted_to_stderr(message: Any) -> None:
60
+ """TODO: should we allow this?"""
61
+ err_console.print(message)
cli/utils/rest_helper.py CHANGED
@@ -12,14 +12,11 @@ from typing import Any, BinaryIO, Dict, List, Optional, Union, cast
12
12
 
13
13
  import requests
14
14
  from requests.exceptions import JSONDecodeError
15
- from rich.console import Console
16
15
  from rich.progress import Progress, SpinnerColumn, TextColumn, wrap_file
17
16
 
18
- from cli.errors import ErrorPrinter
19
17
  from cli.settings import settings
20
18
  from cli.utils import versions
21
-
22
- err_console = Console(stderr=True)
19
+ from cli.utils.console import print_generic_error, print_generic_message, print_hint, print_unformatted, print_unformatted_to_stderr
23
20
 
24
21
  if "REMOTIVE_CLOUD_HTTP_LOGGING" in os.environ:
25
22
  logging.basicConfig()
@@ -84,7 +81,10 @@ class RestHelper:
84
81
 
85
82
  @staticmethod
86
83
  def ensure_auth_token(quiet: bool = False, access_token: Optional[str] = None) -> None:
87
- # TODO: remove this? We already set the default organization as env in remotive.py
84
+ """
85
+ TODO: remove setting org, as we already set the default organization as env in remotive.py?
86
+ TODO: don't sys.exit, raise error instead
87
+ """
88
88
  if "REMOTIVE_CLOUD_ORGANIZATION" not in os.environ:
89
89
  active_account = settings.get_active_account()
90
90
  if active_account:
@@ -98,7 +98,7 @@ class RestHelper:
98
98
  if not token:
99
99
  if quiet:
100
100
  return
101
- ErrorPrinter.print_hint("you are not logged in, please login using [green]remotive cloud auth login[/green]")
101
+ print_hint("you are not logged in, please login using [green]remotive cloud auth login[/green]")
102
102
  sys.exit(1)
103
103
 
104
104
  RestHelper.__headers["Authorization"] = f"Bearer {token.strip()}"
@@ -155,38 +155,45 @@ class RestHelper:
155
155
 
156
156
  @staticmethod
157
157
  def check_api_result(response: requests.Response, allow_status_codes: List[int] | None = None) -> None:
158
+ """
159
+ TODO: don't sys.exit, raise error instead
160
+ """
158
161
  if response.status_code == 426: # CLI upgrade
159
- ErrorPrinter.print_hint(response.text)
162
+ print_hint(response.text)
160
163
  sys.exit(1)
161
164
  if response.status_code > 299:
162
165
  if allow_status_codes is not None and response.status_code in allow_status_codes:
163
166
  return
164
- err_console.print(f":boom: [bold red]Got status code[/bold red]: {response.status_code}")
167
+ print_generic_error(f"Got status code: {response.status_code}")
165
168
  if response.status_code == 401:
166
- err_console.print("Your token is not valid or has expired, please login again or activate another account")
169
+ print_generic_message("Your token is not valid or has expired, please login again or activate another account")
167
170
  else:
168
- err_console.print(response.text)
171
+ print_unformatted_to_stderr(response.text)
169
172
  sys.exit(1)
170
173
 
171
174
  @staticmethod
172
175
  def print_api_result(response: requests.Response) -> None:
176
+ """
177
+ TODO: don't sys.exit, raise error instead
178
+ TODO: dont print from here, return and let caller print instead
179
+ """
173
180
  if response.status_code == 426: # CLI upgrade
174
- ErrorPrinter.print_hint(response.text)
181
+ print_hint(response.text)
175
182
  sys.exit(1)
176
183
 
177
184
  if response.status_code >= 200 and response.status_code < 300:
178
185
  if len(response.content) >= 2:
179
186
  try:
180
- print(json.dumps(response.json()))
187
+ print_unformatted(json.dumps(response.json()))
181
188
  except JSONDecodeError:
182
- err_console.print(":boom: [bold red]Json parse error[/bold red]: Please try again and report if the error persists")
189
+ print_generic_error("Json parse error: Please try again and report if the error persists")
183
190
  sys.exit(0)
184
191
  else:
185
- err_console.print(f":boom: [bold red]Got status code[/bold red]: {response.status_code}")
192
+ print_generic_error(f"Got status code: {response.status_code}")
186
193
  if response.status_code == 401:
187
- err_console.print("Your token is not valid or has expired, please login again or activate another account")
194
+ print_generic_message("Your token is not valid or has expired, please login again or activate another account")
188
195
  else:
189
- err_console.print(response.text)
196
+ print_unformatted_to_stderr(response.text)
190
197
  sys.exit(1)
191
198
 
192
199
  @staticmethod
cli/utils/versions.py CHANGED
@@ -10,8 +10,8 @@ from importlib.metadata import version as python_project_version
10
10
 
11
11
  from packaging.version import InvalidVersion, Version
12
12
 
13
- from cli.errors import ErrorPrinter
14
13
  from cli.settings import Settings
14
+ from cli.utils.console import print_hint
15
15
 
16
16
 
17
17
  def cli_version() -> str:
@@ -117,6 +117,4 @@ def _print_update_info(cur: str, latest: str) -> None:
117
117
  else "upgrade with: pipx install -U remotivelabs-cli"
118
118
  )
119
119
 
120
- ErrorPrinter.print_hint(
121
- f"Update available: remotivelabs-cli {cur} → {latest} , ({instructions}) we always recommend to use latest version"
122
- )
120
+ print_hint(f"Update available: remotivelabs-cli {cur} → {latest} , ({instructions}) we always recommend to use latest version")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: remotivelabs-cli
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: CLI for operating RemotiveCloud and RemotiveBroker
5
5
  Author: Johan Rask
6
6
  Author-email: johan.rask@remotivelabs.com