Skip to main content

pyredfish

A small, dependency-light Python client for the Redfish server management API. Works against iDRAC, iLO, XClarity, OpenBMC and other BMCs, and includes support for the vendor-specific Oem/Public CollectAllLog and DownloadAllLog actions.

  • pyredfish/client.py — the RedfishClient class (the whole library)
  • pyredfish/cli.py — the pyredfish command line tool
  • examples/ — runnable example scripts

Requires Python 3.10+ and requests.

Install

pip install pyredfish

From a checkout:

python3 -m venv venv && source venv/bin/activate
pip install -e .

Installing puts a pyredfish command on your PATH; python -m pyredfish works too.

Quick start

from pyredfish import RedfishClient

with RedfishClient("https://10.0.0.5", "admin", "secret", verify=False) as rf:
    print(rf.system_info())
    print(rf.power_state())        # "On" / "Off"
    rf.power_on()

Or from the shell:

pyredfish -H 10.0.0.5 -u admin -p secret -k info

Connecting

RedfishClient(
    base_url=None,        # "https://10.0.0.5", or "10.0.0.5" (https:// is added)
    username=None,
    password=None,
    *,
    verify=True,          # True | False | "/path/ca.pem"
    timeout=30.0,         # seconds, per request
    use_session=True,     # False -> HTTP Basic Auth on every request
)

If base_url, username or password are omitted they are read from the REDFISH_URL, REDFISH_USER and REDFISH_PASSWORD environment variables:

import os
os.environ["REDFISH_URL"] = "10.0.0.5"
os.environ["REDFISH_USER"] = "admin"
os.environ["REDFISH_PASSWORD"] = "secret"

with RedfishClient(verify=False) as rf:
    print(rf.power_state())

TLS certificates

BMCs ship with self-signed certificates, so verify=False is the common case (urllib3's warning is silenced automatically). If you deployed your own CA, point at it instead and keep verification on:

RedfishClient("https://bmc.example.com", "admin", "secret", verify="/etc/ssl/bmc-ca.pem")

Sessions and tokens

Session handling lives entirely inside the class:

  • No explicit login needed. The first request opens a Redfish session (POST /redfish/v1/SessionService/Sessions) and stores the X-Auth-Token.
  • Automatic re-login. If the token expires and the BMC answers 401, the client logs in again and retries the request once. Bad credentials fail immediately instead of looping.
  • Clean logout. Leaving the with block (or calling logout()) deletes the session on the BMC. This matters: most BMCs allow only a handful of concurrent sessions and leaked ones lock you out until they time out.
  • After logout() the client refuses further calls rather than silently opening a new session. Call login() to reuse it.
rf = RedfishClient("10.0.0.5", "admin", "secret", verify=False)
print(rf.power_state())    # logs in on demand
rf.logout()                # session deleted on the BMC

rf.login()                 # explicit re-open
print(rf.power_state())
rf.logout()

Use use_session=False for BMCs whose SessionService is broken or disabled; every request then carries HTTP Basic Auth instead.


Usage examples

Inventory report

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    info = rf.system_info()
    print(f"{info['Manufacturer']} {info['Model']} (SN {info['SerialNumber']})")
    print(f"BIOS {info['BiosVersion']}, health {info['Health']}")
    print(f"{info['ProcessorCount']} x {info['ProcessorModel']}, "
          f"{info['MemoryGiB']} GiB RAM")

    for cpu in rf.processors():
        print(cpu["Id"], cpu.get("Model"), cpu.get("TotalCores"), "cores")

    for dimm in rf.memory():
        if dimm.get("CapacityMiB"):
            print(dimm["Id"], dimm["CapacityMiB"], "MiB",
                  dimm.get("Manufacturer"), dimm.get("PartNumber"))

    for nic in rf.ethernet_interfaces():
        print(nic["Id"], nic.get("MACAddress"), nic.get("IPv4Addresses"))

    for ctrl in rf.storage():
        for drive in ctrl.get("Drives", []):
            print("drive:", drive["@odata.id"])

Power control

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    if rf.power_state() == "Off":
        rf.power_on()

    rf.power_off()                 # GracefulShutdown — asks the OS
    rf.power_off(force=True)       # ForceOff — pulls the plug
    rf.restart()                   # GracefulRestart
    rf.restart(force=True)         # ForceRestart

    rf.reset("PowerCycle")         # any ResetType the BMC advertises

reset() checks the BMC's ResetType@Redfish.AllowableValues first and raises RedfishError for an unsupported type instead of sending a request that would fail. RedfishClient.RESET_TYPES lists the standard values.

Waiting for a power state

import time

def wait_for_power(rf, wanted, timeout=300, interval=5):
    deadline = time.monotonic() + timeout
    while rf.power_state() != wanted:
        if time.monotonic() > deadline:
            raise TimeoutError(f"still {rf.power_state()}, wanted {wanted}")
        time.sleep(interval)

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    rf.power_off(force=True)
    wait_for_power(rf, "Off")
    rf.power_on()
    wait_for_power(rf, "On")

One-shot PXE boot (reprovisioning)

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    print("supported targets:", rf.boot_options())

    rf.set_boot_override("Pxe", uefi=True)   # next boot only
    rf.restart(force=True)

persistent=True keeps the override for every boot (BootSourceOverrideEnabled = "Continuous"); omit uefi to leave the boot mode alone, or pass uefi=False for legacy BIOS mode. Other common targets are Hdd, Cd, Usb, BiosSetup and Utilities.

Mounting an ISO over virtual media

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    for device in rf.virtual_media():
        print(device["Id"], device.get("MediaTypes"), "inserted:",
              device.get("Inserted"))

    rf.insert_virtual_media("http://10.0.0.9/images/rescue.iso")
    rf.set_boot_override("Cd", uefi=True)
    rf.restart(force=True)

    # ... after the install ...
    rf.eject_virtual_media()

The CD device is found from the vendor profile and, failing that, from each device's MediaTypes — so the same call works on a BMC that calls it CD and on iLO, where it is device 2. Pass media_id= to force one, and manager_index= when the machine has more than one BMC.

Thermal and power draw

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    thermal = rf.thermal()
    for sensor in thermal.get("Temperatures", []):
        print(f"{sensor.get('Name'):<28} {sensor.get('ReadingCelsius')} C "
              f"(upper critical {sensor.get('UpperThresholdCritical')})")
    for fan in thermal.get("Fans", []):
        print(fan.get("Name"), fan.get("Reading"), fan.get("ReadingUnits"))

    for ctrl in rf.power().get("PowerControl", []):
        print("draw:", ctrl.get("PowerConsumedWatts"), "W",
              "| average:", ctrl.get("PowerMetrics", {}).get("AverageConsumedWatts"))
    for psu in rf.power().get("PowerSupplies", []):
        print(psu.get("Name"), psu.get("Status", {}).get("Health"),
              psu.get("LastPowerOutputWatts"), "W")

Firmware inventory

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    for item in rf.firmware_inventory():
        print(f"{item.get('Name'):<40} {item.get('Version')}")

Event log (SEL)

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    print("log services:", rf.log_service_ids())     # ["SEL"] / ["IML", "SL"]

    for entry in rf.log_entries(limit=20):           # vendor-aware default
        print(entry.get("Created"), entry.get("Severity"), entry.get("Message"))

    for entry in rf.log_entries(scope="manager", limit=20):
        print("BMC:", entry.get("Message"))

With no log_id, log_entries() tries the ids in the active vendor profile (SEL on most BMCs, IML/SL on HPE, Sel/Lclog on Dell) and returns the first log it finds. Pass an explicit id to force one, or limit=None for every entry.

scope selects where to look: "system", "manager", or the default "auto" — system first, then the BMC, which is what makes Dell work without special-casing.

Filter without post-processing:

rf.log_entries(severity="Warning")               # Warning and Critical
rf.log_entries(since="2026-09-01T00:00:00Z")     # ISO 8601 or a datetime
rf.log_entries(severity="Critical", since=yesterday, limit=None)

An entry whose timestamp cannot be parsed is kept rather than dropped — hiding a real event is worse than showing an extra one.


Alarms

Event logs are history. The current alarm state lives in the Status.Health of each resource and in the thermal thresholds, so it is read separately:

thermal = rf.thermal()
for sensor in thermal.get("Temperatures", []):
    limit = sensor.get("UpperThresholdCritical")
    if limit and sensor.get("ReadingCelsius", 0) >= limit:
        print("ALARM:", sensor["Name"], sensor["ReadingCelsius"])

for psu in rf.power().get("PowerSupplies", []):
    if psu.get("Status", {}).get("Health") not in (None, "OK"):
        print("ALARM:", psu["Name"], psu["Status"]["Health"])

The same check across system health, sensors, fans, PSUs, DIMMs, CPUs and critical log entries is built into the CLI:

pyredfish -H 10.0.0.5 -u admin -k alarms      # exit code 1 if anything is wrong

The non-zero exit on alarms makes it usable straight from cron or a monitoring check. examples/health_monitor.py polls the same data on a loop.

Push notifications (EventService)

Polling is fine for a handful of machines; beyond that, subscribe and let the BMC POST events to you:

print(rf.event_service()["EventTypesForSubscription"])   # what it can send

uri = rf.subscribe("https://nms.example.com:8443/redfish-events",
                   event_types=["Alert"], context="dc1-rack3")

for sub in rf.subscriptions():
    print(sub["Id"], sub["Destination"], sub.get("Context"))

rf.unsubscribe(uri)      # full URI or just the id

The destination must be an HTTPS endpoint the BMC can reach; Context comes back with every event, so use it to identify the machine. EventTypes is only sent when the BMC advertises it — it was deprecated in Redfish 1.6 and newer firmware rejects it.

pyredfish -H 10.0.0.5 -u admin -k subscriptions
pyredfish -H 10.0.0.5 -u admin -k subscribe https://nms:8443/events --context dc1
pyredfish -H 10.0.0.5 -u admin -k unsubscribe 3

Collecting all logs (OEM)

Many BMCs expose a bundle-everything action outside the Redfish standard:

POST /redfish/v1/Managers/1/LogServices/Actions/Oem/Public/CollectAllLog
POST /redfish/v1/Managers/1/LogServices/Actions/Oem/Public/DownloadAllLog

The client discovers those targets from the Actions.Oem block of the LogServices document and falls back to the paths above when the BMC does not advertise them, so it keeps working across firmware revisions.

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    # collect + download in one call; the file name comes from
    # the server's Content-Disposition header
    path = rf.download_all_log("./logs/")
    print("saved to", path)

Split into two steps when you want control over the wait:

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    task = rf.collect_all_log(timeout=1200)      # blocks until the BMC is done
    print("task state:", task.get("TaskState") if task else "no task returned")

    rf.download_all_log("./logs/bmc-10.0.0.5.tar.gz", collect_first=False)

Notes:

  • collect_all_log() returns a Task if the BMC answers 202 Accepted with a Location header, and polls it until it finishes (timeout=600, interval=5 by default). A failed task raises RedfishError carrying the BMC's own message.
  • The download is streamed in 1 MiB chunks, so a multi-hundred-megabyte bundle never has to fit in memory.
  • If dest is a directory (or ends with a path separator) the file name is taken from Content-Disposition, otherwise dest is used verbatim and its parent directories are created.
  • Firmware differs on the HTTP verb. method="auto" (the default) tries POST and falls back to GET on 400/404/405/501; force it with method="POST" or method="GET".
  • Some firmware wants a body ({"Type": "all"} and similar). Pass it through: rf.collect_all_log(payload={"Type": "all"}).

Bundling logs from many machines

import concurrent.futures, pathlib
from pyredfish import RedfishClient, RedfishError

HOSTS = ["10.0.0.5", "10.0.0.6", "10.0.0.7"]
OUT = pathlib.Path("./logs")
OUT.mkdir(exist_ok=True)

def grab(host):
    try:
        with RedfishClient(host, "admin", "secret", verify=False) as rf:
            target = OUT / host
            target.mkdir(exist_ok=True)
            return host, rf.download_all_log(str(target) + "/")
    except RedfishError as exc:
        return host, f"FAILED: {exc}"

with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool:
    for host, result in pool.map(grab, HOSTS):
        print(f"{host}: {result}")

Each thread gets its own client and therefore its own BMC session — do not share one RedfishClient across threads.



Vendor differences

Redfish is a standard, but every BMC bends it somewhere. pyredfish handles that in three layers, in this order:

  1. Discovery. Action targets, allowed reset types, log service ids and virtual media devices are read from the BMC's own documents. Most differences never reach the other two layers.
  2. Vendor profile. What discovery cannot answer is expressed as data — id candidates, OEM namespaces, HTTP verbs, reset preferences.
  3. Profile hooks. Only when a vendor does something structurally different (HPE's AHS dump) does the profile override behaviour with code.

An unrecognised BMC uses the generic profile and behaves exactly as before, so a new machine never fails just because it is unknown.

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    print(rf.profile.name)          # "hpe", "kaytus" or "generic"

Detection reads ServiceRoot.Vendor, the Oem keys and Manager.Manufacturer — never the hostname or IP. Pin it when detection guesses wrong:

RedfishClient("10.0.0.5", "admin", "secret", verify=False, vendor="hpe")
pyredfish -H 10.0.0.5 -u admin -k --vendor hpe info

Supported profiles

Profile Detected by What it changes
generic fallback Standard Redfish, OEM action then CollectDiagnosticData
dell Oem.Dell, iDRAC vendor string Sel/Lclog under the BMC, CD/RemovableDisk media, SupportAssist collection
hpe Oem.Hpe / Oem.Hp IML/SL/IEL logs, numbered virtual media, AHS download
kaytus Oem.Public, KAYTUS/Inspur Oem/Public action namespace, log id order
lenovo Oem.Lenovo, XClarity/ThinkSystem DiagnosticLog FFDC collection, StandardLog events
openbmc Oem.OpenBMC EventLog, Dump service, Slot_0 virtual media
supermicro Oem.Supermicro Log1 events, Dump service with OEM/AllLog

HPE (iLO 4/5/6) — verified against HPE's Redfish documentation:

  • Event logs are not SEL: the system carries IML (Integrated Management Log) and SL (Security Log), the BMC carries IEL (iLO Event Log). rf.log_entries() picks the right one; rf.log_entries(scope="manager") reads the iLO log.
  • Virtual media devices are numbered, not named: 1 is the virtual floppy/USB, 2 is the virtual CD/DVD. rf.insert_virtual_media(url) finds the CD device without being told.
  • "Collect all logs" is the Active Health System dump, which is not a Redfish action but a query on the AHS resource. download_all_log() maps onto it, so the same call works on iLO and on everything else:
rf.download_all_log("./logs/")                       # whole AHS record
rf.download_all_log("./logs/", days=7)               # last 7 days
rf.download_all_log("./logs/", date_from="2026-08-01", date_to="2026-08-31")

KAYTUS (and the Inspur BMCs it descends from) — the toggle is the Oem/Public namespace, where the log bundle lives on the LogServices collection:

POST /redfish/v1/Managers/1/LogServices/Actions/Oem/Public/CollectAllLog
POST /redfish/v1/Managers/1/LogServices/Actions/Oem/Public/DownloadAllLog

collect_all_log() / download_all_log() use these, preferring the targets the BMC advertises in Actions.Oem and falling back to the paths above.

Lenovo (XCC), Supermicro and OpenBMC use the DMTF-standard LogService.CollectDiagnosticData instead of an OEM action, so download_all_log() runs the action, waits for the task and downloads the resulting entry's AdditionalDataURI. The differences are only which log service hosts it and which parameters it wants — Lenovo collects DiagnosticDataType: "Manager" from DiagnosticLog, Supermicro collects "OEM" + OEMDiagnosticDataType: "AllLog" from Dump. You can call it directly:

entry = rf.collect_diagnostic_data()          # waits for the task
path = rf.download_diagnostic_data("./logs/") # collect + download

Dell (iDRAC) is the one vendor where the bundle cannot be pulled straight over Redfish. collect_all_log() starts a SupportAssist collection (a Tech Support Report) and waits for the job, but iDRAC writes the file to a share rather than serving it back, so download_all_log() raises a RedfishError explaining what to do instead:

rf.collect_all_log(share={"ShareType": "NFS", "IPAddress": "10.0.0.9",
                          "ShareName": "/export/tsr"})

Dell also keeps its event logs under the BMC (Sel, Lclog) rather than the system — log_entries() finds them anyway, because the default scope="auto" falls back from the system to the manager.

Reporting a new BMC

pyredfish probe prints exactly the structure a profile is written from — vendor strings, OEM keys, reset types, log service ids, virtual media ids and the OEM action block. Serial numbers, UUIDs, MACs, IPs and host names are redacted, so the output is safe to paste into an issue:

pyredfish -H 10.0.0.5 -u admin -k probe > my-bmc.json

Adding a profile

Subclass VendorProfile, set the fields that differ, and score matches() against the service root:

from pyredfish.vendors import VendorProfile, register

@register
class AcmeProfile(VendorProfile):
    name = "acme"
    system_log_ids = ("EventLog", "SEL")
    virtual_media_ids = ("Cd1",)
    oem_namespaces = ("Acme",)

    @classmethod
    def matches(cls, root, manager):
        return 10 if "Acme" in cls._oem_keys(root, manager) else 0

Every field, with its generic default:

Field Default Meaning
name "generic" Value accepted by --vendor
system_log_ids ("SEL", "EventLog", "Log") Ordered candidates for the system event log
manager_log_ids ("Log", "EventLog", "SEL") Same, for the BMC's own log
virtual_media_ids ("CD", "CD1", "1") Ordered candidates for the CD/DVD device
oem_namespaces ("Public",) OEM action namespace (#Public.CollectAllLog)
download_verbs ("POST", "GET") Methods tried for the OEM download action
collect_payload {} Extra body for the OEM collect action
diagnostic_log_ids ("Dump", "DiagnosticLog", "Diagnostic") Log services to prefer for CollectDiagnosticData
diagnostic_data_type "Manager" DiagnosticDataType sent with it
oem_diagnostic_data_type None OEMDiagnosticDataType, when the type is OEM
patch_if_match "*" Default If-Match header; None sends none
graceful_off_types ("GracefulShutdown",) Preference order for power_off()
force_off_types ("ForceOff",) Preference order for power_off(force=True)
graceful_restart_types ("GracefulRestart",) Preference order for restart()
force_restart_types ("ForceRestart", "PowerCycle") Preference order for restart(force=True)

Only put equivalent values in the power lists — a graceful list must never contain a forceful type, or a polite shutdown silently becomes a power cut. The client picks the first entry the BMC advertises in ResetType@Redfish.AllowableValues and raises if none of them fit.

Two optional hooks, collect_all_log(client, **kw) and download_all_log(client, dest, **kw), override behaviour when data is not enough — that is how HPE's AHS and Dell's SupportAssist are implemented.

Third-party packages can ship profiles without touching this repository by declaring a pyredfish.vendors entry point:

[project.entry-points."pyredfish.vendors"]
acme = "pyredfish_acme:AcmeProfile"

Raw access

Every Redfish resource is reachable even when there is no helper for it:

with RedfishClient("10.0.0.5", "admin", "secret", verify=False) as rf:
    bios = rf.get("/redfish/v1/Systems/1/Bios")
    print(bios["Attributes"]["BootMode"])

    # staged BIOS change, applied on next reboot
    rf.patch("/redfish/v1/Systems/1/Bios/Settings",
             {"Attributes": {"BootMode": "Uefi", "ProcTurboMode": "Enabled"}})

    # any action
    rf.post("/redfish/v1/Managers/1/Actions/Manager.Reset",
            {"ResetType": "GracefulRestart"})

    # walk a collection
    for account in rf.members("/redfish/v1/AccountService/Accounts"):
        print(account.get("Id"), account.get("UserName"), account.get("RoleId"))

    rf.delete("/redfish/v1/SessionService/Sessions/12")

patch() sends If-Match: * by default; override it with headers={"If-Match": etag} when a BMC insists on a real ETag.

Long-running tasks

Actions that return 202 Accepted can be awaited with wait_for_task():

resp = rf.post("/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
               {"ImageURI": "http://10.0.0.9/fw/bios.bin",
                "TransferProtocol": "HTTP"})
task = rf.wait_for_task("/redfish/v1/TaskService/Tasks/3",
                        timeout=1800, interval=10)
print(task["TaskState"])

Error handling

from pyredfish import RedfishClient, RedfishError, RedfishAuthError

try:
    with RedfishClient("10.0.0.5", "admin", "wrong", verify=False) as rf:
        rf.power_on()
except RedfishAuthError as exc:
    print("check the credentials:", exc)
except RedfishError as exc:
    print("request failed:", exc)
    print("status:", exc.status_code)
    print("body:", exc.body)          # the BMC's parsed JSON error

RedfishError.status_code and .body carry the BMC's own response, and the message is built from Redfish's @Message.ExtendedInfo block so it reads like the vendor's own wording. RedfishAuthError is a subclass of RedfishError, raised on 401/403. Network-level problems surface as requests.exceptions.* (ConnectionError, Timeout) unchanged.


Command line

pyredfish -H 10.0.0.5 -u admin -p secret -k info
pyredfish -H 10.0.0.5 -u admin -k status              # prompts for the password
pyredfish -H 10.0.0.5 -u admin -k off --force
pyredfish -H 10.0.0.5 -u admin -k boot Pxe --uefi
pyredfish -H 10.0.0.5 -u admin -k restart -f
pyredfish -H 10.0.0.5 -u admin -k sel -n 50
pyredfish -H 10.0.0.5 -u admin -k sel --severity Warning --since 2026-09-01
pyredfish -H 10.0.0.5 -u admin -k alarms
pyredfish -H 10.0.0.5 -u admin -k dump ./logs/
pyredfish -H 10.0.0.5 -u admin -k download-log ./logs/
pyredfish -H 10.0.0.5 -u admin -k -j get /redfish/v1/Systems/1

Global options:

Option Meaning
-H, --host, --url BMC address or IP (env REDFISH_URL)
-u, --user user name (env REDFISH_USER)
-p, --password password (env REDFISH_PASSWORD); prompted if omitted
-k, --insecure skip TLS verification
--ca FILE verify against your own CA
--basic-auth use HTTP Basic Auth instead of a session
--timeout SEC per-request timeout (default 30)
-j, --json print raw JSON
-V, --version print the version and exit
--vendor NAME pin the vendor profile instead of detecting it

Commands: info, status, on, off, restart, boot, boot-options, nics, power-usage, firmware, sel, alarms, probe, dump, collect-log, download-log, subscriptions, subscribe, unsubscribe, bmc-reset, get. Run pyredfish --help or pyredfish <command> --help for details. Exit codes: 0 success, 1 Redfish error, 2 bad arguments, 130 interrupted.

Credentials on the command line are visible in ps output — prefer the environment variables or the password prompt on shared machines:

export REDFISH_URL=10.0.0.5 REDFISH_USER=admin REDFISH_PASSWORD=secret
pyredfish -k info

API reference

RedfishClient

Area Methods
Session login(), logout(), context manager
Raw HTTP get(path), post(path, payload), patch(path, payload), delete(path)
Navigation service_root(), systems(), chassis(), managers(), system_uri(i), manager_uri(i), system(i), members(uri), member_uris(uri)
Power power_state(), power_on(), power_off(force=), restart(force=), reset(type)
Boot boot_options(), set_boot_override(target, persistent=, uefi=)
Inventory system_info(), processors(), memory(), ethernet_interfaces(), storage(), firmware_inventory()
Chassis thermal(i), power(i)
Logs log_entries(log_id, limit=, scope=, severity=, since=), log_service_ids(scope=), log_services(), log_services_uri(i)
Log bundles collect_all_log(...), download_all_log(dest, ...), collect_diagnostic_data(...), download_diagnostic_data(dest, ...)
Events event_service(), subscriptions(), subscribe(destination, ...), unsubscribe(id)
Tasks wait_for_task(uri, timeout=, interval=)
Virtual media virtual_media(i), insert_virtual_media(url, ...), eject_virtual_media(...)
BMC reset_bmc(reset_type=)
Vendor profile, detect_vendor(), log_service_ids(scope=)

Every method that touches a system or chassis takes an index (or manager_index / chassis_index) argument, defaulting to 0 — the first resource in the collection. Multi-node chassis are addressed with rf.power_state(index=1) and friends.

system_info() returns a flat dict with the keys Id, Manufacturer, Model, SerialNumber, SKU, UUID, BiosVersion, PowerState, Health, State, ProcessorCount, ProcessorModel, MemoryGiB and HostName; missing fields come back as None rather than raising.

Examples directory

File What it shows
examples/inventory.py full hardware report for one host
examples/power_cycle.py power state machine with waiting
examples/pxe_reinstall.py one-shot PXE boot for reprovisioning
examples/virtual_media_install.py mount an ISO, boot it, eject
examples/collect_logs.py OEM CollectAllLog / DownloadAllLog
examples/fleet_report.py CSV inventory across many BMCs, in parallel
examples/health_monitor.py poll temperature, fans, power and SEL
examples/raw_bios.py raw GET/PATCH against BIOS attributes

Each script takes the BMC as its first argument, or falls back to REDFISH_URL; the credentials come from REDFISH_USER / REDFISH_PASSWORD and are prompted for when unset. They can be run from anywhere.

export REDFISH_USER=admin REDFISH_PASSWORD=secret

python3 examples/inventory.py 10.0.0.5
python3 examples/collect_logs.py 10.0.0.5 ./logs/
python3 examples/virtual_media_install.py 10.0.0.5 http://10.0.0.9/rescue.iso
python3 examples/raw_bios.py 10.0.0.5 BootMode Uefi
python3 examples/fleet_report.py hosts.txt > fleet.csv   # one host per line

examples/_common.py holds the shared connect-from-environment helper the other scripts import.

Compatibility notes

  • Redfish is a standard, but coverage varies. Anything under Oem — including CollectAllLog / DownloadAllLog — is vendor-specific and may not exist on your BMC.
  • The client always checks what the BMC advertises before sending an action (reset types, action targets, log service ids) and raises a clear RedfishError instead of failing obscurely.
  • BMC session limits are low (often 4–8). Always use the context manager or call logout() so sessions are not leaked.

License

GNU General Public License v3.0 or later — see LICENSE.

Releasing to PyPI

The project is packaged with pyproject.toml (setuptools backend); the version lives in pyredfish/__init__.py and everything else is derived from it.

pip install build twine

rm -rf dist/
python -m build          # builds dist/*.whl and dist/*.tar.gz
twine check dist/*       # validates the metadata PyPI will render

twine upload --repository testpypi dist/*   # rehearsal on TestPyPI
twine upload dist/*                         # the real thing

Publishing needs a PyPI account with 2FA enabled and an API token (PyPI no longer accepts passwords for uploads). Put it in ~/.pypirc, or export it:

export TWINE_USERNAME=__token__
export TWINE_PASSWORD=pypi-AgEIcHlwaS5vcmc...

A released version number can never be reused, so bump __version__ before every upload.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyredfish-0.2.0.tar.gz (62.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyredfish-0.2.0-py3-none-any.whl (53.2 kB view details)

Uploaded Python 3

File details

Details for the file pyredfish-0.2.0.tar.gz.

File metadata

  • Download URL: pyredfish-0.2.0.tar.gz
  • Upload date:
  • Size: 62.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for pyredfish-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7925440ed3c33f5e344aa5ecc1197bf672fab4744dd42df8bd8d335dd4097e1f
MD5 bb2c464449222489de2093fc5ce11a9f
BLAKE2b-256 fe267e958ce96bb3fbda3b0de28bfcce75190deafe735b979e756293c4f1a179

See more details on using hashes here.

File details

Details for the file pyredfish-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyredfish-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 53.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for pyredfish-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 95173d9d561a934a6ddcda57f9f2d65b16c9cbcb8ba7fc8cd1aeb447ad186ce7
MD5 74322a1d0476aae14d65bf09a524bfe6
BLAKE2b-256 047f02493c0880e76d0323e3323b3b58d7662ae400b59e2bcd561148ad6c0081

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

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