Skip to content

Markdown

Table

A Table reads and writes one table's records and changes its fields. Get one from App.table(), by name or ID. Getting one doesn't contact Quickbase, so a table that doesn't exist raises 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 has the same methods, which you await, but query() and report() are called without it.

from pyquickbase import Quickbase

with Quickbase(
    realm="acme", token="your_user_token"
) as qb:
    tasks = qb.app("bq1234567").table("Job Tasks")
from pyquickbase import AsyncQuickbase

async with AsyncQuickbase(
    realm="acme", token="your_user_token"
) as qb:
    tasks = qb.app("bq1234567").table("Job Tasks")

Read records

For the full picture, see Reading records.

Query records

Iterate over matching records, fetching further pages as needed.

Parameters

  • where Expression | str | None, optional
    A condition built with 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.
2 more: page_size, max_total
  • 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

An iterator of Record, which only contacts Quickbase once you start iterating.

Raises

  • UnknownFieldError: The table has no such field. Nothing was queried.
  • 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.
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

Example

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"])
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

2 Frame walls 12.0
1 Pour slab 8.0

Fetch one page of records

Fetch one page of records, for controlling paging yourself.

Parameters

  • where Expression | str | None, optional
    A condition built with 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.
2 more: limit, skip
  • 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

A Page with records, total, skip, has_more, and next_skip. Skipping past the end returns no records rather than raising an error.

Raises

  • UnknownFieldError: The table has no such field. Nothing was queried.
  • QueryError: An argument can't be sent as written. Nothing was queried.
query_page(
    *,
    where: Expression | str | None = None,
    select: FieldRefs | None = None,
    order_by: FieldRefs | None = None,
    limit: int = 100,
    skip: int = 0,
) -> Page

Example

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"])
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

3 True 2
1 Pour slab
2 Frame walls

Get the first matching record

Get the first matching record, or None if there isn't one.

Parameters

  • where Expression | str | None, optional
    A condition built with 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().

Returns Record | None

The first matching Record, or None if nothing matches.

Raises

  • UnknownFieldError: The table has no such field. Nothing was queried.
  • QueryError: An argument can't be sent as written. Nothing was queried.
first(
    *,
    where: Expression | str | None = None,
    select: FieldRefs | None = None,
    order_by: FieldRefs | None = None,
) -> Record | None

Example

from pyquickbase import F

task = tasks.first(
    where=F("Status") == "Open", order_by="Due Date"
)
print(task["Title"] if task else "Nothing is open")
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

Pour slab

Get a record by ID

Get a record by its Record ID#, or None if it doesn't exist.

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 | None

The Record, or None if no record has this ID.

Raises

get(
    record_id: int, *, select: FieldRefs | None = None
) -> Record | None

Example

task = tasks.get(2, select=["Title", "Status"])
print(task["Title"], task["Status"])
print(tasks.get(999))
task = await tasks.get(2, select=["Title", "Status"])
print(task["Title"], task["Status"])
print(await tasks.get(999))

Output

Frame walls Open
None

Count records

Count the matching records without fetching them.

Parameters

  • where Expression | str | None, optional
    A condition built with F, or a hand-written string, which gets checked. Defaults to every record.

Returns int

The number of matching records.

Raises

  • UnknownFieldError: The table has no such field. Nothing was queried.
  • QueryError: where can't be sent as written. Nothing was queried.
count(where: Expression | str | None = None) -> int

Example

from pyquickbase import F

print(tasks.count())
print(tasks.count(F("Status") == "Open"))
from pyquickbase import F

print(await tasks.count())
print(await tasks.count(F("Status") == "Open"))

Output

3
2

Show a condition's query text

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.

Parameters

  • where Expression | str, required
    A condition built with F or where_from_dict, or a hand-written string, which gets checked and returned without outer whitespace.

Returns str

The query text.

Raises

compile_where(where: Expression | str) -> str

Example

from pyquickbase import F

where = (F("Status") == "Open") & F("Title").contains("slab")
print(tasks.compile_where(where))
from pyquickbase import F

where = (F("Status") == "Open") & F("Title").contains("slab")
print(await tasks.compile_where(where))

Output

{7.EX.'Open'}AND{6.CT.'slab'}

Write records

For the full picture, see Writing records.

Create records

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() on those tables.

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().
  • 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

A WriteResult, whose row_ids give each row's new Record ID#.

Raises

  • 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: A row names a field the table doesn't have. Nothing was written.
  • WriteError: A row failed. Other records are still written, and are listed in the error's result.
insert(
    records: (
        Iterable[Mapping[str | int, Any]]
        | DataFrame
        | FileRows
    ),
    *,
    batch_size: int = 1000,
    timeout: float | None = None,
) -> WriteResult

Example

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)
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

(4, 5)

Update records

Change existing records. Each row must set Record ID#.

A record that doesn't exist is an error rather than being created.

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().
  • 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

A WriteResult. A record whose values didn't change is in unchanged_ids, not updated_ids.

Raises

  • InvalidValueError: A row can't be sent, such as one without Record ID#. Nothing was written.
  • UnknownFieldError: A row names a field the table doesn't have. Nothing was written.
  • WriteError: A row failed. Other records are still written, and are listed in the error's result.
update(
    records: (
        Iterable[Mapping[str | int, Any]]
        | DataFrame
        | FileRows
    ),
    *,
    batch_size: int = 1000,
    timeout: float | None = None,
) -> WriteResult

Example

result = tasks.update(
    [
        {"Record ID#": 1, "Status": "Done"},
        {"Record ID#": 2, "Status": "Open"},  # Already Open
    ]
)
print(result.updated_ids, result.unchanged_ids)
result = await tasks.update(
    [
        {"Record ID#": 1, "Status": "Done"},
        {"Record ID#": 2, "Status": "Open"},  # Already Open
    ]
)
print(result.updated_ids, result.unchanged_ids)

Output

(1,) (2,)

Create or update records

Update the record whose unique merge_on field matches, or create one if none does.

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().
  • 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

A WriteResult, with new records in created_ids, changed ones in updated_ids, and ones that already matched in unchanged_ids.

Raises

  • InvalidValueError: A row can't be sent, such as one without merge_on. Nothing was written.
  • UnknownFieldError: merge_on or a row names a field the table doesn't have. Nothing was written.
  • BadRequestError: merge_on isn't a unique field. Nothing was written.
  • WriteError: A row failed. Other records are still written, and are listed in the error's result.
upsert(
    records: (
        Iterable[Mapping[str | int, Any]]
        | DataFrame
        | FileRows
    ),
    *,
    merge_on: str | int,
    batch_size: int = 1000,
    timeout: float | None = None,
) -> WriteResult

Example

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)
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

(1,) (4,)

Delete records

Delete the matching records and return how many were deleted.

Parameters

  • where Expression | str, required
    A condition built with 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: where can't be sent as written. Nothing was deleted.
  • UnknownFieldError: where names a field the table doesn't have. Nothing was deleted.
  • PolicyRefusedError: The client's policy doesn't allow deleting here, or more records match than its max_records_per_delete. Nothing was deleted.
delete(where: Expression | str) -> int

Example

from pyquickbase import F

print(tasks.delete(F("Status") == "Done"))
from pyquickbase import F

print(await tasks.delete(F("Status") == "Done"))

Output

1

Preview writes

For the full picture, see Writing records.

Preview an update

Show what 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.

Parameters

  • records Iterable[Mapping[str | int, Any]] | DataFrame | FileRows, required
    The records you'd pass to 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(). A DataFrame or a file's rows work too, as for update().

Returns UpdatePreview

An 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() may report a listed record as unchanged. A file upload is always a change.

Raises

  • InvalidValueError: A row can't be sent, such as one without Record ID#. Nothing was read.
  • UnknownFieldError: A row names a field the table doesn't have. Nothing was read.
preview_update(
    records: (
        Iterable[Mapping[str | int, Any]]
        | DataFrame
        | FileRows
    ),
) -> UpdatePreview

Example

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,
)
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

1 (2,) (999,)

Preview a delete

Show what 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.

Parameters

  • where Expression | str, required
    A condition built with F, or a hand-written string, checked as 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().
  • limit int, default 100
    The most records to return. total still counts every match.

Returns DeletePreview

A DeletePreview: a page whose total is how many records would be deleted, and whose events are what deleting them may set off.

Raises

  • QueryError: where can't be sent as written, or is None. Nothing was queried.
  • UnknownFieldError: The table has no such field. Nothing was queried.
preview_delete(
    where: Expression | str,
    *,
    select: FieldRefs | None = None,
    order_by: FieldRefs | None = None,
    limit: int = 100,
) -> DeletePreview

Example

from pyquickbase import F

page = tasks.preview_delete(
    F("Status") == "Done", select=["Title"]
)
print(page.total, [task["Title"] for task in page.records])
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

1 ['Hang drywall']

Summarize and calculate

For the full picture, see Formulas.

Summarize records

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.

Parameters

  • group_by SummaryGroups | None, optional
    The fields to group by, and 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 or where_from_dict, or a hand-written string.
  • sum FieldRefs | None, optional
    Numeric fields to add up. Empty values are skipped.
9 more: avg, median, std_dev, min, max, distinct_count, compare_periods, max_records_scanned, page_size
  • 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, 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 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 ]

A 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: More records match than max_records_scanned. No records were read. It's a QueryError.
  • QueryError: An argument can't be used, such as a text field in sum. No records were read.
  • UnknownFieldError: The table has no such field. No records were read.
  • PolicyRefusedError: More records match than the client's policy allows summarizing, or it doesn't allow a field. No records were read.
  • InvalidResponseError: A value isn't what its field's type says it should be.
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]

Example

rows = tasks.summarize("Status", sum="Hours")
for row in rows:
    print(row.group["Status"], row.count, row.sum["Hours"])
rows = await tasks.summarize("Status", sum="Hours")
for row in rows:
    print(row.group["Status"], row.count, row.sum["Hours"])

Output

Done 1 6.0
Open 2 20.0

Run a 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.

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

A FormulaResult. Its value is the result as a Python value where that's safe, and text is exactly what Quickbase sent.

Raises

run_formula(
    formula: str, *, record_id: int | None = None
) -> FormulaResult

Example

result = tasks.run_formula("[Hours] * 2", record_id=1)
print(result.value)
result = await tasks.run_formula("[Hours] * 2", record_id=1)
print(result.value)

Output

16

Look up the table's details

For the full picture, see Reading records.

Get the table's details

Get the table's metadata, from the client's cache of the app's tables.

Returns TableInfo

A TableInfo, with the table's id and name.

Raises

info() -> TableInfo

Example

info = tasks.info()
print(info.name)
info = await tasks.info()
print(info.name)

Output

Job Tasks

List fields

List the table's fields, from the cache unless refresh is set.

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 ]

A Field for each of the table's fields, including built-in ones like Record ID#.

Raises

fields(*, refresh: bool = False) -> list[Field]

Example

for field in tasks.fields():
    if field.label in ("Title", "Status"):
        print(field.id, field.label, field.type)
for field in await tasks.fields():
    if field.label in ("Title", "Status"):
        print(field.id, field.label, field.type)

Output

6 Title text
7 Status text-multiple-choice

See where fields are used

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.

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 ]

A FieldUsage for each field, in the order asked for, or the table's order.

Raises

field_usage(
    fields: str | int | Sequence[str | int] | None = None,
) -> list[FieldUsage]

Example

for usage in tasks.field_usage(["Title", "Code"]):
    print(usage.label, usage.counts["reports"])
for usage in await tasks.field_usage(["Title", "Code"]):
    print(usage.label, usage.counts["reports"])

Output

Title 2
Code 2

List relationships

List the relationships in which this table is the child. A parent table lists none.

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 ]

A Relationship for each parent this table links to, or an empty list.

Raises

relationships(
    *, refresh: bool = False
) -> list[Relationship]

Example

for relationship in tasks.relationships():
    print(relationship.foreign_key_field_id)
for relationship in await tasks.relationships():
    print(relationship.foreign_key_field_id)

Output

12

List reports

List the table's reports, from the cache unless refresh is set.

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 ]

A ReportInfo for each report, including ones Quickbase can't run through the API, whose incomplete is true.

Raises

reports(*, refresh: bool = False) -> list[ReportInfo]

Example

for report in tasks.reports():
    print(report.name, report.type)
for report in await tasks.reports():
    print(report.name, report.type)

Output

List All table
List Changes table

Get a 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 on first use.

Parameters

  • name_or_id str, required
    The report's exact name, or its ID, which takes priority over a name.

Returns Report

A Report, for running it or getting its details.

report(name_or_id: str) -> Report

Example

report = tasks.report("List All")
info = report.info()
print(info.name)
report = tasks.report("List All")
info = await report.info()
print(info.name)

Output

List All

List webhooks and automations

List the webhooks, Quickbase actions, notifications, subscriptions, reminders, and record-triggered automations that watch this table. See App.events().

Returns list[ AppEvent ]

An AppEvent for each, active or not.

Raises

events() -> list[AppEvent]

Example

events = tasks.events()
print(len(events))
events = await tasks.events()
print(len(events))

Output

0

Turn field labels into IDs

Turn field labels or IDs into IDs.

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.

Raises

resolve_field_ids(
    fields: Sequence[str | int],
) -> list[int]

Example

print(tasks.resolve_field_ids(["Title", "Status", 3]))
print(await tasks.resolve_field_ids(["Title", "Status", 3]))

Output

[6, 7, 3]

Import a CSV or Excel file

For the full picture, see CSV and Excel files.

Read rows from a CSV or Excel 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.

rows = tasks.rows_from_file("tasks.csv")
for error in rows.errors:
    print(error)
tasks.insert(rows)

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.
9 more: format, sheet, tz, date_format, datetime_format, encoding, delimiter, max_rows, max_unzipped_bytes
  • 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

A FileRows, with the rows to write, each one's row number, and every error.

Raises

  • FileTooLargeError: The file is over max_rows or max_unzipped_bytes.
  • 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.
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

Example

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))
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

True 1 (2,)
1

Work with files

For the full picture, see File attachments.

Download a 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.

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

A 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: 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: The table has no such field. Nothing was sent.
  • NotFoundError: The record, version, or file doesn't exist.
download_file(
    record_id: int,
    field: str | int,
    *,
    version: int | None = None,
    timeout: float | None = None,
) -> FileDownload

Example

download = tasks.download_file(1, "Attachment")
print(download.file_name, download.data)
download = await tasks.download_file(1, "Attachment")
print(download.file_name, download.data)

Output

spec.txt b'one'

Delete a 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.

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: record_id or version isn't a positive integer, or field isn't a file attachment field. Nothing was sent.
  • UnknownFieldError: The table has no such field. Nothing was sent.
  • NotFoundError: The record or version doesn't exist.
delete_file_version(
    record_id: int, field: str | int, *, version: int
) -> None

Example

tasks.delete_file_version(1, "Attachment", version=1)
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.

Make a tool for a model

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.

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 for what each operation takes.

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.
8 more: reports, report_descriptions, formula, max_records_per_call, max_records_per_write, scope, require_preview, max_default_fields
  • 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 says how many it left out. Defaults to every one.

Returns Operation

An Operation, with its schema and run().

Raises

  • PolicyRefusedError: The client's policy doesn't allow this operation at all.
  • ConfigurationError: The table has nothing for it, such as no file fields, or formula is missing or given for another operation.
  • UnknownFieldError: fields or field_descriptions names a field the client can't see, or one the operation doesn't offer.
  • UnknownReportError: reports has an ID this table doesn't have, or report_descriptions has one the operation doesn't offer.
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

Example

count = tasks.operation("count", fields=["Status"])
arguments = {
    "where": {"field": "Status", "op": "==", "value": "Open"}
}
print(count.run(arguments))
count = await tasks.operation("count", fields=["Status"])
arguments = {
    "where": {"field": "Status", "op": "==", "value": "Open"}
}
print(await count.run(arguments))

Output

2

For the full picture, see Using pyquickbase from an AI agent.

Link to the table

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.

Returns str

The URL, on the client's realm.

Raises

url() -> str

Example

print(tasks.url())
print(await tasks.url())

Output

https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/td

Link to a record

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() does.

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

record_url(
    record_id: int, *, edit: bool = False
) -> str

Example

print(tasks.record_url(1))
print(tasks.record_url(1, edit=True))
print(await tasks.record_url(1))
print(await tasks.record_url(1, edit=True))

Output

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

Link to any record

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() does.

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

record_url_template(*, edit: bool = False) -> str

Example

template = tasks.record_url_template()
print(template)
print(template.replace("{record_id}", "2"))
template = await tasks.record_url_template()
print(template)
print(template.replace("{record_id}", "2"))

Output

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

Link to a new record

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() does.

Returns str

The URL, on the client's realm.

Raises

new_record_url() -> str

Example

print(tasks.new_record_url())
print(await tasks.new_record_url())

Output

https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/nwr

Link to a field's settings

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.

Parameters

  • field str | int, required
    The field, by label or ID.

Returns str

The URL, on the client's realm.

Raises

field_url(field: str | int) -> str

Example

print(tasks.field_url("Status"))
print(await tasks.field_url("Status"))

Output

https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/mf?fid=7

Link to the list of fields

Link to the table's list of fields in Quickbase.

It only contacts Quickbase to look up the table's ID, as url() does.

Returns str

The URL, on the client's realm.

Raises

fields_url() -> str

Example

print(tasks.fields_url())
print(await tasks.fields_url())

Output

https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/listfields

Link to the relationships

Link to the table's relationships in Quickbase.

It only contacts Quickbase to look up the table's ID, as url() does.

Returns str

The URL, on the client's realm.

Raises

relationships_url() -> str

Example

print(tasks.relationships_url())
print(await tasks.relationships_url())

Output

https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/Relationships

Link to the table's settings

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() does.

Returns str

The URL, on the client's realm.

Raises

settings_url() -> str

Example

print(tasks.settings_url())
print(await tasks.settings_url())

Output

https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/KeyProps

Link to a form

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() does.

Parameters

  • form_id int, required
    The form's ID.

Returns str

The URL, on the client's realm.

Raises

form_url(form_id: int) -> str

Example

# A form's ID is the dfid in its address in Quickbase.
print(tasks.form_url(10))
# A form's ID is the dfid in its address in Quickbase.
print(await tasks.form_url(10))

Output

https://acme.quickbase.com/nav/app/bq1234567/table/bqtasks123/action/DFormProps?dfid=10

Change the schema

For the full picture, see Changing the schema.

Create a 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() to repair it.

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.
10 more: required, unique, properties, formula, help_text, bold, no_wrap, find_enabled, appears_by_default, add_to_forms
  • 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

The new Field.

Raises

  • 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: formula is blank, or a setting has the wrong type. Nothing was sent.
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

Example

field = tasks.create_field(
    "Priority",
    "text-multiple-choice",
    choices=["Low", "Medium", "High"],
)
print(field.label, field.type)
field = await tasks.create_field(
    "Priority",
    "text-multiple-choice",
    choices=["Low", "Medium", "High"],
)
print(field.label, field.type)

Output

Priority text-multiple-choice

Update a 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.

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.
8 more: unique, properties, formula, help_text, bold, no_wrap, find_enabled, appears_by_default
  • 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

The Field, with its new settings.

Raises

  • UnknownFieldError: The table has no such field. Nothing was sent.
  • InvalidValueError: There's nothing to change, formula is blank, or a setting has the wrong type. Nothing was sent.
  • BadRequestError: Quickbase refused the change, and the field is as it was.
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

Example

field = tasks.update_field(
    "Hours", label="Estimated Hours"
)
print(field.label)
field = await tasks.update_field(
    "Hours", label="Estimated Hours"
)
print(field.label)

Output

Estimated Hours

Delete fields

Delete fields and all their data.

Parameters

  • fields str | int | Sequence[str | int], required
    One field or a list of them, by label or ID.

Raises

  • UnknownFieldError: A field doesn't exist. Nothing was deleted.
  • InvalidValueError: The list is empty. Nothing was sent.
  • WriteError: A field couldn't be deleted, such as a built-in field. The others in the list were still deleted.
delete_fields(
    fields: str | int | Sequence[str | int],
) -> None

Example

tasks.delete_fields("Code")
await tasks.delete_fields("Code")

Create or update a 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(), and anything left as None is ignored on an existing field. choices are compared ignoring order.

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.
9 more: required, unique, properties, formula, help_text, bold, no_wrap, find_enabled, appears_by_default
  • 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

The Field, whether it was found, created, or changed.

Raises

  • SchemaMismatchError: The field exists with a different type, because Quickbase can't always convert one. It wasn't changed.
  • BadRequestError: Quickbase refused the field or the change.
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

Example

field = tasks.ensure_field("Priority", "text")
again = tasks.ensure_field("Priority", "text")
print(field.id == again.id)
field = await tasks.ensure_field("Priority", "text")
again = await tasks.ensure_field("Priority", "text")
print(field.id == again.id)

Output

True

Create a 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() in a script that may run twice.

Parameters

  • parent Table, required
    The 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

The Relationship. Its lookup_field_ids are the new fields on this table, so don't pass them back as lookup_fields.

Raises

create_relationship(
    parent: Table,
    *,
    foreign_key: str,
    lookup_fields: Sequence[str | int] = (),
) -> Relationship

Example

relationship = tasks.create_relationship(
    projects,
    foreign_key="Project",
    lookup_fields=["Name"],
)
print(relationship.foreign_key_field_id)
relationship = await tasks.create_relationship(
    projects,
    foreign_key="Project",
    lookup_fields=["Name"],
)
print(relationship.foreign_key_field_id)

Output

12

Get or create a relationship

Return this table's relationship with parent, creating it if it doesn't exist.

Use the result with ensure_lookup_field() and ensure_summary_field().

Parameters

  • parent Table, required
    The 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

The existing or new Relationship.

Raises

ensure_relationship(
    parent: Table, *, foreign_key: str
) -> Relationship

Example

relationship = tasks.ensure_relationship(
    projects, foreign_key="Project"
)
again = tasks.ensure_relationship(
    projects, foreign_key="Project"
)
print(relationship.id == again.id)
relationship = await tasks.ensure_relationship(
    projects, foreign_key="Project"
)
again = await tasks.ensure_relationship(
    projects, foreign_key="Project"
)
print(relationship.id == again.id)

Output

True

Get or create a 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.

Parameters

  • relationship Relationship, required
    The relationship, from 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

The lookup Field, on this table.

Raises

ensure_lookup_field(
    relationship: Relationship,
    parent_field: str | int,
    *,
    label: str | None = None,
) -> Field

Example

relationship = tasks.ensure_relationship(
    projects, foreign_key="Project"
)
field = tasks.ensure_lookup_field(
    relationship, "Name", label="Project Name"
)
print(field.label)
relationship = await tasks.ensure_relationship(
    projects, foreign_key="Project"
)
field = await tasks.ensure_lookup_field(
    relationship, "Name", label="Project Name"
)
print(field.label)

Output

Project Name

Get or create a 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().

Parameters

  • relationship Relationship, required
    The relationship, from 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.
2 more: function, where
  • 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, 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

The summary Field, on the parent.

Raises

  • InvalidValueError: function isn't one of these, or is count, which takes no field. Nothing was sent.
  • UnknownFieldError: This table has no such field. Nothing was sent.
  • QueryError: where can't be sent as written. Nothing was sent.
  • SchemaMismatchError: The label is already used on the parent by anything but this summary. It wasn't changed.
  • BadRequestError: Quickbase can't apply function to this field, such as sum of a checkbox.
ensure_summary_field(
    relationship: Relationship,
    child_field: str | int,
    label: str,
    *,
    function: str = "sum",
    where: Expression | str | None = None,
) -> Field

Example

relationship = tasks.ensure_relationship(
    projects, foreign_key="Project"
)
field = tasks.ensure_summary_field(
    relationship, "Hours", "Total Hours"
)
print(field.label)
relationship = await tasks.ensure_relationship(
    projects, foreign_key="Project"
)
field = await tasks.ensure_summary_field(
    relationship, "Hours", "Total Hours"
)
print(field.label)

Output

Total Hours

Get or create a 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.

Parameters

  • relationship Relationship, required
    The relationship, from 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().

Returns Field

The count Field, on the parent.

Raises

  • SchemaMismatchError: The label is already used on the parent by anything but this count. It wasn't changed.
  • UnknownFieldError: where uses a field this table doesn't have. Nothing was sent.
  • QueryError: where can't be sent as written. Nothing was sent.
ensure_count_field(
    relationship: Relationship,
    label: str,
    *,
    where: Expression | str | None = None,
) -> Field

Example

relationship = tasks.ensure_relationship(
    projects, foreign_key="Project"
)
field = tasks.ensure_count_field(
    relationship, "Task Count"
)
print(field.label, field.summary_function)
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

Task Count COUNT

Delete a 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.

Parameters

  • relationship Relationship, required
    The relationship, from relationships().

Raises

delete_relationship(
    relationship: Relationship,
) -> None

Example

relationship = (tasks.relationships())[0]
tasks.delete_relationship(relationship)
print(tasks.relationships())
relationship = (await tasks.relationships())[0]
await tasks.delete_relationship(relationship)
print(await tasks.relationships())

Output

[]

Update the table's details

Change the table's name or descriptions, and return its new details. Anything left as None stays as it is.

This Table keeps working after a rename, even if it was found by its old name.

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

The table's TableInfo, with its new settings.

Raises

update_info(
    *,
    name: str | None = None,
    description: str | None = None,
    single_record_name: str | None = None,
    plural_record_name: str | None = None,
) -> TableInfo

Example

info = tasks.update_info(
    description="Work on each job, one record per task.",
)
print(info.description)
info = await tasks.update_info(
    description="Work on each job, one record per task.",
)
print(info.description)

Output

Work on each job, one record per task.

Delete the table

Delete this table and all its records.

Parameters

  • confirm str, required
    The table's exact name, so a wrong variable can't drop the wrong table.

Raises

drop(*, confirm: str) -> None

Example

tasks.drop(confirm="Job Tasks")
await tasks.drop(confirm="Job Tasks")