# pyquickbase API reference: Rules This describes version 1.0.0rc13. Other pages: [Quickbase](https://matthew-williams-solutions.github.io/pyquickbase/reference/quickbase.txt), [App](https://matthew-williams-solutions.github.io/pyquickbase/reference/app.txt), [Table](https://matthew-williams-solutions.github.io/pyquickbase/reference/table.txt), [Report](https://matthew-williams-solutions.github.io/pyquickbase/reference/report.txt), [Operation](https://matthew-williams-solutions.github.io/pyquickbase/reference/operation.txt), [SchemaOperation](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-operation.txt), [Queries](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries.txt), [Records and results](https://matthew-williams-solutions.github.io/pyquickbase/reference/records.txt), [Metadata](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata.txt), [Schema changes](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-changes.txt), [Policies](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies.txt), [Retries](https://matthew-williams-solutions.github.io/pyquickbase/reference/retries.txt), [Tools for models](https://matthew-williams-solutions.github.io/pyquickbase/reference/tools.txt), [Errors](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors.txt). A policy is a list of rules. [`allow`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#allow) and [`deny`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#deny) name what a client may do with tables and fields, [`limit`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#limit) keeps it to some records, and a [`Rule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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](https://matthew-williams-solutions.github.io/pyquickbase/policies/rules/). ### `allow` The rules that grant. Anything no rule allows is refused, and a deny always wins. #### `allow.filter()` 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`. ```python filter( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector]`, required The fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.sort()` Sorting by fields, which reveals their order. `read` already grants it, unless it's given its own `sort`. ```python sort( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector]`, required The fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.insert()` Creating records that set these fields. An upsert needs `insert` and `update`. ```python insert( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector]`, required The fields it may set, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Fields created after binding are never writable. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.update()` Changing these fields in existing records. Writing a field doesn't need read access to it. ```python update( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector]`, required The fields it may change, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Fields created after binding are never writable. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.download()` Downloading files from file fields. `read` only returns a file's details. ```python download( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] | None ) = None, ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector] | None`, optional The file fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Defaults to all. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.delete_file()` Deleting versions of files. ```python delete_file( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] | None ) = None, ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector] | None`, optional The file fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Defaults to all. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.schema()` 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. ```python 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 ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.create_tables()` Creating tables in the bound app. A table this client creates is matched by `tables.created()` and `tables.all()`. ```python create_tables() -> PolicyRule ``` **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.read()` Reading fields' values and details, and filtering and sorting by them. ```python 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 ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector] | None`, optional The fields it may return, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields). A filter shows which records match, so it reveals something about a field's values. - **`sort`** `str | int | FieldSelector | Sequence[str | int | FieldSelector] | None`, optional The fields it may sort by instead, or None for the same as [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.delete()` Deleting records, which removes all of their fields. ```python delete( tables: ( str | TableSelector | Sequence[str | TableSelector] ), *, max_records_per_delete: int | None = None, ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `allow.report()` 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. ```python report( table: ( str | TableSelector | Sequence[str | TableSelector] ), reports: Sequence[str | int] | None = None, ) -> PolicyRule ``` **Parameters** - **`table`** `str | TableSelector | Sequence[str | TableSelector]`, required The table, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) Sync example: ```python 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) ``` Async example: ```python 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: ```text (4,) No rule allows it add allow.delete("Job Tasks") ``` ### `deny` The rules that refuse. A deny always wins over an allow. #### `deny.filter()` 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`. ```python filter( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector]`, required The fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.sort()` Sorting by fields, which reveals their order. `read` already grants it, unless it's given its own `sort`. ```python sort( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector]`, required The fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.insert()` Creating records that set these fields. An upsert needs `insert` and `update`. ```python insert( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector]`, required The fields it may set, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Fields created after binding are never writable. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.update()` Changing these fields in existing records. Writing a field doesn't need read access to it. ```python update( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector]`, required The fields it may change, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Fields created after binding are never writable. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.download()` Downloading files from file fields. `read` only returns a file's details. ```python download( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] | None ) = None, ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector] | None`, optional The file fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Defaults to all. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.delete_file()` Deleting versions of files. ```python delete_file( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] | None ) = None, ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector] | None`, optional The file fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Defaults to all. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.schema()` 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. ```python 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 ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.create_tables()` Creating tables in the bound app. A table this client creates is matched by `tables.created()` and `tables.all()`. ```python create_tables() -> PolicyRule ``` **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.read()` Refuse reading, filtering, and sorting by fields, which hides them unless something else, such as a write, is allowed. ```python read( tables: ( str | TableSelector | Sequence[str | TableSelector] ), fields: ( str | int | FieldSelector | Sequence[str | int | FieldSelector] | None ) = None, ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. - **`fields`** `str | int | FieldSelector | Sequence[str | int | FieldSelector] | None`, optional The fields, by label or ID, or a [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector. Defaults to all. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.delete()` Refuse deleting records. ```python delete( tables: ( str | TableSelector | Sequence[str | TableSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.report()` Refuse running reports. ```python report( tables: ( str | TableSelector | Sequence[str | TableSelector] ), ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) #### `deny.all()` Refuse everything on some fields, in every table, or on some tables. ```python deny.all(fields.classed("sensitive")) deny.all("Payroll") ``` ```python all( selector: ( FieldSelector | str | TableSelector | Sequence[str | TableSelector] ), ) -> PolicyRule ``` **Parameters** - **`selector`** `FieldSelector | str | TableSelector | Sequence[str | TableSelector]`, required A [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) selector, which hides those fields everywhere, or tables by name or ID, or a [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) Sync example: ```python 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 ] ) ``` Async example: ```python 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: ```text ['Title', 'Status', 'Due Date', 'Code', 'Attachment'] ``` ### `limit` Limits on which records a client reaches. #### `limit.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. ```python rows( tables: ( str | TableSelector | Sequence[str | TableSelector] ), where: Expression | str, ) -> PolicyRule ``` **Parameters** - **`tables`** `str | TableSelector | Sequence[str | TableSelector]`, required The tables, by name or ID. A [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables) selector works only if the condition names fields by ID. - **`where`** `Expression | str`, required The condition, built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), or a query string, by label or ID. **Returns** [`PolicyRule`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#PolicyRule) Sync example: ```python 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)) ``` Async example: ```python 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: ```text 2 2 ``` ### `PolicyRule` One rule of a [`Policy`](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies/#Policy), made by [`allow`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#allow), [`deny`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#deny), or [`limit`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#limit). **Attributes** - **`effect`** `Literal['allow', 'deny', 'limit']` [`allow`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#allow), [`deny`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#deny), or [`limit`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies/#Decision) names. Sync example: ```python from pyquickbase import allow rule = allow.update("Job Tasks", ["Status"]) print(rule, rule.effect) ``` Async example: ```python from pyquickbase import allow rule = allow.update("Job Tasks", ["Status"]) print(rule, rule.effect) ``` Output: ```text allow.update('Job Tasks', ['Status']) allow ``` ### `Verb` What a policy grants or refuses, such as `read`, `update`, or `lookup`. ```python 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. ```python SchemaVerb = Literal[ "add_field", "change_field", "delete_field", "formula", "lookup", "summary", "link", "unlink", "change_table", "delete_table", ] ``` ### `Layer` Which check made a [`Decision`](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies/#Decision): `policy`, `rows`, `derived`, or `confirmation`. ```python Layer = Literal["policy", "rows", "derived"] ``` ## Tables and fields For the full picture, see [Writing rules](https://matthew-williams-solutions.github.io/pyquickbase/policies/rules/). ### `fields` Field selectors, for rules and classes. #### `fields.all()` Every field, including ones added after binding, which `new_fields` limits. ```python all() -> FieldSelector ``` **Returns** [`FieldSelector`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#FieldSelector) #### `fields.new()` Only the fields added after the policy was bound. ```python new() -> FieldSelector ``` **Returns** [`FieldSelector`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#FieldSelector) #### `fields.classed()` The fields in any of these classes, including formulas, lookups, and summaries that read one, through any chain of them. ```python classed(*names: str) -> FieldSelector ``` **Parameters** - **`names`** `str`, required The classes, from the policy's `classes`. **Returns** [`FieldSelector`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#FieldSelector) #### `fields.labeled_like()` The fields whose labels match a pattern, ignoring case. Matched when the policy is bound. ```python labeled_like(pattern: str) -> FieldSelector ``` **Parameters** - **`pattern`** `str`, required The pattern, where `*` is any text and `?` is one character, such as `_*`. **Returns** [`FieldSelector`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#FieldSelector) Sync example: ```python 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 ] ) ``` Async example: ```python 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: ```text ['Title', 'Status', 'Hours', 'Due Date', 'Attachment'] ``` ### `tables` Table selectors, for rules. #### `tables.all()` Every table in the bound app, and any this client creates. ```python all() -> TableSelector ``` **Returns** [`TableSelector`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#TableSelector) #### `tables.created()` Only the tables this client creates after the policy is bound. ```python created() -> TableSelector ``` **Returns** [`TableSelector`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#TableSelector) Sync example: ```python 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)) ``` Async example: ```python 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: ```text ['Address'] 1 0 ``` ### `FieldSelector` Which fields a rule or class covers. Make one with [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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. Sync example: ```python from pyquickbase import fields print(fields.labeled_like("_*")) print(fields.classed("pay").kind) ``` Async example: ```python from pyquickbase import fields print(fields.labeled_like("_*")) print(fields.classed("pay").kind) ``` Output: ```text fields.labeled_like('_*') classed ``` ### `TableSelector` Which tables a rule covers. Make one with [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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. Sync example: ```python from pyquickbase import tables print(tables.created()) ``` Async example: ```python from pyquickbase import tables print(tables.created()) ``` Output: ```text tables.created() ``` ## Rules of your own For the full picture, see [Rules of your own](https://matthew-williams-solutions.github.io/pyquickbase/policies/custom-rules/). ### `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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies/#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. #### `Rule.decide()` Refuse a request, or leave it to the other rules. ```python decide( request: ReadRequest | WriteRequest, ) -> Decision | None ``` **Parameters** - **`request`** `ReadRequest | WriteRequest`, required What the request would do, which holds no values. **Returns** [`Decision`](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies/#Decision) `| None` A [`Decision`](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies/#Decision) with `allowed=False` to refuse, or None. Sync example: ```python 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) ``` Async example: ```python 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: ```text (4,) Write one row at a time ``` ### `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. Sync example: ```python 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"}]) ``` Async example: ```python 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: ```text 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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables), [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#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. Sync example: ```python 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"]) ``` Async example: ```python 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: ```text tables [] fields [] query [3, 6] ``` ### `WriteAction` What kind of write a [`WriteRequest`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#WriteRequest) is. ```python 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. ```python DerivedKind = Literal["formula", "lookup", "summary"] ``` ### `ReadAction` What kind of read a [`ReadRequest`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#ReadRequest) is. ```python ReadAction = Literal[ "query", "summarize", "delete", "report", "summary_report", "file", "formula", "tables", "fields", "relationships", "reports", "app", "roles", "field_usage", "app_events", "other", ] ```