kscale 0.3.16__tar.gz → 0.3.18__tar.gz
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.
- {kscale-0.3.16/kscale.egg-info → kscale-0.3.18}/PKG-INFO +1 -1
- {kscale-0.3.16 → kscale-0.3.18}/kscale/__init__.py +1 -1
- {kscale-0.3.16 → kscale-0.3.18}/kscale/cli.py +6 -0
- kscale-0.3.18/kscale/web/cli/clip.py +132 -0
- kscale-0.3.18/kscale/web/cli/group.py +241 -0
- kscale-0.3.18/kscale/web/cli/permission.py +110 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/cli/robot_class.py +4 -4
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/cli/user.py +0 -11
- kscale-0.3.18/kscale/web/clients/base.py +124 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/clients/client.py +6 -0
- kscale-0.3.18/kscale/web/clients/clip.py +118 -0
- kscale-0.3.18/kscale/web/clients/group.py +85 -0
- kscale-0.3.18/kscale/web/clients/permission.py +35 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/clients/user.py +0 -4
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/gen/api.py +173 -4
- {kscale-0.3.16 → kscale-0.3.18/kscale.egg-info}/PKG-INFO +1 -1
- {kscale-0.3.16 → kscale-0.3.18}/kscale.egg-info/SOURCES.txt +6 -0
- {kscale-0.3.16 → kscale-0.3.18}/pyproject.toml +0 -1
- kscale-0.3.16/kscale/web/clients/base.py +0 -423
- {kscale-0.3.16 → kscale-0.3.18}/LICENSE +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/MANIFEST.in +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/README.md +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/artifacts/__init__.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/artifacts/plane.obj +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/artifacts/plane.urdf +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/conf.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/py.typed +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/requirements-dev.txt +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/requirements.txt +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/utils/__init__.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/utils/api_base.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/utils/checksum.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/utils/cli.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/__init__.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/cli/__init__.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/cli/robot.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/clients/__init__.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/clients/robot.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/clients/robot_class.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/gen/__init__.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale/web/utils.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale.egg-info/dependency_links.txt +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale.egg-info/entry_points.txt +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale.egg-info/not-zip-safe +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale.egg-info/requires.txt +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/kscale.egg-info/top_level.txt +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/setup.cfg +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/setup.py +0 -0
- {kscale-0.3.16 → kscale-0.3.18}/tests/test_dummy.py +0 -0
@@ -6,6 +6,9 @@ import click
|
|
6
6
|
import colorlogging
|
7
7
|
|
8
8
|
from kscale.utils.cli import recursive_help
|
9
|
+
from kscale.web.cli.clip import cli as clip_cli
|
10
|
+
from kscale.web.cli.group import cli as group_cli
|
11
|
+
from kscale.web.cli.permission import cli as permission_cli
|
9
12
|
from kscale.web.cli.robot import cli as robot_cli
|
10
13
|
from kscale.web.cli.robot_class import cli as robot_class_cli
|
11
14
|
from kscale.web.cli.user import cli as user_cli
|
@@ -24,6 +27,9 @@ def cli() -> None:
|
|
24
27
|
cli.add_command(user_cli, "user")
|
25
28
|
cli.add_command(robot_class_cli, "robots")
|
26
29
|
cli.add_command(robot_cli, "robot")
|
30
|
+
cli.add_command(clip_cli, "clips")
|
31
|
+
cli.add_command(group_cli, "groups")
|
32
|
+
cli.add_command(permission_cli, "permissions")
|
27
33
|
|
28
34
|
if __name__ == "__main__":
|
29
35
|
# python -m kscale.cli
|
@@ -0,0 +1,132 @@
|
|
1
|
+
"""Defines the CLI for managing clips."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
import click
|
6
|
+
from tabulate import tabulate
|
7
|
+
|
8
|
+
from kscale.utils.cli import coro
|
9
|
+
from kscale.web.clients.clip import ClipClient
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
@click.group()
|
15
|
+
def cli() -> None:
|
16
|
+
"""Manage clips."""
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
@cli.command()
|
21
|
+
@coro
|
22
|
+
async def list() -> None:
|
23
|
+
"""Lists all clips for the authenticated user."""
|
24
|
+
client = ClipClient()
|
25
|
+
clips = await client.get_clips()
|
26
|
+
if clips:
|
27
|
+
# Prepare table data
|
28
|
+
table_data = [
|
29
|
+
[
|
30
|
+
click.style(clip.id, fg="blue"),
|
31
|
+
clip.description or "N/A",
|
32
|
+
clip.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
33
|
+
"N/A" if clip.num_downloads is None else f"{clip.num_downloads:,}",
|
34
|
+
"N/A" if clip.file_size is None else f"{clip.file_size:,} bytes",
|
35
|
+
]
|
36
|
+
for clip in clips
|
37
|
+
]
|
38
|
+
click.echo(
|
39
|
+
tabulate(
|
40
|
+
table_data,
|
41
|
+
headers=["ID", "Description", "Created", "Downloads", "Size"],
|
42
|
+
tablefmt="simple",
|
43
|
+
)
|
44
|
+
)
|
45
|
+
else:
|
46
|
+
click.echo(click.style("No clips found", fg="red"))
|
47
|
+
|
48
|
+
|
49
|
+
@cli.command()
|
50
|
+
@click.argument("clip_id")
|
51
|
+
@coro
|
52
|
+
async def get(clip_id: str) -> None:
|
53
|
+
"""Get information about a specific clip."""
|
54
|
+
client = ClipClient()
|
55
|
+
clip = await client.get_clip(clip_id)
|
56
|
+
click.echo(f"ID: {click.style(clip.id, fg='blue')}")
|
57
|
+
click.echo(f"Description: {click.style(clip.description or 'N/A', fg='green')}")
|
58
|
+
click.echo(f"User ID: {click.style(clip.user_id, fg='yellow')}")
|
59
|
+
click.echo(f"Created: {clip.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
|
60
|
+
click.echo(f"Downloads: {clip.num_downloads or 0}")
|
61
|
+
if clip.file_size:
|
62
|
+
click.echo(f"File Size: {clip.file_size:,} bytes")
|
63
|
+
|
64
|
+
|
65
|
+
@cli.command()
|
66
|
+
@click.option("-d", "--description", type=str, default=None, help="Description for the clip")
|
67
|
+
@coro
|
68
|
+
async def create(description: str | None = None) -> None:
|
69
|
+
"""Create a new clip."""
|
70
|
+
async with ClipClient() as client:
|
71
|
+
clip = await client.create_clip(description)
|
72
|
+
click.echo("Clip created:")
|
73
|
+
click.echo(f" ID: {click.style(clip.id, fg='blue')}")
|
74
|
+
click.echo(f" Description: {click.style(clip.description or 'N/A', fg='green')}")
|
75
|
+
click.echo(f" Created: {clip.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
|
76
|
+
|
77
|
+
|
78
|
+
@cli.command()
|
79
|
+
@click.argument("clip_id")
|
80
|
+
@click.option("-d", "--description", type=str, default=None, help="New description for the clip")
|
81
|
+
@coro
|
82
|
+
async def update(clip_id: str, description: str | None = None) -> None:
|
83
|
+
"""Update a clip's metadata."""
|
84
|
+
async with ClipClient() as client:
|
85
|
+
clip = await client.update_clip(clip_id, description)
|
86
|
+
click.echo("Clip updated:")
|
87
|
+
click.echo(f" ID: {click.style(clip.id, fg='blue')}")
|
88
|
+
click.echo(f" Description: {click.style(clip.description or 'N/A', fg='green')}")
|
89
|
+
|
90
|
+
|
91
|
+
@cli.command()
|
92
|
+
@click.argument("clip_id")
|
93
|
+
@click.confirmation_option(prompt="Are you sure you want to delete this clip?")
|
94
|
+
@coro
|
95
|
+
async def delete(clip_id: str) -> None:
|
96
|
+
"""Delete a clip."""
|
97
|
+
async with ClipClient() as client:
|
98
|
+
await client.delete_clip(clip_id)
|
99
|
+
click.echo(f"Clip deleted: {click.style(clip_id, fg='red')}")
|
100
|
+
|
101
|
+
|
102
|
+
@cli.command()
|
103
|
+
@click.argument("clip_id")
|
104
|
+
@click.argument("file_path", type=click.Path(exists=True))
|
105
|
+
@coro
|
106
|
+
async def upload(clip_id: str, file_path: str) -> None:
|
107
|
+
"""Upload a file for a clip."""
|
108
|
+
async with ClipClient() as client:
|
109
|
+
response = await client.upload_clip(clip_id, file_path)
|
110
|
+
click.echo("File uploaded:")
|
111
|
+
click.echo(f" Filename: {click.style(response.filename, fg='green')}")
|
112
|
+
click.echo(f" Content Type: {response.content_type}")
|
113
|
+
|
114
|
+
|
115
|
+
@cli.command()
|
116
|
+
@click.argument("clip_id")
|
117
|
+
@click.option("-o", "--output", type=click.Path(), help="Output file path")
|
118
|
+
@coro
|
119
|
+
async def download(clip_id: str, output: str | None = None) -> None:
|
120
|
+
"""Download a clip file."""
|
121
|
+
async with ClipClient() as client:
|
122
|
+
if output:
|
123
|
+
output_path = await client.download_clip_to_file(clip_id, output)
|
124
|
+
click.echo(f"Clip downloaded to: {click.style(str(output_path), fg='green')}")
|
125
|
+
else:
|
126
|
+
download_response = await client.download_clip(clip_id)
|
127
|
+
click.echo(f"Download URL: {click.style(download_response.url, fg='blue')}")
|
128
|
+
click.echo(f"MD5 Hash: {download_response.md5_hash}")
|
129
|
+
|
130
|
+
|
131
|
+
if __name__ == "__main__":
|
132
|
+
cli()
|
@@ -0,0 +1,241 @@
|
|
1
|
+
"""Defines the CLI for managing groups."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
import click
|
6
|
+
from tabulate import tabulate
|
7
|
+
|
8
|
+
from kscale.utils.cli import coro
|
9
|
+
from kscale.web.clients.group import GroupClient
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
@click.group()
|
15
|
+
def cli() -> None:
|
16
|
+
"""Manage groups."""
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
@cli.command()
|
21
|
+
@coro
|
22
|
+
async def list() -> None:
|
23
|
+
"""Lists all groups for the authenticated user."""
|
24
|
+
client = GroupClient()
|
25
|
+
groups = await client.get_groups()
|
26
|
+
if groups:
|
27
|
+
# Prepare table data
|
28
|
+
table_data = [
|
29
|
+
[
|
30
|
+
click.style(group.id, fg="blue"),
|
31
|
+
click.style(group.name, fg="green"),
|
32
|
+
group.description or "N/A",
|
33
|
+
group.created_at,
|
34
|
+
"Active" if group.is_active else "Inactive",
|
35
|
+
]
|
36
|
+
for group in groups
|
37
|
+
]
|
38
|
+
click.echo(
|
39
|
+
tabulate(
|
40
|
+
table_data,
|
41
|
+
headers=["ID", "Name", "Description", "Created", "Status"],
|
42
|
+
tablefmt="simple",
|
43
|
+
)
|
44
|
+
)
|
45
|
+
else:
|
46
|
+
click.echo(click.style("No groups found", fg="red"))
|
47
|
+
|
48
|
+
|
49
|
+
@cli.command()
|
50
|
+
@click.argument("group_id")
|
51
|
+
@coro
|
52
|
+
async def get(group_id: str) -> None:
|
53
|
+
"""Get information about a specific group."""
|
54
|
+
client = GroupClient()
|
55
|
+
group = await client.get_group(group_id)
|
56
|
+
click.echo(f"ID: {click.style(group.id, fg='blue')}")
|
57
|
+
click.echo(f"Name: {click.style(group.name, fg='green')}")
|
58
|
+
click.echo(f"Description: {click.style(group.description or 'N/A', fg='yellow')}")
|
59
|
+
click.echo(f"Owner ID: {click.style(group.owner_id, fg='cyan')}")
|
60
|
+
click.echo(f"Created: {group.created_at}")
|
61
|
+
click.echo(f"Updated: {group.updated_at}")
|
62
|
+
click.echo(f"Status: {'Active' if group.is_active else 'Inactive'}")
|
63
|
+
|
64
|
+
|
65
|
+
@cli.command()
|
66
|
+
@click.argument("name")
|
67
|
+
@click.option("-d", "--description", type=str, default=None, help="Description for the group")
|
68
|
+
@coro
|
69
|
+
async def create(name: str, description: str | None = None) -> None:
|
70
|
+
"""Create a new group."""
|
71
|
+
async with GroupClient() as client:
|
72
|
+
group = await client.create_group(name, description)
|
73
|
+
click.echo("Group created:")
|
74
|
+
click.echo(f" ID: {click.style(group.id, fg='blue')}")
|
75
|
+
click.echo(f" Name: {click.style(group.name, fg='green')}")
|
76
|
+
click.echo(f" Description: {click.style(group.description or 'N/A', fg='yellow')}")
|
77
|
+
|
78
|
+
|
79
|
+
@cli.command()
|
80
|
+
@click.argument("group_id")
|
81
|
+
@click.option("-n", "--name", type=str, default=None, help="New name for the group")
|
82
|
+
@click.option("-d", "--description", type=str, default=None, help="New description for the group")
|
83
|
+
@coro
|
84
|
+
async def update(group_id: str, name: str | None = None, description: str | None = None) -> None:
|
85
|
+
"""Update a group's metadata."""
|
86
|
+
async with GroupClient() as client:
|
87
|
+
group = await client.update_group(group_id, name, description)
|
88
|
+
click.echo("Group updated:")
|
89
|
+
click.echo(f" ID: {click.style(group.id, fg='blue')}")
|
90
|
+
click.echo(f" Name: {click.style(group.name, fg='green')}")
|
91
|
+
click.echo(f" Description: {click.style(group.description or 'N/A', fg='yellow')}")
|
92
|
+
|
93
|
+
|
94
|
+
@cli.command()
|
95
|
+
@click.argument("group_id")
|
96
|
+
@click.confirmation_option(prompt="Are you sure you want to delete this group?")
|
97
|
+
@coro
|
98
|
+
async def delete(group_id: str) -> None:
|
99
|
+
"""Delete a group."""
|
100
|
+
async with GroupClient() as client:
|
101
|
+
await client.delete_group(group_id)
|
102
|
+
click.echo(f"Group deleted: {click.style(group_id, fg='red')}")
|
103
|
+
|
104
|
+
|
105
|
+
@cli.group()
|
106
|
+
def membership() -> None:
|
107
|
+
"""Manage group memberships."""
|
108
|
+
pass
|
109
|
+
|
110
|
+
|
111
|
+
@membership.command("list")
|
112
|
+
@click.argument("group_id")
|
113
|
+
@coro
|
114
|
+
async def list_memberships(group_id: str) -> None:
|
115
|
+
"""List all memberships for a group."""
|
116
|
+
client = GroupClient()
|
117
|
+
memberships = await client.get_group_memberships(group_id)
|
118
|
+
if memberships:
|
119
|
+
table_data = [
|
120
|
+
[
|
121
|
+
click.style(membership.id, fg="blue"),
|
122
|
+
click.style(membership.user_id, fg="green"),
|
123
|
+
membership.status,
|
124
|
+
membership.requested_at,
|
125
|
+
membership.approved_at or "N/A",
|
126
|
+
membership.approved_by or "N/A",
|
127
|
+
]
|
128
|
+
for membership in memberships
|
129
|
+
]
|
130
|
+
click.echo(
|
131
|
+
tabulate(
|
132
|
+
table_data,
|
133
|
+
headers=["ID", "User ID", "Status", "Requested", "Approved", "Approved By"],
|
134
|
+
tablefmt="simple",
|
135
|
+
)
|
136
|
+
)
|
137
|
+
else:
|
138
|
+
click.echo(click.style("No memberships found", fg="red"))
|
139
|
+
|
140
|
+
|
141
|
+
@membership.command("request")
|
142
|
+
@click.argument("group_id")
|
143
|
+
@coro
|
144
|
+
async def request_membership(group_id: str) -> None:
|
145
|
+
"""Request to join a group."""
|
146
|
+
async with GroupClient() as client:
|
147
|
+
membership = await client.request_group_membership(group_id)
|
148
|
+
click.echo("Membership request sent:")
|
149
|
+
click.echo(f" ID: {click.style(membership.id, fg='blue')}")
|
150
|
+
click.echo(f" Status: {membership.status}")
|
151
|
+
|
152
|
+
|
153
|
+
@membership.command("approve")
|
154
|
+
@click.argument("group_id")
|
155
|
+
@click.argument("user_id")
|
156
|
+
@coro
|
157
|
+
async def approve_membership(group_id: str, user_id: str) -> None:
|
158
|
+
"""Approve a membership request."""
|
159
|
+
async with GroupClient() as client:
|
160
|
+
membership = await client.approve_group_membership(group_id, user_id)
|
161
|
+
click.echo("Membership approved:")
|
162
|
+
click.echo(f" ID: {click.style(membership.id, fg='blue')}")
|
163
|
+
click.echo(f" Status: {membership.status}")
|
164
|
+
|
165
|
+
|
166
|
+
@membership.command("reject")
|
167
|
+
@click.argument("group_id")
|
168
|
+
@click.argument("user_id")
|
169
|
+
@click.confirmation_option(prompt="Are you sure you want to reject this membership?")
|
170
|
+
@coro
|
171
|
+
async def reject_membership(group_id: str, user_id: str) -> None:
|
172
|
+
"""Reject a membership request."""
|
173
|
+
async with GroupClient() as client:
|
174
|
+
await client.reject_group_membership(group_id, user_id)
|
175
|
+
click.echo(f"Membership rejected for user: {click.style(user_id, fg='red')}")
|
176
|
+
|
177
|
+
|
178
|
+
@cli.group()
|
179
|
+
def share() -> None:
|
180
|
+
"""Manage group resource sharing."""
|
181
|
+
pass
|
182
|
+
|
183
|
+
|
184
|
+
@share.command("list")
|
185
|
+
@click.argument("group_id")
|
186
|
+
@coro
|
187
|
+
async def list_shares(group_id: str) -> None:
|
188
|
+
"""List all resources shared with a group."""
|
189
|
+
client = GroupClient()
|
190
|
+
shares = await client.get_group_shares(group_id)
|
191
|
+
if shares:
|
192
|
+
table_data = [
|
193
|
+
[
|
194
|
+
click.style(share.id, fg="blue"),
|
195
|
+
share.resource_type,
|
196
|
+
click.style(share.resource_id, fg="green"),
|
197
|
+
click.style(share.shared_by, fg="yellow"),
|
198
|
+
share.shared_at,
|
199
|
+
]
|
200
|
+
for share in shares
|
201
|
+
]
|
202
|
+
click.echo(
|
203
|
+
tabulate(
|
204
|
+
table_data,
|
205
|
+
headers=["ID", "Type", "Resource ID", "Shared By", "Shared At"],
|
206
|
+
tablefmt="simple",
|
207
|
+
)
|
208
|
+
)
|
209
|
+
else:
|
210
|
+
click.echo(click.style("No shared resources found", fg="red"))
|
211
|
+
|
212
|
+
|
213
|
+
@share.command("add")
|
214
|
+
@click.argument("group_id")
|
215
|
+
@click.argument("resource_type")
|
216
|
+
@click.argument("resource_id")
|
217
|
+
@coro
|
218
|
+
async def add_share(group_id: str, resource_type: str, resource_id: str) -> None:
|
219
|
+
"""Share a resource with a group."""
|
220
|
+
async with GroupClient() as client:
|
221
|
+
share = await client.share_resource_with_group(group_id, resource_type, resource_id)
|
222
|
+
click.echo("Resource shared:")
|
223
|
+
click.echo(f" ID: {click.style(share.id, fg='blue')}")
|
224
|
+
click.echo(f" Type: {share.resource_type}")
|
225
|
+
click.echo(f" Resource ID: {click.style(share.resource_id, fg='green')}")
|
226
|
+
|
227
|
+
|
228
|
+
@share.command("remove")
|
229
|
+
@click.argument("group_id")
|
230
|
+
@click.argument("share_id")
|
231
|
+
@click.confirmation_option(prompt="Are you sure you want to remove this share?")
|
232
|
+
@coro
|
233
|
+
async def remove_share(group_id: str, share_id: str) -> None:
|
234
|
+
"""Remove a resource share from a group."""
|
235
|
+
async with GroupClient() as client:
|
236
|
+
await client.unshare_resource_from_group(group_id, share_id)
|
237
|
+
click.echo(f"Share removed: {click.style(share_id, fg='red')}")
|
238
|
+
|
239
|
+
|
240
|
+
if __name__ == "__main__":
|
241
|
+
cli()
|
@@ -0,0 +1,110 @@
|
|
1
|
+
"""Defines the CLI for managing permissions."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
import click
|
6
|
+
from tabulate import tabulate
|
7
|
+
|
8
|
+
from kscale.utils.cli import coro
|
9
|
+
from kscale.web.clients.permission import PermissionClient
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
@click.group()
|
15
|
+
def cli() -> None:
|
16
|
+
"""Manage user permissions."""
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
@cli.command("list-all")
|
21
|
+
@coro
|
22
|
+
async def list_all_permissions() -> None:
|
23
|
+
"""List all available permissions."""
|
24
|
+
client = PermissionClient()
|
25
|
+
permissions = await client.get_all_permissions()
|
26
|
+
if permissions:
|
27
|
+
table_data = [
|
28
|
+
[
|
29
|
+
click.style(perm.permission, fg="blue"),
|
30
|
+
perm.description,
|
31
|
+
]
|
32
|
+
for perm in permissions
|
33
|
+
]
|
34
|
+
click.echo(
|
35
|
+
tabulate(
|
36
|
+
table_data,
|
37
|
+
headers=["Permission", "Description"],
|
38
|
+
tablefmt="simple",
|
39
|
+
)
|
40
|
+
)
|
41
|
+
else:
|
42
|
+
click.echo(click.style("No permissions found", fg="red"))
|
43
|
+
|
44
|
+
|
45
|
+
@cli.command()
|
46
|
+
@click.option("-u", "--user-id", type=str, default="me", help="User ID (default: me)")
|
47
|
+
@coro
|
48
|
+
async def list(user_id: str = "me") -> None:
|
49
|
+
"""List permissions for a user."""
|
50
|
+
client = PermissionClient()
|
51
|
+
user_perms = await client.get_user_permissions(user_id)
|
52
|
+
|
53
|
+
click.echo(f"User: {click.style(user_perms.display_name, fg='green')} ({user_perms.email})")
|
54
|
+
click.echo(f"User ID: {click.style(user_perms.user_id, fg='blue')}")
|
55
|
+
click.echo(f"Status: {'Active' if user_perms.is_active else 'Inactive'}")
|
56
|
+
click.echo("\nPermissions:")
|
57
|
+
|
58
|
+
if user_perms.permissions:
|
59
|
+
for perm in user_perms.permissions:
|
60
|
+
click.echo(f" • {click.style(perm, fg='yellow')}")
|
61
|
+
else:
|
62
|
+
click.echo(click.style(" No permissions assigned", fg="red"))
|
63
|
+
|
64
|
+
|
65
|
+
@cli.command()
|
66
|
+
@click.argument("user_id")
|
67
|
+
@click.argument("permissions", nargs=-1, required=True)
|
68
|
+
@coro
|
69
|
+
async def set(user_id: str, permissions: tuple[str, ...]) -> None:
|
70
|
+
"""Set permissions for a user (replaces all existing permissions)."""
|
71
|
+
async with PermissionClient() as client:
|
72
|
+
user_perms = await client.update_user_permissions(user_id, list(permissions))
|
73
|
+
|
74
|
+
click.echo("Permissions updated:")
|
75
|
+
click.echo(f" User: {click.style(user_perms.display_name, fg='green')}")
|
76
|
+
click.echo(f" Permissions: {', '.join([click.style(p, fg='yellow') for p in user_perms.permissions])}")
|
77
|
+
|
78
|
+
|
79
|
+
@cli.command()
|
80
|
+
@click.argument("user_id")
|
81
|
+
@click.argument("permission")
|
82
|
+
@coro
|
83
|
+
async def add(user_id: str, permission: str) -> None:
|
84
|
+
"""Add a permission to a user."""
|
85
|
+
async with PermissionClient() as client:
|
86
|
+
user_perms = await client.add_user_permission(user_id, permission)
|
87
|
+
|
88
|
+
click.echo("Permission added:")
|
89
|
+
click.echo(f" User: {click.style(user_perms.display_name, fg='green')}")
|
90
|
+
click.echo(f" Added: {click.style(permission, fg='yellow')}")
|
91
|
+
click.echo(f" All permissions: {', '.join([click.style(p, fg='yellow') for p in user_perms.permissions])}")
|
92
|
+
|
93
|
+
|
94
|
+
@cli.command()
|
95
|
+
@click.argument("user_id")
|
96
|
+
@click.argument("permission")
|
97
|
+
@coro
|
98
|
+
async def remove(user_id: str, permission: str) -> None:
|
99
|
+
"""Remove a permission from a user."""
|
100
|
+
async with PermissionClient() as client:
|
101
|
+
user_perms = await client.remove_user_permission(user_id, permission)
|
102
|
+
|
103
|
+
click.echo("Permission removed:")
|
104
|
+
click.echo(f" User: {click.style(user_perms.display_name, fg='green')}")
|
105
|
+
click.echo(f" Removed: {click.style(permission, fg='red')}")
|
106
|
+
click.echo(f" Remaining permissions: {', '.join([click.style(p, fg='yellow') for p in user_perms.permissions])}")
|
107
|
+
|
108
|
+
|
109
|
+
if __name__ == "__main__":
|
110
|
+
cli()
|
@@ -192,7 +192,7 @@ async def run_pybullet(
|
|
192
192
|
) -> None:
|
193
193
|
"""Shows the URDF file for a robot class in PyBullet."""
|
194
194
|
try:
|
195
|
-
import pybullet as p
|
195
|
+
import pybullet as p # noqa: PLC0415
|
196
196
|
except ImportError:
|
197
197
|
click.echo(click.style("PyBullet is not installed; install it with `pip install pybullet`", fg="red"))
|
198
198
|
return
|
@@ -453,14 +453,14 @@ async def run_mujoco(class_name: str, scene: str, no_cache: bool) -> None:
|
|
453
453
|
launches the Mujoco viewer using the provided MJCF file.
|
454
454
|
"""
|
455
455
|
try:
|
456
|
-
from mujoco_scenes.errors import TemplateNotFoundError
|
457
|
-
from mujoco_scenes.mjcf import list_scenes, load_mjmodel
|
456
|
+
from mujoco_scenes.errors import TemplateNotFoundError # noqa: PLC0415
|
457
|
+
from mujoco_scenes.mjcf import list_scenes, load_mjmodel # noqa: PLC0415
|
458
458
|
except ImportError:
|
459
459
|
click.echo(click.style("Mujoco Scenes is required; install with `pip install mujoco-scenes`", fg="red"))
|
460
460
|
return
|
461
461
|
|
462
462
|
try:
|
463
|
-
import mujoco.viewer
|
463
|
+
import mujoco.viewer # noqa: PLC0415
|
464
464
|
except ImportError:
|
465
465
|
click.echo(click.style("Mujoco is required; install with `pip install mujoco`", fg="red"))
|
466
466
|
return
|
@@ -30,8 +30,6 @@ async def me() -> None:
|
|
30
30
|
["Email verified", profile.email_verified],
|
31
31
|
["User ID", profile.user.user_id],
|
32
32
|
["Is admin", profile.user.is_admin],
|
33
|
-
["Can upload", profile.user.can_upload],
|
34
|
-
["Can test", profile.user.can_test],
|
35
33
|
],
|
36
34
|
headers=["Key", "Value"],
|
37
35
|
tablefmt="simple",
|
@@ -39,14 +37,5 @@ async def me() -> None:
|
|
39
37
|
)
|
40
38
|
|
41
39
|
|
42
|
-
@cli.command()
|
43
|
-
@coro
|
44
|
-
async def key() -> None:
|
45
|
-
"""Get an API key for the currently-authenticated user."""
|
46
|
-
client = UserClient()
|
47
|
-
api_key = await client.get_api_key()
|
48
|
-
click.echo(f"API key: {click.style(api_key, fg='green')}")
|
49
|
-
|
50
|
-
|
51
40
|
if __name__ == "__main__":
|
52
41
|
cli()
|