aiohpprinter 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,51 @@
1
+ # Copyright 2026 Simone Chemelli and contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Async Python library for HP printers exposing an embedded web server (EWS)."""
5
+
6
+ from .client import HpPrinter
7
+ from .exceptions import (
8
+ HpPrinterConnectionError,
9
+ HpPrinterError,
10
+ HpPrinterHttpError,
11
+ HpPrinterParseError,
12
+ HpPrinterTimeoutError,
13
+ HpPrinterUnsupportedError,
14
+ )
15
+ from .models import (
16
+ HpAdapter,
17
+ HpConsumable,
18
+ HpCopyUsage,
19
+ HpEPrint,
20
+ HpFaxUsage,
21
+ HpPrinterData,
22
+ HpPrinterDevice,
23
+ HpPrinterStatus,
24
+ HpPrinterUsage,
25
+ HpScannerUsage,
26
+ HpWifi,
27
+ )
28
+
29
+ __version__ = "1.0.0"
30
+
31
+ __all__ = [
32
+ "HpAdapter",
33
+ "HpConsumable",
34
+ "HpCopyUsage",
35
+ "HpEPrint",
36
+ "HpFaxUsage",
37
+ "HpPrinter",
38
+ "HpPrinterConnectionError",
39
+ "HpPrinterData",
40
+ "HpPrinterDevice",
41
+ "HpPrinterError",
42
+ "HpPrinterHttpError",
43
+ "HpPrinterParseError",
44
+ "HpPrinterStatus",
45
+ "HpPrinterTimeoutError",
46
+ "HpPrinterUnsupportedError",
47
+ "HpPrinterUsage",
48
+ "HpScannerUsage",
49
+ "HpWifi",
50
+ "__version__",
51
+ ]
aiohpprinter/client.py ADDED
@@ -0,0 +1,322 @@
1
+ # Copyright 2026 Simone Chemelli and contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Async client for an HP printer's embedded web server (EWS)."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ import aiohttp
11
+
12
+ from .const import (
13
+ _LOGGER,
14
+ DEFAULT_PORT,
15
+ DEFAULT_TIMEOUT,
16
+ ENDPOINT_ADAPTERS,
17
+ ENDPOINT_CONSUMABLE_CONFIG,
18
+ ENDPOINT_EPRINT_CONFIG,
19
+ ENDPOINT_NET_APPS_SECURE,
20
+ ENDPOINT_PRODUCT_CONFIG,
21
+ ENDPOINT_PRODUCT_STATUS,
22
+ ENDPOINT_PRODUCT_USAGE,
23
+ JSON_CONTENT_TYPES,
24
+ )
25
+ from .exceptions import (
26
+ HpPrinterConnectionError,
27
+ HpPrinterError,
28
+ HpPrinterHttpError,
29
+ HpPrinterParseError,
30
+ HpPrinterTimeoutError,
31
+ )
32
+ from .models import HpPrinterData
33
+ from .parsers import (
34
+ parse_adapters,
35
+ parse_consumables,
36
+ parse_copy_usage,
37
+ parse_device,
38
+ parse_eprint,
39
+ parse_fax_usage,
40
+ parse_printer_usage,
41
+ parse_scanner_usage,
42
+ parse_status,
43
+ parse_wifi,
44
+ )
45
+ from .xml import parse_document, parse_json_document
46
+
47
+ if TYPE_CHECKING:
48
+ from collections.abc import Awaitable, Callable
49
+ from types import TracebackType
50
+ from typing import Any, Self
51
+
52
+ from .models import (
53
+ HpAdapter,
54
+ HpConsumable,
55
+ HpCopyUsage,
56
+ HpEPrint,
57
+ HpFaxUsage,
58
+ HpPrinterDevice,
59
+ HpPrinterStatus,
60
+ HpPrinterUsage,
61
+ HpScannerUsage,
62
+ HpWifi,
63
+ )
64
+
65
+
66
+ class HpPrinter:
67
+ """Async client for an HP printer's embedded web server (EWS).
68
+
69
+ Session ownership: when no `session` is given, `HpPrinter` creates and
70
+ owns an `aiohttp.ClientSession` for the lifetime of the `async with`
71
+ block (or until :meth:`close` is called). When a `session` is injected
72
+ - the recommended approach for applications such as Home Assistant that
73
+ manage their own connection pooling - the caller owns it and remains
74
+ responsible for closing it; `HpPrinter` never closes a session it did
75
+ not create.
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ host: str,
81
+ session: aiohttp.ClientSession | None = None,
82
+ *,
83
+ port: int = DEFAULT_PORT,
84
+ ssl: bool = False,
85
+ timeout: aiohttp.ClientTimeout = DEFAULT_TIMEOUT,
86
+ ) -> None:
87
+ """Initialize the client for the printer at `host`."""
88
+ self._host = host
89
+ self._port = port
90
+ self._ssl = ssl
91
+ self._timeout = timeout
92
+ self._session = session
93
+ self._owns_session = session is None
94
+
95
+ @property
96
+ def base_url(self) -> str:
97
+ """Return the printer's EWS base URL."""
98
+ protocol = "https" if self._ssl else "http"
99
+ return f"{protocol}://{self._host}:{self._port}"
100
+
101
+ async def __aenter__(self) -> Self:
102
+ """Open the client, creating a session if none was injected."""
103
+ if self._session is None:
104
+ self._session = aiohttp.ClientSession()
105
+
106
+ return self
107
+
108
+ async def __aexit__(
109
+ self,
110
+ exc_type: type[BaseException] | None,
111
+ exc: BaseException | None,
112
+ traceback: TracebackType | None,
113
+ ) -> None:
114
+ """Close the client, releasing only a session it created itself."""
115
+ await self.close()
116
+
117
+ async def close(self) -> None:
118
+ """Close the underlying session, if this client owns it."""
119
+ if self._owns_session and self._session is not None:
120
+ await self._session.close()
121
+ self._session = None
122
+
123
+ async def is_online(self) -> bool:
124
+ """Return whether the printer currently responds to status requests.
125
+
126
+ Any failure - offline, powered down, unreachable, or an unexpected
127
+ response - is treated as "not online" rather than raised, since that
128
+ is what this check exists to answer. Call :meth:`status` directly to
129
+ distinguish those failure modes.
130
+ """
131
+ try:
132
+ await self.status()
133
+ except HpPrinterError:
134
+ return False
135
+
136
+ return True
137
+
138
+ async def device(self) -> HpPrinterDevice:
139
+ """Fetch printer identity information."""
140
+ document = await self._request(ENDPOINT_PRODUCT_CONFIG)
141
+
142
+ return parse_device(document)
143
+
144
+ async def status(self) -> HpPrinterStatus:
145
+ """Fetch the printer's current operational status."""
146
+ document = await self._request(ENDPOINT_PRODUCT_STATUS)
147
+
148
+ return parse_status(document)
149
+
150
+ async def consumables(self) -> list[HpConsumable]:
151
+ """Fetch the merged list of ink/toner consumables."""
152
+ config_document = await self._request(ENDPOINT_CONSUMABLE_CONFIG)
153
+ usage_document = await self._request(ENDPOINT_PRODUCT_USAGE)
154
+
155
+ return parse_consumables(config_document, usage_document)
156
+
157
+ async def printer_usage(
158
+ self,
159
+ document: dict[str, Any] | None = None,
160
+ ) -> HpPrinterUsage:
161
+ """Fetch cumulative printer engine page counters.
162
+
163
+ `document` lets callers that already fetched `ProductUsageDyn` (for
164
+ example :meth:`update`) reuse it instead of requesting it again.
165
+ """
166
+ if document is None:
167
+ document = await self._request(ENDPOINT_PRODUCT_USAGE)
168
+
169
+ return parse_printer_usage(document)
170
+
171
+ async def scanner_usage(
172
+ self,
173
+ document: dict[str, Any] | None = None,
174
+ ) -> HpScannerUsage:
175
+ """Fetch cumulative scanner engine page counters.
176
+
177
+ See :meth:`printer_usage` for the meaning of `document`.
178
+ """
179
+ if document is None:
180
+ document = await self._request(ENDPOINT_PRODUCT_USAGE)
181
+
182
+ return parse_scanner_usage(document)
183
+
184
+ async def copy_usage(
185
+ self,
186
+ document: dict[str, Any] | None = None,
187
+ ) -> HpCopyUsage:
188
+ """Fetch cumulative copy application page counters.
189
+
190
+ See :meth:`printer_usage` for the meaning of `document`.
191
+ """
192
+ if document is None:
193
+ document = await self._request(ENDPOINT_PRODUCT_USAGE)
194
+
195
+ return parse_copy_usage(document)
196
+
197
+ async def fax_usage(
198
+ self,
199
+ document: dict[str, Any] | None = None,
200
+ ) -> HpFaxUsage:
201
+ """Fetch cumulative fax application page counters.
202
+
203
+ See :meth:`printer_usage` for the meaning of `document`.
204
+ """
205
+ if document is None:
206
+ document = await self._request(ENDPOINT_PRODUCT_USAGE)
207
+
208
+ return parse_fax_usage(document)
209
+
210
+ async def adapters(self) -> list[HpAdapter]:
211
+ """Fetch the printer's network adapters."""
212
+ document = await self._request(ENDPOINT_ADAPTERS)
213
+
214
+ return parse_adapters(document)
215
+
216
+ async def eprint(self) -> HpEPrint:
217
+ """Fetch HP ePrint cloud printing configuration."""
218
+ document = await self._request(ENDPOINT_EPRINT_CONFIG)
219
+
220
+ return parse_eprint(document)
221
+
222
+ async def wifi(self) -> HpWifi:
223
+ """Fetch Wireless Direct configuration."""
224
+ document = await self._request(ENDPOINT_NET_APPS_SECURE)
225
+
226
+ return parse_wifi(document)
227
+
228
+ async def update(self) -> HpPrinterData:
229
+ """Fetch everything the printer currently supports.
230
+
231
+ Only the status endpoint is treated as required: if the printer
232
+ does not answer it, `update` returns immediately with
233
+ `online=False` and every other field left at its default. Every
234
+ other endpoint is optional - a missing or failing one leaves its
235
+ field `None` (or an empty list) without aborting the rest of the
236
+ update, since printer models differ in which features they expose.
237
+ """
238
+ try:
239
+ status = await self.status()
240
+ except HpPrinterError:
241
+ return HpPrinterData(online=False)
242
+
243
+ usage_document = await self._safe_request(ENDPOINT_PRODUCT_USAGE)
244
+ config_document = await self._safe_request(ENDPOINT_CONSUMABLE_CONFIG)
245
+
246
+ return HpPrinterData(
247
+ online=True,
248
+ device=await self._safe(self.device),
249
+ status=status,
250
+ consumables=parse_consumables(config_document, usage_document),
251
+ printer_usage=(
252
+ parse_printer_usage(usage_document)
253
+ if usage_document is not None
254
+ else None
255
+ ),
256
+ scanner_usage=(
257
+ parse_scanner_usage(usage_document)
258
+ if usage_document is not None
259
+ else None
260
+ ),
261
+ copy_usage=(
262
+ parse_copy_usage(usage_document) if usage_document is not None else None
263
+ ),
264
+ fax_usage=(
265
+ parse_fax_usage(usage_document) if usage_document is not None else None
266
+ ),
267
+ adapters=await self._safe(self.adapters) or [],
268
+ eprint=await self._safe(self.eprint),
269
+ wifi=await self._safe(self.wifi),
270
+ )
271
+
272
+ async def _safe[T](self, func: Callable[[], Awaitable[T]]) -> T | None:
273
+ """Await `func`, turning any `HpPrinterError` into `None`."""
274
+ try:
275
+ return await func()
276
+ except HpPrinterError:
277
+ _LOGGER.debug("Optional endpoint unavailable for %s", func.__name__)
278
+ return None
279
+
280
+ async def _safe_request(self, endpoint: str) -> dict[str, Any] | None:
281
+ """Fetch and parse `endpoint`, turning any `HpPrinterError` into `None`."""
282
+ try:
283
+ return await self._request(endpoint)
284
+ except HpPrinterError:
285
+ _LOGGER.debug("Optional endpoint unavailable: %s", endpoint)
286
+ return None
287
+
288
+ async def _request(self, endpoint: str) -> dict[str, Any]:
289
+ """Fetch `endpoint` and parse it into a nested dict document."""
290
+ if self._session is None:
291
+ message = (
292
+ "HpPrinter has no active session; "
293
+ "use it as an async context manager or inject one"
294
+ )
295
+ raise HpPrinterConnectionError(message)
296
+
297
+ url = f"{self.base_url}{endpoint}"
298
+
299
+ try:
300
+ async with self._session.get(url, timeout=self._timeout) as response:
301
+ if response.status >= 400: # noqa: PLR2004
302
+ message = f"{url} returned HTTP {response.status}"
303
+ raise HpPrinterHttpError(response.status, message)
304
+
305
+ try:
306
+ content = await response.text()
307
+ except UnicodeDecodeError as ex:
308
+ message = f"Could not decode response from {url}: {ex}"
309
+ raise HpPrinterParseError(message) from ex
310
+
311
+ content_type = response.content_type
312
+ except TimeoutError as ex:
313
+ message = f"Timed out requesting {url}"
314
+ raise HpPrinterTimeoutError(message) from ex
315
+ except aiohttp.ClientError as ex:
316
+ message = f"Could not connect to {url}: {ex}"
317
+ raise HpPrinterConnectionError(message) from ex
318
+
319
+ if content_type in JSON_CONTENT_TYPES:
320
+ return parse_json_document(content)
321
+
322
+ return parse_document(content)
aiohpprinter/const.py ADDED
@@ -0,0 +1,39 @@
1
+ # Copyright 2026 Simone Chemelli and contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Constants for aiohpprinter."""
5
+
6
+ import logging
7
+
8
+ from aiohttp import ClientTimeout
9
+
10
+ _LOGGER = logging.getLogger(__package__)
11
+
12
+ DEFAULT_PORT = 80
13
+ DEFAULT_TIMEOUT = ClientTimeout(total=10)
14
+
15
+ # HP embedded web server (EWS) DevMgmt/ePrint/IoMgmt endpoints. These are the
16
+ # same URIs used by the upstream ha-hpprinter integration.
17
+ ENDPOINT_PRODUCT_STATUS = "/DevMgmt/ProductStatusDyn.xml"
18
+ ENDPOINT_PRODUCT_CONFIG = "/DevMgmt/ProductConfigDyn.xml"
19
+ ENDPOINT_CONSUMABLE_CONFIG = "/DevMgmt/ConsumableConfigDyn.xml"
20
+ ENDPOINT_PRODUCT_USAGE = "/DevMgmt/ProductUsageDyn.xml"
21
+ ENDPOINT_ADAPTERS = "/IoMgmt/Adapters"
22
+ ENDPOINT_EPRINT_CONFIG = "/ePrint/ePrintConfigDyn.xml"
23
+ ENDPOINT_NET_APPS_SECURE = "/DevMgmt/NetAppsSecureDyn.xml"
24
+
25
+ # A small number of endpoints (observed on `/IoMgmt/Adapters`) answer with a
26
+ # JSON body instead of XML, using either of these content types.
27
+ JSON_CONTENT_TYPES = frozenset({"application/javascript", "application/json"})
28
+
29
+ # Keys present on every response's root element that carry protocol/schema
30
+ # metadata rather than printer data, dropped once parsed.
31
+ IGNORED_ROOT_KEYS = ("@schemaLocation", "Version")
32
+
33
+ MARKER_COLOR_TO_LABEL_CODE = {
34
+ "Cyan": "C",
35
+ "Yellow": "Y",
36
+ "Magenta": "M",
37
+ "CyanMagentaYellow": "CMY",
38
+ "Black": "K",
39
+ }
@@ -0,0 +1,35 @@
1
+ # Copyright 2026 Simone Chemelli and contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """aiohpprinter library exceptions."""
5
+
6
+ from __future__ import annotations
7
+
8
+
9
+ class HpPrinterError(Exception):
10
+ """Base class for all aiohpprinter errors."""
11
+
12
+
13
+ class HpPrinterConnectionError(HpPrinterError):
14
+ """Raised when the printer cannot be reached over the network."""
15
+
16
+
17
+ class HpPrinterTimeoutError(HpPrinterError):
18
+ """Raised when a request to the printer times out."""
19
+
20
+
21
+ class HpPrinterHttpError(HpPrinterError):
22
+ """Raised when the printer returns an unsuccessful HTTP status code."""
23
+
24
+ def __init__(self, status: int, message: str) -> None:
25
+ """Initialize the error with the offending HTTP status code."""
26
+ super().__init__(message)
27
+ self.status = status
28
+
29
+
30
+ class HpPrinterParseError(HpPrinterError):
31
+ """Raised when a printer response cannot be parsed as valid XML."""
32
+
33
+
34
+ class HpPrinterUnsupportedError(HpPrinterError):
35
+ """Raised when the printer does not expose a required endpoint."""
aiohpprinter/models.py ADDED
@@ -0,0 +1,179 @@
1
+ # Copyright 2026 Simone Chemelli and contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Typed printer-domain models returned by :class:`aiohpprinter.HpPrinter`.
5
+
6
+ These describe HP printer concepts (device identity, status, consumables,
7
+ usage counters, ...), not Home Assistant entities. Every field is optional
8
+ unless the printer protocol guarantees it, since firmware and product
9
+ generation determine which elements a given model actually reports.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from datetime import datetime
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class HpPrinterDevice:
23
+ """Identity information for the printer as a whole."""
24
+
25
+ make_and_model: str | None = None
26
+ make_and_model_family: str | None = None
27
+ sku_identifier: str | None = None
28
+ serial_number: str | None = None
29
+ product_number: str | None = None
30
+ manufacturer_name: str | None = None
31
+ manufactured_at: datetime | None = None
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class HpPrinterStatus:
36
+ """The printer's current operational status."""
37
+
38
+ device_status: str | None = None
39
+
40
+ @property
41
+ def is_ready(self) -> bool:
42
+ """Return whether the printer is idle and ready to accept jobs."""
43
+ return self.device_status == "ready"
44
+
45
+ @property
46
+ def is_off(self) -> bool:
47
+ """Return whether the printer is powered off or unreachable."""
48
+ return self.device_status is None or self.device_status == "off"
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class HpConsumable:
53
+ """A single ink/toner consumable, merged from config and usage data.
54
+
55
+ ``consumable_id`` is the value the two source endpoints are joined on:
56
+ the consumable label code (for example ``"C"``, ``"M"``, ``"Y"``,
57
+ ``"K"``, or ``"CMY"`` for a combined cartridge).
58
+ """
59
+
60
+ consumable_id: str
61
+ consumable_type: str | None = None
62
+ marker_color: str | None = None
63
+ state: str | None = None
64
+ usage_state: str | None = None
65
+ brand: str | None = None
66
+ station: str | None = None
67
+ percentage_level_remaining: float | None = None
68
+ raw_percentage_level_remaining: float | None = None
69
+ estimated_pages_remaining: int | None = None
70
+ max_capacity: float | None = None
71
+ selectibility_number: str | None = None
72
+ manufacturer_name: str | None = None
73
+ manufactured_at: datetime | None = None
74
+ installed_at: datetime | None = None
75
+ warranty_expires_at: datetime | None = None
76
+ serial_number: str | None = None
77
+ product_number: str | None = None
78
+ supply_serial_number: str | None = None
79
+ unique_id: str | None = None
80
+ counterfeit_refilled_count: int | None = None
81
+ genuine_refilled_count: int | None = None
82
+ total_impressions: int | None = None
83
+
84
+ @property
85
+ def is_ok(self) -> bool:
86
+ """Return whether the consumable reports a healthy life state."""
87
+ return self.state in {"ok", "newGenuineHP"}
88
+
89
+
90
+ @dataclass(frozen=True, slots=True)
91
+ class HpPrinterUsage:
92
+ """Cumulative page counters for the printer engine."""
93
+
94
+ total_impressions: int | None = None
95
+ monochrome_impressions: int | None = None
96
+ color_impressions: int | None = None
97
+ simplex_sheets: int | None = None
98
+ duplex_sheets: int | None = None
99
+ jam_events: int | None = None
100
+ mispick_events: int | None = None
101
+
102
+
103
+ @dataclass(frozen=True, slots=True)
104
+ class HpScannerUsage:
105
+ """Cumulative page counters for the scanner engine."""
106
+
107
+ scan_images: int | None = None
108
+ adf_images: int | None = None
109
+ duplex_sheets: int | None = None
110
+ flatbed_images: int | None = None
111
+ jam_events: int | None = None
112
+ mispick_events: int | None = None
113
+
114
+
115
+ @dataclass(frozen=True, slots=True)
116
+ class HpCopyUsage:
117
+ """Cumulative page counters for the copy application."""
118
+
119
+ total_impressions: int | None = None
120
+ adf_images: int | None = None
121
+ flatbed_images: int | None = None
122
+ monochrome_impressions: int | None = None
123
+ color_impressions: int | None = None
124
+
125
+
126
+ @dataclass(frozen=True, slots=True)
127
+ class HpFaxUsage:
128
+ """Cumulative page counters for the fax application."""
129
+
130
+ total_impressions: int | None = None
131
+
132
+
133
+ @dataclass(frozen=True, slots=True)
134
+ class HpAdapter:
135
+ """A network adapter reported by the printer's I/O management."""
136
+
137
+ name: str | None = None
138
+ connectivity_port_type: str | None = None
139
+ is_connected: bool | None = None
140
+
141
+
142
+ @dataclass(frozen=True, slots=True)
143
+ class HpEPrint:
144
+ """HP ePrint cloud printing configuration."""
145
+
146
+ printer_id: str | None = None
147
+ is_registered: bool | None = None
148
+ cloud_services_enabled: bool | None = None
149
+
150
+
151
+ @dataclass(frozen=True, slots=True)
152
+ class HpWifi:
153
+ """Wireless Direct configuration."""
154
+
155
+ ssid_prefix: str | None = None
156
+ connection_method: str | None = None
157
+
158
+
159
+ @dataclass(frozen=True, slots=True)
160
+ class HpPrinterData:
161
+ """Aggregate snapshot of everything :meth:`HpPrinter.update` collected.
162
+
163
+ Every section besides ``online`` is `None` (or an empty list) when its
164
+ endpoint is not supported by this printer model, or could not be
165
+ retrieved on this update cycle - see the client's optional-endpoint
166
+ handling for details.
167
+ """
168
+
169
+ online: bool
170
+ device: HpPrinterDevice | None = None
171
+ status: HpPrinterStatus | None = None
172
+ consumables: list[HpConsumable] = field(default_factory=list)
173
+ printer_usage: HpPrinterUsage | None = None
174
+ scanner_usage: HpScannerUsage | None = None
175
+ copy_usage: HpCopyUsage | None = None
176
+ fax_usage: HpFaxUsage | None = None
177
+ adapters: list[HpAdapter] = field(default_factory=list)
178
+ eprint: HpEPrint | None = None
179
+ wifi: HpWifi | None = None
@@ -0,0 +1,38 @@
1
+ # Copyright 2026 Simone Chemelli and contributors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Pure, synchronous XML-to-model parsers, one module per EWS endpoint.
5
+
6
+ Parsing in-memory XML/dict data is not I/O, so unlike the HTTP client these
7
+ functions are plain synchronous callables. Each takes the dict produced by
8
+ :func:`aiohpprinter.xml.parse_document` and returns typed models from
9
+ :mod:`aiohpprinter.models`.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from .adapter import parse_adapters
15
+ from .consumable import parse_consumables
16
+ from .device import parse_device
17
+ from .eprint import parse_eprint
18
+ from .status import parse_status
19
+ from .usage import (
20
+ parse_copy_usage,
21
+ parse_fax_usage,
22
+ parse_printer_usage,
23
+ parse_scanner_usage,
24
+ )
25
+ from .wifi import parse_wifi
26
+
27
+ __all__ = [
28
+ "parse_adapters",
29
+ "parse_consumables",
30
+ "parse_copy_usage",
31
+ "parse_device",
32
+ "parse_eprint",
33
+ "parse_fax_usage",
34
+ "parse_printer_usage",
35
+ "parse_scanner_usage",
36
+ "parse_status",
37
+ "parse_wifi",
38
+ ]