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

Commit c447e70

Browse files
add methods_description and extend export and exportstream decorators
to allow driver-provided help text for exported methods. Map od methods and their descriptions is added to GetReport to save on RPC calls.
1 parent d02013a commit c447e70

File tree

4 files changed

+49
-18
lines changed

4 files changed

+49
-18
lines changed

packages/jumpstarter/jumpstarter/client/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class DriverClient(AsyncDriverClient):
3636
description: str | None = None
3737
"""Driver description from GetReport(), used for CLI help text"""
3838

39+
methods_description: dict[str, str] = field(default_factory=dict)
40+
"""Map of method names to their help descriptions from GetReport()"""
41+
3942
def call(self, method, *args):
4043
"""
4144
Invoke driver call

packages/jumpstarter/jumpstarter/client/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ async def client_from_channel(
6060
stack=stack.enter_context(ExitStack()),
6161
children={reports[k].labels["jumpstarter.dev/name"]: clients[k] for k in topo[index]},
6262
description=getattr(report, 'description', None) or None,
63+
methods_description=getattr(report, 'methods_description', {}) or {},
6364
)
6465

6566
clients[index] = client

packages/jumpstarter/jumpstarter/driver/base.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
from .decorators import (
2525
MARKER_DRIVERCALL,
26+
MARKER_HELP,
2627
MARKER_MAGIC,
2728
MARKER_STREAMCALL,
2829
MARKER_STREAMING_DRIVERCALL,
@@ -75,6 +76,9 @@ class Driver(
7576
description: str | None = None
7677
"""Custom description for the driver (shown in CLI help)"""
7778

79+
methods_description: dict[str, str] = field(default_factory=dict, init=False)
80+
"""Map of method names to their help descriptions"""
81+
7882
log_level: str = "INFO"
7983
logger: logging.Logger = field(init=False)
8084

@@ -85,6 +89,23 @@ def __post_init__(self):
8589
self.logger = logging.getLogger(self.__class__.__name__)
8690
self.logger.setLevel(self.log_level)
8791

92+
# Collect help texts from decorated methods
93+
self._collect_methods_description()
94+
95+
def _collect_methods_description(self):
96+
"""Collect help texts from @export and @exportstream decorated methods"""
97+
for name in dir(self):
98+
if name.startswith('_'):
99+
continue
100+
101+
try:
102+
method = getattr(self, name)
103+
if callable(method) and hasattr(method, MARKER_HELP):
104+
help_text = getattr(method, MARKER_HELP)
105+
self.methods_description[name] = help_text
106+
except Exception:
107+
continue
108+
88109
def close(self):
89110
for child in self.children.values():
90111
child.close()
@@ -210,6 +231,7 @@ def report(self, *, root=None, parent=None, name=None):
210231
| ({"jumpstarter.dev/client": self.client()})
211232
| ({"jumpstarter.dev/name": name} if name else {}),
212233
description=self.description or None,
234+
methods_description=self.methods_description or {},
213235
)
214236

215237
def enumerate(self, *, root=None, parent=None, name=None):
Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
from inspect import isasyncgenfunction, iscoroutinefunction, isfunction, isgeneratorfunction
2-
from typing import Final
2+
from typing import Callable, Final
33

44
MARKER_MAGIC: Final[str] = "07c9b9cc"
55
MARKER_DRIVERCALL: Final[str] = "marker_drivercall"
66
MARKER_STREAMCALL: Final[str] = "marker_streamcall"
77
MARKER_STREAMING_DRIVERCALL: Final[str] = "marker_streamingdrivercall"
8+
MARKER_HELP: Final[str] = "marker_help"
89

910

10-
def export(func):
11-
"""
12-
Decorator for exporting method as driver call
13-
"""
14-
if isasyncgenfunction(func) or isgeneratorfunction(func):
15-
setattr(func, MARKER_STREAMING_DRIVERCALL, MARKER_MAGIC)
16-
elif iscoroutinefunction(func) or isfunction(func):
17-
setattr(func, MARKER_DRIVERCALL, MARKER_MAGIC)
18-
else:
19-
raise ValueError(f"unsupported exported function {func}")
20-
return func
11+
def export(func: Callable | None = None, *, help: str | None = None) -> Callable:
12+
"""Decorator for exporting method as driver call"""
13+
def decorator(f: Callable) -> Callable:
14+
if isasyncgenfunction(f) or isgeneratorfunction(f):
15+
setattr(f, MARKER_STREAMING_DRIVERCALL, MARKER_MAGIC)
16+
elif iscoroutinefunction(f) or isfunction(f):
17+
setattr(f, MARKER_DRIVERCALL, MARKER_MAGIC)
18+
else:
19+
raise ValueError(f"unsupported exported function {f}")
20+
if help is not None:
21+
setattr(f, MARKER_HELP, help)
22+
return f
23+
return decorator(func) if func else decorator
2124

2225

23-
def exportstream(func):
24-
"""
25-
Decorator for exporting method as stream
26-
"""
27-
setattr(func, MARKER_STREAMCALL, MARKER_MAGIC)
28-
return func
26+
def exportstream(func: Callable | None = None, *, help: str | None = None) -> Callable:
27+
"""Decorator for exporting method as stream"""
28+
def decorator(f: Callable) -> Callable:
29+
setattr(f, MARKER_STREAMCALL, MARKER_MAGIC)
30+
if help is not None:
31+
setattr(f, MARKER_HELP, help)
32+
return f
33+
return decorator(func) if func else decorator

0 commit comments

Comments
 (0)