# pyquickbase API reference: Table 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), [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), [Rules](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules.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 [`Table`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/) reads and writes one table's records and changes its fields. Get one from [`App.table()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/app/#table), by name or ID. Getting one doesn't contact Quickbase, so a table that doesn't exist raises [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError) on first use. The examples below use `tasks`, made as shown here. `Job Tasks` has `Title`, `Status`, `Hours`, `Due Date`, `Code`, which is unique, and `Attachment`. It starts with three records, and each example starts from those again. `projects` is a table named `Job Projects`, with a `Name` field. A live test runs every example with both clients and checks its output. In the async client, [`AsyncTable`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/) has the same methods, which you `await`, but `query()` and `report()` are called without it. Sync: ```python from pyquickbase import Quickbase with Quickbase( realm="acme", token="your_user_token" ) as qb: tasks = qb.app("bq1234567").table("Job Tasks") ``` Async: ```python from pyquickbase import AsyncQuickbase async with AsyncQuickbase( realm="acme", token="your_user_token" ) as qb: tasks = qb.app("bq1234567").table("Job Tasks") ``` ## What the methods return | Type | What it is | Returned by | |---|---|---| | [`AppEvent`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#AppEvent) | Something in an app that runs when records change or on a schedule, such as a webhook. | [`events()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#events) | | [`DeletePreview`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#DeletePreview) | What `delete()` would delete: a page of the matching records, and what may run. | [`preview_delete()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#preview_delete) | | [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) | A field's metadata. | [`fields()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#fields), [`create_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#create_field), [`update_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#update_field), [`ensure_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_field), [`ensure_lookup_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_lookup_field), [`ensure_summary_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_summary_field), [`ensure_count_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_count_field) | | [`FieldUsage`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#FieldUsage) | Where a field is used in its app, as counted on the field's Usage page in Quickbase. | [`field_usage()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#field_usage) | | [`FileDownload`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileDownload) | A file downloaded from a file attachment field. | [`download_file()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#download_file) | | [`FileRows`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileRows) | A file's rows, converted for a table's fields, from `Table.rows_from_file()`. | [`rows_from_file()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#rows_from_file) | | [`FormulaResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FormulaResult) | The result of `table.run_formula()`. | [`run_formula()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#run_formula) | | [`Operation`](https://matthew-williams-solutions.github.io/pyquickbase/reference/operation/) | One operation on a table, for a model's tool call. Create one with `table.operation()`. | [`operation()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#operation) | | [`Page`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Page) | One page of query results. | [`query_page()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#query_page) | | [`Record`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Record) | One record. Read values by field label or ID: `record["Status"]` or `record[7]`. | [`first()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#first), [`get()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#get) | | [`RecordIterator`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#RecordIterator) | Yields records from a query, fetching further pages as needed. | [`query()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#query) | | [`Relationship`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Relationship) | A parent-child link between two tables. | [`relationships()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#relationships), [`create_relationship()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#create_relationship), [`ensure_relationship()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_relationship) | | [`Report`](https://matthew-williams-solutions.github.io/pyquickbase/reference/report/) | A report on a table. Create one with `table.report("Open tasks")`. | [`report()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#report) | | [`ReportInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#ReportInfo) | A report's metadata. | [`reports()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#reports) | | [`SummaryRow`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#SummaryRow) | One group of records, from `table.summarize()`. Values are keyed by field label. | [`summarize()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#summarize) | | [`TableInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#TableInfo) | A table's metadata. | [`info()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#info), [`update_info()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#update_info) | | [`UpdatePreview`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#UpdatePreview) | What `update()` would do if the records stayed as they are now. | [`preview_update()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#preview_update) | | [`WriteResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#WriteResult) | What a write did. If rows fail, `WriteError.result` holds this, including what succeeded. | [`insert()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#insert), [`update()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#update), [`upsert()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#upsert) | ## Read records For the full picture, see [Reading records](https://matthew-williams-solutions.github.io/pyquickbase/guides/reading/). ### `Table.query()` Iterate over matching records, fetching further pages as needed. ```python query( *, where: Expression | str | None = None, select: FieldRefs | None = None, order_by: FieldRefs | None = None, page_size: int = 1000, max_total: int | None = None, ) -> RecordIterator ``` **Parameters** - **`where`** `Expression | str | None`, optional A condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), or a hand-written string, which gets checked. - **`select`** `FieldRefs | None`, optional The fields to return, by label, ID, or field object. Defaults to the table's default fields. Record ID# is always included. - **`order_by`** `FieldRefs | None`, optional The fields to sort by, by label, ID, or field object. Prefix a label with `-`, or negate an ID, to sort descending. Without this, records come back in Record ID# order. - **`page_size`** `int`, default `1000` Records to request per call to Quickbase. - **`max_total`** `int | None`, optional Stop after this many records in all, across pages. **Returns** [`RecordIterator`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#RecordIterator) An iterator of [`Record`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Record), which only contacts Quickbase once you start iterating. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was queried. - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): An argument can't be sent as written. Nothing was queried. `page_size` and `max_total` raise this right away, and the others on first use. Sync example: ```python from pyquickbase import F open_tasks = tasks.query( where=F("Status") == "Open", select=["Title", "Hours"], order_by="-Hours", ) for task in open_tasks: print(task.id, task["Title"], task["Hours"]) ``` Async example: ```python from pyquickbase import F open_tasks = tasks.query( where=F("Status") == "Open", select=["Title", "Hours"], order_by="-Hours", ) async for task in open_tasks: print(task.id, task["Title"], task["Hours"]) ``` Output: ```text 2 Frame walls 12.0 1 Pour slab 8.0 ``` ### `Table.query_page()` Fetch one page of records, for controlling paging yourself. ```python query_page( *, where: Expression | str | None = None, select: FieldRefs | None = None, order_by: FieldRefs | None = None, limit: int = 100, skip: int = 0, ) -> Page ``` **Parameters** - **`where`** `Expression | str | None`, optional A condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), or a hand-written string, which gets checked. - **`select`** `FieldRefs | None`, optional The fields to return, by label, ID, or field object. Defaults to the table's default fields. Record ID# is always included. - **`order_by`** `FieldRefs | None`, optional The fields to sort by, by label, ID, or field object. Prefix a label with `-`, or negate an ID, to sort descending. Without this, records come back in Record ID# order. - **`limit`** `int`, default `100` The most records to return. - **`skip`** `int`, default `0` How many matching records to pass over first. Use the previous page's `next_skip`. **Returns** [`Page`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Page) A [`Page`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Page) with `records`, `total`, `skip`, `has_more`, and `next_skip`. Skipping past the end returns no records rather than raising an error. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was queried. - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): An argument can't be sent as written. Nothing was queried. Sync example: ```python page = tasks.query_page(select=["Title"], limit=2) print(page.total, page.has_more, page.next_skip) for task in page.records: print(task.id, task["Title"]) ``` Async example: ```python page = await tasks.query_page(select=["Title"], limit=2) print(page.total, page.has_more, page.next_skip) for task in page.records: print(task.id, task["Title"]) ``` Output: ```text 3 True 2 1 Pour slab 2 Frame walls ``` ### `Table.first()` Get the first matching record, or None if there isn't one. ```python first( *, where: Expression | str | None = None, select: FieldRefs | None = None, order_by: FieldRefs | None = None, ) -> Record | None ``` **Parameters** - **`where`** `Expression | str | None`, optional A condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), or a hand-written string, which gets checked. - **`select`** `FieldRefs | None`, optional The fields to return, by label, ID, or field object. Defaults to the table's default fields. - **`order_by`** `FieldRefs | None`, optional The fields to sort by, which decide which record is first. See [`query()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#query). **Returns** [`Record`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Record) `| None` The first matching [`Record`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Record), or None if nothing matches. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was queried. - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): An argument can't be sent as written. Nothing was queried. Sync example: ```python from pyquickbase import F task = tasks.first( where=F("Status") == "Open", order_by="Due Date" ) print(task["Title"] if task else "Nothing is open") ``` Async example: ```python from pyquickbase import F task = await tasks.first( where=F("Status") == "Open", order_by="Due Date" ) print(task["Title"] if task else "Nothing is open") ``` Output: ```text Pour slab ``` ### `Table.get()` Get a record by its Record ID#, or None if it doesn't exist. ```python get( record_id: int, *, select: FieldRefs | None = None ) -> Record | None ``` **Parameters** - **`record_id`** `int`, required The record's Record ID#. - **`select`** `FieldRefs | None`, optional The fields to return, by label, ID, or field object. Defaults to the table's default fields. **Returns** [`Record`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Record) `| None` The [`Record`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Record), or None if no record has this ID. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `record_id` isn't a positive integer. Nothing was sent. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was queried. Sync example: ```python task = tasks.get(2, select=["Title", "Status"]) print(task["Title"], task["Status"]) print(tasks.get(999)) ``` Async example: ```python task = await tasks.get(2, select=["Title", "Status"]) print(task["Title"], task["Status"]) print(await tasks.get(999)) ``` Output: ```text Frame walls Open None ``` ### `Table.count()` Count the matching records without fetching them. ```python count(where: Expression | str | None = None) -> int ``` **Parameters** - **`where`** `Expression | str | None`, optional A condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), or a hand-written string, which gets checked. Defaults to every record. **Returns** `int` The number of matching records. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was queried. - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): `where` can't be sent as written. Nothing was queried. Sync example: ```python from pyquickbase import F print(tasks.count()) print(tasks.count(F("Status") == "Open")) ``` Async example: ```python from pyquickbase import F print(await tasks.count()) print(await tasks.count(F("Status") == "Open")) ``` Output: ```text 3 2 ``` ### `Table.compile_where()` Return a condition as the query text that a query of this table would send. Use it to show a person what a query asked for, as Quickbase's own `{6.EX.'Open'}` form. This only contacts Quickbase to look up the table's fields, which the client's cache usually has. ```python compile_where(where: Expression | str) -> str ``` **Parameters** - **`where`** `Expression | str`, required A condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F) or [`where_from_dict`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#where_from_dict), or a hand-written string, which gets checked and returned without outer whitespace. **Returns** `str` The query text. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field, or the client's policy hides it. - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): `where` can't be sent as written. Sync example: ```python from pyquickbase import F where = (F("Status") == "Open") & F("Title").contains("slab") print(tasks.compile_where(where)) ``` Async example: ```python from pyquickbase import F where = (F("Status") == "Open") & F("Title").contains("slab") print(await tasks.compile_where(where)) ``` Output: ```text {7.EX.'Open'}AND{6.CT.'slab'} ``` ## Write records For the full picture, see [Writing records](https://matthew-williams-solutions.github.io/pyquickbase/guides/writing/). ### `Table.insert()` Create records from `{label or ID: value}` dictionaries. It never changes existing ones. So it refuses a table whose key field isn't `Record ID#`, where Quickbase would change the record when a row has an existing value in that field. Use [`upsert()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#upsert) on those tables. ```python insert( records: ( Iterable[Mapping[str | int, Any]] | DataFrame | FileRows ), *, batch_size: int = 1000, timeout: float | None = None, ) -> WriteResult ``` **Parameters** - **`records`** `Iterable[Mapping[str | int, Any]] | DataFrame | FileRows`, required The records to create, as `{label or ID: value}` dictionaries. A value of `None` leaves the field empty, but a row needs at least one real value, because Quickbase can silently skip a row without one. A pandas or polars DataFrame works too, with a column for each field, where a missing value leaves the field empty, and so does a file's rows from [`rows_from_file()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#rows_from_file). - **`batch_size`** `int`, default `1000` The most records to send in one request. - **`timeout`** `float | None`, optional Seconds to allow for each step of each request, instead of the client's `timeout`, such as for a large file. It applies with your own `http_client` too. **Returns** [`WriteResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#WriteResult) A [`WriteResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#WriteResult), whose `row_ids` give each row's new Record ID#. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): A row can't be sent, such as one that sets `Record ID#` or has no real values, or the table's key field isn't `Record ID#`. Nothing was written. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): A row names a field the table doesn't have. Nothing was written. - [`WriteError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#WriteError): A row failed. Other records are still written, and are listed in the error's `result`. Sync example: ```python from datetime import date rows = [ { "Title": "Install windows", "Due Date": date(2027, 3, 1), }, {"Title": "Paint walls", "Status": "Open"}, ] result = tasks.insert(rows) print(result.created_ids) ``` Async example: ```python from datetime import date rows = [ { "Title": "Install windows", "Due Date": date(2027, 3, 1), }, {"Title": "Paint walls", "Status": "Open"}, ] result = await tasks.insert(rows) print(result.created_ids) ``` Output: ```text (4, 5) ``` ### `Table.update()` Change existing records. Each row must set `Record ID#`. A record that doesn't exist is an error rather than being created. ```python update( records: ( Iterable[Mapping[str | int, Any]] | DataFrame | FileRows ), *, batch_size: int = 1000, timeout: float | None = None, ) -> WriteResult ``` **Parameters** - **`records`** `Iterable[Mapping[str | int, Any]] | DataFrame | FileRows`, required The changes, as `{label or ID: value}` dictionaries that each set `Record ID#`. Fields left out of a row keep their values, and a value of `None` clears the field. A pandas or polars DataFrame works too, with a column for each field, where a missing value clears the field, and so does a file's rows from [`rows_from_file()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#rows_from_file). - **`batch_size`** `int`, default `1000` The most records to send in one request. - **`timeout`** `float | None`, optional Seconds to allow for each step of each request, instead of the client's `timeout`, such as for a large file. It applies with your own `http_client` too. **Returns** [`WriteResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#WriteResult) A [`WriteResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#WriteResult). A record whose values didn't change is in `unchanged_ids`, not `updated_ids`. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): A row can't be sent, such as one without `Record ID#`. Nothing was written. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): A row names a field the table doesn't have. Nothing was written. - [`WriteError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#WriteError): A row failed. Other records are still written, and are listed in the error's `result`. Sync example: ```python result = tasks.update( [ {"Record ID#": 1, "Status": "Done"}, {"Record ID#": 2, "Status": "Open"}, # Already Open ] ) print(result.updated_ids, result.unchanged_ids) ``` Async example: ```python result = await tasks.update( [ {"Record ID#": 1, "Status": "Done"}, {"Record ID#": 2, "Status": "Open"}, # Already Open ] ) print(result.updated_ids, result.unchanged_ids) ``` Output: ```text (1,) (2,) ``` ### `Table.upsert()` Update the record whose unique `merge_on` field matches, or create one if none does. ```python upsert( records: ( Iterable[Mapping[str | int, Any]] | DataFrame | FileRows ), *, merge_on: str | int, batch_size: int = 1000, timeout: float | None = None, ) -> WriteResult ``` **Parameters** - **`records`** `Iterable[Mapping[str | int, Any]] | DataFrame | FileRows`, required The records, as `{label or ID: value}` dictionaries that each set `merge_on`. Fields left out of a row keep their values on a record that already exists. A pandas or polars DataFrame works too, with a column for each field, where a missing value leaves the field empty, and so does a file's rows from [`rows_from_file()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#rows_from_file). - **`merge_on`** `str | int`, required The field that identifies a record, by label or ID. Quickbase requires it to be marked as unique. - **`batch_size`** `int`, default `1000` The most records to send in one request. - **`timeout`** `float | None`, optional Seconds to allow for each step of each request, instead of the client's `timeout`, such as for a large file. It applies with your own `http_client` too. **Returns** [`WriteResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#WriteResult) A [`WriteResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#WriteResult), with new records in `created_ids`, changed ones in `updated_ids`, and ones that already matched in `unchanged_ids`. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): A row can't be sent, such as one without `merge_on`. Nothing was written. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): `merge_on` or a row names a field the table doesn't have. Nothing was written. - [`BadRequestError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#BadRequestError): `merge_on` isn't a unique field. Nothing was written. - [`WriteError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#WriteError): A row failed. Other records are still written, and are listed in the error's `result`. Sync example: ```python result = tasks.upsert( [ {"Code": "T-1", "Status": "Done"}, { "Code": "T-9", "Title": "Inspect site", "Status": "Open", }, ], merge_on="Code", ) print(result.updated_ids, result.created_ids) ``` Async example: ```python result = await tasks.upsert( [ {"Code": "T-1", "Status": "Done"}, { "Code": "T-9", "Title": "Inspect site", "Status": "Open", }, ], merge_on="Code", ) print(result.updated_ids, result.created_ids) ``` Output: ```text (1,) (4,) ``` ### `Table.delete()` Delete the matching records and return how many were deleted. ```python delete(where: Expression | str) -> int ``` **Parameters** - **`where`** `Expression | str`, required A condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), or a hand-written string. It's required and is checked first, because Quickbase would otherwise treat a lowercase operator as "contains" and delete far more than you meant. **Returns** `int` The number of records deleted, which is 0 if none matched. **Raises** - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): `where` can't be sent as written. Nothing was deleted. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): `where` names a field the table doesn't have. Nothing was deleted. - [`PolicyRefusedError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#PolicyRefusedError): The client's policy doesn't allow deleting here, or more records match than its `max_records_per_delete`. Nothing was deleted. Sync example: ```python from pyquickbase import F print(tasks.delete(F("Status") == "Done")) ``` Async example: ```python from pyquickbase import F print(await tasks.delete(F("Status") == "Done")) ``` Output: ```text 1 ``` ## Preview writes For the full picture, see [Writing records](https://matthew-williams-solutions.github.io/pyquickbase/guides/writing/). ### `Table.preview_update()` Show what [`update()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#update) would change with the same records, without changing anything. This only reads, so it works on a read-only client. Records can change before an update, so it can't promise what the update will do. It also can't tell whether Quickbase will accept a value, such as a role that can't edit a field, or one of the table's data rules. ```python preview_update( records: ( Iterable[Mapping[str | int, Any]] | DataFrame | FileRows ), ) -> UpdatePreview ``` **Parameters** - **`records`** `Iterable[Mapping[str | int, Any]] | DataFrame | FileRows`, required The records you'd pass to [`update()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#update). If a record is in several of them, a later one wins. `Record ID#` must be a positive integer here, even as a string works in [`update()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#update). A DataFrame or a file's rows work too, as for [`update()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#update). **Returns** [`UpdatePreview`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#UpdatePreview) An [`UpdatePreview`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#UpdatePreview), with `changes`, `unchanged_ids`, `missing_ids`, and the `events` the update may set off. A value counts as a change whenever the comparison isn't certain, so [`update()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#update) may report a listed record as unchanged. A file upload is always a change. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): A row can't be sent, such as one without `Record ID#`. Nothing was read. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): A row names a field the table doesn't have. Nothing was read. Sync example: ```python preview = tasks.preview_update( [ {"Record ID#": 1, "Status": "Done"}, {"Record ID#": 2, "Status": "Open"}, {"Record ID#": 999, "Status": "Done"}, ] ) print( len(preview.changes), preview.unchanged_ids, preview.missing_ids, ) ``` Async example: ```python preview = await tasks.preview_update( [ {"Record ID#": 1, "Status": "Done"}, {"Record ID#": 2, "Status": "Open"}, {"Record ID#": 999, "Status": "Done"}, ] ) print( len(preview.changes), preview.unchanged_ids, preview.missing_ids, ) ``` Output: ```text 1 (2,) (999,) ``` ### `Table.preview_delete()` Show what [`delete()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#delete) would delete with the same `where`, without deleting anything. This only reads, so it works on a read-only client. Records can change before a delete, so it can't promise what the delete will do, and it can't tell whether the token may delete. ```python preview_delete( where: Expression | str, *, select: FieldRefs | None = None, order_by: FieldRefs | None = None, limit: int = 100, ) -> DeletePreview ``` **Parameters** - **`where`** `Expression | str`, required A condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), or a hand-written string, checked as [`delete()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#delete) checks it. It's required, so that leaving it out can't look like a safe preview. - **`select`** `FieldRefs | None`, optional The fields to return. Defaults to the table's default fields. - **`order_by`** `FieldRefs | None`, optional Which matching records come first. See [`query()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#query). - **`limit`** `int`, default `100` The most records to return. `total` still counts every match. **Returns** [`DeletePreview`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#DeletePreview) A [`DeletePreview`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#DeletePreview): a page whose `total` is how many records would be deleted, and whose `events` are what deleting them may set off. **Raises** - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): `where` can't be sent as written, or is None. Nothing was queried. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was queried. Sync example: ```python from pyquickbase import F page = tasks.preview_delete( F("Status") == "Done", select=["Title"] ) print(page.total, [task["Title"] for task in page.records]) ``` Async example: ```python from pyquickbase import F page = await tasks.preview_delete( F("Status") == "Done", select=["Title"] ) print(page.total, [task["Title"] for task in page.records]) ``` Output: ```text 1 ['Hang drywall'] ``` ## Summarize and calculate For the full picture, see [Formulas](https://matthew-williams-solutions.github.io/pyquickbase/guides/formulas/). ### `Table.summarize()` Count, total, and compare records, overall or per group, without a saved report. A summary that only counts, with no `group_by` or statistics, asks Quickbase how many records match, in one request, and reads none. Any other summary reads every matching record, in pages of up to 25,000. Records can change between pages, so on a table in use, the result may not match any one moment. A field's values are added exactly, and returned as `float`. A median keeps every value of its field in memory until the summary ends, so give it `max_records_scanned` on a large table. ```python summarize( group_by: SummaryGroups | None = None, *, where: Expression | str | None = None, sum: FieldRefs | None = None, avg: FieldRefs | None = None, median: FieldRefs | None = None, std_dev: FieldRefs | None = None, min: FieldRefs | None = None, max: FieldRefs | None = None, distinct_count: FieldRefs | None = None, compare_periods: bool = False, max_records_scanned: int | None = None, page_size: int | None = None, ) -> list[SummaryRow] ``` **Parameters** - **`group_by`** `SummaryGroups | None`, optional The fields to group by, and [`Period`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Period) for dates. Without this, the result is one row for all the matching records. - **`where`** `Expression | str | None`, optional A condition built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F) or [`where_from_dict`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#where_from_dict), or a hand-written string. - **`sum`** `FieldRefs | None`, optional Numeric fields to add up. Empty values are skipped. - **`avg`** `FieldRefs | None`, optional Numeric fields to average. Empty values are skipped. - **`median`** `FieldRefs | None`, optional Numeric fields to find the middle value of. Empty values are skipped. - **`std_dev`** `FieldRefs | None`, optional Numeric fields to find the sample standard deviation of. Empty values are skipped. - **`min`** `FieldRefs | None`, optional Numeric, date, timestamp, or time of day fields to find the lowest of. - **`max`** `FieldRefs | None`, optional Numeric, date, timestamp, or time of day fields to find the highest of. - **`distinct_count`** `FieldRefs | None`, optional Fields to count the different values of, such as how many clients have records. Empty values are skipped. - **`compare_periods`** `bool`, default `False` Say how each group changed since the period before, in its `change`. `group_by` must have exactly one [`Period`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Period), and the other fields in it stay the same, so `[Period("Due", "month"), "Owner"]` compares each owner's months. A filter that cuts a period short makes that period look smaller. - **`max_records_scanned`** `int | None`, optional Raise [`TooManyRecordsError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#TooManyRecordsError) if more records match than this, without reading them. A summary that only counts reads no records, so this doesn't limit it. It never returns a partial result. An agent's tool should always set this. A client's policy can set a limit of its own, `max_records_scanned`. - **`page_size`** `int | None`, optional Records to request per call to Quickbase. By default it's set by the fields the summary reads, from 25,000 for one field down to 1,000, so that a page takes about 25 MB of memory however wide it is. **Returns** `list[` [`SummaryRow`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#SummaryRow) `]` A [`SummaryRow`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#SummaryRow) for each group, sorted by the group's values with empty ones last. If nothing matches, that's an empty list, or without `group_by`, one row with a `count` of 0. **Raises** - [`TooManyRecordsError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#TooManyRecordsError): More records match than `max_records_scanned`. No records were read. It's a [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError). - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): An argument can't be used, such as a text field in `sum`. No records were read. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. No records were read. - [`PolicyRefusedError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#PolicyRefusedError): More records match than the client's policy allows summarizing, or it doesn't allow a field. No records were read. - [`InvalidResponseError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidResponseError): A value isn't what its field's type says it should be. Sync example: ```python rows = tasks.summarize("Status", sum="Hours") for row in rows: print(row.group["Status"], row.count, row.sum["Hours"]) ``` Async example: ```python rows = await tasks.summarize("Status", sum="Hours") for row in rows: print(row.group["Status"], row.count, row.sum["Hours"]) ``` Output: ```text Done 1 6.0 Open 2 20.0 ``` ### `Table.run_formula()` Run a formula against this table, without saving it. Don't run formula text you don't control: a formula can read other tables, whichever table you run it on. ```python run_formula( formula: str, *, record_id: int | None = None ) -> FormulaResult ``` **Parameters** - **`formula`** `str`, required The formula's text, in Quickbase's formula language. It can't be blank. - **`record_id`** `int | None`, optional The record whose values the formula reads. It's required if the formula uses fields. **Returns** [`FormulaResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FormulaResult) A [`FormulaResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FormulaResult). Its `value` is the result as a Python value where that's safe, and `text` is exactly what Quickbase sent. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `formula` is blank, or `record_id` isn't a positive integer. Nothing was sent. - [`BadRequestError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#BadRequestError): Quickbase can't run the formula, and its message says why. - [`NotFoundError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#NotFoundError): The record doesn't exist. Sync example: ```python result = tasks.run_formula("[Hours] * 2", record_id=1) print(result.value) ``` Async example: ```python result = await tasks.run_formula("[Hours] * 2", record_id=1) print(result.value) ``` Output: ```text 16 ``` ## Look up the table's details For the full picture, see [Reading records](https://matthew-williams-solutions.github.io/pyquickbase/guides/reading/). ### `Table.info()` Get the table's metadata, from the client's cache of the app's tables. ```python info() -> TableInfo ``` **Returns** [`TableInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#TableInfo) A [`TableInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#TableInfo), with the table's `id` and `name`. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, even after checking again. Sync example: ```python info = tasks.info() print(info.name) ``` Async example: ```python info = await tasks.info() print(info.name) ``` Output: ```text Job Tasks ``` ### `Table.fields()` List the table's fields, from the cache unless `refresh` is set. ```python fields(*, refresh: bool = False) -> list[Field] ``` **Parameters** - **`refresh`** `bool`, default `False` Ask Quickbase instead of using the client's cache, which keeps metadata for five minutes by default. A change made through this client updates the cache, so this is for changes made elsewhere, such as in the Quickbase interface. **Returns** `list[` [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) `]` A [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) for each of the table's fields, including built-in ones like `Record ID#`. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table. Sync example: ```python for field in tasks.fields(): if field.label in ("Title", "Status"): print(field.id, field.label, field.type) ``` Async example: ```python for field in await tasks.fields(): if field.label in ("Title", "Status"): print(field.id, field.label, field.type) ``` Output: ```text 6 Title text 7 Status text-multiple-choice ``` ### `Table.field_usage()` Say where fields are used, such as in reports, forms, formulas, and pipelines. These are the counts on each field's Usage page in Quickbase, which help to judge what deleting or changing a field would break. ```python field_usage( fields: str | int | Sequence[str | int] | None = None, ) -> list[FieldUsage] ``` **Parameters** - **`fields`** `str | int | Sequence[str | int] | None`, optional One field or a list of them, by label or ID. Defaults to every field. **Returns** `list[` [`FieldUsage`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#FieldUsage) `]` A [`FieldUsage`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#FieldUsage) for each field, in the order asked for, or the table's order. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): A field doesn't exist. Nothing was read. - [`PermissionDeniedError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#PermissionDeniedError): The token isn't an administrator's. Sync example: ```python for usage in tasks.field_usage(["Title", "Code"]): print(usage.label, usage.counts["reports"]) ``` Async example: ```python for usage in await tasks.field_usage(["Title", "Code"]): print(usage.label, usage.counts["reports"]) ``` Output: ```text Title 2 Code 2 ``` ### `Table.relationships()` List the relationships in which this table is the child. A parent table lists none. ```python relationships( *, refresh: bool = False ) -> list[Relationship] ``` **Parameters** - **`refresh`** `bool`, default `False` Ask Quickbase instead of using the client's cache, which keeps metadata for five minutes by default. A change made through this client updates the cache, so this is for changes made elsewhere, such as in the Quickbase interface. **Returns** `list[` [`Relationship`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Relationship) `]` A [`Relationship`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Relationship) for each parent this table links to, or an empty list. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table. - [`PermissionDeniedError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#PermissionDeniedError): The token's role can't edit the app's structure. Sync example: ```python for relationship in tasks.relationships(): print(relationship.foreign_key_field_id) ``` Async example: ```python for relationship in await tasks.relationships(): print(relationship.foreign_key_field_id) ``` Output: ```text 12 ``` ### `Table.reports()` List the table's reports, from the cache unless `refresh` is set. ```python reports(*, refresh: bool = False) -> list[ReportInfo] ``` **Parameters** - **`refresh`** `bool`, default `False` Ask Quickbase instead of using the client's cache, which keeps metadata for five minutes by default. A change made through this client updates the cache, so this is for changes made elsewhere, such as in the Quickbase interface. **Returns** `list[` [`ReportInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#ReportInfo) `]` A [`ReportInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#ReportInfo) for each report, including ones Quickbase can't run through the API, whose `incomplete` is true. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table. Sync example: ```python for report in tasks.reports(): print(report.name, report.type) ``` Async example: ```python for report in await tasks.reports(): print(report.name, report.type) ``` Output: ```text List All table List Changes table ``` ### `Table.report()` Get a report on this table by name or ID. It's checked when first used. This doesn't contact Quickbase, so a report that doesn't exist raises [`UnknownReportError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownReportError) on first use. ```python report(name_or_id: str) -> Report ``` **Parameters** - **`name_or_id`** `str`, required The report's exact name, or its ID, which takes priority over a name. **Returns** [`Report`](https://matthew-williams-solutions.github.io/pyquickbase/reference/report/) A [`Report`](https://matthew-williams-solutions.github.io/pyquickbase/reference/report/), for running it or getting its details. Sync example: ```python report = tasks.report("List All") info = report.info() print(info.name) ``` Async example: ```python report = tasks.report("List All") info = await report.info() print(info.name) ``` Output: ```text List All ``` ### `Table.events()` List the webhooks, Quickbase actions, notifications, subscriptions, reminders, and record-triggered automations that watch this table. See [`App.events()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/app/#events). ```python events() -> list[AppEvent] ``` **Returns** `list[` [`AppEvent`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#AppEvent) `]` An [`AppEvent`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#AppEvent) for each, active or not. **Raises** - [`PermissionDeniedError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#PermissionDeniedError): The token isn't an administrator's. Sync example: ```python events = tasks.events() print(len(events)) ``` Async example: ```python events = await tasks.events() print(len(events)) ``` Output: ```text 0 ``` ### `Table.resolve_field_ids()` Turn field labels or IDs into IDs. ```python resolve_field_ids( fields: Sequence[str | int], ) -> list[int] ``` **Parameters** - **`fields`** `Sequence[str | int]`, required The fields, by label or ID, in any mix. **Returns** `list[int]` The field IDs, in the same order as [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields). **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Sync example: ```python print(tasks.resolve_field_ids(["Title", "Status", 3])) ``` Async example: ```python print(await tasks.resolve_field_ids(["Title", "Status", 3])) ``` Output: ```text [6, 7, 3] ``` ## Import a CSV or Excel file For the full picture, see [CSV and Excel files](https://matthew-williams-solutions.github.io/pyquickbase/guides/spreadsheets/). ### `Table.rows_from_file()` Read a CSV or Excel file's rows, converting each value for its field, to write them. The first row names each column's field, by label. Each value is converted by its field's type, from text such as `1,234.5`, `45%` for a percent, `yes`, `2027-01-15`, or a user's email, or from Excel's own dates and numbers. A value that can't be converted is an error with its row and column, and nothing is written while there are any. Quickbase would otherwise empty a number field given text it can't read, without an error. It reads the whole file into memory, and contacts Quickbase only for the table's fields. The async client reads it on a worker thread, so a large file doesn't stop the event loop. ```python rows = tasks.rows_from_file("tasks.csv") for error in rows.errors: print(error) tasks.insert(rows) ``` ```python rows_from_file( source: FileLike, *, columns: ColumnMap | None = None, skip_read_only: bool = False, format: Optional[Literal["csv", "xlsx"]] = None, sheet: str | None = None, tz: datetime.tzinfo = datetime.timezone.utc, date_format: str | None = None, datetime_format: str | None = None, encoding: str = "utf-8-sig", delimiter: str | None = None, max_rows: int | None = None, max_unzipped_bytes: int | None = 1000000000, ) -> FileRows ``` **Parameters** - **`source`** `FileLike`, required A path, or an open file: text for CSV, and binary for Excel. - **`columns`** `ColumnMap | None`, optional Which field each column goes in, by label or ID, or None to skip a column, such as `{"Task": "Title", "Notes": None}`. Other columns go in the field with their exact name. - **`skip_read_only`** `bool`, default `False` Skip columns for fields that can't be written, such as formulas and Date Created, instead of raising an error, as a file from `to_csv()` has them. - **`format`** `Optional[Literal['csv', 'xlsx']]`, optional `csv` or `xlsx`. By default it's `xlsx` for an `.xlsx` or `.xlsm` path or a binary file, and `csv` otherwise. - **`sheet`** `str | None`, optional For Excel, the worksheet's name. Defaults to the first. - **`tz`** `datetime.tzinfo`, default `datetime.timezone.utc` The time zone of a timestamp without one, such as one from Excel. - **`date_format`** `str | None`, optional How dates are written, for `datetime.strptime()`, such as `%m/%d/%Y`. Defaults to ISO 8601, `2027-01-15`. - **`datetime_format`** `str | None`, optional How timestamps are written, as for `date_format`. Defaults to ISO 8601. - **`encoding`** `str`, default `'utf-8-sig'` A CSV path's text encoding. The default also reads UTF-8 that starts with a byte order mark, as Excel writes it. - **`delimiter`** `str | None`, optional What separates a CSV's values. Defaults to a tab for a `.tsv` or `.tab` path, and a comma otherwise. - **`max_rows`** `int | None`, optional The most rows with values to read, or None for no limit. Set this for a file you didn't make, as reading stops at the limit. Blank rows don't count. - **`max_unzipped_bytes`** `int | None`, default `1000000000` The most an Excel file may unpack to, or None for no limit. It's a zip, so a small file can unpack to something huge. **Returns** [`FileRows`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileRows) A [`FileRows`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileRows), with the `rows` to write, each one's row number, and every error. **Raises** - [`FileTooLargeError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#FileTooLargeError): The file is over `max_rows` or `max_unzipped_bytes`. - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): The file can't be read as a table: it has no header row, names a column twice, has a column that isn't a field, or writes a field that can't be written. Fix these before anything else. Sync example: ```python import io text = io.StringIO( "Title,Hours,Due Date\nPour footings,1,2027-03-01\n" ) rows = tasks.rows_from_file(text) print(rows.ok, len(rows), rows.row_numbers) result = tasks.insert(rows) print(len(result.created_ids)) ``` Async example: ```python import io text = io.StringIO( "Title,Hours,Due Date\nPour footings,1,2027-03-01\n" ) rows = await tasks.rows_from_file(text) print(rows.ok, len(rows), rows.row_numbers) result = await tasks.insert(rows) print(len(result.created_ids)) ``` Output: ```text True 1 (2,) 1 ``` ## Work with files For the full picture, see [File attachments](https://matthew-williams-solutions.github.io/pyquickbase/guides/files/). ### `Table.download_file()` Download a file from a record, by default its latest version. Quickbase keeps a limited number of versions, so an older one may be gone. ```python download_file( record_id: int, field: str | int, *, version: int | None = None, timeout: float | None = None, ) -> FileDownload ``` **Parameters** - **`record_id`** `int`, required The Record ID# of the record that holds the file. - **`field`** `str | int`, required The file attachment field, by label or ID. - **`version`** `int | None`, optional The version to download, starting at 1. Defaults to the latest. - **`timeout`** `float | None`, optional Seconds to allow for each step of the request, instead of the client's `timeout`, such as for a large file. It applies with your own `http_client` too. **Returns** [`FileDownload`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileDownload) A [`FileDownload`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileDownload), with the file's `name`, `data`, and `content_type`. Don't trust `name` as a path, or `content_type` as a description of what's in the file. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `record_id` or `version` isn't a positive integer, `timeout` isn't a positive number, or `field` isn't a file attachment field. Nothing was sent. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was sent. - [`NotFoundError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#NotFoundError): The record, version, or file doesn't exist. Sync example: ```python download = tasks.download_file(1, "Attachment") print(download.file_name, download.data) ``` Async example: ```python download = await tasks.download_file(1, "Attachment") print(download.file_name, download.data) ``` Output: ```text spec.txt b'one' ``` ### `Table.delete_file_version()` Delete one version of a file. Deleting the only version leaves the field empty. The record stays, and the other versions keep their numbers. ```python delete_file_version( record_id: int, field: str | int, *, version: int ) -> None ``` **Parameters** - **`record_id`** `int`, required The Record ID# of the record that holds the file. - **`field`** `str | int`, required The file attachment field, by label or ID. - **`version`** `int`, required The version to delete, starting at 1. It's required, because Quickbase treats version 0 as the latest and deletes it. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `record_id` or `version` isn't a positive integer, or `field` isn't a file attachment field. Nothing was sent. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was sent. - [`NotFoundError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#NotFoundError): The record or version doesn't exist. Sync example: ```python tasks.delete_file_version(1, "Attachment", version=1) ``` Async example: ```python await tasks.delete_file_version(1, "Attachment", version=1) ``` ## Offer an operation to a model For the full picture, see [Using pyquickbase from an AI agent](https://matthew-williams-solutions.github.io/pyquickbase/agents/). ### `Table.operation()` Make one operation on this table into a tool for a model, with its JSON Schema. The schema describes the arguments a model may send, and `run()` checks them against the same lists before calling the table, so a model can't use anything it wasn't offered. The lists hold only what the client's policy allows, narrowed by [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields). ```python search = tasks.operation( "query", fields=["Title", "Status"] ) tool = { "name": "search_tasks", "input_schema": search.schema, } scope = F("Project") == project page = search.run(arguments, scope=scope) ``` See [Using pyquickbase from an AI agent](https://matthew-williams-solutions.github.io/pyquickbase/agents/operations/#giving-a-model-operations) for what each operation takes. ```python operation( name: OperationName, *, fields: Sequence[str | int] | None = None, field_descriptions: Mapping[str | int, str] | None = None, reports: Sequence[str] | None = None, report_descriptions: Mapping[str, str] | None = None, formula: str | None = None, max_records_per_call: int | None = None, max_records_per_write: int = 100, scope: Expression | None = None, require_preview: bool = False, max_default_fields: int | None = None, ) -> Operation ``` **Parameters** - **`name`** `OperationName`, required What the operation does, such as `query` or `insert`. Each runs the method of the same name, and `run_report` and `run_summary_report` run a report. - **`fields`** `Sequence[str | int] | None`, optional The only fields it may use, by label or ID. Defaults to every field the client may use for the operation. - **`field_descriptions`** `Mapping[str | int, str] | None`, optional What fields mean, as `{label or ID: text}`, which the schema shows the model. Each must be for a field the operation offers. - **`reports`** `Sequence[str] | None`, optional The reports `run_report` or `run_summary_report` may run, by ID. Defaults to every report of that kind the client may run. - **`report_descriptions`** `Mapping[str, str] | None`, optional What reports are for, as `{ID: text}`, which the schema shows the model. Each must be for a report the operation offers. - **`formula`** `str | None`, optional For `run_formula`, the formula's text, which your code fixes. The model only chooses the record, as a formula can read any table. - **`max_records_per_call`** `int | None`, optional The most records one call may return, or for `delete`, may match. A delete that matches more is refused. Give `preview_delete` the same number, and its result says when a delete would be refused. Defaults to 100, or for `summarize`, 10,000 records read. The client's policy can lower it, and its `max_paging_depth` also limits how far `skip` can page. - **`max_records_per_write`** `int`, default `100` The most records one `insert` or `update` may send. - **`scope`** `Expression | None`, optional A condition your code fixes, such as the user's project, that every record the operation reaches must match, as `run()`'s `scope` does. Given here, the schema also tells the model what it is, leaves its fields out of what a write may set, and refuses a write that sets one with a message that says why. An `insert` needs a scope of only `==` conditions joined with `&`, whose fields the client's policy lets it set. A report or formula can't take one. - **`require_preview`** `bool`, default `False` For `update` and `delete`: run only with the `confirmation` that `preview_update` or `preview_delete` returns for exactly the same arguments, within 15 minutes, so a model can't skip the preview. A delete is also refused if how many records match has changed since. Make the preview with the same client and `scope`. - **`max_default_fields`** `int | None`, optional For `query`: the most of the table's default fields to return when the model doesn't name any in `select`. A table with more returns its first ones, and [`tool_result`](https://matthew-williams-solutions.github.io/pyquickbase/reference/tools/#tool_result) says how many it left out. Defaults to every one. **Returns** [`Operation`](https://matthew-williams-solutions.github.io/pyquickbase/reference/operation/) An [`Operation`](https://matthew-williams-solutions.github.io/pyquickbase/reference/operation/), with its `schema` and `run()`. **Raises** - [`PolicyRefusedError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#PolicyRefusedError): The client's policy doesn't allow this operation at all. - [`ConfigurationError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#ConfigurationError): The table has nothing for it, such as no file fields, or `formula` is missing or given for another operation. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) or `field_descriptions` names a field the client can't see, or one the operation doesn't offer. - [`UnknownReportError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownReportError): `reports` has an ID this table doesn't have, or `report_descriptions` has one the operation doesn't offer. Sync example: ```python count = tasks.operation("count", fields=["Status"]) arguments = { "where": {"field": "Status", "op": "==", "value": "Open"} } print(count.run(arguments)) ``` Async example: ```python count = await tasks.operation("count", fields=["Status"]) arguments = { "where": {"field": "Status", "op": "==", "value": "Open"} } print(await count.run(arguments)) ``` Output: ```text 2 ``` ## Link to the Quickbase interface For the full picture, see [Using pyquickbase from an AI agent](https://matthew-williams-solutions.github.io/pyquickbase/agents/). ### `Table.url()` Link to the table's home page in Quickbase. This only contacts Quickbase to look up the table's ID, as any method does, and the client's cache usually has it. ```python url() -> str ``` **Returns** `str` The URL, on the client's realm. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python print(tasks.url()) ``` Async example: ```python print(await tasks.url()) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/td ``` ### `Table.record_url()` Link to a record's page in Quickbase, for viewing it or editing it. This doesn't check that the record exists, so a link to one that doesn't opens an error page. It only contacts Quickbase to look up the table's ID, as [`url()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#url) does. ```python record_url( record_id: int, *, edit: bool = False ) -> str ``` **Parameters** - **`record_id`** `int`, required The record's Record ID#. - **`edit`** `bool`, default `False` Link to the page that edits the record, rather than the one that shows it. **Returns** `str` The URL, on the client's realm. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `record_id` isn't a positive integer. - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python print(tasks.record_url(1)) print(tasks.record_url(1, edit=True)) ``` Async example: ```python print(await tasks.record_url(1)) print(await tasks.record_url(1, edit=True)) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/dr?rid=1 https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/er?rid=1 ``` ### `Table.record_url_template()` Link to any record's page in Quickbase, with `{record_id}` where its ID goes. A tool can send this once, rather than a link for each record, and each record's `Record ID#` fills it in. The placeholder is text, not part of a working URL, so replace it with `template.replace("{record_id}", str(record_id))`. It only contacts Quickbase to look up the table's ID, as [`url()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#url) does. ```python record_url_template(*, edit: bool = False) -> str ``` **Parameters** - **`edit`** `bool`, default `False` Link to the page that edits a record, rather than the one that shows it. **Returns** `str` The URL, on the client's realm, with `{record_id}` for the record's ID. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python template = tasks.record_url_template() print(template) print(template.replace("{record_id}", "2")) ``` Async example: ```python template = await tasks.record_url_template() print(template) print(template.replace("{record_id}", "2")) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/dr?rid={record_id} https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/dr?rid=2 ``` ### `Table.new_record_url()` Link to the page in Quickbase that adds a record to the table. It only contacts Quickbase to look up the table's ID, as [`url()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#url) does. ```python new_record_url() -> str ``` **Returns** `str` The URL, on the client's realm. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python print(tasks.new_record_url()) ``` Async example: ```python print(await tasks.new_record_url()) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/nwr ``` ### `Table.field_url()` Link to the page in Quickbase that edits a field's settings. It only contacts Quickbase to look up the table's and field's IDs, as any method does, and the client's cache usually has them. ```python field_url(field: str | int) -> str ``` **Parameters** - **`field`** `str | int`, required The field, by label or ID. **Returns** `str` The URL, on the client's realm. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field, or the client's policy hides it. - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python print(tasks.field_url("Status")) ``` Async example: ```python print(await tasks.field_url("Status")) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/mf?fid=7 ``` ### `Table.fields_url()` Link to the table's list of fields in Quickbase. It only contacts Quickbase to look up the table's ID, as [`url()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#url) does. ```python fields_url() -> str ``` **Returns** `str` The URL, on the client's realm. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python print(tasks.fields_url()) ``` Async example: ```python print(await tasks.fields_url()) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/listfields ``` ### `Table.relationships_url()` Link to the table's relationships in Quickbase. It only contacts Quickbase to look up the table's ID, as [`url()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#url) does. ```python relationships_url() -> str ``` **Returns** `str` The URL, on the client's realm. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python print(tasks.relationships_url()) ``` Async example: ```python print(await tasks.relationships_url()) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/Relationships ``` ### `Table.settings_url()` Link to the table's settings in Quickbase, such as its name and key field. It only contacts Quickbase to look up the table's ID, as [`url()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#url) does. ```python settings_url() -> str ``` **Returns** `str` The URL, on the client's realm. **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python print(tasks.settings_url()) ``` Async example: ```python print(await tasks.settings_url()) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/KeyProps ``` ### `Table.form_url()` Link to the page in Quickbase that edits one of the table's forms. The API can't list forms, so this takes the form's ID, the `dfid` in its address in Quickbase, and doesn't check that the form exists. It only contacts Quickbase to look up the table's ID, as [`url()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#url) does. ```python form_url(form_id: int) -> str ``` **Parameters** - **`form_id`** `int`, required The form's ID. **Returns** `str` The URL, on the client's realm. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `form_id` isn't a positive integer. - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or the client's policy hides it. Sync example: ```python # A form's ID is the dfid in its address in Quickbase. print(tasks.form_url(10)) ``` Async example: ```python # A form's ID is the dfid in its address in Quickbase. print(await tasks.form_url(10)) ``` Output: ```text https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/DFormProps?dfid=10 ``` ## Change the schema For the full picture, see [Changing the schema](https://matthew-williams-solutions.github.io/pyquickbase/guides/schema/). ### `Table.create_field()` Create a field and return it. Quickbase sets `required` and `unique` in a second request, so if that one fails the field exists without them. Use [`ensure_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_field) to repair it. ```python create_field( label: str, type: str, *, choices: Sequence[str] | None = None, required: bool | None = None, unique: bool | None = None, properties: Mapping[str, Any] | None = None, formula: str | None = None, help_text: str | None = None, bold: bool | None = None, no_wrap: bool | None = None, find_enabled: bool | None = None, appears_by_default: bool | None = None, add_to_forms: bool | None = None, ) -> Field ``` **Parameters** - **`label`** `str`, required The new field's label. Another field can't have the same label, ignoring case. - **`type`** `str`, required One of `text`, `text-multiple-choice`, `text-multi-line`, `rich-text`, `numeric`, `currency`, `rating`, `percent`, `multitext`, `email`, `url`, `duration`, `date`, `datetime`, `timestamp`, `timeofday`, `checkbox`, `user`, `multiuser`, `address`, `phone`, or `file`. A `datetime` field reports its type as `timestamp`. - **`choices`** `Sequence[str] | None`, optional The options of a `text-multiple-choice` or `multitext` field. Quickbase still accepts other values unless `properties` sets `allowNewChoices` to False. - **`required`** `bool | None`, optional Whether the field must have a value. Quickbase refuses this if the table has records, because the new field is empty on all of them. - **`unique`** `bool | None`, optional Whether values must be different on every record. - **`properties`** `Mapping[str, Any] | None`, optional Other settings, sent as they are, such as `{"decimalPlaces": 2}`. Quickbase refuses a key it doesn't know. - **`formula`** `str | None`, optional Makes this a formula field. `type` must match the formula's result, and can't be `timestamp` or `text-multi-line`. Use `datetime` for a timestamp formula. - **`help_text`** `str | None`, optional The help Quickbase shows beside the field on forms. - **`bold`** `bool | None`, optional Whether forms and reports show the field's values in bold. - **`no_wrap`** `bool | None`, optional Whether forms and reports keep the field's values on one line. - **`find_enabled`** `bool | None`, optional Whether the field's values are searched by Quickbase's search box. - **`appears_by_default`** `bool | None`, optional Whether a query or report without chosen columns returns the field. - **`add_to_forms`** `bool | None`, optional Whether Quickbase adds the field to the table's forms. Quickbase doesn't report this afterward. **Returns** [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) The new [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field). **Raises** - [`BadRequestError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#BadRequestError): Quickbase refused the field, such as for an unknown type, a label that's already used, or `choices` on a type without them. If it refused `required` or `unique`, the field was still created, and the message says so. - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `formula` is blank, or a setting has the wrong type. Nothing was sent. Sync example: ```python field = tasks.create_field( "Priority", "text-multiple-choice", choices=["Low", "Medium", "High"], ) print(field.label, field.type) ``` Async example: ```python field = await tasks.create_field( "Priority", "text-multiple-choice", choices=["Low", "Medium", "High"], ) print(field.label, field.type) ``` Output: ```text Priority text-multiple-choice ``` ### `Table.update_field()` Change a field's settings and return it. Anything left as None stays as it is. A field's type can't be changed here. ```python update_field( field: str | int, *, label: str | None = None, required: bool | None = None, unique: bool | None = None, properties: Mapping[str, Any] | None = None, formula: str | None = None, help_text: str | None = None, bold: bool | None = None, no_wrap: bool | None = None, find_enabled: bool | None = None, appears_by_default: bool | None = None, ) -> Field ``` **Parameters** - **`field`** `str | int`, required The field's label or ID. - **`label`** `str | None`, optional A new label. - **`required`** `bool | None`, optional Whether the field must have a value. Quickbase refuses `True` if any record has this field empty. - **`unique`** `bool | None`, optional Whether values must be different on every record. Quickbase refuses `True` if the field already holds duplicates. - **`properties`** `Mapping[str, Any] | None`, optional Other settings, sent as they are. `{"choices": [...]}` replaces the whole list of choices. - **`formula`** `str | None`, optional New formula text, only for a formula field. It can't be blank, because Quickbase would accept that and empty the field's values. - **`help_text`** `str | None`, optional The help Quickbase shows beside the field on forms. - **`bold`** `bool | None`, optional Whether forms and reports show the field's values in bold. - **`no_wrap`** `bool | None`, optional Whether forms and reports keep the field's values on one line. - **`find_enabled`** `bool | None`, optional Whether the field's values are searched by Quickbase's search box. - **`appears_by_default`** `bool | None`, optional Whether a query or report without chosen columns returns the field. **Returns** [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) The [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field), with its new settings. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The table has no such field. Nothing was sent. - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): There's nothing to change, `formula` is blank, or a setting has the wrong type. Nothing was sent. - [`BadRequestError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#BadRequestError): Quickbase refused the change, and the field is as it was. Sync example: ```python field = tasks.update_field( "Hours", label="Estimated Hours" ) print(field.label) ``` Async example: ```python field = await tasks.update_field( "Hours", label="Estimated Hours" ) print(field.label) ``` Output: ```text Estimated Hours ``` ### `Table.delete_fields()` Delete fields and all their data. ```python delete_fields( fields: str | int | Sequence[str | int], ) -> None ``` **Parameters** - **`fields`** `str | int | Sequence[str | int]`, required One field or a list of them, by label or ID. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): A field doesn't exist. Nothing was deleted. - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): The list is empty. Nothing was sent. - [`WriteError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#WriteError): A field couldn't be deleted, such as a built-in field. The others in the list were still deleted. Sync example: ```python tasks.delete_fields("Code") ``` Async example: ```python await tasks.delete_fields("Code") ``` ### `Table.ensure_field()` Return the field with this label, creating it or changing it to match what you pass. This makes a setup script safe to run again, and repairs a field whose `required` or `unique` didn't get set, because Quickbase sets those in a second request. The arguments are the same as for [`create_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#create_field), and anything left as None is ignored on an existing field. `choices` are compared ignoring order. ```python ensure_field( label: str, type: str, *, choices: Sequence[str] | None = None, required: bool | None = None, unique: bool | None = None, properties: Mapping[str, Any] | None = None, formula: str | None = None, help_text: str | None = None, bold: bool | None = None, no_wrap: bool | None = None, find_enabled: bool | None = None, appears_by_default: bool | None = None, ) -> Field ``` **Parameters** - **`label`** `str`, required The field's label, ignoring case. - **`type`** `str`, required The field's type. `datetime` matches a field that reports `timestamp`. - **`choices`** `Sequence[str] | None`, optional The options of a `text-multiple-choice` or `multitext` field. - **`required`** `bool | None`, optional Whether the field must have a value. Quickbase refuses `True` if any record has this field empty, which includes a new field on a table with records. - **`unique`** `bool | None`, optional Whether values must be different on every record. - **`properties`** `Mapping[str, Any] | None`, optional Other settings, sent as they are. - **`formula`** `str | None`, optional The text of a formula field. - **`help_text`** `str | None`, optional The help Quickbase shows beside the field on forms. - **`bold`** `bool | None`, optional Whether forms and reports show the field's values in bold. - **`no_wrap`** `bool | None`, optional Whether forms and reports keep the field's values on one line. - **`find_enabled`** `bool | None`, optional Whether the field's values are searched by Quickbase's search box. - **`appears_by_default`** `bool | None`, optional Whether a query or report without chosen columns returns the field. **Returns** [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) The [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field), whether it was found, created, or changed. **Raises** - [`SchemaMismatchError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#SchemaMismatchError): The field exists with a different type, because Quickbase can't always convert one. It wasn't changed. - [`BadRequestError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#BadRequestError): Quickbase refused the field or the change. Sync example: ```python field = tasks.ensure_field("Priority", "text") again = tasks.ensure_field("Priority", "text") print(field.id == again.id) ``` Async example: ```python field = await tasks.ensure_field("Priority", "text") again = await tasks.ensure_field("Priority", "text") print(field.id == again.id) ``` Output: ```text True ``` ### `Table.create_relationship()` Make this table a child of `parent`, with a new foreign key field on this table. Quickbase also adds two fields to the parent: a list of the child's records, and a link to add one. Calling this again makes another relationship, even with the same label, so use [`ensure_relationship()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_relationship) in a script that may run twice. ```python create_relationship( parent: Table, *, foreign_key: str, lookup_fields: Sequence[str | int] = (), ) -> Relationship ``` **Parameters** - **`parent`** `Table`, required The [`Table`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/) to link to, which becomes the parent. - **`foreign_key`** `str`, required The label of the new field on this table, which holds the Record ID# of each record's parent. - **`lookup_fields`** `Sequence[str | int]`, default `()` Fields of the parent to show on this table, by label or ID. Quickbase names each new field after the foreign key and the field, such as `Project - Name`. **Returns** [`Relationship`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Relationship) The [`Relationship`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Relationship). Its `lookup_field_ids` are the new fields on this table, so don't pass them back as `lookup_fields`. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The parent has no such field. Nothing was sent. Sync example: ```python relationship = tasks.create_relationship( projects, foreign_key="Project", lookup_fields=["Name"], ) print(relationship.foreign_key_field_id) ``` Async example: ```python relationship = await tasks.create_relationship( projects, foreign_key="Project", lookup_fields=["Name"], ) print(relationship.foreign_key_field_id) ``` Output: ```text 12 ``` ### `Table.ensure_relationship()` Return this table's relationship with `parent`, creating it if it doesn't exist. Use the result with [`ensure_lookup_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_lookup_field) and [`ensure_summary_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_summary_field). ```python ensure_relationship( parent: Table, *, foreign_key: str ) -> Relationship ``` **Parameters** - **`parent`** `Table`, required The [`Table`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/) to link to, which becomes the parent. - **`foreign_key`** `str`, required The label of the field on this table that holds the Record ID# of each record's parent, ignoring case when looking for an existing relationship. **Returns** [`Relationship`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Relationship) The existing or new [`Relationship`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Relationship). **Raises** - [`UnknownTableError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownTableError): This app has no such table, or no such parent. - [`BadRequestError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#BadRequestError): Quickbase refused to create the relationship. - [`PolicyRefusedError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#PolicyRefusedError): The client's policy doesn't allow it. Nothing was created. Sync example: ```python relationship = tasks.ensure_relationship( projects, foreign_key="Project" ) again = tasks.ensure_relationship( projects, foreign_key="Project" ) print(relationship.id == again.id) ``` Async example: ```python relationship = await tasks.ensure_relationship( projects, foreign_key="Project" ) again = await tasks.ensure_relationship( projects, foreign_key="Project" ) print(relationship.id == again.id) ``` Output: ```text True ``` ### `Table.ensure_lookup_field()` Return this table's lookup of `parent_field`, creating it if it doesn't exist. Call this on the child table, and the new field is on the child. It's found by what it points at, so renaming it doesn't make a script create another. ```python ensure_lookup_field( relationship: Relationship, parent_field: str | int, *, label: str | None = None, ) -> Field ``` **Parameters** - **`relationship`** `Relationship`, required The relationship, from [`ensure_relationship()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_relationship). - **`parent_field`** `str | int`, required The parent's field to look up, by label or ID. - **`label`** `str | None`, optional A label for the lookup, since Quickbase names one after the foreign key and the field, such as `Project - Name`. An existing lookup is renamed to match. **Returns** [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) The lookup [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field), on this table. **Raises** - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): The parent has no such field. Nothing was sent. Sync example: ```python relationship = tasks.ensure_relationship( projects, foreign_key="Project" ) field = tasks.ensure_lookup_field( relationship, "Name", label="Project Name" ) print(field.label) ``` Async example: ```python relationship = await tasks.ensure_relationship( projects, foreign_key="Project" ) field = await tasks.ensure_lookup_field( relationship, "Name", label="Project Name" ) print(field.label) ``` Output: ```text Project Name ``` ### `Table.ensure_summary_field()` Return the summary of this table's `child_field` on the parent, creating it if needed. Call this on the child table, and the new field is on the parent. **A summary with no child records is empty, not 0**, so wrap it in `Nz()` in a formula if your code expects a number. To count the child records, use [`ensure_count_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_count_field). ```python ensure_summary_field( relationship: Relationship, child_field: str | int, label: str, *, function: str = "sum", where: Expression | str | None = None, ) -> Field ``` **Parameters** - **`relationship`** `Relationship`, required The relationship, from [`ensure_relationship()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_relationship). - **`child_field`** `str | int`, required This table's field to summarize, by label or ID. - **`label`** `str`, required The label of the summary field on the parent. - **`function`** `str`, default `'sum'` The calculation: `sum`, `avg`, `max`, `min`, `std_dev`, `distinct_count`, `combined_text`, or `combined_user`. Quickbase's names, such as `STD-DEV`, work too. - **`where`** `Expression | str | None`, optional Only combine the child records that match, as a condition on this table built with [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F), or a string such as `{7.EX.true}`. It's checked first, as Quickbase saves one it can't use and the summary is then always empty. **Returns** [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) The summary [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field), on the parent. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `function` isn't one of these, or is `count`, which takes no field. Nothing was sent. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): This table has no such field. Nothing was sent. - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): `where` can't be sent as written. Nothing was sent. - [`SchemaMismatchError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#SchemaMismatchError): The label is already used on the parent by anything but this summary. It wasn't changed. - [`BadRequestError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#BadRequestError): Quickbase can't apply `function` to this field, such as `sum` of a checkbox. Sync example: ```python relationship = tasks.ensure_relationship( projects, foreign_key="Project" ) field = tasks.ensure_summary_field( relationship, "Hours", "Total Hours" ) print(field.label) ``` Async example: ```python relationship = await tasks.ensure_relationship( projects, foreign_key="Project" ) field = await tasks.ensure_summary_field( relationship, "Hours", "Total Hours" ) print(field.label) ``` Output: ```text Total Hours ``` ### `Table.ensure_count_field()` Return the count of this table's records for each parent, creating it if needed. Call this on the child table, and the new field is on the parent. Unlike other summaries, a parent with no child records counts 0, not empty. ```python ensure_count_field( relationship: Relationship, label: str, *, where: Expression | str | None = None, ) -> Field ``` **Parameters** - **`relationship`** `Relationship`, required The relationship, from [`ensure_relationship()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_relationship). - **`label`** `str`, required The label of the count field on the parent. - **`where`** `Expression | str | None`, optional Only count the records that match, as in [`ensure_summary_field()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#ensure_summary_field). **Returns** [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field) The count [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field), on the parent. **Raises** - [`SchemaMismatchError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#SchemaMismatchError): The label is already used on the parent by anything but this count. It wasn't changed. - [`UnknownFieldError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#UnknownFieldError): `where` uses a field this table doesn't have. Nothing was sent. - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): `where` can't be sent as written. Nothing was sent. Sync example: ```python relationship = tasks.ensure_relationship( projects, foreign_key="Project" ) field = tasks.ensure_count_field( relationship, "Task Count" ) print(field.label, field.summary_function) ``` Async example: ```python relationship = await tasks.ensure_relationship( projects, foreign_key="Project" ) field = await tasks.ensure_count_field( relationship, "Task Count" ) print(field.label, field.summary_function) ``` Output: ```text Task Count COUNT ``` ### `Table.delete_relationship()` Delete a relationship in which this table is the child. Quickbase also deletes its lookup fields on this table, and its summary fields and the fields that list and add child records on the parent. The foreign key field stays, with its values, as a plain number field. ```python delete_relationship( relationship: Relationship, ) -> None ``` **Parameters** - **`relationship`** `Relationship`, required The relationship, from [`relationships()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#relationships). **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): This table isn't the relationship's child. Nothing was sent. - [`NotFoundError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#NotFoundError): The relationship doesn't exist. Sync example: ```python relationship = (tasks.relationships())[0] tasks.delete_relationship(relationship) print(tasks.relationships()) ``` Async example: ```python relationship = (await tasks.relationships())[0] await tasks.delete_relationship(relationship) print(await tasks.relationships()) ``` Output: ```text [] ``` ### `Table.update_info()` Change the table's name or descriptions, and return its new details. Anything left as None stays as it is. This [`Table`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/) keeps working after a rename, even if it was found by its old name. ```python update_info( *, name: str | None = None, description: str | None = None, single_record_name: str | None = None, plural_record_name: str | None = None, ) -> TableInfo ``` **Parameters** - **`name`** `str | None`, optional A new name. Another table in the app can't have it. - **`description`** `str | None`, optional A new description. - **`single_record_name`** `str | None`, optional What the table calls one record, such as `Project`. - **`plural_record_name`** `str | None`, optional What the table calls several records, such as `Projects`. **Returns** [`TableInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#TableInfo) The table's [`TableInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#TableInfo), with its new settings. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): There's nothing to change. Nothing was sent. - [`BadRequestError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#BadRequestError): Quickbase refused the change, such as for a name another table has. Sync example: ```python info = tasks.update_info( description="Work on each job, one record per task.", ) print(info.description) ``` Async example: ```python info = await tasks.update_info( description="Work on each job, one record per task.", ) print(info.description) ``` Output: ```text Work on each job, one record per task. ``` ### `Table.drop()` Delete this table and all its records. ```python drop(*, confirm: str) -> None ``` **Parameters** - **`confirm`** `str`, required The table's exact name, so a wrong variable can't drop the wrong table. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `confirm` isn't the table's exact name. Nothing was sent. Sync example: ```python tasks.drop(confirm="Job Tasks") ``` Async example: ```python await tasks.drop(confirm="Job Tasks") ```