falaw.errors#
Typed exceptions for falaw operations.
The bare fal_client exposes FalClientError, FalClientHTTPError, and
FalClientTimeoutError. These are correct but coarse — every 403, 429,
402, and timeout looks the same to a caller. This module wraps fal_client
errors in a more specific hierarchy so:
An agent can catch
FalAccountLockedand surface a “go fix billing” message instead of debugging a generic 403.A retry layer can catch
FalRateLimitedand back off, but letFalAccountLockedpropagate (no amount of retry will fix it).Scripts can catch
FalDurationOutOfRangeto fall back to splitting a shot, instead of silently truncating to whatever the model produced.
Use translate() to convert a fal_client exception to the appropriate
typed subclass — falaw.core.call_fal() does this automatically.
Examples
>>> err = FalAccountLocked(
... "account locked",
... status_code=403,
... detail="Verify your email at fal.ai/settings",
... url="https://fal.ai/dashboard/billing",
... )
>>> err.url
'https://fal.ai/dashboard/billing'
>>> isinstance(err, FalError)
True
Functions
Import and return |
|
|
Import and return |
|
Convert an underlying fal_client / network exception to a typed falaw exception. |
Exceptions
|
fal account is locked / suspended / awaiting verification. |
|
The bytes behind a fal-served asset URL could not be retrieved. |
|
Server rejected the request payload — typical 400 / 422. |
|
A real network call was attempted but the fal.ai client is not installed. |
|
The requested duration is outside what the model can produce. |
Base for all falaw-raised exceptions. |
|
|
Wraps an HTTP error from fal.ai with the original status, body, and headers. |
|
Account balance is insufficient — typical 402. |
|
A model was queued but never returned — distinct from a network timeout. |
|
An argument cannot be canonicalised into falaw's hashed JSON form. |
|
The prompt exceeds the model's hard length cap (falaw#76). |
|
fal is throttling requests — typical 429. |
|
fal-side server error — typical 5xx. |
|
The fal call timed out before producing a result. |
|
Missing or invalid API credentials — typical 401. |
- exception falaw.errors.FalAccountLocked(message, *, status_code, detail='', body=None, headers=None, application=None, url=None, cause=None)[source]#
Bases:
FalHTTPErrorfal account is locked / suspended / awaiting verification.
Typical 403 with a body indicating the account is not in good standing. No amount of retrying or model-switching will fix this — the user has to act (verify email, top up billing, contact support).
- exception falaw.errors.FalAssetFetchError(message, *, url, cause=None)[source]#
Bases:
FalErrorThe bytes behind a fal-served asset URL could not be retrieved.
Raised by
falaw.contentwhen content-addressing an artifact fails — the URL 404s (fal deletes expired files permanently), the transfer errors, or the response is empty. It is deliberately loud: returning an Artifact whoseasset_idis not the SHA-256 of any bytes would breaklacing.Artifact’s content-hash contract and poison every downstream cache key derived from it.
- exception falaw.errors.FalBadRequest(message, *, status_code, detail='', body=None, headers=None, application=None, url=None, cause=None)[source]#
Bases:
FalHTTPErrorServer rejected the request payload — typical 400 / 422.
- exception falaw.errors.FalClientNotInstalled(what='calling fal.ai', *, missing='fal_client')[source]#
Bases:
FalError,ImportErrorA real network call was attempted but the fal.ai client is not installed.
pip install falawcarries no fal.ai client, so that callers that only build, price, hash and cache plans install none. Anything that actually reaches fal.ai needs thefalextra:pip install 'falaw[fal]'. It is anImportError, soexcept ImportErrorstill matches.
- exception falaw.errors.FalDurationOutOfRange(message, *, model_id, requested, valid_range)[source]#
Bases:
FalErrorThe requested duration is outside what the model can produce.
Raised by Plan/Execute when the caller asks for
duration_sthat the model’s declaredexpected_duration_rangecannot satisfy. Callers can catch this to split the shot, repeat it, or pick a different model.
- exception falaw.errors.FalHTTPError(message, *, status_code, detail='', body=None, headers=None, application=None, url=None, cause=None)[source]#
Bases:
FalErrorWraps an HTTP error from fal.ai with the original status, body, and headers.
Subclasses pick out specific status codes / patterns. Use this base when you want to catch any HTTP failure (e.g. for retry).
- exception falaw.errors.FalInsufficientFunds(message, *, status_code, detail='', body=None, headers=None, application=None, url=None, cause=None)[source]#
Bases:
FalHTTPErrorAccount balance is insufficient — typical 402.
- exception falaw.errors.FalModelHung(message, *, model_id, elapsed_s)[source]#
Bases:
FalErrorA model was queued but never returned — distinct from a network timeout.
Raised by higher-level orchestration that sets a per-call wall-clock budget (e.g. “give up on this lipsync after 5 minutes and pick another model”).
- exception falaw.errors.FalNonCanonicalArgument(message, *, path)[source]#
Bases:
FalErrorAn argument cannot be canonicalised into falaw’s hashed JSON form.
Raised by
falaw.canonicalwhen a value reaches a key-composition site (the per-call cache key,plan_hash, the dry-run artifact id) that JSON cannot represent faithfully: a non-JSON object, a non-finite float, or a non-string mapping key. Deliberately loud: the old behaviour —json.dumps(..., default=str)— silently collided structurally different calls into one cache key, handing back the wrong artifact as a supposed saving (falaw#17).pathnames the offending node, e.g.arguments.extra.ref.
- exception falaw.errors.FalPromptTooLong(message, *, model, limit, length)[source]#
Bases:
FalError,ValueErrorThe prompt exceeds the model’s hard length cap (falaw#76).
Raised before any call is queued: fal answers an over-long prompt with an HTTP 422 only after the request round-trips.
limit/lengthare in characters.
- exception falaw.errors.FalRateLimited(message, *, retry_after_s=None, **kwargs)[source]#
Bases:
FalHTTPErrorfal is throttling requests — typical 429.
retry_after_sis parsed from theRetry-Afterheader if present, elseNone(caller decides backoff).
- exception falaw.errors.FalServerError(message, *, status_code, detail='', body=None, headers=None, application=None, url=None, cause=None)[source]#
Bases:
FalHTTPErrorfal-side server error — typical 5xx. Generally retryable.
- exception falaw.errors.FalTimeout(message, *, elapsed_s, application=None)[source]#
Bases:
FalErrorThe fal call timed out before producing a result.
- exception falaw.errors.FalUnauthorized(message, *, status_code, detail='', body=None, headers=None, application=None, url=None, cause=None)[source]#
Bases:
FalHTTPErrorMissing or invalid API credentials — typical 401.
- falaw.errors.import_fal_client()[source]#
Import and return
fal_client, or raiseFalClientNotInstalled.
- falaw.errors.import_httpx(what='fetching from fal.ai')[source]#
Import and return
httpx(part of thefalextra), or raiseFalClientNotInstalled.
- falaw.errors.translate(exc, *, application=None)[source]#
Convert an underlying fal_client / network exception to a typed falaw exception.
Returns the original exception unchanged if no specific subclass applies (so callers always get something — never silently swallowed).
Logic:
FalClientHTTPErrorwith 403 + lock-pattern body →FalAccountLockedFalClientHTTPErrorwith 401 →FalUnauthorizedFalClientHTTPErrorwith 402 →FalInsufficientFundsFalClientHTTPErrorwith 429 →FalRateLimited(parses Retry-After)FalClientHTTPErrorwith 4xx →FalBadRequestFalClientHTTPErrorwith 5xx →FalServerErrorFalClientTimeoutError→FalTimeoutanything else → returned unchanged
- Return type: