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

@@ -56,6 +56,6 @@ def revoke(
56
56
  Rest.handle_patch(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}/revoke", quiet=True)
57
57
  if delete:
58
58
  Rest.handle_delete(f"/api/project/{project}/admin/accounts/{service_account}/keys/{name}", quiet=True)
59
- token_with_name = [token for token in settings.list_service_account_tokens() if token.name == name]
59
+ token_with_name = [token for token in settings.list_service_account_token_files() if token.name == name]
60
60
  if len(token_with_name) > 0:
61
61
  settings.remove_token_file(name)
@@ -11,12 +11,12 @@ from typing import Any, Dict, List, Tuple, Union
11
11
 
12
12
  import grpc
13
13
  import socketio
14
- from remotivelabs.broker.sync import BrokerException, Client, SignalIdentifier, SignalsInFrame
15
14
  from rich import print as pretty_print
16
15
  from rich.console import Console
17
16
  from socketio.exceptions import ConnectionError as SocketIoConnectionError
18
17
 
19
18
  from cli.broker.lib.broker import SubscribableSignal
19
+ from cli.broker.lib.client import BrokerException, Client, SignalIdentifier, SignalsInFrame
20
20
  from cli.errors import ErrorPrinter
21
21
  from cli.settings import settings
22
22
 
cli/remotive.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
- from importlib.metadata import version
5
4
 
6
5
  import typer
7
6
  from rich import print as rich_print
@@ -10,17 +9,16 @@ from trogon import Trogon
10
9
  from typer.main import get_group
11
10
 
12
11
  from cli.broker.brokers import app as broker_app
13
- from cli.cloud.cloud_cli import app as cloud_app
12
+ from cli.cloud import app as cloud_app
14
13
  from cli.connect.connect import app as connect_app
15
- from cli.settings import settings
16
- from cli.settings.core import Settings
14
+ from cli.settings import Settings, settings
17
15
  from cli.settings.migration.migrate_all_token_files import migrate_any_legacy_tokens
18
16
  from cli.settings.migration.migrate_config_file import migrate_config_file
19
17
  from cli.settings.migration.migrate_legacy_dirs import migrate_legacy_settings_dirs
20
18
  from cli.tools.tools import app as tools_app
21
19
  from cli.topology.cmd import app as topology_app
22
20
  from cli.typer import typer_utils
23
- from cli.utils.version_check import check_for_update
21
+ from cli.utils import versions
24
22
 
25
23
  err_console = Console(stderr=True)
26
24
 
@@ -45,8 +43,7 @@ For documentation - https://docs.remotivelabs.com
45
43
 
46
44
  def version_callback(value: bool) -> None:
47
45
  if value:
48
- my_version = version("remotivelabs-cli")
49
- typer.echo(my_version)
46
+ typer.echo(f"remotivelabs-cli {versions.cli_version()} ({versions.platform_info()})")
50
47
 
51
48
 
52
49
  def test_callback(value: int) -> None:
@@ -56,7 +53,7 @@ def test_callback(value: int) -> None:
56
53
 
57
54
 
58
55
  def check_for_newer_version(settings: Settings) -> None:
59
- check_for_update("remotivelabs-cli", version("remotivelabs-cli"), settings)
56
+ versions.check_for_update(settings)
60
57
 
61
58
 
62
59
  def run_migrations(settings: Settings) -> None:
@@ -84,9 +81,9 @@ def set_default_org_as_env(settings: Settings) -> None:
84
81
  This has to be done early before it is read
85
82
  """
86
83
  if "REMOTIVE_CLOUD_ORGANIZATION" not in os.environ:
87
- org = settings.get_cli_config().get_active_default_organisation()
88
- if org is not None:
89
- os.environ["REMOTIVE_CLOUD_ORGANIZATION"] = org
84
+ active_account = settings.get_active_account()
85
+ if active_account and active_account.default_organization:
86
+ os.environ["REMOTIVE_CLOUD_ORGANIZATION"] = active_account.default_organization
90
87
 
91
88
 
92
89
  @app.callback()
@@ -115,21 +112,15 @@ def tui(ctx: typer.Context) -> None:
115
112
 
116
113
 
117
114
  app.add_typer(broker_app, name="broker", help="Manage a single broker - local or cloud")
118
- app.add_typer(
119
- cloud_app,
120
- name="cloud",
121
- help="Manage resources in RemotiveCloud",
122
- )
115
+ app.add_typer(cloud_app, name="cloud", help="Manage resources in RemotiveCloud")
123
116
  app.add_typer(connect_app, name="connect", help="Integrations with other systems")
124
117
  app.add_typer(tools_app, name="tools")
118
+ app.add_typer(
119
+ topology_app,
120
+ name="topology",
121
+ help="""
122
+ Interact and manage RemotiveTopology resources
125
123
 
126
- if is_featue_flag_enabled("REMOTIVE_TOPOLOGY_ENABLED"):
127
- app.add_typer(
128
- topology_app,
129
- name="topology",
130
- help="""
131
- RemotiveTopology actions
132
-
133
- Read more at https://docs.remotivelabs.com/docs/remotive-topology
134
- """,
135
- )
124
+ Read more at https://docs.remotivelabs.com/docs/remotive-topology
125
+ """,
126
+ )
cli/settings/__init__.py CHANGED
@@ -1,14 +1,13 @@
1
1
  from cli.settings.config_file import Account, ConfigFile
2
2
  from cli.settings.config_file import dumps as dumps_config_file
3
3
  from cli.settings.config_file import loads as loads_config_file
4
- from cli.settings.core import InvalidSettingsFilePathError, Settings, TokenNotFoundError, settings
4
+ from cli.settings.core import InvalidSettingsFilePathError, Settings, settings
5
5
  from cli.settings.token_file import TokenFile
6
6
  from cli.settings.token_file import dumps as dumps_token_file
7
7
  from cli.settings.token_file import loads as loads_token_file
8
8
 
9
9
  __all__ = [
10
10
  "settings",
11
- "TokenNotFoundError",
12
11
  "InvalidSettingsFilePathError",
13
12
  "Settings",
14
13
  "TokenFile",
@@ -1,42 +1,48 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
- import json
5
- from dataclasses import dataclass
6
3
  from typing import Any, Optional
7
4
 
8
- from dacite import from_dict
5
+ from pydantic import BaseModel, Field, model_validator
9
6
 
10
7
  from cli.settings.token_file import TokenFile
11
8
 
12
9
 
13
- def loads(data: str) -> ConfigFile:
14
- d = json.loads(data)
15
- return from_dict(ConfigFile, d)
16
-
17
-
18
- def dumps(config: ConfigFile) -> str:
19
- return json.dumps(dataclasses.asdict(config), default=str)
10
+ class Account(BaseModel):
11
+ """
12
+ Account represents an account in the configuration file.
20
13
 
14
+ TODO: Add email field to Account
15
+ """
21
16
 
22
- @dataclass
23
- class Account:
24
17
  credentials_file: str
25
18
  default_organization: Optional[str] = None
26
- # Add project as well
27
19
 
28
20
 
29
- @dataclass
30
- class ConfigFile:
21
+ class ConfigFile(BaseModel):
22
+ """
23
+ ConfigFile represents the configuration file for the CLI.
24
+
25
+ TODO: Should all setters return a new instance of the ConfigFile?
26
+ """
27
+
31
28
  version: str = "1.0"
32
29
  active: Optional[str] = None
33
- accounts: dict[str, Account] = dataclasses.field(default_factory=dict)
30
+ accounts: dict[str, Account] = Field(default_factory=dict)
31
+
32
+ @model_validator(mode="before")
33
+ @classmethod
34
+ def _validate_json_data(cls, json_data: Any) -> Any:
35
+ """Try to migrate old formats and missing fields as best we can."""
36
+ if not isinstance(json_data, dict):
37
+ return json_data
34
38
 
35
- def get_active_default_organisation(self) -> Optional[str]:
36
- active_account = self.get_active()
37
- return active_account.default_organization if active_account else None
39
+ # If the active account is not in accounts, remove it
40
+ if "active" in json_data and json_data["active"] not in json_data["accounts"]:
41
+ del json_data["active"]
38
42
 
39
- def get_active(self) -> Optional[Account]:
43
+ return json_data
44
+
45
+ def get_active_account(self) -> Optional[Account]:
40
46
  if not self.active:
41
47
  return None
42
48
  account = self.get_account(self.active)
@@ -44,50 +50,64 @@ class ConfigFile:
44
50
  raise KeyError(f"Activated account {self.active} is not a valid account")
45
51
  return account
46
52
 
47
- def activate(self, email: str) -> None:
53
+ def activate_account(self, email: str) -> None:
48
54
  account = self.get_account(email)
49
55
  if not account:
50
56
  raise KeyError(f"Account {email} does not exists")
51
57
  self.active = email
52
58
 
59
+ def _update_account(self, email: str, **updates: Any) -> None:
60
+ """TODO: Consider using model_copy and always return a new instance of ConfigFile"""
61
+ existing_account = self.get_account(email)
62
+ if existing_account:
63
+ updated_account = existing_account.model_copy(update=updates)
64
+ else:
65
+ updated_account = Account(**updates)
66
+
67
+ new_accounts = {**self.accounts, email: updated_account}
68
+ self.accounts = new_accounts
69
+
70
+ def init_account(self, email: str, token_file: TokenFile) -> None:
71
+ """
72
+ Create a new account with the given email and token file.
73
+ """
74
+ self._update_account(email, credentials_file=token_file.get_token_file_name())
75
+
76
+ def set_default_organization_for_account(self, email: str, default_organization: Optional[str] = None) -> None:
77
+ if not self.get_account(email):
78
+ raise KeyError(f"Account with email {email} has not been initialized with token")
79
+ self._update_account(email, default_organization=default_organization)
80
+
53
81
  def get_account(self, email: str) -> Optional[Account]:
54
- if not self.accounts:
55
- return None
56
- return self.accounts.get(email, None)
82
+ return self.accounts.get(email)
57
83
 
58
84
  def remove_account(self, email: str) -> None:
59
- if self.accounts:
60
- self.accounts.pop(email, None)
85
+ self.accounts.pop(email, None)
61
86
 
62
- def init_account(self, email: str, token_file: TokenFile) -> None:
63
- if self.accounts is None:
64
- self.accounts = {}
87
+ @classmethod
88
+ def from_json_str(cls, data: str) -> ConfigFile:
89
+ return cls.model_validate_json(data)
65
90
 
66
- account = self.get_account(email)
67
- if not account:
68
- account = Account(credentials_file=token_file.get_token_file_name())
69
- else:
70
- account.credentials_file = token_file.get_token_file_name()
71
- self.accounts[email] = account
91
+ @classmethod
92
+ def from_dict(cls, data: dict[str, Any]) -> ConfigFile:
93
+ return cls.model_validate(data)
72
94
 
73
- def set_account_field(self, email: str, default_organization: Optional[str] = None) -> ConfigFile:
74
- if self.accounts is None:
75
- self.accounts = {}
95
+ def to_json_str(self) -> str:
96
+ return self.model_dump_json()
76
97
 
77
- account = self.get_account(email)
78
- if not account:
79
- raise KeyError(f"Account with email {email} has not been initialized with token")
98
+ def to_dict(self) -> dict[str, Any]:
99
+ return self.model_dump()
80
100
 
81
- # Update only fields explicitly passed
82
- if default_organization is not None:
83
- account.default_organization = default_organization
84
101
 
85
- return self
102
+ def loads(data: str) -> ConfigFile:
103
+ """
104
+ Creates a ConfigFile from a JSON string.
105
+ """
106
+ return ConfigFile.from_json_str(data)
86
107
 
87
- @staticmethod
88
- def from_json_str(data: str) -> ConfigFile:
89
- return loads(data)
90
108
 
91
- @staticmethod
92
- def from_dict(data: dict[str, Any]) -> ConfigFile:
93
- return from_dict(ConfigFile, data)
109
+ def dumps(config: ConfigFile) -> str:
110
+ """
111
+ Returns the JSON string representation of the ConfigFile.
112
+ """
113
+ return config.to_json_str()