Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 831608f

Browse files
committed
Add support for jmp pkg show command
1 parent f45e1ba commit 831608f

6 files changed

Lines changed: 135 additions & 75 deletions

File tree

packages/jumpstarter-cli-pkg/jumpstarter_cli_pkg/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from jumpstarter_cli_common import AliasedGroup
33

44
from .commands.list import list
5+
from .commands.show import show
56

67

78
@click.group(cls=AliasedGroup)
@@ -10,6 +11,7 @@ def pkg():
1011

1112

1213
pkg.add_command(list)
14+
pkg.add_command(show)
1315

1416
if __name__ == "__main__":
1517
pkg()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import asyncclick as click
2+
from jumpstarter_cli_common import OutputType, opt_output_all
3+
from jumpstarter_cli_common.exceptions import handle_exceptions
4+
from jumpstarter_cli_pkg.repository import LocalDriverRepository
5+
6+
7+
@click.command("show")
8+
@click.argument("package")
9+
@click.option("--drivers", is_flag=True, help="Print drivers only.")
10+
@click.option("--driver-clients", is_flag=True, help="Print driver clients only.")
11+
@click.option("--adapters", is_flag=True, help="Print adapters only.")
12+
@opt_output_all
13+
@handle_exceptions
14+
def show(package: str, drivers: bool, driver_clients: bool, adapters: bool, output: OutputType):
15+
local_repo = LocalDriverRepository.from_venv()
16+
local_package = local_repo.get_package(package)
17+
click.echo("Name: " + local_package.name)
18+
click.echo("Version: " + local_package.version)
19+
click.echo("Summary: " + (local_package.summary if local_package.summary else ""))
20+
click.echo("Categories: " + ", ".join(local_package.categories))
21+
click.echo("License: " + (local_package.license if local_package.license else ""))
22+
click.echo("Drivers:")
23+
for driver in local_package.drivers.items:
24+
click.echo(f" {driver.name}:")
25+
click.echo(f" Name: {driver.name}")
26+
click.echo(f" Type: {driver.type}")
27+
click.echo("Driver Clients:")
28+
for driver_client in local_package.driver_clients.items:
29+
click.echo(f" {driver_client.name}:")
30+
click.echo(f" Name: {driver_client.name}")
31+
click.echo(f" Type: {driver_client.type}")
32+
click.echo("Adapters:")
33+
for adapter in local_package.adapters.items:
34+
click.echo(f" {driver_client.name}:")
35+
click.echo(f" Name: {adapter.name}")
36+
click.echo(f" Type: {adapter.type}")
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
from abc import ABC, abstractmethod
22

33
from .package import (
4+
V1Alpha1DriverPackage,
45
V1Alpha1DriverPackageList,
56
)
67

78

89
class DriverRepository(ABC):
910
"""
10-
A repository of driver packages.
11+
A repository of Jumpstarter plugin packages.
1112
"""
1213

1314
@abstractmethod
1415
def list_packages(self) -> V1Alpha1DriverPackageList:
1516
"""
16-
List all available driver packages.
17+
List all available packages.
18+
"""
19+
pass
20+
21+
@abstractmethod
22+
def get_package(self, name: str) -> V1Alpha1DriverPackage:
23+
"""
24+
Get a package by name.
1725
"""
1826
pass
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class EntryPointGroups:
2+
DRIVER_ENTRY_POINT_GROUP = "jumpstarter.drivers"
3+
DRIVER_CLIENT_ENTRY_POINT_GROUP = "jumpstarter.clients"
4+
ADAPTER_ENTRY_POINT_GROUP = "jumpstarter.adapters"
Lines changed: 28 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,52 @@
1-
from importlib.metadata import entry_points
1+
from importlib.metadata import Distribution, PackageNotFoundError, distribution, distributions
22

33
from .base import DriverRepository
4+
from .entry_points import EntryPointGroups
45
from .package import (
5-
V1Alpha1AdapterEntryPoint,
6-
V1Alpha1DriverClientEntryPoint,
7-
V1Alpha1DriverEntryPoint,
86
V1Alpha1DriverPackage,
97
V1Alpha1DriverPackageList,
108
)
9+
from jumpstarter.common.exceptions import JumpstarterException
1110

1211

1312
class LocalDriverRepository(DriverRepository):
1413
"""
1514
A local repository of driver packages from the current venv.
1615
"""
1716

18-
DRIVER_ENTRY_POINT_GROUP = "jumpstarter.drivers"
19-
DRIVER_CLIENT_ENTRY_POINT_GROUP = "jumpstarter.clients"
20-
ADAPTER_ENTRY_POINT_GROUP = "jumpstarter.adapters"
21-
2217
@staticmethod
2318
def from_venv():
2419
"""
2520
Create a `LocalDriverRepository` from the current venv.
2621
"""
2722
return LocalDriverRepository()
2823

29-
def _get_driver_packages_from_entry_points(self) -> list[V1Alpha1DriverPackage]:
30-
# Create a dict of driver packages to collect entry points
31-
driver_packages: dict[str, V1Alpha1DriverPackage] = {}
32-
33-
# Closure to process entry points for a specific entry point group
34-
def _process_entry_points(group: str):
35-
"""Process entry points for a specific group and add them to driver_packages."""
36-
for entry_point in list(entry_points(group=group)):
37-
package_id = f"{entry_point.dist.name}=={entry_point.dist.version}"
38-
# Check if the package is in the driver packages
39-
if package_id not in driver_packages:
40-
# Create a new package
41-
if entry_point.dist is not None:
42-
# Create the package from the entry point distribution metadata
43-
driver_packages[package_id] = V1Alpha1DriverPackage.from_distribution(entry_point.dist)
44-
else:
45-
# Skip this entry point if the distribution metadata is not available
46-
continue
47-
# Add the driver/client to the package
48-
match group:
49-
case LocalDriverRepository.DRIVER_ENTRY_POINT_GROUP:
50-
driver_packages[package_id].drivers.items.append(
51-
V1Alpha1DriverEntryPoint.from_entry_point(entry_point)
52-
)
53-
case LocalDriverRepository.DRIVER_CLIENT_ENTRY_POINT_GROUP:
54-
driver_packages[package_id].driver_clients.items.append(
55-
V1Alpha1DriverClientEntryPoint.from_entry_point(entry_point)
56-
)
57-
case LocalDriverRepository.ADAPTER_ENTRY_POINT_GROUP:
58-
driver_packages[package_id].adapters.items.append(
59-
V1Alpha1AdapterEntryPoint.from_entry_point(entry_point)
60-
)
61-
62-
# Process driver entry points
63-
_process_entry_points(LocalDriverRepository.DRIVER_ENTRY_POINT_GROUP)
64-
65-
# Process client entry points
66-
_process_entry_points(LocalDriverRepository.DRIVER_CLIENT_ENTRY_POINT_GROUP)
67-
68-
# Process adapter entry points
69-
_process_entry_points(LocalDriverRepository.DRIVER_CLIENT_ENTRY_POINT_GROUP)
70-
71-
# Return the assembled driver packages list
72-
return list(driver_packages.values())
24+
def _is_jumpstarter_package(self, dist: Distribution) -> bool:
25+
for entry_point in list(dist.entry_points):
26+
match entry_point.group:
27+
case (
28+
EntryPointGroups.DRIVER_ENTRY_POINT_GROUP
29+
| EntryPointGroups.DRIVER_CLIENT_ENTRY_POINT_GROUP
30+
| EntryPointGroups.ADAPTER_ENTRY_POINT_GROUP
31+
):
32+
return True
33+
case _:
34+
return False
35+
return False
7336

7437
def list_packages(self) -> V1Alpha1DriverPackageList:
7538
# Get the local drivers using the Jumpstarter drivers entry point
76-
driver_packages = self._get_driver_packages_from_entry_points()
39+
driver_packages = []
40+
# Iterate through the local package distributions
41+
for dist in distributions():
42+
# Check if the distribution is a Jumpstarter package
43+
if self._is_jumpstarter_package(dist):
44+
driver_packages.append(V1Alpha1DriverPackage.from_distribution(dist))
7745
return V1Alpha1DriverPackageList(items=driver_packages)
46+
47+
def get_package(self, name: str) -> V1Alpha1DriverPackage:
48+
try:
49+
# Convert the distribution to a driver package object
50+
return V1Alpha1DriverPackage.from_distribution(distribution(name))
51+
except PackageNotFoundError as e:
52+
raise JumpstarterException(f"Package '{name}' metadata could not be found") from e

packages/jumpstarter-cli-pkg/jumpstarter_cli_pkg/repository/package.py

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from pydantic import Field
55

6+
from .entry_points import EntryPointGroups
7+
from jumpstarter.common.exceptions import JumpstarterException
68
from jumpstarter.models import JsonBaseModel, ListBaseModel
79

810

@@ -57,7 +59,7 @@ def from_entry_point(ep: EntryPoint):
5759
return V1Alpha1DriverEntryPoint(name=ep.name, type=ep.value.replace(":", "."), package=ep.dist.name)
5860

5961

60-
class V1Alpha1AdapterEntryPointList(ListBaseModel[V1Alpha1DriverEntryPoint]):
62+
class V1Alpha1AdapterEntryPointList(ListBaseModel[V1Alpha1AdapterEntryPoint]):
6163
"""
6264
A list of Jumpstarter adapter list models.
6365
"""
@@ -73,7 +75,7 @@ class V1Alpha1DriverEntryPointList(ListBaseModel[V1Alpha1DriverEntryPoint]):
7375
kind: Literal["DriverEntryPointList"] = Field(default="DriverEntryPointList")
7476

7577

76-
class V1Alpha1DriverClientEntryPointList(ListBaseModel[V1Alpha1DriverEntryPoint]):
78+
class V1Alpha1DriverClientEntryPointList(ListBaseModel[V1Alpha1DriverClientEntryPoint]):
7779
"""
7880
A list of Jumpstarter driver client classes.
7981
"""
@@ -107,26 +109,39 @@ def requires_dist_to_categories(name: str, requires_dist: list[str]) -> list[str
107109
Convert the `Requires-Dist` metadata to Jumpstarter driver categories.
108110
"""
109111
categories = []
110-
# Check the package name
111-
match name:
112-
case "jumpstarter-driver-composite":
113-
categories.append("composite")
114-
case "jumpstarter-driver-network":
115-
categories.append("network")
116-
case "jumpstarter-driver-opendal":
117-
categories.append("storage")
118-
case "jumpstarter-driver-power":
119-
categories.append("power")
112+
113+
# Define constants for root driver package names
114+
DRIVER_COMPOSITE = "jumpstarter-driver-composite"
115+
DRIVER_NETWORK = "jumpstarter-driver-network"
116+
DRIVER_OPENDAL = "jumpstarter-driver-storage"
117+
DRIVER_POWER = "jumpstarter-driver-power"
118+
119+
# Define constants for category names
120+
CATEGORY_COMPOSITE = "composite"
121+
CATEGORY_NETWORK = "network"
122+
CATEGORY_STORAGE = "storage"
123+
CATEGORY_POWER = "power"
124+
125+
# Check package name
126+
if name == DRIVER_COMPOSITE:
127+
categories.append(CATEGORY_COMPOSITE)
128+
elif name == DRIVER_NETWORK:
129+
categories.append(CATEGORY_NETWORK)
130+
elif name == DRIVER_OPENDAL:
131+
categories.append(CATEGORY_STORAGE)
132+
elif name == DRIVER_POWER:
133+
categories.append(CATEGORY_POWER)
134+
120135
# Check package dependencies
121136
for dist in requires_dist:
122-
if "jumpstarter-driver-composite" in dist and "composite" not in categories:
123-
categories.append("composite")
124-
elif "jumpstarter-driver-network" in dist and "network" not in categories:
125-
categories.append("network")
126-
elif "jumpstarter-driver-opendal" in dist and "storage" not in categories:
127-
categories.append("storage")
128-
elif "jumpstarter-driver-power" in dist and "power" not in categories:
129-
categories.append("power")
137+
if DRIVER_COMPOSITE in dist and CATEGORY_COMPOSITE not in categories:
138+
categories.append(CATEGORY_COMPOSITE)
139+
elif DRIVER_NETWORK in dist and CATEGORY_NETWORK not in categories:
140+
categories.append(CATEGORY_NETWORK)
141+
elif DRIVER_OPENDAL in dist and CATEGORY_STORAGE not in categories:
142+
categories.append(CATEGORY_STORAGE)
143+
elif DRIVER_POWER in dist and CATEGORY_POWER not in categories:
144+
categories.append(CATEGORY_POWER)
130145

131146
return categories
132147

@@ -135,6 +150,23 @@ def from_distribution(dist: Distribution):
135150
"""
136151
Create a `DriverPackage` from an `importlib.metadata.EntryPoint`.
137152
"""
153+
# Collect entry points for each type
154+
drivers = []
155+
driver_clients = []
156+
adapters = []
157+
# Iterate through the entry points
158+
for ep in list(dist.entry_points):
159+
match ep.group:
160+
case EntryPointGroups.DRIVER_ENTRY_POINT_GROUP:
161+
drivers.append(V1Alpha1DriverEntryPoint.from_entry_point(ep))
162+
case EntryPointGroups.DRIVER_CLIENT_ENTRY_POINT_GROUP:
163+
driver_clients.append(V1Alpha1DriverClientEntryPoint.from_entry_point(ep))
164+
case EntryPointGroups.ADAPTER_ENTRY_POINT_GROUP:
165+
adapters.append(V1Alpha1AdapterEntryPoint.from_entry_point(ep))
166+
# Check if any entry points were found
167+
if len(drivers) + len(driver_clients) + len(adapters) == 0:
168+
raise JumpstarterException(f"No valid Jumpstarter entry points found for package '{dist.name}'")
169+
# Return the completed driver package
138170
return V1Alpha1DriverPackage(
139171
name=dist.name,
140172
categories=V1Alpha1DriverPackage.requires_dist_to_categories(
@@ -143,6 +175,9 @@ def from_distribution(dist: Distribution):
143175
version=dist.version,
144176
summary=dist.metadata.get("Summary"),
145177
license=dist.metadata.get("License"),
178+
drivers=V1Alpha1DriverEntryPointList(items=drivers),
179+
driver_clients=V1Alpha1DriverClientEntryPointList(items=driver_clients),
180+
adapters=V1Alpha1AdapterEntryPointList(items=adapters),
146181
)
147182

148183
def list_drivers(self) -> V1Alpha1DriverEntryPointList:

0 commit comments

Comments
 (0)