Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ serve-docs:
clean-docs:
uv run --isolated --all-packages --group docs $(MAKE) -C docs clean

doctest:
uv run --isolated --all-packages --group docs $(MAKE) -C docs doctest

test-%: packages/%
uv run --isolated --directory $< pytest

Expand All @@ -33,7 +36,7 @@ clean-test:
sync:
uv sync --all-packages --all-extras

test: test-packages
test: test-packages doctest

generate:
buf generate
Expand Down
78 changes: 48 additions & 30 deletions docs/source/api-reference/adapters/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,55 +41,73 @@ export:
path: /tmp/test.sock
```

Forward a remote TCP port to a local TCP port

```{testcode}
# random port on localhost
with TcpPortforwardAdapter(client.tcp_port) as addr:
print(addr[0], addr[1]) # 127.0.0.1 38406

# specific address and port
with TcpPortforwardAdapter(client.tcp_port, local_host="192.0.2.1", local_port=8080) as addr:
print(addr[0], addr[1]) # 192.0.2.1 8080
### Forward a remote TCP port to a local TCP port

```{doctest}
>>> # random port on localhost
>>> with TcpPortforwardAdapter(client=client.tcp_port) as addr:
... print(addr[0], addr[1])
127.0.0.1 ...
>>>
>>> # specific address and port
>>> with TcpPortforwardAdapter(client=client.tcp_port, local_host="127.0.0.2", local_port=8080) as addr:
... print(addr[0], addr[1])
127.0.0.2 8080
```

Forward a remote Unix domain socket to a local socket

```{testcode}
with UnixPortforwardAdapter(client.unix_socket) as addr:
print(addr) # /tmp/jumpstarter-w30wxu64/socket

# the type of the remote socket and the local one doesn't have to match
# e.g. forward a remote Unix domain socket to a local TCP port
with TcpPortforwardAdapter(client.unix_socket) as addr:
print(addr[0], addr[1]) # 127.0.0.1 38406
### Forward a remote Unix domain socket to a local socket

```{doctest}
>>> with UnixPortforwardAdapter(client=client.unix_socket) as addr:
... print(addr)
/tmp/jumpstarter-.../socket
>>> # the type of the remote socket and the local one doesn't have to match
>>> # e.g. forward a remote Unix domain socket to a local TCP port
>>> with TcpPortforwardAdapter(client=client.unix_socket) as addr:
... print(addr[0], addr[1])
127.0.0.1 ...
```

Connect to a remote TCP port with a web-based VNC client

```{testcode}
with NovncAdapter(client.tcp_port) as url:
print(url) # https://novnc.com/noVNC/vnc.html?autoconnect=1&reconnect=1&host=127.0.0.1&port=36459
# open the url in browser to access the VNC client
```{doctest}
>>> with NovncAdapter(client=client.tcp_port) as url:
... print(url) # open the url in browser to access the VNC client
https://novnc.com/noVNC/vnc.html?autoconnect=1&reconnect=1&host=127.0.0.1&port=...
```

Interact with a remote TCP port as if it's a serial console

See [pexpect](https://pexpect.readthedocs.io/en/stable/api/fdpexpect.html) for API documentation

```{testcode}
with PexpectAdapter(client.tcp_port) as expect:
expect.expect("localhost login:")
expect.send("root\n")
expect.expect("Password:")
expect.send("secret\n")
```{doctest}
>>> # the server echos all inputs
>>> with PexpectAdapter(client=client.tcp_port) as expect:
... assert expect.send("hello") == 5 # written 5 bytes
... assert expect.expect(["hi", "hello"]) == 1 # found string at index 1
```

Connect to a remote TCP port with the fabric SSH client

See [fabric](https://docs.fabfile.org/en/latest/api/connection.html#fabric.connection.Connection) for API documentation

```{testcode}
:skipif: True
with FabricAdapter(client=client.tcp_port, connect_kwargs={"password": "secret"}) as conn:
conn.run("uname")
```

```{testsetup} *
from jumpstarter_driver_network.adapters import *
from jumpstarter_driver_network.driver import *
from jumpstarter_driver_composite.driver import Composite
from jumpstarter.common.utils import serve

instance = serve(Composite(children={"tcp_port": EchoNetwork(), "unix_socket": EchoNetwork()}))

client = instance.__enter__()
```

```{testcleanup} *
instance.__exit__(None, None, None)
```
13 changes: 4 additions & 9 deletions docs/source/api-reference/drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,9 @@ This project is still evolving, so these docs may be incomplete or out-of-date.
```

## Example
```{testsetup} *
import jumpstarter.common.importlib

def import_class(class_path, allow, unsafe):
return globals()["ExampleClient"]

jumpstarter.common.importlib.import_class = import_class
```

```{testcode}
from sys import modules
from types import SimpleNamespace
from anyio import connect_tcp, sleep
from contextlib import asynccontextmanager
from collections.abc import Generator
Expand Down Expand Up @@ -74,6 +67,8 @@ class ExampleClient(DriverClient):
def echo_generator(self, message) -> Generator[str, None, None]:
yield from self.streamingcall("echo_generator", message)

modules["example"] = SimpleNamespace(ExampleClient=ExampleClient)

with serve(ExampleDriver()) as client:
print(client.echo("hello"))
assert list(client.echo_generator("hello")) == ["hello"] * 10
Expand Down
24 changes: 18 additions & 6 deletions docs/source/api-reference/drivers/pyserial.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,32 @@ Using expect without a context manager
session = pyserialclient.open()
session.sendline("Hello, world!")
session.expect("Hello, world!")
session.close()
pyserialclient.close()
```

Using a simple BlockingStream with a context manager
```{testcode}
with pyserialclient.stream() as stream:
stream.write(b"Hello, world!")
data = stream.read(13)
stream.send(b"Hello, world!")
data = stream.receive()
```

Using a simple BlockingStream without a context manager
```{testcode}
stream = pyserialclient.open_stream()
stream.write(b"Hello, world!")
data = stream.read(13)
stream.close()
stream.send(b"Hello, world!")
data = stream.receive()
```

```{testsetup} *
from jumpstarter_driver_pyserial.driver import PySerial
from jumpstarter.common.utils import serve

instance = serve(PySerial(url="loop://"))

pyserialclient = instance.__enter__()
```

```{testcleanup} *
instance.__exit__(None, None, None)
```
1 change: 1 addition & 0 deletions docs/source/api-reference/drivers/yepkit.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The yepkit ykush driver provides a `PowerClient` with the following API:
### Examples
Powering on and off a device
```{testcode}
:skipif: True
client.power.on()
time.sleep(1)
client.power.off()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def open(self) -> fdspawn:
self._context_manager = self.pexpect()
return self._context_manager.__enter__()

def close(self):
if hasattr(self, "_context_manager"):
self._context_manager.__exit__(None, None, None)

@contextmanager
def pexpect(self):
"""
Expand Down