remotivelabs-cli 0.0.24__py3-none-any.whl → 0.0.26__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 (42) 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 +59 -49
  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 +11 -11
  11. cli/cloud/__init__.py +0 -1
  12. cli/cloud/auth.py +40 -35
  13. cli/cloud/auth_tokens.py +39 -36
  14. cli/cloud/brokers.py +24 -33
  15. cli/cloud/cloud_cli.py +11 -7
  16. cli/cloud/configs.py +19 -11
  17. cli/cloud/filestorage.py +155 -0
  18. cli/cloud/projects.py +10 -7
  19. cli/cloud/recordings.py +127 -108
  20. cli/cloud/recordings_playback.py +52 -39
  21. cli/cloud/rest_helper.py +248 -190
  22. cli/cloud/resumable_upload.py +62 -0
  23. cli/cloud/sample_recordings.py +5 -5
  24. cli/cloud/service_account_tokens.py +18 -16
  25. cli/cloud/service_accounts.py +9 -9
  26. cli/connect/__init__.py +0 -1
  27. cli/connect/connect.py +7 -6
  28. cli/connect/protopie/protopie.py +32 -16
  29. cli/errors.py +6 -5
  30. cli/remotive.py +13 -9
  31. cli/requirements.txt +4 -1
  32. cli/settings.py +9 -9
  33. cli/tools/__init__.py +0 -1
  34. cli/tools/can/__init__.py +0 -1
  35. cli/tools/can/can.py +8 -8
  36. cli/tools/tools.py +2 -2
  37. {remotivelabs_cli-0.0.24.dist-info → remotivelabs_cli-0.0.26.dist-info}/METADATA +6 -4
  38. remotivelabs_cli-0.0.26.dist-info/RECORD +44 -0
  39. remotivelabs_cli-0.0.24.dist-info/RECORD +0 -42
  40. {remotivelabs_cli-0.0.24.dist-info → remotivelabs_cli-0.0.26.dist-info}/LICENSE +0 -0
  41. {remotivelabs_cli-0.0.24.dist-info → remotivelabs_cli-0.0.26.dist-info}/WHEEL +0 -0
  42. {remotivelabs_cli-0.0.24.dist-info → remotivelabs_cli-0.0.26.dist-info}/entry_points.txt +0 -0
cli/cloud/configs.py CHANGED
@@ -7,22 +7,22 @@ import requests
7
7
  import typer
8
8
  from rich.progress import Progress, SpinnerColumn, TextColumn
9
9
 
10
- from . import rest_helper as rest
10
+ from .rest_helper import RestHelper as Rest
11
11
 
12
12
  app = typer.Typer()
13
13
 
14
14
 
15
15
  @app.command("list")
16
- def list_signal_databases(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
17
- rest.handle_get(f"/api/project/{project}/files/config")
16
+ def list_signal_databases(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
17
+ Rest.handle_get(f"/api/project/{project}/files/config")
18
18
 
19
19
 
20
20
  @app.command("delete")
21
21
  def delete(
22
22
  signal_db_file: str = typer.Argument("", help="Signal database file"),
23
23
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
24
- ):
25
- rest.handle_delete(f"/api/project/{project}/files/config/{signal_db_file}")
24
+ ) -> None:
25
+ Rest.handle_delete(f"/api/project/{project}/files/config/{signal_db_file}")
26
26
 
27
27
 
28
28
  @app.command()
@@ -38,35 +38,42 @@ def upload(
38
38
  help="Path to signal database file to upload",
39
39
  ),
40
40
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
41
- ):
41
+ ) -> None:
42
42
  """
43
43
  Uploads signal database to project
44
44
  """
45
- rest.upload_file(path=path, url=f"/api/project/{project}/files/config/{os.path.basename(path)}/uploadfile")
45
+ res = Rest.handle_put(url=f"/api/project/{project}/files/config/{os.path.basename(path)}/uploadfile", return_response=True)
46
+ if res is not None:
47
+ Rest.upload_file_with_signed_url(
48
+ path=path, url=res.text, upload_headers={"Content-Type": "application/octet-stream"}, progress_label=f"Uploading {path}..."
49
+ )
46
50
 
47
51
 
48
52
  @app.command()
49
53
  def download(
50
54
  signal_db_file: str = typer.Argument("", help="Signal database file"),
51
55
  project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
52
- ):
56
+ ) -> None:
53
57
  with Progress(
54
58
  SpinnerColumn(),
55
59
  TextColumn("[progress.description]{task.description}"),
56
60
  transient=True,
57
61
  ) as progress:
58
- rest.ensure_auth_token()
62
+ Rest.ensure_auth_token()
59
63
 
60
64
  progress.add_task(description=f"Downloading {signal_db_file}", total=None)
61
65
 
62
66
  # First request the download url from cloud. This is a public signed url that is valid
63
67
  # for a short period of time
64
68
  get_signed_url_resp = requests.get(
65
- f"{rest.base_url}/api/project/{project}/files/config/{signal_db_file}/download", headers=rest.headers, allow_redirects=True
69
+ f"{Rest.get_base_url()}/api/project/{project}/files/config/{signal_db_file}/download",
70
+ headers=Rest.get_headers(),
71
+ allow_redirects=True,
72
+ timeout=60,
66
73
  )
67
74
  if get_signed_url_resp.status_code == 200:
68
75
  # Next download the actual file
69
- download_resp = requests.get(url=get_signed_url_resp.text, stream=True)
76
+ download_resp = requests.get(url=get_signed_url_resp.text, stream=True, timeout=60)
70
77
  if download_resp.status_code == 200:
71
78
  with open(signal_db_file, "wb") as out_file:
72
79
  shutil.copyfileobj(download_resp.raw, out_file)
@@ -86,4 +93,5 @@ def download(
86
93
  # print(r.status_code)
87
94
  # print(r.text)
88
95
 
96
+ # pylint: disable=C0301
89
97
  # curl -X PUT -H 'Content-Type: application/octet-stream' --upload-file docker-compose.yml 'https://storage.googleapis.com/beamylabs-fileuploads-dev/projects/beamyhack/recording/myrecording?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=recordings-upload-account%40beamycloud-dev.iam.gserviceaccount.com%2F20220729%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20220729T134012Z&X-Goog-Expires=3000&X-Goog-SignedHeaders=content-type%3Bhost&X-Goog-Signature=d1fa7639349d6453aebfce8814d6e5685af03952d07aa4e3cb0d44dba7cf5e572f684c8120dba17cbc7ea6a0ef5450542a3c745c65e04272b34265d0ddcf1b67e6f2b5bfa446264a62d77bd7faabf45ad6bd2aec5225f57004b0a31cfe0480cba063a3807d86346b1da99ecbae3f3e6da8f44f06396dfc1fdc6f89e475abdf969142cef6f369f03aff41000c8abb28aa82185246746fd6c16b6b381baa2d586382a3d3067b6376ddba2b55b2b6f9d942913a1cbfbc61491ba6a615d7d5a0d9a476c357431143e9cea1411dfad9f01b1e1176dc8c056cbf08cccfd401a55d63c19d038f3ab42b712abc48d759047ac07862c4fae937c341e19b568bb60a4e4086'
@@ -0,0 +1,155 @@
1
+ import os.path
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ import typer
6
+
7
+ from ..errors import ErrorPrinter
8
+ from . import resumable_upload as upload
9
+ from .rest_helper import RestHelper as Rest
10
+
11
+ app = typer.Typer(
12
+ rich_markup_mode="rich",
13
+ help="""
14
+ Manage files ([yellow]Beta feature not available for all customers[/yellow])
15
+
16
+ Copy file from local to remote storage and vice versa, list and delete files.
17
+
18
+ """,
19
+ )
20
+
21
+
22
+ @app.command(name="ls")
23
+ def list_files(
24
+ project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
25
+ prefix: str = typer.Argument(default="rcs://", help="Remote storage path"),
26
+ ) -> None:
27
+ """
28
+ Listing remote files
29
+
30
+ This will list files and directories in project top level directory
31
+ remotive cloud storage ls rcs://
32
+
33
+ This will list all files and directories matching the path
34
+ remotive cloud storage ls rcs://fileOrDirectoryPrefix
35
+
36
+ This will list all files and directories in the specified directory
37
+ remotive cloud storage ls rcs://fileOrDirectory/
38
+ """
39
+
40
+ if prefix.startswith("rcs://"):
41
+ prefix = __check_rcs_path(prefix)
42
+ else:
43
+ ErrorPrinter.print_hint("Path must start with rcs://")
44
+ sys.exit(1)
45
+
46
+ Rest.handle_get(
47
+ f"/api/project/{project}/files/storage{prefix}",
48
+ )
49
+
50
+
51
+ @app.command(name="rm")
52
+ def delete_file(
53
+ project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
54
+ path: str = typer.Argument(default=..., help="Remote storage path to file to delete"),
55
+ ) -> None:
56
+ """
57
+ [red]Deletes[/red] a file from remote storage, this cannot be undone :fire:
58
+
59
+ [white]remotive cloud storage rm rcs://directory/filename[/white]
60
+ """
61
+ if path.startswith("rcs://"):
62
+ prefix = __check_rcs_path(path)
63
+ else:
64
+ ErrorPrinter.print_hint("Path must start with rcs://")
65
+ sys.exit(1)
66
+
67
+ Rest.handle_delete(
68
+ f"/api/project/{project}/files/storage{prefix}",
69
+ )
70
+
71
+
72
+ @app.command(name="cp")
73
+ def copy_file( # noqa: C901 # type: ignore[too-many-branches] # pylint: disable=no-member
74
+ source: str = typer.Argument(default=..., help="Remote or local path to source file"),
75
+ dest: str = typer.Argument(default=..., help="Remote or local path to destination file"),
76
+ project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
77
+ ) -> None:
78
+ """
79
+ Copies a file to or from remote storage
80
+
81
+ remotive cloud storage cp rcs://dir/filename .
82
+ remotive cloud storage cp rcs://dir/filename filename
83
+
84
+ remotive cloud storage cp filename rcs://dir/
85
+ remotive cloud storage cp filename rcs://dir/filename
86
+ """
87
+
88
+ if not source.startswith("rcs://") and not dest.startswith("rcs://"):
89
+ ErrorPrinter.print_hint("Source or destination path must be an rcs:// path")
90
+ sys.exit(2)
91
+
92
+ if source.startswith("rcs://") and dest.startswith("rcs://"):
93
+ ErrorPrinter.print_hint("Currently one of source and destination path must be a local path")
94
+ sys.exit(2)
95
+
96
+ if source.startswith("rcs://"):
97
+ __copy_to_local(source=source, dest=dest, project=project)
98
+ else:
99
+ __copy_to_remote(source=source, dest=dest, project=project)
100
+
101
+
102
+ def __copy_to_remote(source: str, dest: str, project: str) -> None:
103
+ path = Path(source)
104
+ if not path.exists():
105
+ ErrorPrinter.print_hint("Source file does not exist")
106
+ sys.exit(1)
107
+ filename = source.rsplit("/", 1)[-1]
108
+ rcs_path = __check_rcs_path(dest)
109
+ if rcs_path.endswith("/"):
110
+ rcs_path = rcs_path + filename
111
+ res = Rest.handle_post(f"/api/project/{project}/files/storage{rcs_path}", return_response=True)
112
+ if res is None:
113
+ return
114
+ json = res.json()
115
+ url = json["url"]
116
+ content_type = json["contentType"]
117
+ try:
118
+ upload.upload_signed_url(url, source, content_type)
119
+ except IsADirectoryError:
120
+ ErrorPrinter.print_hint(f"Supplied source file '{source}' is a directory but must be a file")
121
+
122
+
123
+ def __copy_to_local(source: str, dest: str, project: str) -> None:
124
+ rcs_path = __check_rcs_path(source)
125
+ filename = source.rsplit("/", 1)[-1]
126
+ path = Path(dest)
127
+ if path.is_dir():
128
+ if not path.exists():
129
+ ErrorPrinter.print_generic_error("Destination directory does not exist")
130
+ sys.exit(1)
131
+ else:
132
+ dest = os.path.join(path.absolute(), filename)
133
+
134
+ else:
135
+ if not path.parent.is_dir() or not path.parent.exists():
136
+ ErrorPrinter.print_generic_error("Destination directory does not exist")
137
+ sys.exit(1)
138
+ dest = str(Path(dest).absolute())
139
+
140
+ res = Rest.handle_get(
141
+ f"/api/project/{project}/files/storage{rcs_path}?download=true",
142
+ return_response=True,
143
+ )
144
+ if res is None:
145
+ return
146
+
147
+ Rest.download_file(save_file_name=dest, url=res.text)
148
+
149
+
150
+ def __check_rcs_path(path: str) -> str:
151
+ rcs_path = path.replace("rcs://", "/")
152
+ if rcs_path.startswith("/."):
153
+ ErrorPrinter.print_hint("Invalid path")
154
+ sys.exit(1)
155
+ return rcs_path
cli/cloud/projects.py CHANGED
@@ -2,14 +2,17 @@ import json
2
2
 
3
3
  import typer
4
4
 
5
- from . import rest_helper as rest
5
+ from .rest_helper import RestHelper as Rest
6
6
 
7
7
  app = typer.Typer()
8
8
 
9
9
 
10
10
  @app.command(name="list", help="List your projects")
11
- def list_projects(organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION")):
12
- r = rest.handle_get(url=f"/api/bu/{organisation}/me", return_response=True)
11
+ def list_projects(organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION")) -> None:
12
+ r = Rest.handle_get(url=f"/api/bu/{organisation}/me", return_response=True)
13
+ if r is None:
14
+ return
15
+
13
16
  if r.status_code == 200:
14
17
  # extract the project uid parts
15
18
  projects = r.json()["projects"]
@@ -25,16 +28,16 @@ def create_project(
25
28
  organisation: str = typer.Option(..., help="Organisation ID", envvar="REMOTIVE_CLOUD_ORGANISATION"),
26
29
  project_uid: str = typer.Option(..., help="Project UID"),
27
30
  project_display_name: str = typer.Option(default="", help="Project display name"),
28
- ):
31
+ ) -> None:
29
32
  create_project_req = {
30
33
  "uid": project_uid,
31
34
  "displayName": project_display_name if project_display_name != "" else project_uid,
32
35
  "description": "",
33
36
  }
34
37
 
35
- rest.handle_post(url=f"/api/bu/{organisation}/project", body=json.dumps(create_project_req))
38
+ Rest.handle_post(url=f"/api/bu/{organisation}/project", body=json.dumps(create_project_req))
36
39
 
37
40
 
38
41
  @app.command(name="delete")
39
- def delete(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")):
40
- rest.handle_delete(url=f"/api/project/{project}")
42
+ def delete(project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT")) -> None:
43
+ Rest.handle_delete(url=f"/api/project/{project}")