<!-- Source: https://medius.k4tech.net/bindings/python/usage -->
# Calls & errors

_The Python patterns: blocking, exceptions, lifecycle, builders_

What every `Device` call looks like in [Python](https://www.python.org): when it blocks, how a failure surfaces, when the handle is freed, and how the generic targets are built. The full call list is on [API index](/bindings/python/api.md); the value types on [Types & errors](/bindings/python/types.md). What each command _does_ lives in the [Rust Library](/library.md) and [Native API](/native.md) sections.

## Fire-and-forget vs blocking

_What the two badges mean_

Every `Device` call carries one of two badges, by whether it waits for [the box](/native/hardware.md) to answer or [fires and forgets](/native/injection.md#fire-and-forget).

| Badge | Means | Which calls |
| --- | --- | --- |
| _Fire-and-forget_ | Queues a [frame](/native/frame.md) and returns at once. No reply is read. | [`move_rel`](/library/move.md), [`wheel`](/library/move.md), [`inject`](/library/inject.md), [`press`](/library/inject.md), [`soft_release`](/library/inject.md), [`lock`](/library/lock.md)/[`unlock`](/library/lock.md), [`led`](/library/led.md), [`reset`](/library/admin.md), [`reapply`](/library/admin.md), [`set_movement_riding`](/library/options.md), [`set_emit_pace`](/library/options.md) … |
| _Blocks_ | Sends, then waits for the box's reply (or times out). | [`Device.open`](/bindings/python/api.md#connect) / [`find`](/bindings/python/api.md#connect) (the [handshake](/native/connection.md#handshake)), every [`query_*`](/native/commands/requests.md) / [`caps`](/native/commands/requests.md) / [`counters`](/library/diagnostics.md), and a stream [`recv()`](/bindings/python/streams.md) |

> **Note**
>
> A _Fire-and-forget_ call returning without raising means the frame was _queued_, not that the box acted on it. Confirm with a _Blocks_ query like [`dev.query_health()`](/bindings/python/api.md#queries). Blocking calls use the default reply wait, [`medius.default_query_timeout_ms()`](/bindings/python/api.md#module) (1000 ms).

## Errors

_A failed call raises MediusError_

There is no status return in Python: a failed call **raises**. The base type is [`MediusError`](/bindings/python/types.md#mediuserror) (an [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception) subclass); each [status code](/library/types/errors.md) (a [`Status`](/bindings/python/types.md#status) [IntEnum](https://docs.python.org/3/library/enum.html)) maps to its own [subclass](/bindings/python/types.md#subclasses), so you can catch the case you care about. Catch `MediusError` for all of them.

```text
class MediusError(Exception):
    status:    Status   # the failure code
    message:   str      # the box's last error text, may be ""
    proto_ver: int      # offending byte for BadProtoVerError, else 0

str(err)   # "ERR_NOT_FOUND: no medius port found"  (or only the name)
```

#### STATUS → EXCEPTION

| `Status` | Subclass raised | Typically means |
| --- | --- | --- |
| `ERR_IO` | `IoError` | serial read/write failed |
| `ERR_NOT_FOUND` | `NotFoundError` | no box at that path / none present |
| `ERR_NO_REPLY` | `NoReplyError` | box never answered |
| `ERR_BAD_PROTO_VER` | `BadProtoVerError` | firmware protocol mismatch (see `.proto_ver`) |
| `ERR_QUERY_TIMEOUT` | `QueryTimeoutError` | a `query_*` outran its wait |
| `ERR_DISCONNECTED` | `DisconnectedError` | the link dropped (see below) |
| `ERR_FRAME_TOO_LONG` | `FrameTooLongError` | payload over the frame limit |
| `ERR_FLASH_TOOL` | `FlashToolError` | [esptool](https://github.com/espressif/esptool) flash failed |
| `ERR_INVALID_ARG` | `InvalidArgError` | a bad argument value |
| `ERR_PANIC` | `PanicError` | the native core panicked |
| `ERR_UNKNOWN` | `MediusError` | anything unmapped |

#### EXAMPLE

```python
from medius import Device, MediusError, DisconnectedError

try:
    with Device.find() as dev:
        dev.move_rel(10, 0)
        print(dev.query_health())
except DisconnectedError:
    print("the link dropped mid-session")
except MediusError as e:
    print(e.status, "-", e.message)   # e.g. Status.ERR_NOT_FOUND - no medius port found
```

> **Warning**
>
> A dropped link raises `DisconnectedError` from a normal call, but a [stream](/bindings/python/streams.md) iterator (`for ev in dev.catch_events():`) stops cleanly instead: it returns when the link drops rather than raising. Calling `recv()` directly still raises. Box-side telemetry behind these errors is on [Diagnostics](/library/diagnostics.md).

## Lifecycle

_Open, clone, and release the handle_

A `Device` owns a native handle. Open one, optionally [`clone()`](/bindings/python/api.md#connect) it, and release it three ways. Connection sharing is on [Connection](/library/connection.md) and [Lifecycle](/library/lifecycle.md).

| Open with | Does |
| --- | --- |
| `Device.find()` | First box found + handshake. _Blocks_ |
| `Device.open(path)` | One serial path + handshake. _Blocks_ |
| `dev.clone()` | Another handle to the _same_ link; the connection is shared. |
| `MockBox().open()` | Open over an in-process [mock box](/library/features/mock.md) (needs the mock feature). |

```
Device.find() ──┐
                ├──▶  one USB-serial link  (stays up while ANY handle is open)
dev.clone() ────┘

  each handle is freed on its own; the link closes with the last one
```

#### THREE WAYS TO RELEASE

| Route | When the handle frees |
| --- | --- |
| `with Device.find() as dev:` | When the [context manager](https://docs.python.org/3/reference/datamodel.html#context-managers) block exits (`__exit__`). Preferred. |
| `dev.close()` | Immediately. Idempotent, so it's safe to call twice. |
| [garbage collection](https://docs.python.org/3/glossary.html#term-garbage-collection) | Best-effort via `__del__`. Don't rely on timing. |

```python
# preferred: the context manager closes on exit
with Device.find() as dev:
    dev.move_rel(5, 5)

# or hand a clone to a worker thread and close each when done
dev = Device.find()
worker = dev.clone()
worker.close()
dev.close()
```

> **Note**
>
> `EventStream`, `LogStream`, and `MockBox` follow the same pattern: a `with` block, `.close()`, or GC. Streams hold the device alive while open; see [Streams](/bindings/python/streams.md).

## Building targets

_Usage · Motion · LockTarget_

The [`inject`](/library/inject.md) / [`press`](/library/inject.md) / [`move_axis`](/library/move.md) / [`lock`](/library/lock.md) calls take a _target object_, not a bare value. Build it with a classmethod, then pass it in. One[`Usage`](/bindings/python/types.md#input) (button, key, or media) feeds every inject verb.

| Builder | Feeds | What it makes |
| --- | --- | --- |
| [`Usage.button(button)`](/bindings/python/types.md#input) | `dev.inject(input, action)`, `dev.press(input)`   see [Inject](/library/inject.md) | a mouse-button usage |
| `Usage.key(key)` | `dev.inject(input, action)`, `dev.press(input)`   see [Inject](/library/inject.md) | a keyboard-key usage ([keycodes](/native/commands/usage.md#keycodes)) |
| `Usage.media(media)` | `dev.inject(input, action)`, `dev.press(input)`   see [Inject](/library/inject.md) | a consumer/media usage ([usages](/native/commands/usage.md#consumer)) |
| [`Motion.cursor(dx, dy)`](/bindings/python/types.md#motion) | `dev.move_axis(motion)`   see [Move](/library/move.md) | a relative cursor nudge |
| `Motion.wheel(delta)` | `dev.move_axis(motion)`   see [Move](/library/move.md) | a wheel turn |
| [`LockTarget.x()`](/bindings/python/types.md#locktarget) / `y()` / `wheel()` | `dev.lock(target, direction)` / `unlock`   see [Lock](/library/lock.md) | an axis lock target |
| `LockTarget.usage(usage)` (or `button`/`key`/`media`) | `dev.lock(target, direction)` / `unlock`   see [Lock](/library/lock.md) | a usage lock target |

#### EXAMPLE

```python
from medius import Device, Usage, Motion, LockTarget, Button, Action, LockDirection

with Device.find() as dev:
    dev.inject(Usage.button(Button.LEFT), Action.PRESS)        # generic inject
    dev.move_axis(Motion.cursor(10, -4))                       # generic move
    dev.lock(LockTarget.button(Button.LEFT), LockDirection.BOTH)
```

> **Note**
>
> `action` is an [`Action`](/bindings/python/types.md#action) (`PRESS` / `SOFT_RELEASE` / `FORCE_RELEASE`); the [injection model](/native/injection.md) defines what each does. `Usage.button` takes a [`Button`](/bindings/python/types.md#button); `Usage.key`/`media` accept a [`Key`](/bindings/python/types.md#key)/[`MediaKey`](/bindings/python/types.md#mediakey) or a raw `int`.
