Policies¶
A client takes one policy, which says what it may read and change.
| Policy | What the client can do | Use it for |
|---|---|---|
| None | Whatever the token can | Your own scripts |
Policy.read_only() |
Read whatever the token can, and change nothing | Reports, dashboards, and anything that only looks |
Policy(rules=[...]), bound to an app |
Only what its rules allow | An AI agent, or a job with a narrow purpose |
A policy protects against bugs in your code and mistakes by a model. See what it doesn't do, and give your token access to as little as possible, so that Quickbase enforces the limit too.
How a policy decides¶
A policy works on both sides of each request, including one sent through qb.request():
flowchart TD
R["<b>Your request</b>"] --> C["<b>Checked before sending</b><br/>against every rule"]
C -- refused --> X["<b>PolicyRefusedError</b><br/>nothing sent"]
C -- allowed --> Q["<b>Quickbase</b><br/>applies the token's role"]
Q --> T["<b>Trimmed on return</b><br/>hidden tables and fields<br/>left out"]
A table method may read the table's metadata before its own request is checked. A hidden table or
field looks as if it doesn't exist. When a request is refused, error.decision.layer says which check
refused it:
layer |
Refused because |
|---|---|
policy |
No rule allows it, or a deny rule covers it. A deny always wins |
rows |
A row limit can't be added to it, or it would show a row-limited table's records elsewhere |
derived |
A formula, lookup, or summary would read something the client can't |
A confirmation that require_preview asks for isn't part of the policy. A missing or mismatched one
raises ConfirmationError.
Binding a policy¶
- Write a
Policyof rules, by table name and field label. - Bind it to the app once. Binding looks up every name, reports every problem at once, and pins each table and field by ID.
- Give the bound policy to a client. From then on, every request is checked against it.
flowchart TD
P["<b>Policy</b><br/>tables and fields<br/>by name"] -- "bind(app)" --> B["<b>BoundPolicy</b><br/>tables and fields<br/>by ID"]
B -- "policy=" --> C["<b>Client</b>"]
B -- "to_json(), then<br/>from_json()" --> O["<b>Another process</b>"]
A bound policy holds tables and fields by ID, so renaming one doesn't change what a rule means. A
field added after binding follows new_fields until you bind again, and a table the client creates
gives it a new policy, qb.policy, that includes it.
Making a client read-only¶
Policy.read_only() guarantees a client can't change data. It only sends GET requests, record
queries, report runs, and formula runs, and reads them as a client without a policy would. Running a
report adds to its usage count in Quickbase, but changes no records. It names no table, so a client
takes it without binding.
from pyquickbase import Policy, Quickbase
with Quickbase(realm="acme", token="your_user_token", policy=Policy.read_only()) as qb:
print(qb.app("bq1234567").tables())