Skip to content

Client and connections

TapClient finds the running server; TapConnection attaches devices and holds them. The server's device list and info types, and starting the server from code. Guide: The tap server, Python + pytest.

TapClient

One gRPC channel to a host server. Cheap to create; share one per process.

:meth:create is the usual entry point: it resolves the endpoint (see :func:resolve_endpoint) and, when discovering from daemon.json, checks that the server answers Info. The constructor takes an already-resolved :class:Endpoint and does no I/O. Use it as a context manager, or call :meth:close when done.

Methods:

Name Description
create

A client for address (token: token or TAP_TOKEN), or for TAP_SERVER,

info

Daemon version and pid, protocol version, ADB executable, state dir, whether the

devices

Every device ADB lists, with its state (FREE, LEASED, QUARANTINED,

connect

Open a TapConnection named name.

connections

Every live client connection, held or observed, with the devices attached to it.

resume

The held connection named name (see connect(..., hold=...)), for this process

close

Closes the channel. Close every TapConnection first.

endpoint

endpoint: Endpoint

The server address and bearer token this client uses.

address

address: str

The server's host:port.

create

create(address: str | None = None, token: str | None = None) -> TapClient

A client for address (token: token or TAP_TOKEN), or for TAP_SERVER, or for the live server daemon.json records. Raises TapError telling you to run tap start when none is running. Never starts a server.

info

info() -> ServerInfo

Daemon version and pid, protocol version, ADB executable, state dir, whether the daemon carries a driver to install (driver_available) and its default timeouts.

devices

devices() -> list[DeviceEntry]

Every device ADB lists, with its state (FREE, LEASED, QUARANTINED, OFFLINE, UNAUTHORIZED); only FREE and LEASED devices can be attached.

connect

connect(name: str, first_event_timeout: float = FIRST_EVENT_TIMEOUT, *, hold: float | None = None) -> TapConnection

Open a TapConnection named name.

By default the connection is observed: its liveness stream is established before this returns (the server's first event, observing, is awaited for up to first_event_timeout seconds, else TapError and the new id is disconnected), so every later attach_device belongs to a live connection: if this process dies, the server detaches every device the connection owns. A later closing event (the daemon reaped the connection or is shutting down) makes it unusable with the daemon's reason.

With hold (seconds, 1 to 86400) the connection is held instead: it has no liveness stream and outlives this process, so a later process can pick it up with :meth:resume. Every call naming it restarts its idle clock; it ends after hold seconds without one, on close from any process, or when the daemon stops. Its name must be unique among held connections (else ServerError, reason DAEMON_PRECONDITION). Tests should keep the default: a crashed test run would otherwise hold its devices until the timeout. hold is experimental (the agent surface): it may change in any release.

Use the result as a context manager, or call close when done.

connections

connections() -> list[ConnectionEntry]

Every live client connection, held or observed, with the devices attached to it.

resume

resume(name: str) -> TapConnection

The held connection named name (see connect(..., hold=...)), for this process to use; attached_devices() returns its devices. Raises TapError when no held connection has that name (it was closed, or it expired).

Experimental: not covered by the compatibility promise; it may change in any release.

close

close() -> None

Closes the channel. Close every TapConnection first.

TapConnection

An identity at the server that owns attached devices, returned by TapClient.connect.

An observed connection (the default) has its liveness stream already running on a background thread. If this process dies, the server detaches every device owned by the connection. If the stream ends unexpectedly (server restart, network loss) or the daemon sends closing, the connection becomes unusable: attach_device and every call on its devices raise TapError (broken holds the cause).

A held connection (hold is its idle timeout in seconds) has no stream: it lives in the daemon until close, its idle timeout, or daemon shutdown, and other processes reach it with TapClient.resume. Dropping the object without close leaves it for them.

Methods:

Name Description
ensure_usable

Raises TapError when the connection is closed or its liveness stream ended.

available_serials

Serials a test can use, from TapClient.devices: online and not quarantined, free

attach_device

Attach serial: install and start the driver (unless skip_driver_install) and

attached_devices

The devices attached to this connection now, as Device objects — including ones

close

Disconnects (the server detaches this connection's devices), then drops the liveness

event_log

The device calls this connection made, as the server logged them: every command

client

client: TapClient

The client this connection was opened on.

id

id: str

The server's id for this connection.

name

name: str | None

The name given to connect, shown by tap status.

hold

hold: float | None

For a held connection, the idle timeout in seconds after which the server ends it; None for an ordinary one that ends with this process (experimental).

on_event

on_event: Callable[[str], None] | None

broken

broken: TapError | None

Why the liveness stream ended unexpectedly, or None while it is healthy.

usable

usable: bool

True while the connection can attach devices: not closed and its stream is alive.

events

events: list[str]

The most recent liveness-stream events (at most MAX_EVENTS), oldest first: observing and closing: <reason>; heartbeats are not recorded.

ensure_usable

ensure_usable(operation: str) -> None

Raises TapError when the connection is closed or its liveness stream ended.

available_serials

available_serials() -> list[str]

Serials a test can use, from TapClient.devices: online and not quarantined, free ones first, then ones another session holds (attach_device then waits for them when wait_for_device is set). Exclusive use is enforced by the session itself, so there is nothing to acquire beforehand.

attach_device

attach_device(serial: str, *, timeouts: Timeouts | None = None, skip_driver_install: bool = False, wait_for_device: float = 0) -> Device

Attach serial: install and start the driver (unless skip_driver_install) and take the device's session. timeouts are this device's defaults; wait_for_device > 0 waits that many seconds for another session to release it. Raises TapError without an RPC once the connection is closed or broken.

attached_devices

attached_devices(timeouts: Timeouts | None = None) -> list[Device]

The devices attached to this connection now, as Device objects — including ones another process attached to a held connection. Nothing is re-attached: each keeps its session and generation. timeouts are this process's defaults for them. Raises TapError when the connection no longer exists at the server.

close

close() -> None

Disconnects (the server detaches this connection's devices), then drops the liveness stream. For a held connection this ends it for every process. Idempotent: later calls do nothing.

event_log

event_log(after_seq: int = 0) -> EventLog

The device calls this connection made, as the server logged them: every command except the diagnostic device_info / dump_hierarchy, and the app calls that change the device (install, uninstall, force-stop, clear-data, grant, launch, cold launch). Only events with seq > after_seq. A selector appears as sent, so a snapshot ref shows as the selector it named. The log is data, for turning a session into a test in any language; nothing renders code from it. Typed text is recorded verbatim, passwords included.

Experimental: not covered by the compatibility promise; it may change in any release.

Endpoint

Where a server listens and the bearer token its RPCs need (None: send no token).

address

address: str

token

token: str | None

start_daemon

start_daemon(binary: str | None = None, directory: Path | None = None, adb: str | None = None, timeout: float = 45.0) -> DaemonStartResult

tap start: starts the daemon in the background unless one is already running in the state dir. The executable picks the port, detaches the process and waits for Info to answer; nothing is parsed from the daemon process itself.

stop_daemon

stop_daemon(binary: str | None = None, directory: Path | None = None, timeout: float = 30.0) -> None

tap stop: stops the daemon recorded in the state dir; a no-op when none is running.

DaemonStartResult

address of the server; started is False when it was already running.

address

address: str

started

started: bool

DeviceEntry

One device ADB lists (TapClient.devices()). client_connection_id is the holding connection when this daemon holds the device; quarantine_reason says why it is QUARANTINED.

serial

serial: str

state

state: DeviceState

client_connection_id

client_connection_id: str | None

quarantine_reason

quarantine_reason: str | None

DeviceState

Bases: Enum

A device's state in TapClient.devices(). Only FREE and LEASED devices can be attached; UNKNOWN is a state this client version does not know.

FREE

FREE

LEASED

LEASED

QUARANTINED

QUARANTINED

OFFLINE

OFFLINE

UNAUTHORIZED

UNAUTHORIZED

UNKNOWN

UNKNOWN

ServerInfo

What the server reports about itself (TapClient.info()).

daemon_version

daemon_version: str

host_build_id

host_build_id: str

protocol_version

protocol_version: str

adb_executable

adb_executable: str

state_dir

state_dir: str

driver_available

driver_available: bool

pid

pid: int

defaults

defaults: ServerDefaults

ServerDefaults

The daemon's default timeouts in seconds (ServerInfo.defaults).

action

action: float

wait

wait: float

lifecycle

lifecycle: float

idle_stable

idle_stable: float

acquire

acquire: float

ConnectionEntry

A live client connection (TapClient.connections()). hold is the idle timeout in seconds of a held connection, None for an observed one; idle is how long, in seconds, no call has named it (0 while one is running).

id

id: str

name

name: str

hold

hold: float | None

idle

idle: float

attached_devices

attached_devices: tuple[AttachedDeviceEntry, ...]

AttachedDeviceEntry

A device attached to a connection (ConnectionEntry.attached_devices).

serial

serial: str

generation

generation: int