Error Handling¶
Every exception raised by DeUX inherits from a single root class,
deux.DeuxError. A single except deux.DeuxError
handler is therefore sufficient to catch any library error without
masking unrelated exceptions from your own code.
import deux
try:
card = deux.DuiCard("DashboardCard")
except deux.DeuxError as exc:
log.error("DUI load failed: %s", exc)
Exception hierarchy¶
DeuxError
├── DeckError # runtime device / transport failures
├── PackageError # malformed or unloadable .dui packages
├── SSRFError # blocked private / loopback URL access
├── IconifyError # iconify fetch / cache failures
├── ImageFetchError # image binding fetch failures
├── RasterizeError # SVG rasterisation failures
└── SplashError # splash-screen rendering failures
The publicly re-exported names from deux are
DeuxError, DeckError,
PackageError, and
SSRFError.
The remaining classes are accessible through their owning sub-packages
(for example IconifyError,
ImageFetchError).
URL safety and SSRF¶
.dui packages are designed to be distributed between users. To
mitigate Server-Side Request Forgery (SSRF), DeUX validates every URL
used by image: and iconify: bindings before fetching. Hostnames are
resolved via DNS and rejected when any resulting IP address falls in a
blocked range:
- Loopback —
127.0.0.0/8,::1 - Private (RFC 1918) —
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 - Link-local —
169.254.0.0/16,fe80::/10 - Cloud metadata —
169.254.169.254
A blocked request raises SSRFError:
import deux
try:
deux.DuiCard("SomeCard") # contains image binding to 192.168.1.1
except deux.SSRFError as exc:
log.warning("Blocked private URL in DUI: %s", exc)
Allowing private URLs¶
If you genuinely need to fetch from LAN resources (a local Home
Assistant, a self-hosted icon server, …) opt in explicitly with
set_allow_private_urls:
import deux
# Permit private / loopback / link-local addresses for this process.
deux.set_allow_private_urls(True)
This is process-global; only enable it when you trust the DUI packages your application loads.
Patterns¶
Fail fast on package load: catch PackageError during startup to
surface broken packages before the device session begins.
try:
deux.load_all_packages()
except deux.PackageError as exc:
sys.exit(f"Refusing to start with bad DUI packages: {exc}")
Resilient runtime: within event handlers, catch the narrow sub-class you care about and let everything else bubble up to the deck manager's logger.
@card.on("refresh")
async def handle():
try:
await refresh_data()
except deux.DeckError as exc:
log.warning("device unavailable, skipping refresh: %s", exc)