Skip to content

Markdown

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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector], required
    The fields, by label or ID, or a fields selector.

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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector], required
    The fields, by label or ID, or a fields selector.

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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector], required
    The fields it may set, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector], required
    The fields it may change, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    The file fields, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    The file fields, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • verbs SchemaVerb, required
    What it grants, from those above.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    Which fields change_field and delete_field may change. Defaults to all.
  • field_types Sequence[str] | None, optional
    The types add_field may 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    The fields it may return, by label or ID, or a fields selector. Defaults to all.
  • filter str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    The fields it may filter on instead, or None for the same as fields. 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
  • sort str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    The fields it may sort by instead, or None for the same as fields.
  • max_paging_depth int | None, default 100
    The most records one query or report can reach, counting skip, 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_scanned int | None, default 10000
    The most records summarize() may read for one summary, or None for no limit.
  • user_emails bool, default False
    Whether to keep email addresses in user values, and the uploader's details in file values. Without this, a user is only their id and name.

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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • max_records_per_delete int | 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 through request(), 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

  • table str | TableSelector | Sequence[str | TableSelector], required
    The table, by name or ID, or a tables selector when reports is None.
  • reports Sequence[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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector], required
    The fields, by label or ID, or a fields selector.

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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector], required
    The fields, by label or ID, or a fields selector.

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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector], required
    The fields it may set, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector], required
    The fields it may change, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    The file fields, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    The file fields, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • verbs SchemaVerb, required
    What it grants, from those above.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    Which fields change_field and delete_field may change. Defaults to all.
  • field_types Sequence[str] | None, optional
    The types add_field may 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.
  • fields str | int | FieldSelector | Sequence[str | int | FieldSelector] | None, optional
    The fields, by label or ID, or a fields selector. 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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.

Returns PolicyRule

delete(
    tables: (
        str | TableSelector | Sequence[str | TableSelector]
    ),
) -> PolicyRule

Deny running reports

Refuse running reports.

Parameters

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID, or a tables selector.

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

  • selector FieldSelector | str | TableSelector | Sequence[str | TableSelector], required
    A fields selector, which hides those fields everywhere, or tables by name or ID, or a tables selector.

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

  • tables str | TableSelector | Sequence[str | TableSelector], required
    The tables, by name or ID. A tables selector works only if the condition names fields by ID.
  • where Expression | str, required
    The condition, built with F, 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

  • effect Literal['allow', 'deny', 'limit']
    allow, deny, or limit.
  • tables tuple[TableSelector, ...] | None
    The tables it covers, or None for the app itself, as for create_tables.
  • grants tuple[Grant, ...]
    The verbs it covers, and on which fields.
  • options Mapping[str, Any]
    Its limits, such as max_paging_depth, and for limit.rows(), its where.
  • text str
    The rule as written, which a Decision names.

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

Which check made a Decision: policy, rows, derived, or confirmation.

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

Select new fields

Only the fields added after the policy was bound.

Returns 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

  • names str, required
    The classes, from the policy's classes.

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

  • pattern str, 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

  • kind Literal['all', 'new', 'classed', 'labeled_like', 'named']
    all, new, classed, labeled_like, or named.
  • values tuple[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

  • kind Literal['all', 'created', 'named']
    all, created, or named.
  • values tuple[str, ...]
    For named, 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

  • request ReadRequest | 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

  • action WriteAction
    insert only creates records, and update only changes them. upsert may do either. That includes a write that merges on any field other than Record 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't Record ID#, or through request(), which can't tell. delete deletes records, and delete_file deletes a version of a file. create_table, update_table, delete_table, create_field, update_field, delete_field, create_relationship, update_relationship, and delete_relationship change the schema. update_relationship adds lookup or summary fields. other is any other write.
  • table_id str | None
    The table it writes to. For a relationship, the child table.
  • field_ids frozenset[int] | None
    The fields whose values it sets, or for update_field and delete_field, the fields it changes or deletes. For update, this leaves out Record ID#, which only says which record to change. None for delete, which affects whole records, and for other schema changes.
  • row_count int | None
    How many rows are in this request, which is one batch of a larger write. None for anything but insert, update, and upsert, as a delete by filter can't know.
  • method str
    The HTTP method.
  • path str
    The request's path, without its query parameters.
  • app_id str | None
    For create_table and update_table, the app, and for copy_app and delete_app, the app copied or deleted.
  • parent_table_id str | None
    For a relationship, the parent table. Only create_relationship says which it is, so through request() it's None for the others.
  • field_type str | None
    For create_field, the new field's type, such as text.
  • derived frozenset[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

  • action ReadAction
    query reads records, and summarize is a page that summarize() reads to count and total. delete deletes records by a filter, which a read policy checks because the number deleted shows how many matched. report runs a table report, and summary_report a summary or chart report. file downloads a file, and formula runs one. tables, fields, relationships, reports, app, and roles read metadata. field_usage reads where fields are used, and app_events the app's webhooks, notifications, and automations. other is anything else, including a report run through request(), which doesn't say what kind of report it is. A change to tables, fields, or relationships isn't a read.
  • table_id str | None
    The table it reads, or None for tables, app, roles, app_events, and other.
  • field_ids frozenset[int] | None
    The fields it returns. For file, the file field, and for field_usage, the one field it asks about. None for a report, whose columns are chosen in Quickbase, and for other metadata.
  • filter_field_ids frozenset[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_ids frozenset[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.
  • limit int | None
    The most records it asks for, which Quickbase calls top. None if it doesn't say, so Quickbase returns them all.
  • skip int | None
    How many matching records it passes over first.
  • report_id str | None
    The report it runs.
  • method str
    The HTTP method.
  • path str
    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",
]