kscale 0.0.13__tar.gz → 0.1.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. {kscale-0.0.13/kscale.egg-info → kscale-0.1.1}/PKG-INFO +19 -10
  2. {kscale-0.0.13 → kscale-0.1.1}/README.md +0 -6
  3. {kscale-0.0.13 → kscale-0.1.1}/kscale/__init__.py +1 -1
  4. {kscale-0.0.13 → kscale-0.1.1}/kscale/api.py +0 -3
  5. kscale-0.1.1/kscale/cli.py +32 -0
  6. {kscale-0.0.13 → kscale-0.1.1}/kscale/conf.py +4 -1
  7. {kscale-0.0.13 → kscale-0.1.1}/kscale/requirements.txt +11 -2
  8. kscale-0.1.1/kscale/web/api.py +14 -0
  9. kscale-0.1.1/kscale/web/cli/robot.py +100 -0
  10. kscale-0.1.1/kscale/web/cli/robot_class.py +113 -0
  11. kscale-0.1.1/kscale/web/cli/token.py +33 -0
  12. kscale-0.1.1/kscale/web/cli/user.py +33 -0
  13. kscale-0.1.1/kscale/web/clients/__init__.py +0 -0
  14. kscale-0.1.1/kscale/web/clients/base.py +314 -0
  15. kscale-0.1.1/kscale/web/clients/client.py +11 -0
  16. kscale-0.1.1/kscale/web/clients/robot.py +39 -0
  17. kscale-0.1.1/kscale/web/clients/robot_class.py +121 -0
  18. kscale-0.1.1/kscale/web/clients/user.py +10 -0
  19. kscale-0.1.1/kscale/web/gen/__init__.py +0 -0
  20. kscale-0.1.1/kscale/web/gen/api.py +73 -0
  21. kscale-0.1.1/kscale/web/utils.py +31 -0
  22. {kscale-0.0.13 → kscale-0.1.1/kscale.egg-info}/PKG-INFO +19 -10
  23. {kscale-0.0.13 → kscale-0.1.1}/kscale.egg-info/SOURCES.txt +11 -5
  24. {kscale-0.0.13 → kscale-0.1.1}/kscale.egg-info/entry_points.txt +1 -0
  25. {kscale-0.0.13 → kscale-0.1.1}/kscale.egg-info/requires.txt +9 -2
  26. {kscale-0.0.13 → kscale-0.1.1}/pyproject.toml +2 -2
  27. {kscale-0.0.13 → kscale-0.1.1}/setup.py +1 -18
  28. kscale-0.0.13/kscale/cli.py +0 -25
  29. kscale-0.0.13/kscale/web/api.py +0 -98
  30. kscale-0.0.13/kscale/web/gen/api.py +0 -612
  31. kscale-0.0.13/kscale/web/kernels.py +0 -207
  32. kscale-0.0.13/kscale/web/krec.py +0 -175
  33. kscale-0.0.13/kscale/web/pybullet.py +0 -188
  34. kscale-0.0.13/kscale/web/urdf.py +0 -185
  35. kscale-0.0.13/kscale/web/utils.py +0 -48
  36. kscale-0.0.13/kscale/web/www_client.py +0 -134
  37. {kscale-0.0.13 → kscale-0.1.1}/LICENSE +0 -0
  38. {kscale-0.0.13 → kscale-0.1.1}/MANIFEST.in +0 -0
  39. {kscale-0.0.13 → kscale-0.1.1}/kscale/artifacts/__init__.py +0 -0
  40. {kscale-0.0.13 → kscale-0.1.1}/kscale/artifacts/plane.obj +0 -0
  41. {kscale-0.0.13 → kscale-0.1.1}/kscale/artifacts/plane.urdf +0 -0
  42. {kscale-0.0.13 → kscale-0.1.1}/kscale/py.typed +0 -0
  43. {kscale-0.0.13 → kscale-0.1.1}/kscale/requirements-dev.txt +0 -0
  44. {kscale-0.0.13 → kscale-0.1.1}/kscale/utils/__init__.py +0 -0
  45. {kscale-0.0.13 → kscale-0.1.1}/kscale/utils/api_base.py +0 -0
  46. {kscale-0.0.13 → kscale-0.1.1}/kscale/utils/checksum.py +0 -0
  47. {kscale-0.0.13 → kscale-0.1.1}/kscale/utils/cli.py +0 -0
  48. {kscale-0.0.13 → kscale-0.1.1}/kscale/web/__init__.py +0 -0
  49. {kscale-0.0.13/kscale/web/gen → kscale-0.1.1/kscale/web/cli}/__init__.py +0 -0
  50. {kscale-0.0.13 → kscale-0.1.1}/kscale.egg-info/dependency_links.txt +0 -0
  51. {kscale-0.0.13 → kscale-0.1.1}/kscale.egg-info/not-zip-safe +0 -0
  52. {kscale-0.0.13 → kscale-0.1.1}/kscale.egg-info/top_level.txt +0 -0
  53. {kscale-0.0.13 → kscale-0.1.1}/setup.cfg +0 -0
  54. {kscale-0.0.13 → kscale-0.1.1}/tests/test_dummy.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: kscale
3
- Version: 0.0.13
3
+ Version: 0.1.1
4
4
  Summary: The kscale project
5
5
  Home-page: https://github.com/kscalelabs/kscale
6
6
  Author: Benjamin Bolte
@@ -9,11 +9,18 @@ Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: omegaconf
11
11
  Requires-Dist: email_validator
12
+ Requires-Dist: aiohttp
13
+ Requires-Dist: cryptography
12
14
  Requires-Dist: httpx
13
- Requires-Dist: requests
14
15
  Requires-Dist: pydantic
15
- Requires-Dist: click
16
+ Requires-Dist: pyjwt
17
+ Requires-Dist: requests
18
+ Requires-Dist: yarl
16
19
  Requires-Dist: aiofiles
20
+ Requires-Dist: click
21
+ Requires-Dist: colorlogging
22
+ Requires-Dist: tabulate
23
+ Requires-Dist: async-lru
17
24
  Requires-Dist: krec
18
25
  Provides-Extra: dev
19
26
  Requires-Dist: black; extra == "dev"
@@ -22,12 +29,14 @@ Requires-Dist: mypy; extra == "dev"
22
29
  Requires-Dist: pytest; extra == "dev"
23
30
  Requires-Dist: ruff; extra == "dev"
24
31
  Requires-Dist: datamodel-code-generator; extra == "dev"
25
-
26
- <p align="center">
27
- <picture>
28
- <img alt="K-Scale Open Source Robotics" src="https://media.kscale.dev/kscale-open-source-header.png" style="max-width: 100%;">
29
- </picture>
30
- </p>
32
+ Dynamic: author
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: provides-extra
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
31
40
 
32
41
  <div align="center">
33
42
 
@@ -1,9 +1,3 @@
1
- <p align="center">
2
- <picture>
3
- <img alt="K-Scale Open Source Robotics" src="https://media.kscale.dev/kscale-open-source-header.png" style="max-width: 100%;">
4
- </picture>
5
- </p>
6
-
7
1
  <div align="center">
8
2
 
9
3
  [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/kscalelabs/ksim/blob/main/LICENSE)
@@ -1,6 +1,6 @@
1
1
  """Defines the common interface for the K-Scale Python API."""
2
2
 
3
- __version__ = "0.0.13"
3
+ __version__ = "0.1.1"
4
4
 
5
5
  from pathlib import Path
6
6
 
@@ -9,6 +9,3 @@ class K(
9
9
  APIBase,
10
10
  ):
11
11
  """Defines a common interface for the K-Scale API."""
12
-
13
- def __init__(self, api_key: str | None = None) -> None:
14
- self.api_key = api_key
@@ -0,0 +1,32 @@
1
+ """Defines the top-level KOL CLI."""
2
+
3
+ import logging
4
+
5
+ import click
6
+ import colorlogging
7
+
8
+ from kscale.utils.cli import recursive_help
9
+ from kscale.web.cli.robot import cli as robot_cli
10
+ from kscale.web.cli.robot_class import cli as robot_class_cli
11
+ from kscale.web.cli.token import cli as token_cli
12
+ from kscale.web.cli.user import cli as user_cli
13
+
14
+
15
+ @click.group()
16
+ def cli() -> None:
17
+ """Command line interface for interacting with the K-Scale web API."""
18
+ colorlogging.configure()
19
+
20
+ # Suppress aiohttp access logging
21
+ logging.getLogger("httpx").setLevel(logging.WARNING)
22
+ logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
23
+
24
+
25
+ cli.add_command(token_cli, "token")
26
+ cli.add_command(user_cli, "user")
27
+ cli.add_command(robot_class_cli, "robots")
28
+ cli.add_command(robot_cli, "robot")
29
+
30
+ if __name__ == "__main__":
31
+ # python -m kscale.cli
32
+ print(recursive_help(cli))
@@ -8,6 +8,9 @@ from pathlib import Path
8
8
 
9
9
  from omegaconf import II, OmegaConf
10
10
 
11
+ # This is the public API endpoint for the K-Scale WWW API.
12
+ DEFAULT_API_ROOT = "https://api.kscale.dev"
13
+
11
14
 
12
15
  def get_path() -> Path:
13
16
  if "KSCALE_CONFIG_DIR" in os.environ:
@@ -17,7 +20,7 @@ def get_path() -> Path:
17
20
 
18
21
  @dataclass
19
22
  class WWWSettings:
20
- api_key: str | None = field(default=None)
23
+ api_root: str = field(default=DEFAULT_API_ROOT)
21
24
  cache_dir: str = field(default=II("oc.env:KSCALE_CACHE_DIR,'~/.kscale/cache/'"))
22
25
 
23
26
 
@@ -5,13 +5,22 @@ omegaconf
5
5
  email_validator
6
6
 
7
7
  # HTTP requests
8
+ aiohttp
9
+ cryptography
8
10
  httpx
9
- requests
10
11
  pydantic
12
+ pyjwt
13
+ requests
14
+ yarl
11
15
 
12
16
  # CLI
13
- click
14
17
  aiofiles
18
+ click
19
+ colorlogging
20
+ tabulate
21
+
22
+ # Async
23
+ async-lru
15
24
 
16
25
  # K-Scale
17
26
  krec
@@ -0,0 +1,14 @@
1
+ """Defines a common interface for the K-Scale WWW API."""
2
+
3
+ from kscale.utils.api_base import APIBase
4
+ from kscale.web.clients.client import WWWClient
5
+ from kscale.web.gen.api import ProfileResponse
6
+
7
+
8
+ class WebAPI(APIBase):
9
+ async def www_client(self) -> WWWClient:
10
+ return WWWClient()
11
+
12
+ async def get_profile_info(self) -> ProfileResponse:
13
+ client = await self.www_client()
14
+ return await client.get_profile_info()
@@ -0,0 +1,100 @@
1
+ """Defines the CLI for getting information about robots."""
2
+
3
+ import click
4
+ from tabulate import tabulate
5
+
6
+ from kscale.utils.cli import coro
7
+ from kscale.web.clients.robot import RobotClient
8
+
9
+
10
+ @click.group()
11
+ def cli() -> None:
12
+ """Get information about robots."""
13
+ pass
14
+
15
+
16
+ @cli.command()
17
+ @coro
18
+ async def list() -> None:
19
+ client = RobotClient()
20
+ robots = await client.get_all_robots()
21
+ if robots:
22
+ table_data = [
23
+ [
24
+ click.style(robot.id, fg="blue"),
25
+ click.style(robot.robot_name, fg="green"),
26
+ click.style(robot.class_id, fg="yellow"),
27
+ robot.description or "N/A",
28
+ ]
29
+ for robot in robots
30
+ ]
31
+ click.echo(tabulate(table_data, headers=["ID", "Name", "Class", "Description"], tablefmt="simple"))
32
+ else:
33
+ click.echo(click.style("No robots found", fg="red"))
34
+
35
+
36
+ @cli.command()
37
+ @click.option("-u", "--user-id", type=str, default="me")
38
+ @coro
39
+ async def user(user_id: str = "me") -> None:
40
+ client = RobotClient()
41
+ robots = await client.get_user_robots(user_id)
42
+ if robots:
43
+ table_data = [
44
+ [
45
+ click.style(robot.id, fg="blue"),
46
+ click.style(robot.robot_name, fg="green"),
47
+ click.style(robot.class_id, fg="yellow"),
48
+ robot.description or "N/A",
49
+ ]
50
+ for robot in robots
51
+ ]
52
+ click.echo(tabulate(table_data, headers=["ID", "Name", "Class", "Description"], tablefmt="simple"))
53
+ else:
54
+ click.echo(click.style("No robots found", fg="red"))
55
+
56
+
57
+ @cli.command()
58
+ @click.argument("robot_id")
59
+ @coro
60
+ async def id(robot_id: str) -> None:
61
+ client = RobotClient()
62
+ robot = await client.get_robot_by_id(robot_id)
63
+ click.echo("Robot:")
64
+ click.echo(f" ID: {click.style(robot.id, fg='blue')}")
65
+ click.echo(f" Name: {click.style(robot.robot_name, fg='green')}")
66
+ click.echo(f" Class: {click.style(robot.class_name, fg='yellow')}")
67
+ click.echo(f" Description: {click.style(robot.description or 'N/A', fg='yellow')}")
68
+
69
+
70
+ @cli.command()
71
+ @click.argument("robot_name")
72
+ @coro
73
+ async def name(robot_name: str) -> None:
74
+ client = RobotClient()
75
+ robot = await client.get_robot_by_name(robot_name)
76
+ click.echo("Robot:")
77
+ click.echo(f" ID: {click.style(robot.id, fg='blue')}")
78
+ click.echo(f" Name: {click.style(robot.robot_name, fg='green')}")
79
+ click.echo(f" Class: {click.style(robot.class_name, fg='yellow')}")
80
+ click.echo(f" Description: {click.style(robot.description or 'N/A', fg='yellow')}")
81
+
82
+
83
+ @cli.command()
84
+ @click.argument("class_name")
85
+ @click.argument("name")
86
+ @click.option("-c", "--class-name", type=str, required=True)
87
+ @click.option("-d", "--description", type=str, default=None)
88
+ @coro
89
+ async def add(name: str, class_name: str, description: str | None = None) -> None:
90
+ client = RobotClient()
91
+ robot = await client.add_robot(name, class_name, description)
92
+ click.echo("Robot added:")
93
+ click.echo(f" ID: {click.style(robot.id, fg='blue')}")
94
+ click.echo(f" Name: {click.style(robot.robot_name, fg='green')}")
95
+ click.echo(f" Class: {click.style(robot.class_name, fg='yellow')}")
96
+ click.echo(f" Description: {click.style(robot.description or 'N/A', fg='yellow')}")
97
+
98
+
99
+ if __name__ == "__main__":
100
+ cli()
@@ -0,0 +1,113 @@
1
+ """Defines the CLI for getting information about robot classes."""
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.robot_class import RobotClassClient
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ @click.group()
15
+ def cli() -> None:
16
+ """Get information about robot classes."""
17
+ pass
18
+
19
+
20
+ @cli.command()
21
+ @coro
22
+ async def list() -> None:
23
+ """Lists all robot classes."""
24
+ client = RobotClassClient()
25
+ robot_classes = await client.get_robot_classes()
26
+ if robot_classes:
27
+ # Prepare table data
28
+ table_data = [
29
+ [
30
+ click.style(rc.id, fg="blue"),
31
+ click.style(rc.class_name, fg="green"),
32
+ rc.description or "N/A",
33
+ ]
34
+ for rc in robot_classes
35
+ ]
36
+ click.echo(tabulate(table_data, headers=["ID", "Name", "Description"], tablefmt="simple"))
37
+ else:
38
+ click.echo(click.style("No robot classes found", fg="red"))
39
+
40
+
41
+ @cli.command()
42
+ @click.argument("name")
43
+ @click.option("-d", "--description", type=str, default=None)
44
+ @coro
45
+ async def add(
46
+ name: str,
47
+ description: str | None = None,
48
+ ) -> None:
49
+ """Adds a new robot class."""
50
+ async with RobotClassClient() as client:
51
+ robot_class = await client.create_robot_class(name, description)
52
+ click.echo("Robot class created:")
53
+ click.echo(f" ID: {click.style(robot_class.id, fg='blue')}")
54
+ click.echo(f" Name: {click.style(robot_class.class_name, fg='green')}")
55
+ click.echo(f" Description: {click.style(robot_class.description or 'N/A', fg='yellow')}")
56
+
57
+
58
+ @cli.command()
59
+ @click.argument("current_name")
60
+ @click.option("-n", "--name", type=str, default=None)
61
+ @click.option("-d", "--description", type=str, default=None)
62
+ @coro
63
+ async def update(current_name: str, name: str | None = None, description: str | None = None) -> None:
64
+ """Updates a robot class."""
65
+ async with RobotClassClient() as client:
66
+ robot_class = await client.update_robot_class(current_name, name, description)
67
+ click.echo("Robot class updated:")
68
+ click.echo(f" ID: {click.style(robot_class.id, fg='blue')}")
69
+ click.echo(f" Name: {click.style(robot_class.class_name, fg='green')}")
70
+ click.echo(f" Description: {click.style(robot_class.description or 'N/A', fg='yellow')}")
71
+
72
+
73
+ @cli.command()
74
+ @click.argument("name")
75
+ @coro
76
+ async def delete(name: str) -> None:
77
+ """Deletes a robot class."""
78
+ async with RobotClassClient() as client:
79
+ await client.delete_robot_class(name)
80
+ click.echo(f"Robot class deleted: {click.style(name, fg='red')}")
81
+
82
+
83
+ @cli.group()
84
+ def urdf() -> None:
85
+ """Handle the robot class URDF."""
86
+ pass
87
+
88
+
89
+ @urdf.command()
90
+ @click.argument("class_name")
91
+ @click.argument("urdf_file")
92
+ @coro
93
+ async def upload(class_name: str, urdf_file: str) -> None:
94
+ """Uploads a URDF file to a robot class."""
95
+ async with RobotClassClient() as client:
96
+ response = await client.upload_robot_class_urdf(class_name, urdf_file)
97
+ click.echo("URDF uploaded:")
98
+ click.echo(f" Filename: {click.style(response.filename, fg='green')}")
99
+
100
+
101
+ @urdf.command()
102
+ @click.argument("class_name")
103
+ @click.option("--no-cache", is_flag=True, default=False)
104
+ @coro
105
+ async def download(class_name: str, no_cache: bool) -> None:
106
+ """Downloads a URDF file from a robot class."""
107
+ async with RobotClassClient() as client:
108
+ urdf_file = await client.download_robot_class_urdf(class_name, cache=not no_cache)
109
+ click.echo(f"URDF downloaded: {click.style(urdf_file, fg='green')}")
110
+
111
+
112
+ if __name__ == "__main__":
113
+ cli()
@@ -0,0 +1,33 @@
1
+ """Defines the CLI for interacting with K-Scale's OpenID Connect server."""
2
+
3
+ import logging
4
+
5
+ import click
6
+
7
+ from kscale.utils.cli import coro
8
+ from kscale.web.clients.base import BaseClient
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @click.group()
14
+ def cli() -> None:
15
+ """Retrieve an OICD token from the K-Scale authentication server."""
16
+ pass
17
+
18
+
19
+ @cli.command()
20
+ @coro
21
+ async def get() -> None:
22
+ """Get a bearer token from OpenID Connect."""
23
+ logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
24
+ async with BaseClient() as client:
25
+ try:
26
+ token = await client.get_bearer_token()
27
+ logger.info("Bearer token: %s", token)
28
+ except Exception:
29
+ logger.exception("Error getting bearer token")
30
+
31
+
32
+ if __name__ == "__main__":
33
+ cli()
@@ -0,0 +1,33 @@
1
+ """Defines the CLI for getting information about the current user."""
2
+
3
+ import logging
4
+
5
+ import click
6
+
7
+ from kscale.utils.cli import coro
8
+ from kscale.web.clients.user import UserClient
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @click.group()
14
+ def cli() -> None:
15
+ """Get information about the currently-authenticated user."""
16
+ pass
17
+
18
+
19
+ @cli.command()
20
+ @coro
21
+ async def me() -> None:
22
+ client = UserClient()
23
+ profile = await client.get_profile_info()
24
+ logger.info("Email: %s", profile.email)
25
+ logger.info("Email verified: %s", profile.email_verified)
26
+ logger.info("User ID: %s", profile.user.user_id)
27
+ logger.info("Is admin: %s", profile.user.is_admin)
28
+ logger.info("Can upload: %s", profile.user.can_upload)
29
+ logger.info("Can test: %s", profile.user.can_test)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ cli()
File without changes