Skip to content

Add device management SDK and CLI#469

Open
rvirani1 wants to merge 1 commit intomainfrom
sdk-device-mgmt
Open

Add device management SDK and CLI#469
rvirani1 wants to merge 1 commit intomainfrom
sdk-device-mgmt

Conversation

@rvirani1
Copy link
Copy Markdown
Contributor

@rvirani1 rvirani1 commented May 1, 2026

Description

Adds Python SDK + CLI bindings for the new external Deployments / Device Management API (added to the platform in roboflow/roboflow#11350 and hardened in #11441). Closes ENT-1179.

The SDK wraps every route documented in docs/api/deployments/overview.md on roboflow/roboflow master — 1:1, no invented surface. PATCH /config, stream commands, device delete, and fleet-groups CRUD are explicitly out of scope per the doc's "Versioning and scope" section.

New surface:

  • Workspace.devices(), Workspace.device(id), Workspace.create_device(...)
  • Device class with config(), config_history(), streams(), stream(id), logs(...), telemetry(...), events(...), refresh()
  • roboflow device {list, get, create, config, config-history, streams, stream, logs, telemetry, events} with --json, exit codes 0/1/2/3, rate-limit hints
  • Typed exceptions in roboflow/adapters/devicesapi.py: DeviceBadRequestError (400), DeviceAuthError (401/403), DeviceNotFoundError (404), DeviceRateLimitedError (429), DeviceApiError (5xx)

Files:

  • roboflow/adapters/devicesapi.py (new) — HTTP layer for all 10 routes
  • roboflow/core/device.py (new) — Device model
  • roboflow/core/workspace.py — wired in devices() / device(id) / create_device(...)
  • roboflow/cli/handlers/device.py (new) + registration in roboflow/cli/__init__.py
  • tests/test_device.py (new), tests/cli/test_device_handler.py (new)
  • CLI-COMMANDS.md — quickstart entries

Dependencies: none new — uses requests and typer already required by the SDK.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How has this change been tested, please provide a testcase or example of how you tested the change?

Unit tests — 33 new tests pass (python -m unittest tests.test_device tests.cli.test_device_handler). Full repo suite: 545/545 pass. Coverage:

  • URL building for every route; CSV serialization for service/severity; opaque cursor round-trip for events
  • Error mapping for 400/401/403/404/404+GraphMethodException/429/500 → typed exceptions
  • Device method dispatch; Workspace.devices()/device(id)/create_device integration
  • CLI: subcommand registration, --json and text output, exit-code mapping (404→3, 401→2, 429→1)

Lint / type: ruff check roboflow tests clean, ruff format --check roboflow clean, mypy roboflow clean (no new issues).

Live staging validation against api.roboflow.one workspace device-manager-demo-workspace

# Endpoint Result
1 GET /:ws/devices/v2 OK — 96 devices, schema matches mappers.js
2 POST /:ws/devices/v2 Not exercised (avoid mutating shared workspace); covered by unit tests
3 GET /:ws/devices/v2/:id OK — schema match
4 GET .../config OK — 16 keys incl. environment_variables, services
5 GET .../config/history OK — pagination roundtrip works
6 GET .../streams OK — schema match
7 GET .../streams/:sid OK — schema match
8 GET .../logs OK — schema match
9 GET .../telemetry?time_period=1h OK — 31 buckets, schema match
10 GET .../events Server-side 500 when no filter; works with entity_type=stream. Not an SDK bug — flagged separately.

Negative cases against staging — every one mapped to the correct typed exception with the correct status code: bad time_period (400), bad events cursor (400), bad logs start_time (400), unknown deviceId (404), invalid api_key (401).

CLI parity verified end-to-end: roboflow device list --json → 96 entries; roboflow device get <id> → human-readable text; roboflow device telemetry <id> --time-period 1h --json → 31 buckets; 404 → exit 3; 401 → exit 2 with the device:read scope hint.

Will the change affect Universe? If so was this change tested in universe?

No. Pure-Python SDK and CLI changes only; no Universe surface touched.

Any specific deployment considerations

Nope

Infrastructure impact

  • This change affects infrastructure needs (GPUs, Cloud Function sizing, storage etc.)

Docs

  • Docs updated? What were the changes:
    • CLI-COMMANDS.md — added device commands to the quickstart and the command-groups table
    • Inline docstrings on the Device class call out that config() is sensitive (may include environment_variables and integration credentials)
    • Follow-up doc PR for the full reference will land in roboflow-product-docs per the repo's CLI documentation policy

Copy link
Copy Markdown

@NVergunst-ROBO NVergunst-ROBO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving a couple notes

Copy link
Copy Markdown

@NVergunst-ROBO NVergunst-ROBO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple nits


def list_devices(api_key: str, workspace: str) -> Dict[str, Any]:
"""``GET /:workspace/devices/v2`` — returns the parsed JSON response."""
response = requests.get(_build_url(workspace, "", api_key))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These requests calls do not pass timeout=, so a slow or stuck API can hang the CLI and any SDK caller forever. A default connect/read timeout would make behaviour better

else:
message = response.text
except Exception: # noqa: BLE001
message = response.text
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response.text can be huge, maybe add a truncate for max length?

try:
result = devicesapi.list_devices(api_key, ws)
except Exception as exc: # noqa: BLE001
output_error(args, str(exc), hint=_hint_for(exc), exit_code=_exit_code_for(exc))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

str(exc) on some requests failures can include the request URL, and the key is in the query string. Perhaps a secrets redaction would avoid accidentally printing the key in the terminal.

output_error(args, str(exc), hint=_hint_for(exc), exit_code=_exit_code_for(exc))
return

output(args, config)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, config can include env vars and integration secrets, maybe redact

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants