Handling errors¶
This page covers what the SDK raises, what each error means for your data, and what's retried for you.
Every exception this library defines derives from QuickbaseError, and the async client raises the same
ones.
Catching errors¶
Catch the specific errors you can act on, and QuickbaseError for the rest.
from pyquickbase import (
AuthenticationError,
F,
QueryError,
Quickbase,
QuickbaseError,
RateLimitError,
)
with Quickbase(realm="acme", token="your_user_token") as qb:
tasks = qb.app("bq1234567").table("Tasks")
try:
print(tasks.count(F("Status") == "Open"))
except QueryError as error:
print("Fix the query:", error)
except AuthenticationError:
print("Check the token, and the app and table IDs")
except RateLimitError as error:
print(f"Still rate limited. Try again in {error.retry_after} seconds.")
except QuickbaseError as error:
print(error)
The other examples on this page use tasks, a table from a client made in the same way.
Catching a class catches everything under it. WritePermissionError is under both WriteError and
PermissionDeniedError, so either one catches a refused write:
QuickbaseError
├── ConfigurationError
├── QueryError
├── InvalidValueError
├── ConfirmationError
├── PolicyRefusedError
├── SchemaMismatchError
├── UnknownTableError
├── UnknownFieldError
├── UnknownReportError
├── UnsupportedReportError
├── NetworkError
│ └── RequestTimeoutError
├── InvalidResponseError
├── WriteError
│ ├── SchemaChangeError
│ └── WritePermissionError
└── APIError
├── BadRequestError
├── AuthenticationError
├── PermissionDeniedError
│ └── WritePermissionError
├── NotFoundError
├── RateLimitError
└── ServerError
The ten listed first, from ConfigurationError to UnsupportedReportError, mean the request wasn't
sent, though the SDK may have read metadata or counted records first.
ConfigurationError, QueryError, and InvalidValueError are also ValueErrors, and the three
Unknown...Errors are also LookupErrors.
Reading a missing key from a Record raises Python's KeyError, as any mapping does. That includes
record.id on a report row without a Record ID# column.
Mistakes caught before sending¶
These mean the request wasn't sent, though the SDK may have read the table's metadata first to check it.
| Exception | Cause |
|---|---|
ConfigurationError |
Invalid client settings, or missing environment variables |
QueryError |
A query that can't be sent as written, such as a lowercase operator in a hand-written string |
InvalidValueError |
A row, value, or file request that can't be sent |
SchemaMismatchError |
An ensure_ method found a field that isn't what it was asked for, or the schema changed since a plan was made |
UnknownTableError |
No table with that name or ID in this app, or the client's policy hides it |
UnknownFieldError |
No field with that label or ID in this table, or the token's role or the client's policy hides it |
UnknownReportError |
No report with that name or ID on this table, several with that name, or one the role can't use |
UnsupportedReportError |
A report that can't be read as records, such as a summary or chart |
PolicyRefusedError |
The client's policy doesn't allow this request |
A PolicyRefusedError's decision says what was refused, by name, which rule refused it, and what
would allow it. Its message follows one form: Refused by <layer>: <what>. <reason>. (rule: <rule>)
Nothing was sent. To allow it: <hint>.
from pyquickbase import QueryError
try:
tasks.delete("{7.ex.'Done'}")
except QueryError as error:
print(error) # Quickbase would read the lowercase operator as "contains"
Try it in the playground, with simulated data.
Writes that fail partway¶
Quickbase writes the rows it can and reports the rest, so a WriteError doesn't mean nothing changed.
Its result holds what was written and each failed row, and result.row_ids matches an insert's new
records to your rows.
from pyquickbase import WriteError
rows = [{"Title": "Order chairs"}, {"Title": "Book venue", "Status": "Finished"}]
try:
tasks.insert(rows)
except WriteError as error:
for row, record_id in zip(rows, error.result.row_ids):
print(row["Title"], record_id) # None for a row that failed
for failed in error.result.errors:
print(failed.index, failed.reason, failed.messages)
When a later batch of a large write fails, the error's __cause__ is that batch's own error, such as a
RateLimitError, and result holds the batches before it. See When rows fail
for each row's reason.
Refused writes¶
Quickbase refuses a write in two ways, depending on the role. A role with neither add nor modify rights on
the table gets a 403. Any other refusal, including a field the role can only view, is a row error in a
successful response. WritePermissionError covers both. It's a WriteError and a
PermissionDeniedError, so either one catches it, and its status_code is 403 or 207.
If some rows failed for another reason too, you get a plain WriteError, so check each row's reason. A row
with one field the role can't change is refused whole, so its other fields don't change either.
Errors from Quickbase¶
These derive from APIError, which has status_code, method, path, message, description, and
request_id. Include request_id when reporting a problem to Quickbase support.
| Exception | Status |
|---|---|
BadRequestError |
400 |
AuthenticationError |
401 |
PermissionDeniedError |
403 |
NotFoundError |
404 |
RateLimitError |
429, with retry_after |
ServerError |
5xx |
A missing app is a 401
Quickbase answers with a 401 rather than a 404 for an app or table that doesn't exist, or that the
token can't reach, with the same wording it uses for a bad token. So AuthenticationError can mean
any of these, and its message says to check the ID too. A bad realm or app can also return a 400.
Messages can contain HTML
Quickbase's formula error messages can include HTML, such as a javascript: link, and errors pass
them on unchanged. Escape them before showing them in a web page.
When a write may have landed¶
These errors don't say whether Quickbase applied a write, so check the data before trying again.
| Exception | Cause |
|---|---|
NetworkError |
The request failed before a response arrived |
RequestTimeoutError |
The request timed out, a kind of NetworkError |
ServerError |
Quickbase failed while handling it |
InvalidResponseError |
Quickbase reported success, but the response wasn't in the expected format |
Retries¶
The client retries these for you, so an error you catch means the retries ran out.
| Failure | Reads | Writes |
|---|---|---|
429, rate limited, honoring Retry-After |
Yes | Yes |
| Couldn't connect | Yes | Yes, as nothing was sent |
| A timeout or dropped connection after sending | Yes | No, as it may have been applied |
| 502, 503, or 504 | Yes | No |
Reads are GET requests, record queries, report runs, and formula runs. A long burst of requests can still
use up a call's retries and raise RateLimitError, so handle it if you make many calls at once. See
Configuring clients to change the retries.
Errors for a model¶
tool_error() turns any of these into plain data with a fixed code, whether any data changed, and
whether it's retryable. See Returning results to a model.