Skip to content

Resiliency

pyOverkiz retries transient failures automatically and raises typed exceptions for everything else. This page explains what the library handles for you, so you can decide what (if anything) to handle yourself.

What the library retries

Every request is wrapped with backoff decorators using exponential delays with full jitter. Each retry policy is capped by both a maximum number of attempts (max_tries) and a maximum wall-clock budget (max_time), so retries always give up rather than looping indefinitely.

Failure Retried Budget On retry
Connection errors — TimeoutError, ClientConnectorError, ServerDisconnectedError yes 3 tries / ~30s reopen the connection
NotAuthenticatedError (session expired) yes 2 tries / ~60s call login()
TooManyConcurrentRequestsError yes 5 tries / ~120s
TooManyExecutionsError yes 5 tries / ~300s
ExecutionQueueFullError yes 5 tries / ~120s
Event-listener errors — InvalidEventListenerIdError, NoRegisteredEventListenerError yes (on fetch_events) 2 tries / ~30s re-register the listener

Everything else — BadCredentialsError, TooManyRequestsError, MaintenanceError, UnsupportedOperationError, and so on — is not retried and is raised directly.

Backoff timing

Delays grow exponentially — the ceilings are 1s, 2s, 4s, 8s, … — but full jitter is applied on top, so each actual wait is a random value between 0 and that ceiling (e.g. the first retry waits a random 0–1s, the second a random 0–2s). The randomization is deliberate: it spreads retries out so many clients do not all retry in lockstep. Because of the jitter, the per-attempt delays are not a fixed sequence; the max_time budget is the upper bound that matters.

Connection errors fail fast

Transient transport failures are retried quickly and then given up on (3 attempts within roughly 30 seconds). This is deliberate: it is better to surface a failure to the caller than to keep a request hanging for minutes. For a polling loop such as fetch_events(), letting a call fail and retrying on the next poll tick is usually the right behaviour.

A dropped keep-alive socket (ServerDisconnectedError) is treated as a connection blip and simply retried — it does not trigger a re-login. A genuine session expiry is reported by the server as a Not authenticated response, which raises NotAuthenticatedError and escalates to a re-login through the separate auth policy.

max_time does not interrupt a hung request

The max_time budget only bounds the scheduling of retries between attempts; it cannot cancel a single in-flight request. A request that hangs is bounded by the session timeout (see below), which surfaces as a TimeoutError and is then retried.

Request timeout

When pyOverkiz creates its own session, it applies a default per-request timeout (total=15s, sock_connect=10s). This is kept below the connection-retry budget so a hung socket fails fast and is retried, instead of blocking on aiohttp's 300-second default.

If you pass your own ClientSession, you own its timeout — pyOverkiz does not override it. Configure one explicitly:

from aiohttp import ClientSession, ClientTimeout

from pyoverkiz.auth.credentials import UsernamePasswordCredentials
from pyoverkiz.client import OverkizClient
from pyoverkiz.enums import Server

session = ClientSession(timeout=ClientTimeout(total=15, sock_connect=10))

client = OverkizClient(
    server=Server.SOMFY_EUROPE,
    credentials=UsernamePasswordCredentials("you@example.com", "password"),
    session=session,
)

What you should handle

Because the library already retries transient failures, most consumers only need to handle the terminal cases:

  • BadCredentialsError — the credentials are wrong; retrying will not help. Prompt for new credentials.
  • TooManyRequestsError — you are being rate limited. Reduce your polling frequency. This is intentionally not retried so you do not make the situation worse.
  • MaintenanceError / ServiceUnavailableError — the backend is temporarily unavailable. Back off and try again later.
  • NotAuthenticatedError — only reaches you if the automatic re-login also failed. Call login() and retry.
import asyncio

from pyoverkiz.auth.credentials import UsernamePasswordCredentials
from pyoverkiz.client import OverkizClient
from pyoverkiz.enums import Server
from pyoverkiz.exceptions import (
    MaintenanceError,
    NotAuthenticatedError,
    TooManyRequestsError,
)


async def fetch_devices() -> None:
    async with OverkizClient(
        server=Server.SOMFY_EUROPE,
        credentials=UsernamePasswordCredentials("you@example.com", "password"),
    ) as client:
        await client.login()
        try:
            print(await client.get_devices())
        except NotAuthenticatedError:
            await client.login()
            print(await client.get_devices())
        except TooManyRequestsError:
            # Rate limited — slow down your polling.
            await asyncio.sleep(60)
        except MaintenanceError:
            # Backend under maintenance — try again later.
            pass


asyncio.run(fetch_devices())

All exceptions derive from BaseOverkizError, so you can catch that as a catch-all for anything the library raises.