remotivelabs-cli 0.5.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 (84) hide show
  1. remotivelabs/cli/__init__.py +0 -0
  2. remotivelabs/cli/api/cloud/tokens.py +62 -0
  3. remotivelabs/cli/broker/__init__.py +33 -0
  4. remotivelabs/cli/broker/defaults.py +1 -0
  5. remotivelabs/cli/broker/discovery.py +43 -0
  6. remotivelabs/cli/broker/export.py +92 -0
  7. remotivelabs/cli/broker/files.py +119 -0
  8. remotivelabs/cli/broker/lib/__about__.py +4 -0
  9. remotivelabs/cli/broker/lib/broker.py +625 -0
  10. remotivelabs/cli/broker/lib/client.py +224 -0
  11. remotivelabs/cli/broker/lib/helper.py +277 -0
  12. remotivelabs/cli/broker/lib/signalcreator.py +196 -0
  13. remotivelabs/cli/broker/license_flows.py +167 -0
  14. remotivelabs/cli/broker/licenses.py +98 -0
  15. remotivelabs/cli/broker/playback.py +117 -0
  16. remotivelabs/cli/broker/record.py +41 -0
  17. remotivelabs/cli/broker/recording_session/__init__.py +3 -0
  18. remotivelabs/cli/broker/recording_session/client.py +67 -0
  19. remotivelabs/cli/broker/recording_session/cmd.py +254 -0
  20. remotivelabs/cli/broker/recording_session/time.py +49 -0
  21. remotivelabs/cli/broker/scripting.py +129 -0
  22. remotivelabs/cli/broker/signals.py +220 -0
  23. remotivelabs/cli/broker/version.py +31 -0
  24. remotivelabs/cli/cloud/__init__.py +17 -0
  25. remotivelabs/cli/cloud/auth/__init__.py +3 -0
  26. remotivelabs/cli/cloud/auth/cmd.py +128 -0
  27. remotivelabs/cli/cloud/auth/login.py +283 -0
  28. remotivelabs/cli/cloud/auth_tokens.py +149 -0
  29. remotivelabs/cli/cloud/brokers.py +109 -0
  30. remotivelabs/cli/cloud/configs.py +109 -0
  31. remotivelabs/cli/cloud/licenses/__init__.py +0 -0
  32. remotivelabs/cli/cloud/licenses/cmd.py +14 -0
  33. remotivelabs/cli/cloud/organisations.py +112 -0
  34. remotivelabs/cli/cloud/projects.py +44 -0
  35. remotivelabs/cli/cloud/recordings.py +580 -0
  36. remotivelabs/cli/cloud/recordings_playback.py +274 -0
  37. remotivelabs/cli/cloud/resumable_upload.py +87 -0
  38. remotivelabs/cli/cloud/sample_recordings.py +25 -0
  39. remotivelabs/cli/cloud/service_account_tokens.py +62 -0
  40. remotivelabs/cli/cloud/service_accounts.py +72 -0
  41. remotivelabs/cli/cloud/storage/__init__.py +5 -0
  42. remotivelabs/cli/cloud/storage/cmd.py +76 -0
  43. remotivelabs/cli/cloud/storage/copy.py +86 -0
  44. remotivelabs/cli/cloud/storage/uri_or_path.py +45 -0
  45. remotivelabs/cli/cloud/uri.py +113 -0
  46. remotivelabs/cli/connect/__init__.py +0 -0
  47. remotivelabs/cli/connect/connect.py +118 -0
  48. remotivelabs/cli/connect/protopie/protopie.py +185 -0
  49. remotivelabs/cli/py.typed +0 -0
  50. remotivelabs/cli/remotive.py +123 -0
  51. remotivelabs/cli/settings/__init__.py +20 -0
  52. remotivelabs/cli/settings/config_file.py +113 -0
  53. remotivelabs/cli/settings/core.py +333 -0
  54. remotivelabs/cli/settings/migration/__init__.py +0 -0
  55. remotivelabs/cli/settings/migration/migrate_all_token_files.py +80 -0
  56. remotivelabs/cli/settings/migration/migrate_config_file.py +64 -0
  57. remotivelabs/cli/settings/migration/migrate_legacy_dirs.py +50 -0
  58. remotivelabs/cli/settings/migration/migrate_token_file.py +52 -0
  59. remotivelabs/cli/settings/migration/migration_tools.py +38 -0
  60. remotivelabs/cli/settings/state_file.py +67 -0
  61. remotivelabs/cli/settings/token_file.py +128 -0
  62. remotivelabs/cli/tools/__init__.py +0 -0
  63. remotivelabs/cli/tools/can/__init__.py +0 -0
  64. remotivelabs/cli/tools/can/can.py +78 -0
  65. remotivelabs/cli/tools/tools.py +9 -0
  66. remotivelabs/cli/topology/__init__.py +28 -0
  67. remotivelabs/cli/topology/all.py +322 -0
  68. remotivelabs/cli/topology/cli/__init__.py +3 -0
  69. remotivelabs/cli/topology/cli/run_in_docker.py +58 -0
  70. remotivelabs/cli/topology/cli/topology_cli.py +16 -0
  71. remotivelabs/cli/topology/cmd.py +130 -0
  72. remotivelabs/cli/topology/start_trial.py +134 -0
  73. remotivelabs/cli/typer/__init__.py +0 -0
  74. remotivelabs/cli/typer/typer_utils.py +27 -0
  75. remotivelabs/cli/utils/__init__.py +0 -0
  76. remotivelabs/cli/utils/console.py +99 -0
  77. remotivelabs/cli/utils/rest_helper.py +369 -0
  78. remotivelabs/cli/utils/time.py +11 -0
  79. remotivelabs/cli/utils/versions.py +120 -0
  80. remotivelabs_cli-0.5.0a1.dist-info/METADATA +51 -0
  81. remotivelabs_cli-0.5.0a1.dist-info/RECORD +84 -0
  82. remotivelabs_cli-0.5.0a1.dist-info/WHEEL +4 -0
  83. remotivelabs_cli-0.5.0a1.dist-info/entry_points.txt +3 -0
  84. remotivelabs_cli-0.5.0a1.dist-info/licenses/LICENSE +17 -0
@@ -0,0 +1,167 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ from typing import Union
6
+
7
+ import grpc
8
+ import requests
9
+ import typer
10
+ from rich.progress import Progress, SpinnerColumn, TextColumn
11
+
12
+ from remotivelabs.cli.broker.lib.broker import Broker, LicenseInfo
13
+ from remotivelabs.cli.utils.console import print_unformatted_to_stderr
14
+ from remotivelabs.cli.utils.rest_helper import RestHelper as Rest
15
+
16
+
17
+ class LicenseFlow:
18
+ def describe_with_url(self, url: str) -> LicenseInfo:
19
+ b = LicenseFlow.__try_connect_to_broker(
20
+ url=url,
21
+ progress_label=f"Fetching license from broker ({url})...",
22
+ on_error_progress_label=f"Fetching license from broker ({url})... make sure broker is running.",
23
+ )
24
+ return b.get_license()
25
+
26
+ def describe_with_hotspot(self, url: Union[str, None] = "http://192.168.4.1:50051") -> LicenseInfo:
27
+ if url is None:
28
+ url = "http://192.168.4.1:50051"
29
+
30
+ b = LicenseFlow.__try_connect_to_broker(
31
+ url=url, progress_label=f"Fetching license from broker using hotspot ({url})... Make sure to switch to remotivelabs-xxx Wi-Fi"
32
+ )
33
+ return b.get_license()
34
+
35
+ def request_with_url_with_internet(self, url: str) -> None:
36
+ print_unformatted_to_stderr("This requires internet connection from your computer during the entire licensing process")
37
+
38
+ email = LicenseFlow.__try_authenticate_and_get_email_from_cloud()
39
+
40
+ broker_to_license = LicenseFlow.__try_connect_to_broker(
41
+ url=url,
42
+ progress_label=f"Fetching existing broker license...({url})...",
43
+ )
44
+
45
+ existing_license = broker_to_license.get_license()
46
+ if existing_license.valid:
47
+ apply = typer.confirm(
48
+ f"This broker already has a valid license, and is licensed to {existing_license.email}. Do you still wish to proceed?"
49
+ )
50
+ if not apply:
51
+ return
52
+
53
+ print_unformatted_to_stderr(
54
+ ":point_right: [bold yellow]This will request a new license or use an existing license if "
55
+ "this hardware has already been licensed[/bold yellow]"
56
+ )
57
+ apply_license = typer.confirm("Do you want to continue and agree to terms and conditions at https://remotivelabs.com/license?")
58
+ if not apply_license:
59
+ return
60
+
61
+ existing_license = broker_to_license.get_license()
62
+ license_data_b64 = LicenseFlow.__try_request_license(email, existing_license, "Requesting license...")
63
+
64
+ with use_progress("Applying license to broker..."):
65
+ new_license = broker_to_license.apply_license(license_data_b64)
66
+ if new_license.valid:
67
+ print_unformatted_to_stderr(f":thumbsup: Successfully applied license, it remains valid until {new_license.expires}")
68
+
69
+ def request_with_hotspot(self, url: Union[str, None] = "http://192.168.4.1:50051") -> None:
70
+ """
71
+ This flow expects changes between networks and tries to guide the user accordingly
72
+ :param url: If None it will use the default hotspot IP
73
+ """
74
+ if url is None:
75
+ url = "http://192.168.4.1:50051"
76
+
77
+ print_unformatted_to_stderr(
78
+ "Licensing a broker over its wifi hotspot will require you to switch between internet connectivity "
79
+ "and remotivelabs-xxx wifi hotspot"
80
+ )
81
+
82
+ email = LicenseFlow.__try_authenticate_and_get_email_from_cloud()
83
+
84
+ broker_to_license = LicenseFlow.__try_connect_to_broker(
85
+ url=url,
86
+ progress_label=f"Fetching license from broker using hotspot ({url})... Make sure to switch to remotivelabs-xxx wifi",
87
+ )
88
+ existing_license = broker_to_license.get_license()
89
+ if existing_license.valid:
90
+ apply = typer.confirm(
91
+ f"This broker already has a valid license and is licensed to {existing_license.email}. Do you still wish to proceed?"
92
+ )
93
+ if not apply:
94
+ return
95
+
96
+ print_unformatted_to_stderr(
97
+ ":point_right: [bold yellow]This will request a new license or use an existing license if "
98
+ "this hardware has already been licensed[/bold yellow]"
99
+ )
100
+ apply_license = typer.confirm("Do you want to continue and agree to terms and conditions at https://remotivelabs.com/license?")
101
+ if not apply_license:
102
+ return
103
+
104
+ license_data_b64 = LicenseFlow.__try_request_license(email, existing_license)
105
+
106
+ broker_to_license = LicenseFlow.__try_connect_to_broker(
107
+ url=url,
108
+ progress_label="Applying license to broker... Make sure to switch back to remotivelabs-xxx wifi hotspot",
109
+ )
110
+ new_license = broker_to_license.apply_license(license_data_b64)
111
+ print_unformatted_to_stderr(f":thumbsup: Successfully applied license, expires {new_license.expires}")
112
+
113
+ @staticmethod
114
+ def __try_connect_to_broker(url: str, progress_label: str, on_error_progress_label: Union[str, None] = None) -> Broker:
115
+ with use_progress(progress_label) as p:
116
+ while True:
117
+ try:
118
+ broker_to_license = Broker(url=url)
119
+ break
120
+ except grpc.RpcError:
121
+ if on_error_progress_label is not None:
122
+ p.update(
123
+ p.task_ids[0],
124
+ description=on_error_progress_label,
125
+ )
126
+ time.sleep(1)
127
+ if on_error_progress_label is None:
128
+ print_unformatted_to_stderr(f":white_check_mark: {progress_label}")
129
+ else:
130
+ print_unformatted_to_stderr(f":white_check_mark: {on_error_progress_label}")
131
+ return broker_to_license
132
+
133
+ @staticmethod
134
+ def __try_authenticate_and_get_email_from_cloud() -> str:
135
+ with use_progress("Fetching user info from cloud... Make sure you are connected to internet"):
136
+ while True:
137
+ try:
138
+ r = Rest.handle_get("/api/whoami", return_response=True, use_progress_indicator=False, timeout=5)
139
+ break
140
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
141
+ time.sleep(1)
142
+ print_unformatted_to_stderr(":white_check_mark: Fetching user info from cloud... Make sure you are connected to internet")
143
+ if r is None:
144
+ return ""
145
+ return str(r.json()["email"])
146
+
147
+ @staticmethod
148
+ def __try_request_license(
149
+ email: str,
150
+ existing_license: LicenseInfo,
151
+ progress_label: str = "Requesting license... make sure to switch back to network with internet access",
152
+ ) -> bytes:
153
+ with use_progress(progress_label):
154
+ while True:
155
+ try:
156
+ license_data_b64 = Rest.request_license(email, json.loads(existing_license.machine_id)).encode()
157
+ break
158
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
159
+ time.sleep(1)
160
+ print_unformatted_to_stderr(f":white_check_mark: {progress_label}")
161
+ return bytes(license_data_b64)
162
+
163
+
164
+ def use_progress(label: str) -> Progress:
165
+ p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True, expand=False)
166
+ p.add_task(label, total=1)
167
+ return p
@@ -0,0 +1,98 @@
1
+ import dataclasses
2
+ import json
3
+ from enum import Enum
4
+
5
+ import typer
6
+ from rich import print_json
7
+
8
+ from remotivelabs.cli.broker.license_flows import LicenseFlow
9
+ from remotivelabs.cli.typer import typer_utils
10
+
11
+ app = typer_utils.create_typer()
12
+
13
+ help_text = """
14
+ More info on our docs page
15
+ https://docs.remotivelabs.com/docs/remotive-broker/getting-started
16
+
17
+ --connect describes how the connection is made to the broker and helps us properly connect to broker
18
+
19
+ url = Connects to the broker with the specified url, this is
20
+ hotspot = If you are using RemotiveBox (our reference hardware) you can connect to the
21
+ broker over its wifi hotspot. Getting the license from a RemotiveBox over its wifi hotspot
22
+ requires you switch wi-fi network to the RemotiveBox hotspot called 'remotivelabs-xxx' where
23
+ 'xxx' is a random generated id.
24
+
25
+ --url is the broker url, this is mandatory when connect type is "url"
26
+ """
27
+
28
+
29
+ class Connection(str, Enum):
30
+ hotspot = "hotspot"
31
+ url = "url"
32
+ # TODO - Support discover using mdns
33
+
34
+
35
+ @app.command()
36
+ def describe(
37
+ connect: Connection = typer.Option("url", case_sensitive=False, help="How to connect to broker"),
38
+ url: str = typer.Option("http://localhost:50051", is_eager=False, help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
39
+ ) -> None:
40
+ """
41
+ Show licence information
42
+
43
+ More info on our docs page
44
+ https://docs.remotivelabs.com/docs/remotive-broker/getting-started
45
+
46
+ --connect describes how the connection is made to the broker and helps us properly connect to broker
47
+
48
+ url = Connects to the broker with the specified url, this is
49
+ hotspot = If you are using RemotiveBox (our reference hardware) you can connect to the
50
+ broker over its wifi hotspot. Getting the license from a RemotiveBox over its wifi hotspot
51
+ requires you switch wi-fi network to the RemotiveBox hotspot called 'remotivelabs-xxx' where
52
+ 'xxx' is a random generated id.
53
+
54
+ --url is the broker url, this is mandatory when connect type is "url"
55
+ """
56
+ license_flow = LicenseFlow()
57
+ if connect == Connection.url:
58
+ existing_license = license_flow.describe_with_url(url)
59
+ print_json(json.dumps(dataclasses.asdict(existing_license)))
60
+ if connect == Connection.hotspot:
61
+ existing_license = license_flow.describe_with_hotspot(url if url != "http://localhost:50051" else None)
62
+ print_json(json.dumps(dataclasses.asdict(existing_license)))
63
+
64
+
65
+ @app.command()
66
+ def request(
67
+ connect: Connection = typer.Option("url", case_sensitive=False, help="How to connect to broker"),
68
+ url: str = typer.Option(
69
+ "http://localhost:50051",
70
+ is_eager=False,
71
+ help="Broker url, this is mandatory when connect type is 'url'",
72
+ envvar="REMOTIVE_BROKER_URL",
73
+ ),
74
+ ) -> None:
75
+ """
76
+ Requests and applies a new or existing License to a broker, Note that internet access is required on your
77
+ computer
78
+
79
+ More info on our docs page
80
+ https://docs.remotivelabs.com/docs/remotive-broker/getting-started
81
+
82
+ --connect describes how the connection is made to the broker and helps us properly connect to broker
83
+
84
+ url = Use url to connect to broker (use --url)
85
+ hotspot = If you are using RemotiveBox (our reference hardware) you can connect to the
86
+ broker over its wifi hotspot. Licensing a broker on a RemotiveBox over its wifi hotspot
87
+ requires you switch wi-fi network to the RemotiveBox hotspot called 'remotivelabs-xxx' where
88
+ 'xxx' is a random generated id.
89
+ https://docs.remotivelabs.com/docs/remotive-broker/getting-started/remotive-box
90
+
91
+ --url is the broker url, this is mandatory when connect type is "url"
92
+ """
93
+
94
+ license_flow = LicenseFlow()
95
+ if connect == Connection.url:
96
+ license_flow.request_with_url_with_internet(url)
97
+ if connect == Connection.hotspot:
98
+ license_flow.request_with_hotspot(url if url != "http://localhost:50051" else None)
@@ -0,0 +1,117 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+ import grpc
6
+ import typer
7
+
8
+ from remotivelabs.cli.broker.lib.broker import Broker
9
+ from remotivelabs.cli.typer import typer_utils
10
+ from remotivelabs.cli.utils.console import print_generic_error, print_generic_message, print_grpc_error, print_success
11
+
12
+ app = typer_utils.create_typer(help=help)
13
+
14
+
15
+ def recording_and_namespace(recording: str) -> Dict[str, str]:
16
+ splitted = recording.split("::")
17
+ if len(splitted) != 2:
18
+ print_generic_error("Invalid --recording option, expected file_name::namespace")
19
+ raise typer.Exit(1)
20
+ return {"recording": splitted[0], "namespace": splitted[1]}
21
+
22
+
23
+ @app.command()
24
+ def play(
25
+ recording: List[str] = typer.Option(..., help="Which recording and which namespace to play"),
26
+ url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
27
+ api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
28
+ ) -> None:
29
+ """
30
+ Play recording files on broker.
31
+
32
+ Separate recording file and namespace with ::
33
+
34
+ remotive broker playback play --recording myrecording_can0::can0 --recording myrecording_can1::can1
35
+ """
36
+
37
+ rec = list(map(recording_and_namespace, recording))
38
+ try:
39
+ broker = Broker(url, api_key)
40
+ status = broker.play(rec)
41
+ # TODO: use log instead of print for debug information?
42
+ print_generic_message(str(status))
43
+ except grpc.RpcError as err:
44
+ print_grpc_error(err)
45
+
46
+
47
+ @app.command()
48
+ def stop(
49
+ recording: List[str] = typer.Option(..., help="Which recording and which namespace to stop"),
50
+ url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
51
+ api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
52
+ ) -> None:
53
+ """
54
+ Stop recordings that are beeing played on brokers are done with the same syntax as when you start them.
55
+
56
+ Separate recording file and namespace with ::
57
+
58
+ remotive broker playback stop --recording myrecording_can0::can0 --recording myrecording_can1::can1
59
+ """
60
+
61
+ rec = list(map(recording_and_namespace, recording))
62
+
63
+ try:
64
+ broker = Broker(url, api_key)
65
+ broker.stop_play(rec)
66
+ print_success("Recording stopped")
67
+ except grpc.RpcError as err:
68
+ print_grpc_error(err)
69
+
70
+
71
+ @app.command()
72
+ def pause(
73
+ recording: List[str] = typer.Option(..., help="Which recording and which namespace to stop"),
74
+ url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
75
+ api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
76
+ ) -> None:
77
+ """
78
+ Pause recordings that are beeing played on brokers are done with the same syntax as when you start them.
79
+
80
+ Separate recording file and namespace with ::
81
+
82
+ remotive broker playback pause --recording myrecording_can0::can0 --recording myrecording_can1::can1
83
+ """
84
+
85
+ rec = list(map(recording_and_namespace, recording))
86
+ try:
87
+ broker = Broker(url, api_key)
88
+ broker.pause_play(rec)
89
+ print_success("Recording paused")
90
+ except grpc.RpcError as err:
91
+ print_grpc_error(err)
92
+
93
+
94
+ @app.command()
95
+ def seek(
96
+ recording: List[str] = typer.Option(..., help="Which recording and which namespace to stop"),
97
+ seconds: float = typer.Option(..., help="Target offset in seconds"),
98
+ url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
99
+ api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
100
+ ) -> None:
101
+ """
102
+ Seeks to a position in seconds into the recording
103
+
104
+ Separate recording file and namespace with ::
105
+
106
+ remotive broker playback seek --recording myrecording_can0::can0 --recording myrecording_can1::can1 --seconds 23
107
+ """
108
+
109
+ broker = Broker(url, api_key)
110
+
111
+ rec = list(map(recording_and_namespace, recording))
112
+
113
+ try:
114
+ broker = Broker(url, api_key)
115
+ broker.seek(rec, int(seconds * 1000000))
116
+ except grpc.RpcError as err:
117
+ print_grpc_error(err)
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List
4
+
5
+ import grpc
6
+ import typer
7
+
8
+ from remotivelabs.cli.broker.lib.broker import Broker
9
+ from remotivelabs.cli.typer import typer_utils
10
+ from remotivelabs.cli.utils.console import print_grpc_error, print_success
11
+
12
+ app = typer_utils.create_typer(help=help)
13
+
14
+
15
+ @app.command()
16
+ def start(
17
+ filename: str = typer.Argument(..., help="Path to local file to upload"),
18
+ namespace: List[str] = typer.Option(..., help="Namespace to record"),
19
+ url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
20
+ api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
21
+ ) -> None:
22
+ try:
23
+ broker = Broker(url, api_key)
24
+ broker.record_multiple(namespace, filename)
25
+ except grpc.RpcError as rpc_error:
26
+ print_grpc_error(rpc_error)
27
+
28
+
29
+ @app.command()
30
+ def stop(
31
+ filename: str = typer.Argument(..., help="Path to local file to upload"),
32
+ namespace: List[str] = typer.Option(..., help="Namespace to record"),
33
+ url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
34
+ api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
35
+ ) -> None:
36
+ try:
37
+ broker = Broker(url, api_key)
38
+ broker.stop_multiple(namespace, filename)
39
+ print_success("Recording stopped")
40
+ except grpc.RpcError as rpc_error:
41
+ print_grpc_error(rpc_error)
@@ -0,0 +1,3 @@
1
+ from remotivelabs.cli.broker.recording_session.cmd import app
2
+
3
+ __all__ = ["app"]
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import AsyncGenerator, Iterable, Optional, Set
4
+
5
+ from remotivelabs.broker.recording_session import File, RecordingSessionClient
6
+ from remotivelabs.broker.recording_session.file import FileType
7
+
8
+
9
+ class RecursiveFilesListingClient:
10
+ """
11
+ Client for recursively listing files using a broker API.
12
+
13
+ This class provides functionality to recursively list files and directories using
14
+ a RecordingSessionClient. It retrieves files from specified paths and can process them
15
+ with customizable options such as filtering by file types or including directories
16
+ in the results.
17
+
18
+ Attributes:
19
+ broker_url (str): The URL of the broker API.
20
+ api_key (Optional[str]): The API key used for authentication, if required by the
21
+ broker API.
22
+ """
23
+
24
+ def __init__(self, client: RecordingSessionClient):
25
+ self._broker_client = client
26
+
27
+ async def _list_files(self, path: str = "/") -> list[File]:
28
+ return await self._broker_client.list_recording_files(path=path)
29
+
30
+ async def list_all_files(
31
+ self,
32
+ path: str = "/",
33
+ file_types: Optional[Iterable[FileType]] = None,
34
+ ) -> list[File]:
35
+ files: list[File] = []
36
+ async for f in self._iter_files_recursive(root=path, return_types=file_types):
37
+ files.append(f)
38
+ return files
39
+
40
+ async def _iter_files_recursive(
41
+ self,
42
+ root: str = "/",
43
+ *,
44
+ return_types: Optional[Iterable[FileType]] = None,
45
+ include_dirs: bool = False,
46
+ ) -> AsyncGenerator[File, None]:
47
+ seen: Set[str] = set()
48
+
49
+ async def _walk(path: str) -> AsyncGenerator[File, None]:
50
+ if path in seen:
51
+ return
52
+ seen.add(path)
53
+
54
+ resp = await self._list_files(path)
55
+ for f in resp or []:
56
+ if f.type == FileType.FILE_TYPE_FOLDER:
57
+ # Optionally yield the directory itself
58
+ if include_dirs and (return_types is None or f.type in return_types):
59
+ yield f
60
+ # Recurse into the folder
61
+ async for file in _walk(f.path):
62
+ yield file
63
+ elif return_types is None or f.type in return_types:
64
+ yield f
65
+
66
+ async for file in _walk(root):
67
+ yield file