aiohpprinter
Source Code: https://github.com/chemelli74/aiohpprinter
Python library to get information from HP printers
aiohpprinter talks directly to an HP printer's embedded web server (EWS) -
the same /DevMgmt/*.xml, /ePrint/*.xml and /IoMgmt/* endpoints the
ha-hpprinter Home Assistant
integration uses - and exposes the result as typed, async Python instead of
raw XML. It has no Home Assistant dependency and can be used from any async
Python application.
Installation
Install this via pip (or your favourite package manager):
pip install aiohpprinter
Basic usage
import asyncio
from aiohpprinter import HpPrinter
async def main() -> None:
async with HpPrinter("192.168.1.100") as printer:
data = await printer.update()
print(data.online)
if data.device:
print(data.device.make_and_model)
for consumable in data.consumables:
print(consumable.consumable_id, consumable.percentage_level_remaining)
asyncio.run(main())
update() is the one-call aggregate: it fetches every endpoint the printer
answers and returns an HpPrinterData snapshot. Each endpoint is also
available individually - printer.device(), printer.status(),
printer.consumables(), printer.printer_usage(), printer.scanner_usage(),
printer.copy_usage(), printer.fax_usage(), printer.adapters(),
printer.eprint(), printer.wifi() - for callers that only need one piece
of data, or that want failures raised instead of absorbed (see
Exception handling below).
Session handling
HpPrinter accepts an optional session: aiohttp.ClientSession:
- No session given:
HpPrintercreates its ownaiohttp.ClientSessionwhen enteringasync with, and closes it automatically on exit (or whenclose()is called explicitly). Use this for a standalone script. - Session injected: the caller owns that session and is responsible for
closing it -
HpPrinternever closes a session it did not create. This is the recommended mode for an application such as Home Assistant that manages one shared session and its connection pooling itself.
import aiohttp
async with aiohttp.ClientSession() as session:
async with HpPrinter("192.168.1.100", session=session) as printer:
device = await printer.device()
# `session` is still open here; `printer`'s own state was released.
Supported HP functionality
| Data | Endpoint | Notes |
|---|---|---|
| Device identity | /DevMgmt/ProductConfigDyn.xml |
Model, serial number, manufacturer |
| Status | /DevMgmt/ProductStatusDyn.xml |
Used as the "is the printer reachable" check |
| Consumables (ink/toner/printhead) | /DevMgmt/ConsumableConfigDyn.xml |
Life state, warranty, capacity |
| Consumable usage | /DevMgmt/ProductUsageDyn.xml |
Merged into the same consumable, by label code |
| Printer engine usage | /DevMgmt/ProductUsageDyn.xml |
Page counters |
| Scanner engine usage | /DevMgmt/ProductUsageDyn.xml |
Page counters |
| Copy application usage | /DevMgmt/ProductUsageDyn.xml |
Page counters |
| Fax application usage | /DevMgmt/ProductUsageDyn.xml |
Page counters |
| Network adapters | /IoMgmt/Adapters |
May reply as JSON instead of XML on some models |
| HP ePrint / cloud printing | /ePrint/ePrintConfigDyn.xml |
Registration and cloud services state |
| Wireless Direct | /DevMgmt/NetAppsSecureDyn.xml |
SSID prefix, connection method |
Only the status endpoint is treated as required (it is what is_online()
and update() use to decide whether the printer is reachable at all).
Every other endpoint is optional: not every HP printer supports scanning,
copying, faxing, cloud printing or Wireless Direct, and update() leaves
the corresponding field None (or an empty list) instead of failing when
one is missing.
Exception handling
HpPrinterError
├── HpPrinterConnectionError # network/transport failure
├── HpPrinterTimeoutError # request exceeded the client timeout
├── HpPrinterHttpError # non-2xx HTTP status (`.status` holds it)
├── HpPrinterParseError # response body was not valid XML/JSON
└── HpPrinterUnsupportedError # reserved for a required feature genuinely absent
The single-endpoint methods (device(), status(), consumables(), ...)
raise these normally, so a caller that wants to distinguish "printer is
unreachable" from "printer sent garbage" can catch the specific subclass it
cares about, or HpPrinterError to catch anything.
update() and is_online() are the exception to that: they are explicitly
designed to tolerate a printer that only partially answers (see Supported
HP functionality above), so they catch every HpPrinterError from the
optional endpoints internally and never raise for that reason. update()
still lets a failure while checking is_online() be represented as
online=False rather than an exception, since that is the question it
exists to answer.
Adding a New Printer Model
aiohpprinter's test suite is fixture-driven: every directory under
tests/fixtures/ is picked up automatically by both the parser suite
(tests/test_parsers.py) and an end-to-end update() test that serves the
fixture's files from a local HTTP server (test_update_fixture in
tests/test_client.py). No new test function is required.
-
Capture the printer with the fixture script, giving its IP address or hostname:
python scripts/capture_fixture.py 192.168.1.100
The model name, and so the fixture directory, is taken from the printer's
ProductConfigDyn.xml: when it only reports its family (MakeAndModel"HP OfficeJet Pro 9020 series"), the variant fromSKUIdentifier("9022e") replaces the series number. Pass--model "..."to override it.This creates e.g.
tests/fixtures/hp_officejet_pro_9022e/with:- every endpoint
aiohpprinterreads (ProductStatusDyn.xmlis required; any other endpoint answering 404 is skipped, since that model doesn't support that feature).Adaptersis saved asAdapters.jsonwhen the printer answers it as JSON; - every other resource the printer lists in
/DevMgmt/DiscoveryTree.xml(pass--no-discoveryto skip these); expected.json, generated from what the current parsers return;- a
README.mdrecording the fixture's provenance.
Serial numbers, IDs, MAC/IP addresses, hostnames, SSIDs, e-mail addresses and credentials are replaced with
REDACTED. Use--port/--sslfor a non-default EWS, and--forceto recapture over an existing directory. - every endpoint
-
Review the captured files for anything identifying the redaction missed, and check
expected.json: it records what the parsers return today, so a wrong or missing value there is a parser bug to fix, not a value to keep. -
Fix the relevant parser in
src/aiohpprinter/parsers/if a field is missing or wrong, correctexpected.jsonto match, then run the suite:uv run pytest(oruv run pytest -k <model>for just the new fixture).
Known limitations
- Real captures of every endpoint exist for three models: HP OfficeJet
Pro 9022e, HP ENVY Photo 7830 All-in-One Printer and HP Color LaserJet
M255dw; an HP LaserJet 200 color M251nw is covered by
ProductUsageDyn.xmlonly. None of them reports an ePrintPrinterID, so that field is only covered by a unit test. If you have another printer, contributing a capture per Adding a New Printer Model above is very welcome. - A field is looked up at one fixed XML path per printer generation
(tolerant of a
PEIDattribute appearing or not, seeaiohpprinter.parsers.common.text), but not of a field moving to a different element entirely on some model; that would need a fixture demonstrating the variation and a parser update. - Fax usage has no real-world fixture at all (the one real capture this library has access to came from a printer without fax hardware).
Manual testing against a real printer
library_test.py connects to a real printer and prints an update()
snapshot:
python library_test.py --host 192.168.1.100
Contributors ✨
Thanks goes to these wonderful people (emoji key):
|
Simone Chemelli |
This project follows the all-contributors specification. Contributions of any kind welcome!
Credits
This package was created with Copier and the browniebroke/pypackage-template project template.
Metadata
Release files for aiohpprinter 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| aiohpprinter-1.0.0.tar.gz | 33.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aiohpprinter-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.7 kB
Release files / aiohpprinter-1.0.0.tar.gz
| Download URL | aiohpprinter-1.0.0.tar.gz |
|---|---|
| Size | 33.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
827b4b7e231caf1150990e7d0ef7c3dd283b8dd911490088bbfb77ba9d4bb23f
|
|
BLAKE2b-256 checksum How to use checksums |
7a76131240cf2bd3fd228845f3d3f3bb3cfebb290a01fe4556cee9453a070806
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / aiohpprinter-1.0.0-py3-none-any.whl
| Download URL | aiohpprinter-1.0.0-py3-none-any.whl |
|---|---|
| Size | 27.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
88b0de079a71d36a70f01a09efb2ac176c7051bf5999e931856ba7be104bc595
|
|
BLAKE2b-256 checksum How to use checksums |
492fa527ea544030da18bbce1e54705df96318f064da83fc74b422f290bfdc45
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for aiohpprinter 1.0.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.