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
whereExpression | str | None, optional
A condition built withF, or a hand-written string, which gets checked.selectFieldRefs | 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_byFieldRefs | 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_sizeint, default1000
Records to request per call to Quickbase.max_totalint | 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_sizeandmax_totalraise 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
whereExpression | str | None, optional
A condition built withF, or a hand-written string, which gets checked.selectFieldRefs | 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_byFieldRefs | 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
limitint, default100
The most records to return.skipint, default0
How many matching records to pass over first. Use the previous page'snext_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
whereExpression | str | None, optional
A condition built withF, or a hand-written string, which gets checked.selectFieldRefs | None, optional
The fields to return, by label, ID, or field object. Defaults to the table's default fields.order_byFieldRefs | None, optional
The fields to sort by, which decide which record is first. Seequery().
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_idint, required
The record's Record ID#.selectFieldRefs | 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
InvalidValueError:record_idisn't a positive integer. Nothing was sent.UnknownFieldError: The table has no such field. Nothing was queried.
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
whereExpression | str | None, optional
A condition built withF, 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:wherecan'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
whereExpression | str, required
A condition built withForwhere_from_dict, or a hand-written string, which gets checked and returned without outer whitespace.
Returns str
The query text.
Raises
UnknownFieldError: The table has no such field, or the client's policy hides it.QueryError:wherecan't be sent as written.
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
recordsIterable[Mapping[str | int, Any]] | DataFrame | FileRows, required
The records to create, as{label or ID: value}dictionaries. A value ofNoneleaves 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 fromrows_from_file().batch_sizeint, default1000
The most records to send in one request.timeoutfloat | None, optional
Seconds to allow for each step of each request, instead of the client'stimeout, such as for a large file. It applies with your ownhttp_clienttoo.
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 setsRecord ID#or has no real values, or the table's key field isn'tRecord 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'sresult.
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
recordsIterable[Mapping[str | int, Any]] | DataFrame | FileRows, required
The changes, as{label or ID: value}dictionaries that each setRecord ID#. Fields left out of a row keep their values, and a value ofNoneclears 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 fromrows_from_file().batch_sizeint, default1000
The most records to send in one request.timeoutfloat | None, optional
Seconds to allow for each step of each request, instead of the client'stimeout, such as for a large file. It applies with your ownhttp_clienttoo.
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 withoutRecord 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'sresult.
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
recordsIterable[Mapping[str | int, Any]] | DataFrame | FileRows, required
The records, as{label or ID: value}dictionaries that each setmerge_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 fromrows_from_file().merge_onstr | int, required
The field that identifies a record, by label or ID. Quickbase requires it to be marked as unique.batch_sizeint, default1000
The most records to send in one request.timeoutfloat | None, optional
Seconds to allow for each step of each request, instead of the client'stimeout, such as for a large file. It applies with your ownhttp_clienttoo.
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 withoutmerge_on. Nothing was written.UnknownFieldError:merge_onor a row names a field the table doesn't have. Nothing was written.BadRequestError:merge_onisn't a unique field. Nothing was written.WriteError: A row failed. Other records are still written, and are listed in the error'sresult.
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
whereExpression | str, required
A condition built withF, 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:wherecan't be sent as written. Nothing was deleted.UnknownFieldError:wherenames 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 itsmax_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
recordsIterable[Mapping[str | int, Any]] | DataFrame | FileRows, required
The records you'd pass toupdate(). 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 inupdate(). A DataFrame or a file's rows work too, as forupdate().
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 withoutRecord 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
whereExpression | str, required
A condition built withF, or a hand-written string, checked asdelete()checks it. It's required, so that leaving it out can't look like a safe preview.selectFieldRefs | None, optional
The fields to return. Defaults to the table's default fields.order_byFieldRefs | None, optional
Which matching records come first. Seequery().limitint, default100
The most records to return.totalstill 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:wherecan'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_bySummaryGroups | None, optional
The fields to group by, andPeriodfor dates. Without this, the result is one row for all the matching records.whereExpression | str | None, optional
A condition built withForwhere_from_dict, or a hand-written string.sumFieldRefs | 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
avgFieldRefs | None, optional
Numeric fields to average. Empty values are skipped.medianFieldRefs | None, optional
Numeric fields to find the middle value of. Empty values are skipped.std_devFieldRefs | None, optional
Numeric fields to find the sample standard deviation of. Empty values are skipped.minFieldRefs | None, optional
Numeric, date, timestamp, or time of day fields to find the lowest of.maxFieldRefs | None, optional
Numeric, date, timestamp, or time of day fields to find the highest of.distinct_countFieldRefs | None, optional
Fields to count the different values of, such as how many clients have records. Empty values are skipped.compare_periodsbool, defaultFalse
Say how each group changed since the period before, in itschange.group_bymust have exactly onePeriod, 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_scannedint | None, optional
RaiseTooManyRecordsErrorif 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_sizeint | 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 thanmax_records_scanned. No records were read. It's aQueryError.QueryError: An argument can't be used, such as a text field insum. 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
formulastr, required
The formula's text, in Quickbase's formula language. It can't be blank.record_idint | 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
InvalidValueError:formulais blank, orrecord_idisn't a positive integer. Nothing was sent.BadRequestError: Quickbase can't run the formula, and its message says why.NotFoundError: The record doesn't exist.
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
UnknownTableError: This app has no such table, even after checking again.
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
refreshbool, defaultFalse
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
UnknownTableError: This app has no such table.
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
fieldsstr | 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
UnknownFieldError: A field doesn't exist. Nothing was read.PermissionDeniedError: The token isn't an administrator's.
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
refreshbool, defaultFalse
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
UnknownTableError: This app has no such table.PermissionDeniedError: The token's role can't edit the app's structure.
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
refreshbool, defaultFalse
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
UnknownTableError: This app has no such table.
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_idstr, 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
PermissionDeniedError: The token isn't an administrator's.
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
fieldsSequence[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
UnknownFieldError: The table has no such field.
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
sourceFileLike, required
A path, or an open file: text for CSV, and binary for Excel.columnsColumnMap | 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_onlybool, defaultFalse
Skip columns for fields that can't be written, such as formulas and Date Created, instead of raising an error, as a file fromto_csv()has them.
9 more: format, sheet, tz, date_format, datetime_format, encoding, delimiter, max_rows, max_unzipped_bytes
formatOptional[Literal['csv', 'xlsx']], optional
csvorxlsx. By default it'sxlsxfor an.xlsxor.xlsmpath or a binary file, andcsvotherwise.sheetstr | None, optional
For Excel, the worksheet's name. Defaults to the first.tzdatetime.tzinfo, defaultdatetime.timezone.utc
The time zone of a timestamp without one, such as one from Excel.date_formatstr | None, optional
How dates are written, fordatetime.strptime(), such as%m/%d/%Y. Defaults to ISO 8601,2027-01-15.datetime_formatstr | None, optional
How timestamps are written, as fordate_format. Defaults to ISO 8601.encodingstr, 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.delimiterstr | None, optional
What separates a CSV's values. Defaults to a tab for a.tsvor.tabpath, and a comma otherwise.max_rowsint | 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_bytesint | None, default1000000000
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 overmax_rowsormax_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_idint, required
The Record ID# of the record that holds the file.fieldstr | int, required
The file attachment field, by label or ID.versionint | None, optional
The version to download, starting at 1. Defaults to the latest.timeoutfloat | None, optional
Seconds to allow for each step of the request, instead of the client'stimeout, such as for a large file. It applies with your ownhttp_clienttoo.
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_idorversionisn't a positive integer,timeoutisn't a positive number, orfieldisn'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_idint, required
The Record ID# of the record that holds the file.fieldstr | int, required
The file attachment field, by label or ID.versionint, 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_idorversionisn't a positive integer, orfieldisn'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
nameOperationName, required
What the operation does, such asqueryorinsert. Each runs the method of the same name, andrun_reportandrun_summary_reportrun a report.fieldsSequence[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_descriptionsMapping[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
reportsSequence[str] | None, optional
The reportsrun_reportorrun_summary_reportmay run, by ID. Defaults to every report of that kind the client may run.report_descriptionsMapping[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.formulastr | None, optional
Forrun_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_callint | None, optional
The most records one call may return, or fordelete, may match. A delete that matches more is refused. Givepreview_deletethe same number, and its result says when a delete would be refused. Defaults to 100, or forsummarize, 10,000 records read. The client's policy can lower it, and itsmax_paging_depthalso limits how farskipcan page.max_records_per_writeint, default100
The most records oneinsertorupdatemay send.scopeExpression | None, optional
A condition your code fixes, such as the user's project, that every record the operation reaches must match, asrun()'sscopedoes. 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. Aninsertneeds 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_previewbool, defaultFalse
Forupdateanddelete: run only with theconfirmationthatpreview_updateorpreview_deletereturns 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 andscope.max_default_fieldsint | None, optional
Forquery: the most of the table's default fields to return when the model doesn't name any inselect. A table with more returns its first ones, andtool_resultsays 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, orformulais missing or given for another operation.UnknownFieldError:fieldsorfield_descriptionsnames a field the client can't see, or one the operation doesn't offer.UnknownReportError:reportshas an ID this table doesn't have, orreport_descriptionshas 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
Link to the Quickbase interface¶
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
UnknownTableError: This app has no such table, or the client's policy hides it.
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_idint, required
The record's Record ID#.editbool, defaultFalse
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:record_idisn't a positive integer.UnknownTableError: This app has no such table, or the client's policy hides it.
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
editbool, defaultFalse
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: This app has no such table, or the client's policy hides it.
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
UnknownTableError: This app has no such table, or the client's policy hides it.
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
fieldstr | int, required
The field, by label or ID.
Returns str
The URL, on the client's realm.
Raises
UnknownFieldError: The table has no such field, or the client's policy hides it.UnknownTableError: This app has no such table, or the client's policy hides it.
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
UnknownTableError: This app has no such table, or the client's policy hides it.
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
UnknownTableError: This app has no such table, or the client's policy hides it.
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
UnknownTableError: This app has no such table, or the client's policy hides it.
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_idint, required
The form's ID.
Returns str
The URL, on the client's realm.
Raises
InvalidValueError:form_idisn't a positive integer.UnknownTableError: This app has no such table, or the client's policy hides it.
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
labelstr, required
The new field's label. Another field can't have the same label, ignoring case.typestr, required
One oftext,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, orfile. Adatetimefield reports its type astimestamp.choicesSequence[str] | None, optional
The options of atext-multiple-choiceormultitextfield. Quickbase still accepts other values unlesspropertiessetsallowNewChoicesto False.
10 more: required, unique, properties, formula, help_text, bold, no_wrap, find_enabled, appears_by_default, add_to_forms
requiredbool | 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.uniquebool | None, optional
Whether values must be different on every record.propertiesMapping[str, Any] | None, optional
Other settings, sent as they are, such as{"decimalPlaces": 2}. Quickbase refuses a key it doesn't know.formulastr | None, optional
Makes this a formula field.typemust match the formula's result, and can't betimestamportext-multi-line. Usedatetimefor a timestamp formula.help_textstr | None, optional
The help Quickbase shows beside the field on forms.boldbool | None, optional
Whether forms and reports show the field's values in bold.no_wrapbool | None, optional
Whether forms and reports keep the field's values on one line.find_enabledbool | None, optional
Whether the field's values are searched by Quickbase's search box.appears_by_defaultbool | None, optional
Whether a query or report without chosen columns returns the field.add_to_formsbool | 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, orchoiceson a type without them. If it refusedrequiredorunique, the field was still created, and the message says so.InvalidValueError:formulais 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
fieldstr | int, required
The field's label or ID.labelstr | None, optional
A new label.requiredbool | None, optional
Whether the field must have a value. Quickbase refusesTrueif any record has this field empty.
8 more: unique, properties, formula, help_text, bold, no_wrap, find_enabled, appears_by_default
uniquebool | None, optional
Whether values must be different on every record. Quickbase refusesTrueif the field already holds duplicates.propertiesMapping[str, Any] | None, optional
Other settings, sent as they are.{"choices": [...]}replaces the whole list of choices.formulastr | 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_textstr | None, optional
The help Quickbase shows beside the field on forms.boldbool | None, optional
Whether forms and reports show the field's values in bold.no_wrapbool | None, optional
Whether forms and reports keep the field's values on one line.find_enabledbool | None, optional
Whether the field's values are searched by Quickbase's search box.appears_by_defaultbool | 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,formulais 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
fieldsstr | 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
labelstr, required
The field's label, ignoring case.typestr, required
The field's type.datetimematches a field that reportstimestamp.choicesSequence[str] | None, optional
The options of atext-multiple-choiceormultitextfield.
9 more: required, unique, properties, formula, help_text, bold, no_wrap, find_enabled, appears_by_default
requiredbool | None, optional
Whether the field must have a value. Quickbase refusesTrueif any record has this field empty, which includes a new field on a table with records.uniquebool | None, optional
Whether values must be different on every record.propertiesMapping[str, Any] | None, optional
Other settings, sent as they are.formulastr | None, optional
The text of a formula field.help_textstr | None, optional
The help Quickbase shows beside the field on forms.boldbool | None, optional
Whether forms and reports show the field's values in bold.no_wrapbool | None, optional
Whether forms and reports keep the field's values on one line.find_enabledbool | None, optional
Whether the field's values are searched by Quickbase's search box.appears_by_defaultbool | 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
parentTable, required
TheTableto link to, which becomes the parent.foreign_keystr, required
The label of the new field on this table, which holds the Record ID# of each record's parent.lookup_fieldsSequence[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 asProject - 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
UnknownFieldError: The parent has no such field. Nothing was sent.
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
parentTable, required
TheTableto link to, which becomes the parent.foreign_keystr, 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
UnknownTableError: This app has no such table, or no such parent.BadRequestError: Quickbase refused to create the relationship.PolicyRefusedError: The client's policy doesn't allow it. Nothing was created.
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
relationshipRelationship, required
The relationship, fromensure_relationship().parent_fieldstr | int, required
The parent's field to look up, by label or ID.labelstr | None, optional
A label for the lookup, since Quickbase names one after the foreign key and the field, such asProject - Name. An existing lookup is renamed to match.
Returns Field
The lookup Field, on this table.
Raises
UnknownFieldError: The parent has no such field. Nothing was sent.
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
relationshipRelationship, required
The relationship, fromensure_relationship().child_fieldstr | int, required
This table's field to summarize, by label or ID.labelstr, required
The label of the summary field on the parent.
2 more: function, where
functionstr, default'sum'
The calculation:sum,avg,max,min,std_dev,distinct_count,combined_text, orcombined_user. Quickbase's names, such asSTD-DEV, work too.whereExpression | str | None, optional
Only combine the child records that match, as a condition on this table built withF, 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:functionisn't one of these, or iscount, which takes no field. Nothing was sent.UnknownFieldError: This table has no such field. Nothing was sent.QueryError:wherecan'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 applyfunctionto this field, such assumof 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
relationshipRelationship, required
The relationship, fromensure_relationship().labelstr, required
The label of the count field on the parent.whereExpression | str | None, optional
Only count the records that match, as inensure_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:whereuses a field this table doesn't have. Nothing was sent.QueryError:wherecan'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
relationshipRelationship, required
The relationship, fromrelationships().
Raises
InvalidValueError: This table isn't the relationship's child. Nothing was sent.NotFoundError: The relationship doesn't exist.
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
namestr | None, optional
A new name. Another table in the app can't have it.descriptionstr | None, optional
A new description.single_record_namestr | None, optional
What the table calls one record, such asProject.plural_record_namestr | None, optional
What the table calls several records, such asProjects.
Returns TableInfo
The table's TableInfo, with its new settings.
Raises
InvalidValueError: There's nothing to change. Nothing was sent.BadRequestError: Quickbase refused the change, such as for a name another table has.
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
confirmstr, required
The table's exact name, so a wrong variable can't drop the wrong table.
Raises
InvalidValueError:confirmisn't the table's exact name. Nothing was sent.
drop(*, confirm: str) -> None
Example
tasks.drop(confirm="Job Tasks")
await tasks.drop(confirm="Job Tasks")