Rules¶
A policy is a list of rules. allow and deny name what a client may do with tables and fields, limit keeps it to some records, and a Rule of your own can refuse anything else. Anything no rule allows is refused, and a deny always wins.
Each example makes its own client from the environment, with bqtasks123 standing for the ID of the Job Tasks table. A live test runs every example with both clients and checks its output.
Allow, deny, and limit¶
For the full picture, see Writing rules.
allow¶
The rules that grant. Anything no rule allows is refused, and a deny always wins.
Example
from pyquickbase import (
Quickbase,
F,
Policy,
PolicyRefusedError,
allow,
)
policy = Policy(
rules=[
allow.read("Job Tasks"),
allow.insert("Job Tasks", ["Title", "Status"]),
]
)
with Quickbase.from_env() as setup:
bound = policy.bind(setup.app("bq1234567"))
with Quickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
result = tasks.insert([{"Title": "Paint walls"}])
print(result.created_ids)
try:
tasks.delete(F("Title") == "Paint walls")
except PolicyRefusedError as error:
print(error.decision.reason)
print(error.decision.hint)
from pyquickbase import (
AsyncQuickbase,
F,
Policy,
PolicyRefusedError,
allow,
)
policy = Policy(
rules=[
allow.read("Job Tasks"),
allow.insert("Job Tasks", ["Title", "Status"]),
]
)
async with AsyncQuickbase.from_env() as setup:
bound = await policy.bind(setup.app("bq1234567"))
async with AsyncQuickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
result = await tasks.insert([{"Title": "Paint walls"}])
print(result.created_ids)
try:
await tasks.delete(F("Title") == "Paint walls")
except PolicyRefusedError as error:
print(error.decision.reason)
print(error.decision.hint)
Output
(4,)
No rule allows it
add allow.delete("Job Tasks")
Allow filtering¶
Filtering on fields, which shows which records match, so it reveals something about their values. read already grants it, unless it's given its own filter.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector], required
The fields, by label or ID, or afieldsselector.
Returns PolicyRule
filter(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
),
) -> PolicyRule
Allow sorting¶
Sorting by fields, which reveals their order. read already grants it, unless it's given its own sort.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector], required
The fields, by label or ID, or afieldsselector.
Returns PolicyRule
sort(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
),
) -> PolicyRule
Allow creating records¶
Creating records that set these fields. An upsert needs insert and update.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector], required
The fields it may set, by label or ID, or afieldsselector. Fields created after binding are never writable.
Returns PolicyRule
insert(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
),
) -> PolicyRule
Allow updating records¶
Changing these fields in existing records. Writing a field doesn't need read access to it.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector], required
The fields it may change, by label or ID, or afieldsselector. Fields created after binding are never writable.
Returns PolicyRule
update(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
),
) -> PolicyRule
Allow downloading files¶
Downloading files from file fields. read only returns a file's details.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
The file fields, by label or ID, or afieldsselector. Defaults to all.
Returns PolicyRule
download(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
) -> PolicyRule
Allow deleting file versions¶
Deleting versions of files.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
The file fields, by label or ID, or afieldsselector. Defaults to all.
Returns PolicyRule
delete_file(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
) -> PolicyRule
Allow schema changes¶
Changing tables' schema, granted on the table where the change lands.
add_field, change_field, and delete_field change fields, and formula makes or changes a formula, which also needs add_field or change_field. lookup adds a lookup to a child table, and also needs read on the parent's field. summary adds a summary to a parent table, and also needs read on the child's field, and filter on every field its where uses. link and unlink need a grant on both tables, as a relationship adds a link field and an "Add" record formula to the parent, and unlink also needs delete_field wherever its lookups and summaries are. change_table and delete_table change or delete the table itself.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.verbsSchemaVerb, required
What it grants, from those above.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
Which fieldschange_fieldanddelete_fieldmay change. Defaults to all.field_typesSequence[str] | None, optional
The typesadd_fieldmay create, such as["text", "date"], or None for any. A formula counts by its result type.
Returns PolicyRule
schema(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
*verbs: SchemaVerb,
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
field_types: Sequence[str] | None = None,
) -> PolicyRule
Allow creating tables¶
Creating tables in the bound app. A table this client creates is matched by tables.created() and tables.all().
Returns PolicyRule
create_tables() -> PolicyRule
Allow reading¶
Reading fields' values and details, and filtering and sorting by them.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
The fields it may return, by label or ID, or afieldsselector. Defaults to all.filterstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
The fields it may filter on instead, or None for the same asfields. A filter shows which records match, so it reveals something about a field's values.
4 more: sort, max_paging_depth, max_records_scanned, user_emails
sortstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
The fields it may sort by instead, or None for the same asfields.max_paging_depthint | None, default100
The most records one query or report can reach, countingskip, or None for no limit. It's how far paging can go, not the size of a page. Where several rules read a table, the highest applies.max_records_scannedint | None, default10000
The most recordssummarize()may read for one summary, or None for no limit.user_emailsbool, defaultFalse
Whether to keep email addresses in user values, and the uploader's details in file values. Without this, a user is only theiridandname.
Returns PolicyRule
read(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
*,
filter: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
sort: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
max_paging_depth: int | None = 100,
max_records_scanned: int | None = 10000,
user_emails: bool = False,
) -> PolicyRule
Allow deleting records¶
Deleting records, which removes all of their fields.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.max_records_per_deleteint | None, optional
The most records one delete may remove, or None for no limit. With a limit, the client counts what matches before deleting, and refuses a delete throughrequest(), which it can't count.
Returns PolicyRule
delete(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
*,
max_records_per_delete: int | None = None,
) -> PolicyRule
Allow running reports¶
Running reports. A table report's columns are trimmed to what's readable, but its own filter and sort can use any field. A summary or chart report's columns can't be matched to fields, so it shows all of them.
Parameters
tablestr | TableSelector | Sequence[str | TableSelector], required
The table, by name or ID, or atablesselector whenreportsis None.reportsSequence[str | int] | None, optional
The reports, by name or ID, or None for every report.
Returns PolicyRule
report(
table: (
str | TableSelector | Sequence[str | TableSelector]
),
reports: Sequence[str | int] | None = None,
) -> PolicyRule
deny¶
The rules that refuse. A deny always wins over an allow.
Example
from pyquickbase import Quickbase, Policy, allow, deny
policy = Policy(
rules=[
allow.read("Job Tasks"),
deny.read("Job Tasks", ["Hours"]),
]
)
with Quickbase.from_env() as setup:
bound = policy.bind(setup.app("bq1234567"))
with Quickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
print(
[
item.label
for item in tasks.fields()
if item.id > 5
]
)
from pyquickbase import AsyncQuickbase, Policy, allow, deny
policy = Policy(
rules=[
allow.read("Job Tasks"),
deny.read("Job Tasks", ["Hours"]),
]
)
async with AsyncQuickbase.from_env() as setup:
bound = await policy.bind(setup.app("bq1234567"))
async with AsyncQuickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
print(
[
item.label
for item in await tasks.fields()
if item.id > 5
]
)
Output
['Title', 'Status', 'Due Date', 'Code', 'Attachment']
Deny filtering¶
Filtering on fields, which shows which records match, so it reveals something about their values. read already grants it, unless it's given its own filter.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector], required
The fields, by label or ID, or afieldsselector.
Returns PolicyRule
filter(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
),
) -> PolicyRule
Deny sorting¶
Sorting by fields, which reveals their order. read already grants it, unless it's given its own sort.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector], required
The fields, by label or ID, or afieldsselector.
Returns PolicyRule
sort(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
),
) -> PolicyRule
Deny creating records¶
Creating records that set these fields. An upsert needs insert and update.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector], required
The fields it may set, by label or ID, or afieldsselector. Fields created after binding are never writable.
Returns PolicyRule
insert(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
),
) -> PolicyRule
Deny updating records¶
Changing these fields in existing records. Writing a field doesn't need read access to it.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector], required
The fields it may change, by label or ID, or afieldsselector. Fields created after binding are never writable.
Returns PolicyRule
update(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
),
) -> PolicyRule
Deny downloading files¶
Downloading files from file fields. read only returns a file's details.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
The file fields, by label or ID, or afieldsselector. Defaults to all.
Returns PolicyRule
download(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
) -> PolicyRule
Deny deleting file versions¶
Deleting versions of files.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
The file fields, by label or ID, or afieldsselector. Defaults to all.
Returns PolicyRule
delete_file(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
) -> PolicyRule
Deny schema changes¶
Changing tables' schema, granted on the table where the change lands.
add_field, change_field, and delete_field change fields, and formula makes or changes a formula, which also needs add_field or change_field. lookup adds a lookup to a child table, and also needs read on the parent's field. summary adds a summary to a parent table, and also needs read on the child's field, and filter on every field its where uses. link and unlink need a grant on both tables, as a relationship adds a link field and an "Add" record formula to the parent, and unlink also needs delete_field wherever its lookups and summaries are. change_table and delete_table change or delete the table itself.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.verbsSchemaVerb, required
What it grants, from those above.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
Which fieldschange_fieldanddelete_fieldmay change. Defaults to all.field_typesSequence[str] | None, optional
The typesadd_fieldmay create, such as["text", "date"], or None for any. A formula counts by its result type.
Returns PolicyRule
schema(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
*verbs: SchemaVerb,
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
field_types: Sequence[str] | None = None,
) -> PolicyRule
Deny creating tables¶
Creating tables in the bound app. A table this client creates is matched by tables.created() and tables.all().
Returns PolicyRule
create_tables() -> PolicyRule
Deny reading¶
Refuse reading, filtering, and sorting by fields, which hides them unless something else, such as a write, is allowed.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.fieldsstr | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
The fields, by label or ID, or afieldsselector. Defaults to all.
Returns PolicyRule
read(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
fields: (
str
| int
| FieldSelector
| Sequence[str | int | FieldSelector]
| None
) = None,
) -> PolicyRule
Deny deleting records¶
Refuse deleting records.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.
Returns PolicyRule
delete(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
) -> PolicyRule
Deny running reports¶
Refuse running reports.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID, or atablesselector.
Returns PolicyRule
report(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
) -> PolicyRule
Deny everything¶
Refuse everything on some fields, in every table, or on some tables.
deny.all(fields.classed("sensitive"))
deny.all("Payroll")
Parameters
selectorFieldSelector | str | TableSelector | Sequence[str | TableSelector], required
Afieldsselector, which hides those fields everywhere, or tables by name or ID, or atablesselector.
Returns PolicyRule
all(
selector: (
FieldSelector
| str
| TableSelector
| Sequence[str | TableSelector]
),
) -> PolicyRule
limit¶
Limits on which records a client reaches.
Example
from pyquickbase import (
Quickbase,
F,
Policy,
allow,
limit,
)
policy = Policy(
rules=[
allow.read("Job Tasks"),
limit.rows("Job Tasks", F("Status") == "Open"),
]
)
with Quickbase.from_env() as setup:
bound = policy.bind(setup.app("bq1234567"))
with Quickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
print(tasks.count())
print(tasks.count(F("Hours") > 7))
from pyquickbase import (
AsyncQuickbase,
F,
Policy,
allow,
limit,
)
policy = Policy(
rules=[
allow.read("Job Tasks"),
limit.rows("Job Tasks", F("Status") == "Open"),
]
)
async with AsyncQuickbase.from_env() as setup:
bound = await policy.bind(setup.app("bq1234567"))
async with AsyncQuickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
print(await tasks.count())
print(await tasks.count(F("Hours") > 7))
Output
2
2
Limit the rows¶
Only reach the records that match a condition. It's ANDed onto every query, summary, and delete the client sends, and into a summary field's filter.
This is defense in depth: Quickbase's record rules for the token's role are still the real boundary. Reports, file downloads, and formulas on the table are refused, as the limit can't be added to them, and so is a lookup from it, which would show its records anywhere. The fields the condition uses may be filtered on.
Parameters
tablesstr | TableSelector | Sequence[str | TableSelector], required
The tables, by name or ID. Atablesselector works only if the condition names fields by ID.whereExpression | str, required
The condition, built withF, or a query string, by label or ID.
Returns PolicyRule
rows(
tables: (
str | TableSelector | Sequence[str | TableSelector]
),
where: Expression | str,
) -> PolicyRule
PolicyRule¶
One rule of a Policy, made by allow, deny, or limit.
Attributes
effectLiteral['allow', 'deny', 'limit']
allow,deny, orlimit.tablestuple[TableSelector, ...] | None
The tables it covers, or None for the app itself, as forcreate_tables.grantstuple[Grant, ...]
The verbs it covers, and on which fields.optionsMapping[str, Any]
Its limits, such asmax_paging_depth, and forlimit.rows(), itswhere.textstr
The rule as written, which aDecisionnames.
Example
from pyquickbase import allow
rule = allow.update("Job Tasks", ["Status"])
print(rule, rule.effect)
from pyquickbase import allow
rule = allow.update("Job Tasks", ["Status"])
print(rule, rule.effect)
Output
allow.update('Job Tasks', ['Status']) allow
Verb¶
What a policy grants or refuses, such as read, update, or lookup.
Verb = Literal[
"read",
"filter",
"sort",
"insert",
"update",
"delete",
"download",
"delete_file",
"report",
"run_formula",
"add_field",
"change_field",
"delete_field",
"formula",
"lookup",
"summary",
"link",
"unlink",
"change_table",
"delete_table",
"create_tables",
]
SchemaVerb¶
What allow.schema() grants on a table, where the change lands.
SchemaVerb = Literal[
"add_field",
"change_field",
"delete_field",
"formula",
"lookup",
"summary",
"link",
"unlink",
"change_table",
"delete_table",
]
Layer = Literal["policy", "rows", "derived"]
Tables and fields¶
For the full picture, see Writing rules.
fields¶
Field selectors, for rules and classes.
Example
from pyquickbase import (
Quickbase,
Policy,
allow,
deny,
fields,
)
policy = Policy(
classes={"codes": [fields.labeled_like("cod*")]},
rules=[
allow.read("Job Tasks"),
deny.all(fields.classed("codes")),
],
)
with Quickbase.from_env() as setup:
bound = policy.bind(setup.app("bq1234567"))
with Quickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
print(
[
item.label
for item in tasks.fields()
if item.id > 5
]
)
from pyquickbase import (
AsyncQuickbase,
Policy,
allow,
deny,
fields,
)
policy = Policy(
classes={"codes": [fields.labeled_like("cod*")]},
rules=[
allow.read("Job Tasks"),
deny.all(fields.classed("codes")),
],
)
async with AsyncQuickbase.from_env() as setup:
bound = await policy.bind(setup.app("bq1234567"))
async with AsyncQuickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
print(
[
item.label
for item in await tasks.fields()
if item.id > 5
]
)
Output
['Title', 'Status', 'Hours', 'Due Date', 'Attachment']
Select every field¶
Every field, including ones added after binding, which new_fields limits.
Returns FieldSelector
all() -> FieldSelector
new() -> FieldSelector
Select fields by class¶
The fields in any of these classes, including formulas, lookups, and summaries that read one, through any chain of them.
Parameters
namesstr, required
The classes, from the policy'sclasses.
Returns FieldSelector
classed(*names: str) -> FieldSelector
Select fields by label¶
The fields whose labels match a pattern, ignoring case. Matched when the policy is bound.
Parameters
patternstr, required
The pattern, where*is any text and?is one character, such as_*.
Returns FieldSelector
labeled_like(pattern: str) -> FieldSelector
tables¶
Table selectors, for rules.
Example
from pyquickbase import Quickbase, Policy, allow, tables
policy = Policy(
rules=[
allow.create_tables(),
allow.read(tables.created()),
allow.schema(tables.created(), "add_field"),
]
)
with Quickbase.from_env() as setup:
bound = policy.bind(setup.app("bq1234567"))
with Quickbase.from_env(policy=bound) as qb:
sites = qb.app("bq1234567").create_table(
"Job Sites"
)
sites.create_field("Address", "text")
print(
[
item.label
for item in sites.fields()
if item.id > 5
]
)
print(len(qb.policy.created), len(bound.created))
from pyquickbase import AsyncQuickbase, Policy, allow, tables
policy = Policy(
rules=[
allow.create_tables(),
allow.read(tables.created()),
allow.schema(tables.created(), "add_field"),
]
)
async with AsyncQuickbase.from_env() as setup:
bound = await policy.bind(setup.app("bq1234567"))
async with AsyncQuickbase.from_env(policy=bound) as qb:
sites = await qb.app("bq1234567").create_table(
"Job Sites"
)
await sites.create_field("Address", "text")
print(
[
item.label
for item in await sites.fields()
if item.id > 5
]
)
print(len(qb.policy.created), len(bound.created))
Output
['Address']
1 0
Select every table¶
Every table in the bound app, and any this client creates.
Returns TableSelector
all() -> TableSelector
Select created tables¶
Only the tables this client creates after the policy is bound.
Returns TableSelector
created() -> TableSelector
FieldSelector¶
Which fields a rule or class covers. Make one with fields, or name fields by label or ID.
Attributes
kindLiteral['all', 'new', 'classed', 'labeled_like', 'named']
all,new,classed,labeled_like, ornamed.valuestuple[str | int, ...]
The classes, the pattern, or the labels and IDs.
Example
from pyquickbase import fields
print(fields.labeled_like("_*"))
print(fields.classed("pay").kind)
from pyquickbase import fields
print(fields.labeled_like("_*"))
print(fields.classed("pay").kind)
Output
fields.labeled_like('_*')
classed
TableSelector¶
Which tables a rule covers. Make one with tables, or name a table by name or ID.
Attributes
kindLiteral['all', 'created', 'named']
all,created, ornamed.valuestuple[str, ...]
Fornamed, the table's name or ID.
Example
from pyquickbase import tables
print(tables.created())
from pyquickbase import tables
print(tables.created())
Output
tables.created()
Rules of your own¶
For the full picture, see Rules of your own.
Rule¶
Custom logic, added to a policy's rules. It can only refuse.
decide() gets each request the built-in rules allowed, and returns None to leave it, or a Decision with allowed=False to refuse it. BoundPolicy.offers() asks about one field and one verb at a time, so a rule that decides each field on its own is offered exactly what it allows.
Example
from pyquickbase import (
Quickbase,
Decision,
Policy,
PolicyRefusedError,
WriteRequest,
allow,
fields,
)
class OneRowAtATime:
def decide(self, request):
if (
isinstance(request, WriteRequest)
and (request.row_count or 0) > 1
):
return Decision(
allowed=False,
verb="insert",
reason="Write one row at a time",
)
return None
policy = Policy(
rules=[
allow.read("Job Tasks"),
allow.insert("Job Tasks", fields.all()),
OneRowAtATime(),
]
)
with Quickbase.from_env() as setup:
bound = policy.bind(setup.app("bq1234567"))
with Quickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
print(
(tasks.insert([{"Title": "One"}])).created_ids
)
try:
tasks.insert([{"Title": "A"}, {"Title": "B"}])
except PolicyRefusedError as error:
print(error.decision.reason)
from pyquickbase import (
AsyncQuickbase,
Decision,
Policy,
PolicyRefusedError,
WriteRequest,
allow,
fields,
)
class OneRowAtATime:
def decide(self, request):
if (
isinstance(request, WriteRequest)
and (request.row_count or 0) > 1
):
return Decision(
allowed=False,
verb="insert",
reason="Write one row at a time",
)
return None
policy = Policy(
rules=[
allow.read("Job Tasks"),
allow.insert("Job Tasks", fields.all()),
OneRowAtATime(),
]
)
async with AsyncQuickbase.from_env() as setup:
bound = await policy.bind(setup.app("bq1234567"))
async with AsyncQuickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
print(
(await tasks.insert([{"Title": "One"}])).created_ids
)
try:
await tasks.insert([{"Title": "A"}, {"Title": "B"}])
except PolicyRefusedError as error:
print(error.decision.reason)
Output
(4,)
Write one row at a time
Refuse a request¶
Refuse a request, or leave it to the other rules.
Parameters
requestReadRequest | WriteRequest, required
What the request would do, which holds no values.
Returns Decision | None
A Decision with allowed=False to refuse, or None.
decide(
request: ReadRequest | WriteRequest,
) -> Decision | None
WriteRequest¶
What a write would do, for a policy to decide on.
It holds no values from the request, so a policy can log it safely. None always means that this couldn't be determined, so check for it rather than treating it as nothing.
Attributes
actionWriteAction
insertonly creates records, andupdateonly changes them.upsertmay do either. That includes a write that merges on any field other thanRecord ID#, a row whose field IDs aren't written in the usual way, and any write without a merge field to a table whose key field isn'tRecord ID#, or throughrequest(), which can't tell.deletedeletes records, anddelete_filedeletes a version of a file.create_table,update_table,delete_table,create_field,update_field,delete_field,create_relationship,update_relationship, anddelete_relationshipchange the schema.update_relationshipadds lookup or summary fields.otheris any other write.table_idstr | None
The table it writes to. For a relationship, the child table.field_idsfrozenset[int] | None
The fields whose values it sets, or forupdate_fieldanddelete_field, the fields it changes or deletes. Forupdate, this leaves outRecord ID#, which only says which record to change.Nonefordelete, which affects whole records, and for other schema changes.row_countint | None
How many rows are in this request, which is one batch of a larger write.Nonefor anything butinsert,update, andupsert, as a delete by filter can't know.methodstr
The HTTP method.pathstr
The request's path, without its query parameters.app_idstr | None
Forcreate_tableandupdate_table, the app, and forcopy_appanddelete_app, the app copied or deleted.parent_table_idstr | None
For a relationship, the parent table. Onlycreate_relationshipsays which it is, so throughrequest()it'sNonefor the others.field_typestr | None
Forcreate_field, the new field's type, such astext.derivedfrozenset[DerivedKind]
The formula, lookup, and summary fields it makes or changes, which read other fields. A summary is made on the parent table.
Example
from pyquickbase import (
Quickbase,
Policy,
WriteRequest,
allow,
fields,
)
class Log:
def decide(self, request):
if isinstance(request, WriteRequest):
print(request.action, request.row_count)
print(sorted(request.field_ids))
return None
policy = Policy(
rules=[
allow.read("Job Tasks"),
allow.insert("Job Tasks", fields.all()),
Log(),
]
)
with Quickbase.from_env() as setup:
bound = policy.bind(setup.app("bq1234567"))
with Quickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
tasks.insert([{"Title": "Paint walls"}])
from pyquickbase import (
AsyncQuickbase,
Policy,
WriteRequest,
allow,
fields,
)
class Log:
def decide(self, request):
if isinstance(request, WriteRequest):
print(request.action, request.row_count)
print(sorted(request.field_ids))
return None
policy = Policy(
rules=[
allow.read("Job Tasks"),
allow.insert("Job Tasks", fields.all()),
Log(),
]
)
async with AsyncQuickbase.from_env() as setup:
bound = await policy.bind(setup.app("bq1234567"))
async with AsyncQuickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
await tasks.insert([{"Title": "Paint walls"}])
Output
insert 1
[6]
ReadRequest¶
What a read would do, for a policy to decide on.
It holds no values from the request, so a policy can log it safely. None always means that this couldn't be determined, so check for it rather than treating it as nothing. Record ID# appears like any other field.
Attributes
actionReadAction
queryreads records, andsummarizeis a page thatsummarize()reads to count and total.deletedeletes records by a filter, which a read policy checks because the number deleted shows how many matched.reportruns a table report, andsummary_reporta summary or chart report.filedownloads a file, andformularuns one.tables,fields,relationships,reports,app, androlesread metadata.field_usagereads where fields are used, andapp_eventsthe app's webhooks, notifications, and automations.otheris anything else, including a report run throughrequest(), which doesn't say what kind of report it is. A change to tables, fields, or relationships isn't a read.table_idstr | None
The table it reads, or None fortables,app,roles,app_events, andother.field_idsfrozenset[int] | None
The fields it returns. Forfile, the file field, and forfield_usage, the one field it asks about. None for a report, whose columns are chosen in Quickbase, and for other metadata.filter_field_idsfrozenset[int] | None
The fields its filter uses, which is empty without one. None if the filter can't be read, and for a report, whose filter is its own.sort_field_idsfrozenset[int] | None
The fields it sorts by. None if it doesn't say, as the table's default sort would apply, and for a report.limitint | None
The most records it asks for, which Quickbase callstop. None if it doesn't say, so Quickbase returns them all.skipint | None
How many matching records it passes over first.report_idstr | None
The report it runs.methodstr
The HTTP method.pathstr
The request's path, without its query parameters.
Example
from pyquickbase import (
Quickbase,
Policy,
ReadRequest,
allow,
)
class Log:
def decide(self, request):
if isinstance(request, ReadRequest):
print(
request.action,
sorted(request.field_ids or []),
)
return None
policy = Policy(rules=[allow.read("Job Tasks"), Log()])
with Quickbase.from_env() as setup:
bound = policy.bind(setup.app("bq1234567"))
with Quickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
tasks.first(select=["Title"])
from pyquickbase import (
AsyncQuickbase,
Policy,
ReadRequest,
allow,
)
class Log:
def decide(self, request):
if isinstance(request, ReadRequest):
print(
request.action,
sorted(request.field_ids or []),
)
return None
policy = Policy(rules=[allow.read("Job Tasks"), Log()])
async with AsyncQuickbase.from_env() as setup:
bound = await policy.bind(setup.app("bq1234567"))
async with AsyncQuickbase.from_env(policy=bound) as qb:
tasks = qb.app("bq1234567").table("Job Tasks")
await tasks.first(select=["Title"])
Output
tables []
fields []
query [3, 6]
WriteAction¶
What kind of write a WriteRequest is.
WriteAction = Literal[
"insert",
"update",
"upsert",
"delete",
"delete_file",
"create_table",
"update_table",
"delete_table",
"create_field",
"update_field",
"delete_field",
"create_relationship",
"update_relationship",
"delete_relationship",
"create_app",
"copy_app",
"delete_app",
"other",
]
DerivedKind¶
A field whose values come from other fields: a formula, a lookup of a parent's field, or a summary of a child's. WriteRequest.derived lists the ones a write makes or changes.
DerivedKind = Literal["formula", "lookup", "summary"]
ReadAction¶
What kind of read a ReadRequest is.
ReadAction = Literal[
"query",
"summarize",
"delete",
"report",
"summary_report",
"file",
"formula",
"tables",
"fields",
"relationships",
"reports",
"app",
"roles",
"field_usage",
"app_events",
"other",
]