Skip to main content

ReadyView Dashboard Python Library

The ReadyView Dashboard Python library provides all current ReadyView dashboard API calls to interface with the ReadyLinks ReadyView cloud-managed platform. ReadyView generates the library based on dashboard API's OpenAPI spec to keep it up to date with the latest API releases, and provides the full source code for the library including the tools used to generate the library. ReadyLinks welcomes constructive pull requests that maintain backwards compatibility with prior versions. The library requires Python 3.8+, receives support from the community, and you can install it via PyPI:

pip install --upgrade readyview

Usage

  1. Export your Organization ID and API Key as an environment variable, for example:
export READYVIEW_DASHBOARD_ORG_ID=YOUR_ORG_ID
export READYVIEW_DASHBOARD_API_KEY=YOUR_API_KEY

The library calls https://api.readylinks.io/. To use another ReadyView API, such as a test server, set READYVIEW_BASE_URL. 2. Import the library with a single line at the top of your Python script.

import readyview
  1. Instantiate the dashboard client.
dashboard = readyview.Dashboard()
  1. Start programmatically interacting with your ReadyView dashboard!
my_networks = dashboard.network.get_networks()

Errors and strict mode

By default, a call that fails returns None, so existing scripts keep working. To find out why a call failed, turn on strict mode:

import readyview

dashboard = readyview.Dashboard(raise_errors=True)

try:
    config = dashboard.switch.get_switch_configurations("AA:BB:CC:DD:EE:FF")
except readyview.ReadyViewAPIError as error:
    print(error)
    # GET /v1/devices/AA:BB:CC:DD:EE:FF/switch failed with HTTP 504: The device did not answer within the time limit.

In strict mode, a call that fails raises readyview.ReadyViewAPIError, which has:

  • status_code: the HTTP status, such as 401, 404, 429 or 504. It is None when no reply arrived, because the API could not be reached or did not answer in time. Those calls raise the subclass readyview.ReadyViewConnectionError.
  • message: why the call failed, as the API explained it.
  • method and path: the call that failed, such as GET and /v1/devices/AA:BB:CC:DD:EE:FF/switch.
  • retry_after: when the reply has a Retry-After header (usually with a 429, when you are rate limited), how many seconds it asks you to wait before trying again. Otherwise None.

The error is built from the API's reply only. It never repeats what you sent, or the values the API rejected, since those can be passwords.

Before anything is sent, a configuration is checked against the models in readyview.switch_post, whether you build a SwitchPost yourself or pass a dict to update_switch() or apply_settings(). One they cannot accept raises pydantic's ValidationError, in either mode. Its message names the field and the problem, but not the value, which can be a password.

Printing a model, or showing it in a notebook or a log line, leaves out its passwords, keys and community strings too, since repr() and str() skip them. model_dump() and the body that is sent still include them.

Calls that work return the same values in both modes, with two differences in strict mode: any 2xx status counts as success, and a reply that is not JSON (say, a hotel network's sign-in page) raises ReadyViewAPIError.

Logging

The library logs to the readyview logger with Python's logging module. It logs each reply at DEBUG. In the default mode, it also logs a failed call: an error status at INFO, and a request that got no reply at WARNING, which earlier versions printed. To see why calls return None without switching to strict mode:

import logging

logging.basicConfig()
logging.getLogger("readyview").setLevel(logging.INFO)

SNMPv3 users

SnmpV3User builds a user that a switch accepts. It checks the firmware's rules as it is built: a name of 3-16 characters, passwords of 9-15, printable ASCII without spaces, ' or \. dashboard.switch.add_snmp_user() then adds it safely:

import readyview
from readyview import SnmpV3User

dashboard = readyview.Dashboard()
user = SnmpV3User.auth_priv("nms-poller", auth_password="...", privacy_password="...")

for device in dashboard.network.get_devices("YOUR-NETWORK-ID"):
    if device["device_type"] == "Switch" and device["status"] == "Online":
        result = dashboard.switch.add_snmp_user(device["mac_address"], user, current=device)
        print(device["name"], result.status.value, result.detail)

For each switch, add_snmp_user:

  • reads the switch first, and leaves it alone if it already has the user, so it is safe to run again
  • reports a conflict when the name exists with other settings, since a switch cannot edit a user: change it with replace_snmp_user (below). A user the switch stores encrypted (ReadyView's SNMP user form does by default) counts as the same user when its name, access and algorithms match: its passwords cannot be compared
  • reports full when the switch already has 10 users
  • turns the SNMP service on if it is off
  • checks the reply for refused values, then reads the configuration back until the switch reports the user, the only real confirmation

password_mode="cipher" (on SnmpV3User.auth_priv() or auth_no_priv()) has the switch store the passwords encrypted, as ReadyView's SNMP user form does by default:

  • It hides rather than protects. The switch encrypts with a key built into its firmware. That keeps the passwords out of plain sight in ReadyView and the API, but it is not real secrecy.
  • The read-back can't check the passwords. The switch reports them encrypted, so the read-back confirms everything but the passwords.
  • Some passwords are refused. The switch would mistake a password whose last digits match a firmware pattern for one it had already encrypted, and store a different password. Those are refused.

Passing the switch's record as current= (the device records above include each switch's SNMP settings) saves the first read. When the record shows the switch without the user, the change goes out at once and the read-back is the only live read. The result still comes from the switch: a record that is out of date can cost a read, never change the outcome.

It never raises for a switch it could not configure: result.status, result.detail and result.ok say what happened. Its live configuration reads are spaced to stay under the API's limit of 30 a minute, and a read the API turns away with HTTP 429 is tried again after a pause. SnmpV3User.auth_no_priv() and SnmpV3User.no_auth_no_priv() build the other security levels, and dashboard.switch.get_snmp_users(mac) lists a switch's users without their passwords.

Secure connections

On Python 3.10 and later the library checks the API's certificate against the computer's own certificate store, as a browser does, through truststore. That is what makes it work on a network that inspects HTTPS, where the traffic is re-signed with a certificate that company IT has installed on the computer. On Python 3.8 and 3.9, run python -m pip install pip-system-certs, or set REQUESTS_CA_BUNDLE to the network's certificate. A REQUESTS_CA_BUNDLE you set is always used as given. When the check fails, the error says why and what to do.

Automation

Operations that report an outcome

These dashboard.switch methods each make one change and return an Outcome:

  • ok: whether the switch ends up as asked
  • status: what happened, such as sent, refused, up to date or error
  • detail: the API's reason, or what changed

They never raise for a switch they could not change, so a loop over many switches reports every one:

Method What it does
apply_settings(mac, change) Sends a SwitchPost change and reports any value the switch refused
use_template(mac, template_id) Applies a configuration template; reports skipped when the template is for another model
upgrade_firmware(mac, version=None, channel=None) Starts an upgrade; reports up to date when the switch already has the firmware
add_bandwidth_profiles(mac, profiles) Adds bandwidth profiles, or replaces those with the same ids
delete_bandwidth_profile(mac, profile_id) Deletes a bandwidth profile; the clients bound to it go back to their port's default
bind_bandwidth_profile(mac, client_mac, profile_id, timeout=0) Finds the client's port and limits the client to a profile. timeout= waits that many seconds for a profile just added to show up
unbind_bandwidth_profile(mac, client_mac) Finds the client's binding and removes its bandwidth limit; reports not bound when it has none
update_client(mac, client_mac, **fields) Changes a connected client, e.g. location="Room 101"
attach_vlan_model(mac, name, client_mac=None, client_name=None) Attaches a client to a remote VLAN model and keeps everyone already attached; reports on another model for a client attached elsewhere, which the switch would skip without a word
detach_vlan_model(mac, name, client_mac=None, client_name=None) Detaches a client from a remote VLAN model; the others stay attached
delete_vlan_model(mac, name, timeout=60) Detaches everyone, waits until the switch reports nobody attached, then deletes the model
add_snmp_user(mac, user, current=None) Adds an SNMPv3 user (see above)
check_snmp_user(mac, name, privilege=, level=, ...) Says what add_snmp_user would do, without the passwords
replace_snmp_user(mac, user) Adds the user, or replaces the one with its name (new passwords, access or algorithms): a delete row and the user in one request, then a read-back. Needs an API with readylinks_models 2.24.0 or later
delete_snmp_user(mac, name) Deletes an SNMPv3 user and reads the switch back until it is gone; reports not configured when the switch has no such user. Needs an API with readylinks_models 2.24.0 or later

Each of the others takes dry_run=True to make a check instead: it only reads, and says what it would do.

  • Would change: the status is readyview.WOULD_CHANGE ("would change"), and the detail says what, such as from 8.047 to 8.048 or changes syslog.server_ip_address.
  • Nothing to do: a switch it would leave alone gets the answer a run would give: already configured, up to date, or skipped for a template of another model.
  • Cannot go ahead: the status says why, with ok False, such as no such profile.

Checks use the switch's record (current=, as automation.select_switches() returns it) or the API's cached configuration. They never make a live read. The upgrade check asks the API which build the upgrade would install (GET /v1/devices/{mac}/switch/upgrade).

A switch applies a change shortly after it answers, and a switch managed over MQTT gives no per-value answer. So a status such as sent, bound or attached means the change went out and nothing in the reply was refused, not that it was read back. The methods that build on a change read the switch again until it shows it: delete_vlan_model before it deletes, and bind_bandwidth_profile with a timeout. All live switch reads are spaced to stay under the API's limit of 30 a minute.

Scripts that change many switches

readyview.automation holds the parts every automation script repeats. That is the --network / --switch / --all-networks / --dry-run / --yes / --parallel N command line, plus picking the online switches, with a firmware minimum if you set one. It shows the plan, asks before changing anything, reports each switch, and ends with a summary and an exit status. A complete script:

from readyview import automation
from readyview.switch_post import Lldp, SwitchPost

parser = automation.command_line("Turn LLDP on.")
args = parser.parse_args()
change = SwitchPost(lldp=Lldp(enabled=True))
raise SystemExit(automation.run(
    args, "turn LLDP on",
    lambda dashboard, switch: dashboard.switch.apply_settings(switch["mac_address"], change),
    min_firmware=7.655,
))

--parallel N (or run(..., parallel=N)) changes up to N switches at the same time, 1-16. automation.command_line(description, parallel=6) sets a script's default; it is 1 otherwise. Each switch spends most of its time waiting: on its reply, and for the change to show up when it is read back. Running several at once overlaps those waits, while live switch reads stay paced across all of them, so the API's rate limit holds. The outcomes are printed in the order of the plan.

--only MAC [MAC ...] changes only those of the chosen switches, for example the ones a run could not change. The others are listed as skipped.

--all-networks takes every online switch in the organization. It reads the switches once, as does a --switch list of more than 30. The device list allows 15 requests a minute, so a few hundred MACs looked up ten at a time would hit that limit.

Pass check= to make the plan exact: usually the same call with dry_run=True. A dry run, and the confirmation, then ask each switch first.

  • Switches to change lists only the ones the run would change, each with what would change.
  • Already as asked lists the ones the run would leave as they are.
  • Skipped gets the ones the change does not apply to.

Runs with --yes make no checks.

automation.run(
    args, "upgrade to the recommended firmware",
    lambda dashboard, switch: dashboard.switch.upgrade_firmware(switch["mac_address"]),
    check=lambda dashboard, switch: dashboard.switch.upgrade_firmware(switch["mac_address"], dry_run=True),
)

Ctrl+C starts no more switches and finishes the ones already started, one at a time or in parallel, so no switch is left halfway. The summary counts the rest as "not started".

automation.ask_secret(label, env_var) reads a password from the environment, or has it typed twice at a hidden prompt.

A program that runs these scripts, such as ReadyView's Automation scripts page, can follow them without reading the printed text. Set READYVIEW_EVENTS_FD to an open file descriptor, and run() also writes its plan, each switch as it starts and finishes, and the summary there, as one JSON object per line. The readyview.automation module documents the events.

Reports

readyview.reports.gather(dashboard, switches) reads every switch's ports and recent metrics from ReadyView's cache, several at a time. A switch that cannot be read is listed with the reason. device_rows(), client_rows(), fiber_rows() and firmware_rows() turn that into report rows, and find_device(mac, ...) finds where a device is connected. The rows are plain dicts, ready for pandas:

import pandas as pd
from readyview import reports

switches = dashboard.device.get_devices(params={"device_type": ["Switch"]})
data = reports.gather(dashboard, switches)
pd.DataFrame(reports.device_rows(switches, data)).to_excel("switches.xlsx", index=False)

Contribute

Create a branch to add your feature/fix then submit a merge request to main for review. Be sure to follow the correct format in your commit message to trigger the pipeline to submit a new release. Example commit below.

feat: Added the ability to update switch configurations.
fix: Fixes some bug. 
BREAKING CHANGE: Updated function API. 

Run the tests before you submit:

pip install -e ".[test]"
pytest

Metadata

Release files for readyview 1.25.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for readyview 1.25.0
File Size Uploaded Provenance
readyview-1.25.0.tar.gz 126.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for readyview 1.25.0
File Interpreter ABI Platform
readyview-1.25.0-py3-none-any.whl Python 3 none any Details

Total release size: 195.8 kB

Release files / readyview-1.25.0.tar.gz

Download URL readyview-1.25.0.tar.gz
Size 126.3 kB
Tags Source
SHA-256 checksum
How to use checksums
0cf3d5536d5330ee6b5d24638dc82c3336d12b2349c5bf0ff69813772a51a8c6
BLAKE2b-256 checksum
How to use checksums
4d811fc4dd8c9fa772ff0fbb442dd93043d050a7e6cc20a76e30b4159b25ae1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via Hatch/1.18.1 {"ci":true,"cpu":"x86_64","distro":{"id":"trixie","libc":{"lib":"glibc","version":"2.41"},"name":"Debian GNU/Linux","version":"13"},"implementation":{"name":"CPython","version":"3.11.17"},"installer":{"name":"hatch","version":"1.18.1"},"openssl_version":"OpenSSL 3.5.7 9 Jun 2026","python":"3.11.17","system":{"name":"Linux","release":"5.15.154+"}} HTTPX2/2.14.0

Release files / readyview-1.25.0-py3-none-any.whl

Download URL readyview-1.25.0-py3-none-any.whl
Size 69.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
814906ee6eb526f1dad862ffe0d1dee996671fd6177eae2752a5304e5f19f984
BLAKE2b-256 checksum
How to use checksums
dcd432dae122f9637c0dceba6b10dacd9175d974478e34547525110ec5eef42f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via Hatch/1.18.1 {"ci":true,"cpu":"x86_64","distro":{"id":"trixie","libc":{"lib":"glibc","version":"2.41"},"name":"Debian GNU/Linux","version":"13"},"implementation":{"name":"CPython","version":"3.11.17"},"installer":{"name":"hatch","version":"1.18.1"},"openssl_version":"OpenSSL 3.5.7 9 Jun 2026","python":"3.11.17","system":{"name":"Linux","release":"5.15.154+"}} HTTPX2/2.14.0

Security for readyview 1.25.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page