# pyquickbase API reference: Records and results This describes version 1.0.0rc13. Other pages: [Quickbase](https://matthew-williams-solutions.github.io/pyquickbase/reference/quickbase.txt), [App](https://matthew-williams-solutions.github.io/pyquickbase/reference/app.txt), [Table](https://matthew-williams-solutions.github.io/pyquickbase/reference/table.txt), [Report](https://matthew-williams-solutions.github.io/pyquickbase/reference/report.txt), [Operation](https://matthew-williams-solutions.github.io/pyquickbase/reference/operation.txt), [SchemaOperation](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-operation.txt), [Queries](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries.txt), [Metadata](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata.txt), [Schema changes](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-changes.txt), [Policies](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies.txt), [Rules](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules.txt), [Retries](https://matthew-williams-solutions.github.io/pyquickbase/reference/retries.txt), [Tools for models](https://matthew-williams-solutions.github.io/pyquickbase/reference/tools.txt), [Errors](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors.txt). These are what reads and writes give you back, and what you pass to write a file. The examples below use `tasks`, made as shown here, and `app`, which is `qb.app("bq1234567")`. `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. A live test runs every example with both clients and checks its output. Sync: ```python from pyquickbase import Quickbase with Quickbase( realm="acme", token="your_user_token" ) as qb: tasks = qb.app("bq1234567").table("Job Tasks") ``` Async: ```python from pyquickbase import AsyncQuickbase async with AsyncQuickbase( realm="acme", token="your_user_token" ) as qb: tasks = qb.app("bq1234567").table("Job Tasks") ``` ## Read results For the full picture, see [Reading records](https://matthew-williams-solutions.github.io/pyquickbase/guides/reading/). ### `Record` One record. Read values by field label or ID: `record["Status"]` or `record[7]`. Iterating yields labels, so `dict(record)` gives `{label: value}`. Date fields are `date` objects and timestamp fields are timezone-aware `datetime` objects, or None when empty. A user field holds a [`User`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#User), a `multiuser` field a list of them, and a file field a [`FileAttachment`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileAttachment), or None when empty. **Attributes** - **`id`** `int` The Record ID#. Queries always select it, but a report may not include it. Sync example: ```python task = tasks.get( 1, select=["Title", "Status", "Due Date"] ) print(task.id, task["Title"], task[7]) print(task["Due Date"]) print(sorted(label for label in task)) ``` Async example: ```python task = await tasks.get( 1, select=["Title", "Status", "Due Date"] ) print(task.id, task["Title"], task[7]) print(task["Due Date"]) print(sorted(label for label in task)) ``` Output: ```text 1 Pour slab Open 2027-01-15 ['Due Date', 'Record ID#', 'Status', 'Title'] ``` ### `RecordIterator` Yields records from a query, fetching further pages as needed. A class rather than an async generator, so stopping early with `break` needs no cleanup. #### `RecordIterator.to_csv()` Write the remaining records to a CSV file as they're fetched, with a header row. Dates and timestamps are ISO 8601, with a timestamp's offset. A user is their email, or their ID if the client's policy hides emails, a file is its latest version's name, and a list's values are separated by `;`. `rows_from_file()` reads the file back. ```python to_csv( destination: FileLike, *, spreadsheet_safe: bool = True, encoding: str = "utf-8-sig", ) -> int ``` **Parameters** - **`destination`** `FileLike`, required A path, or a file open for writing text. - **`spreadsheet_safe`** `bool`, default `True` Write text that a spreadsheet would run as a formula, which starts with `=`, `+`, `-`, or `@`, with a leading `'`, so opening the file can't run it. `rows_from_file()` removes it again. - **`encoding`** `str`, default `'utf-8-sig'` The text encoding for a path. The default writes a byte order mark, which Excel needs to read UTF-8. **Returns** `int` How many records were written. #### `RecordIterator.to_excel()` Write the remaining records to an Excel file, with a header row. Needs `pyquickbase[excel]`. Dates and numbers are Excel's own, and a timestamp is written in UTC, as Excel has no time zones. Text is always text, so a value starting with `=` is never a formula. Values are written as for `to_csv()`. ```python to_excel( destination: FileLike, *, sheet: str = "Records" ) -> int ``` **Parameters** - **`destination`** `FileLike`, required A path, or a file open for writing bytes. - **`sheet`** `str`, default `'Records'` The worksheet's name. **Returns** `int` How many records were written. #### `RecordIterator.to_pandas()` Fetch the remaining records into a pandas DataFrame, with a column for each field. A user field holds the user's email, or their ID if the client's policy hides emails, and a file field holds the latest version's file name. Needs `pyquickbase[pandas]`. ```python to_pandas() -> pandas.DataFrame ``` **Returns** `pandas.DataFrame` A DataFrame, which has the columns even when nothing matched. #### `RecordIterator.to_polars()` Fetch the remaining records into a polars DataFrame. See `to_pandas()`. Needs `pyquickbase[polars]`. ```python to_polars() -> polars.DataFrame ``` **Returns** `polars.DataFrame` A DataFrame, which has the columns even when nothing matched. Sync example: ```python records = tasks.query(select=["Title"], max_total=2) for task in records: print(task.id, task["Title"]) ``` Async example: ```python records = tasks.query(select=["Title"], max_total=2) async for task in records: print(task.id, task["Title"]) ``` Output: ```text 1 Pour slab 2 Frame walls ``` ### `Page` One page of query results. **Attributes** - **`records`** `tuple[Record, ...]` The [`Record`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Record) objects on this page. - **`total`** `int` How many records match, not just the ones on this page. - **`skip`** `int` How many matching records came before this page. - **`columns`** `tuple[str, ...]` The labels of the fields it returned, in order, even when it has no records. - **`has_more`** `bool` Whether more matching records come after this page. - **`next_skip`** `int` The `skip` that fetches the following page. #### `Page.to_csv()` Write this page's records to a CSV file, with a header row. See `RecordIterator.to_csv()`. ```python to_csv( destination: FileLike, *, spreadsheet_safe: bool = True, encoding: str = "utf-8-sig", ) -> int ``` **Parameters** - **`destination`** `FileLike`, required A path, or a file open for writing text. - **`spreadsheet_safe`** `bool`, default `True` Write text that a spreadsheet would run as a formula with a leading `'`. - **`encoding`** `str`, default `'utf-8-sig'` The text encoding for a path. **Returns** `int` How many records were written. #### `Page.to_excel()` Write this page's records to an Excel file, with a header row. See `RecordIterator.to_excel()`. Needs `pyquickbase[excel]`. ```python to_excel( destination: FileLike, *, sheet: str = "Records" ) -> int ``` **Parameters** - **`destination`** `FileLike`, required A path, or a file open for writing bytes. - **`sheet`** `str`, default `'Records'` The worksheet's name. **Returns** `int` How many records were written. #### `Page.to_pandas()` This page's records as a pandas DataFrame, with a column for each field returned. A user field holds the user's email, or their ID if the client's policy hides emails, and a file field holds the latest version's file name. Needs `pyquickbase[pandas]`. ```python to_pandas() -> pandas.DataFrame ``` **Returns** `pandas.DataFrame` A DataFrame, which has the columns even when the page has no records. #### `Page.to_polars()` This page's records as a polars DataFrame. See `to_pandas()`. Needs `pyquickbase[polars]`. ```python to_polars() -> polars.DataFrame ``` **Returns** `polars.DataFrame` A DataFrame, which has the columns even when the page has no records. Sync example: ```python page = tasks.query_page(limit=2) print(len(page.records), page.total, page.skip) print(page.has_more, page.next_skip) ``` Async example: ```python page = await tasks.query_page(limit=2) print(len(page.records), page.total, page.skip) print(page.has_more, page.next_skip) ``` Output: ```text 2 3 0 True 2 ``` ### `User` A Quickbase user, as a `user` or `multiuser` field holds one. `repr` leaves out the email address, because reprs end up in logs. Pass a [`User`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#User) as a value to write it to a user field, or to compare with one in [`F`](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries/#F). **Attributes** - **`id`** `str` The user's ID. - **`name`** `str` The user's name, as Quickbase shows it. - **`email`** `str | None` The user's email address, or None if the client's policy leaves it out. - **`user_name`** `str | None` The user's Quickbase user name, if Quickbase sent one. Sync example: ```python from pyquickbase import User task = tasks.get(1, select=["Record Owner"]) owner = task["Record Owner"] print(isinstance(owner, User), bool(owner.email)) ``` Async example: ```python from pyquickbase import User task = await tasks.get(1, select=["Record Owner"]) owner = task["Record Owner"] print(isinstance(owner, User), bool(owner.email)) ``` Output: ```text True True ``` ## Write results For the full picture, see [Writing records](https://matthew-williams-solutions.github.io/pyquickbase/guides/writing/). ### `WriteResult` What a write did. If rows fail, `WriteError.result` holds this, including what succeeded. **Attributes** - **`created_ids`** `tuple[int, ...]` The Record ID# of each new record, in the order of the rows that succeeded. Use `row_ids` to match them to your rows. - **`updated_ids`** `tuple[int, ...]` The records whose values changed. - **`unchanged_ids`** `tuple[int, ...]` The records that already held these values. - **`errors`** `tuple[RowError, ...]` A [`RowError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#RowError) for each row that failed. - **`row_ids`** `tuple[int | None, ...]` For `insert()`, the new Record ID# of each row, in the order of your rows, or None for a row that failed. It stops short at a batch that wasn't sent, or whose response can't be matched to its rows, so compare its length with your rows. Empty for `update()` and `upsert()`. Sync example: ```python result = tasks.upsert( [ {"Code": "T-1", "Status": "Open"}, {"Code": "T-2", "Status": "Done"}, {"Code": "T-9", "Title": "Inspect site"}, ], merge_on="Code", ) print(result.created_ids) print(result.updated_ids) print(result.unchanged_ids) print(result.errors) ``` Async example: ```python result = await tasks.upsert( [ {"Code": "T-1", "Status": "Open"}, {"Code": "T-2", "Status": "Done"}, {"Code": "T-9", "Title": "Inspect site"}, ], merge_on="Code", ) print(result.created_ids) print(result.updated_ids) print(result.unchanged_ids) print(result.errors) ``` Output: ```text (4,) (2,) (1,) () ``` ### `RowError` Why one row failed. **Attributes** - **`index`** `int` The row's position in the rows you passed, starting from 0. - **`messages`** `tuple[str, ...]` Quickbase's wording, which can change, so use `reason` to decide what to do. For a `rule` failure, this holds the rule's message. - **`reason`** `Literal['duplicate', 'permission', 'invalid_choice', 'rule', 'other']` `duplicate` for a value already in a unique field, `permission` for a refusal by the token's role, `invalid_choice` for a value outside a field's choices, `rule` for a refusal by one of the table's custom data rules, and `other` for anything else, including wording this version doesn't recognize. - **`field_id`** `int | None` The field Quickbase named, for `duplicate` and `invalid_choice`. Sync example: ```python from pyquickbase import WriteError try: tasks.insert( [ {"Title": "New", "Code": "T-8"}, {"Title": "Repeat", "Code": "T-1"}, ] ) except WriteError as error: print(error.result.created_ids) for failure in error.result.errors: print(failure.index, failure.reason, failure.field_id) ``` Async example: ```python from pyquickbase import WriteError try: await tasks.insert( [ {"Title": "New", "Code": "T-8"}, {"Title": "Repeat", "Code": "T-1"}, ] ) except WriteError as error: print(error.result.created_ids) for failure in error.result.errors: print(failure.index, failure.reason, failure.field_id) ``` Output: ```text (4,) 1 duplicate 10 ``` ## Files For the full picture, see [File attachments](https://matthew-williams-solutions.github.io/pyquickbase/guides/files/). ### `FileUpload` A file to write to a file attachment field, as a value in `insert()`, `update()`, or `upsert()`. `repr` leaves out the contents, because they may be sensitive and reprs end up in logs. ```python FileUpload(file_name: str, data: bytes) ``` **Parameters** - **`file_name`** `str`, required The file's name. It can't contain a path or control characters, because Quickbase stores them as sent and could pass them to whoever downloads the file later. - **`data`** `bytes`, required The file's contents. **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): `file_name` is empty, or contains a path or control characters. Sync example: ```python from pyquickbase import FileUpload upload = FileUpload("spec.txt", b"Pour on Monday") result = tasks.update( [{"Record ID#": 1, "Attachment": upload}] ) print(result.updated_ids) ``` Async example: ```python from pyquickbase import FileUpload upload = FileUpload("spec.txt", b"Pour on Monday") result = await tasks.update( [{"Record ID#": 1, "Attachment": upload}] ) print(result.updated_ids) ``` Output: ```text (1,) ``` ### `FileDownload` A file downloaded from a file attachment field. `repr` leaves out the contents, because they may be sensitive and reprs end up in logs. **Attributes** - **`file_name`** `str` The file's name. Quickbase stores names as sent, such as `../../x`, so don't use this as a path without cleaning it. - **`data`** `bytes` The file's contents. - **`content_type`** `str | None` The media type, if reported. It's based on the name rather than the contents, so it doesn't tell you what's in the file. Sync example: ```python from pyquickbase import FileUpload upload = FileUpload("spec.txt", b"Pour on Monday") tasks.update([{"Record ID#": 1, "Attachment": upload}]) download = tasks.download_file(1, "Attachment") print(download.file_name, download.data) ``` Async example: ```python from pyquickbase import FileUpload upload = FileUpload("spec.txt", b"Pour on Monday") await tasks.update([{"Record ID#": 1, "Attachment": upload}]) download = await tasks.download_file(1, "Attachment") print(download.file_name, download.data) ``` Output: ```text spec.txt b'Pour on Monday' ``` ### `FileAttachment` A file attachment field's value, which holds each version Quickbase keeps. An empty field reads as None, so a [`FileAttachment`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileAttachment) always has at least one version. Download the contents with `table.download_file()`. **Attributes** - **`url`** `str` The file's path in Quickbase, relative to the realm. - **`versions`** `tuple[FileVersion, ...]` Each version Quickbase keeps, oldest first. - **`latest`** `FileVersion` The newest version. Sync example: ```python from pyquickbase import FileUpload upload = FileUpload("plan.txt", b"v1") tasks.update([{"Record ID#": 2, "Attachment": upload}]) task = tasks.get(2, select=["Attachment"]) attachment = task["Attachment"] print(attachment.latest.file_name, len(attachment.versions)) ``` Async example: ```python from pyquickbase import FileUpload upload = FileUpload("plan.txt", b"v1") await tasks.update([{"Record ID#": 2, "Attachment": upload}]) task = await tasks.get(2, select=["Attachment"]) attachment = task["Attachment"] print(attachment.latest.file_name, len(attachment.versions)) ``` Output: ```text plan.txt 1 ``` ### `FileVersion` One version of a file in a file attachment field. **Attributes** - **`number`** `int` The version's number, starting at 1. Numbers aren't reused after a delete. - **`file_name`** `str` The file's name. Quickbase stores names as sent, such as `../../x`, so don't use this as a path without cleaning it. - **`uploaded`** `datetime.datetime | None` When it was uploaded, or None if Quickbase didn't say. - **`creator`** `User | None` Who uploaded it, or None if the client's policy leaves it out. Sync example: ```python from pyquickbase import FileUpload upload = FileUpload("notes.txt", b"v1") tasks.update([{"Record ID#": 3, "Attachment": upload}]) task = tasks.get(3, select=["Attachment"]) latest = task["Attachment"].latest print(latest.number, latest.file_name) ``` Async example: ```python from pyquickbase import FileUpload upload = FileUpload("notes.txt", b"v1") await tasks.update([{"Record ID#": 3, "Attachment": upload}]) task = await tasks.get(3, select=["Attachment"]) latest = task["Attachment"].latest print(latest.number, latest.file_name) ``` Output: ```text 1 notes.txt ``` ## CSV and Excel files For the full picture, see [CSV and Excel files](https://matthew-williams-solutions.github.io/pyquickbase/guides/spreadsheets/). ### `FileRows` A file's rows, converted for a table's fields, from [`Table.rows_from_file()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#rows_from_file). Pass it to `insert()`, `update()`, `upsert()`, or `preview_update()`, which refuse it, writing nothing, while it has errors. Iterating it gives its rows. `repr` shows only its size, because values may be sensitive and reprs end up in logs. **Attributes** - **`rows`** `tuple[Mapping[str, Any], ...]` Each row that has a value, as `{field label: value}`. A blank cell is None, which leaves a field empty in `insert()`, and clears it in `update()` and `upsert()`. - **`row_numbers`** `tuple[int, ...]` Each row's number in the file, as for `FileRowError.row`, so `rows[0]` came from row `row_numbers[0]`. A row whose cells are all blank is left out. - **`columns`** `Mapping[str, str]` Which field each column was read into, as `{column: field label}`. - **`skipped`** `tuple[str, ...]` The columns that weren't read. - **`errors`** `tuple[FileRowError, ...]` Every value that can't be written. Fix these in the file, or map or skip their columns. - **`ok`** `bool` Whether every value can be written. #### `FileRows.raise_for_errors()` Raise [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError) listing the first errors, if there are any. ```python raise_for_errors() -> None ``` **Raises** - [`InvalidValueError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#InvalidValueError): The file has values that can't be written. Sync example: ```python import io text = io.StringIO('Title,Hours\nA,"1,234.5"\nB,lots\n') rows = tasks.rows_from_file(text) print(rows.rows[0]["Hours"], rows.ok) print(rows.columns) ``` Async example: ```python import io text = io.StringIO('Title,Hours\nA,"1,234.5"\nB,lots\n') rows = await tasks.rows_from_file(text) print(rows.rows[0]["Hours"], rows.ok) print(rows.columns) ``` Output: ```text 1234.5 False {'Title': 'Title', 'Hours': 'Hours'} ``` ### `FileRowError` A value in a file that can't be written to its field. **Attributes** - **`row`** `int` The row's number as a spreadsheet shows it, counting the header as row 1. - **`column`** `str` The file's column. - **`message`** `str` What's wrong, which never repeats the value. Sync example: ```python import io text = io.StringIO("Title,Due Date\nA,next week\n") rows = tasks.rows_from_file(text) for error in rows.errors: print(error.row, error.column) print(error.message) ``` Async example: ```python import io text = io.StringIO("Title,Due Date\nA,next week\n") rows = await tasks.rows_from_file(text) for error in rows.errors: print(error.row, error.column) print(error.message) ``` Output: ```text 2 Due Date isn't a date in the form YYYY-MM-DD. Set date_format for another. ``` ### `FileFormat` A file's format: `csv`, including tab-separated files, or `xlsx`. ```python FileFormat = Literal["csv", "xlsx"] ``` ### `FileLike` A path, or an open file: text for CSV, and binary for Excel. ```python FileLike = str | os.PathLike[str] | IO[str] | IO[bytes] ``` ### `ColumnMap` Which field each file column goes in, by label or ID, or None to skip it. ```python ColumnMap = Mapping[str, str | int | None] ``` ## Formula results For the full picture, see [Formulas](https://matthew-williams-solutions.github.io/pyquickbase/guides/formulas/). ### `FormulaResult` The result of `table.run_formula()`. **Attributes** - **`text`** `str` Exactly what Quickbase sent, which is always a string. - **`type`** `str` Quickbase's name for its type, such as `number`, `bool`, or `date`. - **`value`** `Any` The result as a Python value, converted only where that's safe. A `bool` becomes `True` or `False`, and a `number` becomes a `Decimal`, or None if either is empty. A `null` becomes None. Anything else stays as text, including dates and timestamps, because they use the app's display format and have no time zone. Sync example: ```python result = tasks.run_formula("1 + 1") print(result.type, result.text, result.value) ``` Async example: ```python result = await tasks.run_formula("1 + 1") print(result.type, result.text, result.value) ``` Output: ```text number 2 2 ``` ## Previews of writes For the full picture, see [Writing records](https://matthew-williams-solutions.github.io/pyquickbase/guides/writing/). ### `UpdatePreview` What `update()` would do if the records stayed as they are now. **Attributes** - **`changes`** `tuple[RecordChange, ...]` The records that would change, in the order of your rows. - **`unchanged_ids`** `tuple[int, ...]` The records that already hold these values. - **`missing_ids`** `tuple[int, ...]` The records that don't exist, or that the token can't see. `update()` would report these as failed rows. - **`events`** `tuple[AppEvent, ...] | None` The active webhooks, Quickbase actions, email notifications, and automations on the table, which the update may set off. Quickbase doesn't say which changes set each one off, so some may not run. None if they couldn't be read. Sync example: ```python preview = tasks.preview_update( [ {"Record ID#": 1, "Status": "Done"}, {"Record ID#": 2, "Status": "Open"}, {"Record ID#": 999, "Status": "Done"}, ] ) print(len(preview.changes)) print(preview.unchanged_ids, preview.missing_ids) ``` Async example: ```python preview = await tasks.preview_update( [ {"Record ID#": 1, "Status": "Done"}, {"Record ID#": 2, "Status": "Open"}, {"Record ID#": 999, "Status": "Done"}, ] ) print(len(preview.changes)) print(preview.unchanged_ids, preview.missing_ids) ``` Output: ```text 1 (2,) (999,) ``` ### `DeletePreview` What `delete()` would delete: a page of the matching records, and what may run. **Attributes** - **`records`** `tuple[Record, ...]` The first records that would be deleted. - **`total`** `int` How many records would be deleted. - **`skip`** `int` How many matching records came before this page. - **`columns`** `tuple[str, ...]` The labels of the fields it returned, in order. - **`events`** `tuple[AppEvent, ...] | None` The active webhooks, Quickbase actions, email notifications, and automations on the table, which deleting records may set off. Quickbase doesn't say which changes set each one off, so some may not run. None if they couldn't be read. Sync example: ```python from pyquickbase import F preview = tasks.preview_delete(F("Status") == "Done") print(preview.total, preview.events) ``` Async example: ```python from pyquickbase import F preview = await tasks.preview_delete(F("Status") == "Done") print(preview.total, preview.events) ``` Output: ```text 1 () ``` ### `RecordChange` What a write would change on one record. **Attributes** - **`record_id`** `int` The record's Record ID#. - **`fields`** `Mapping[str, FieldChange]` What would change, as `{field label: FieldChange}`. Sync example: ```python preview = tasks.preview_update( [{"Record ID#": 1, "Status": "Done"}] ) change = preview.changes[0] print(change.record_id, sorted(change.fields)) ``` Async example: ```python preview = await tasks.preview_update( [{"Record ID#": 1, "Status": "Done"}] ) change = preview.changes[0] print(change.record_id, sorted(change.fields)) ``` Output: ```text 1 ['Status'] ``` ### `FieldChange` One field's value before and after a write, with an empty field as None. `repr` hides both values, because they may be sensitive and reprs end up in logs. **Attributes** - **`before`** `Any` The value now. A file field shows the file's name, never its contents. - **`after`** `Any` The value the write would set. A file field shows the file's name here too. Sync example: ```python preview = tasks.preview_update( [{"Record ID#": 1, "Status": "Done"}] ) status = preview.changes[0].fields["Status"] print(status.before, status.after) ``` Async example: ```python preview = await tasks.preview_update( [{"Record ID#": 1, "Status": "Done"}] ) status = preview.changes[0].fields["Status"] print(status.before, status.after) ``` Output: ```text Open Done ``` ## Summaries For the full picture, see [Reading records](https://matthew-williams-solutions.github.io/pyquickbase/guides/reading/). ### `SummaryRow` One group of records, from `table.summarize()`. Values are keyed by field label. `repr` shows only the row's shape, because values may be sensitive and reprs end up in logs. **Attributes** - **`group`** `Mapping[str, Any]` The values that define this group, or empty without `group_by`. An empty value is None, and a user is their email. - **`count`** `int` How many records are in the group, including those with empty values. - **`sum`** `Mapping[str, float]` The sum of each `sum` field, which is 0 if every value is empty. - **`avg`** `Mapping[str, float | None]` The average of each `avg` field, or None if every value is empty. - **`median`** `Mapping[str, float | None]` The middle value of each `median` field, or the average of the two middle ones, or None if every value is empty. - **`std_dev`** `Mapping[str, float | None]` The sample standard deviation of each `std_dev` field, or None with fewer than two values. - **`min`** `Mapping[str, Any]` The lowest value of each `min` field, or None if every value is empty. - **`max`** `Mapping[str, Any]` The highest value of each `max` field, or None if every value is empty. - **`distinct_count`** `Mapping[str, int]` How many different values each `distinct_count` field has, leaving out empty ones. - **`change`** `PeriodChange | None` With `compare_periods`, how the group changed since the period before. None for the first period, and without `compare_periods`. #### `SummaryRow.statistic()` One statistic's values, by field label, such as `row.statistic("sum")`. ```python statistic(name: StatisticName) -> Mapping[str, Any] ``` **Parameters** - **`name`** `StatisticName`, required The statistic, such as `sum` or `median`. **Returns** `Mapping[str, Any]` Its values, by field label, which is empty if the summary didn't ask for it. Sync example: ```python rows = tasks.summarize( "Status", sum="Hours", median="Hours" ) for row in rows: print(row.group["Status"], row.count) print(row.sum["Hours"], row.median["Hours"]) ``` Async example: ```python rows = await tasks.summarize( "Status", sum="Hours", median="Hours" ) for row in rows: print(row.group["Status"], row.count) print(row.sum["Hours"], row.median["Hours"]) ``` Output: ```text Done 1 6.0 6.0 Open 2 20.0 10.0 ``` ### `PeriodChange` How a group changed since the period before it, from `summarize(compare_periods=True)`. **Attributes** - **`previous`** `str` The period it's compared with, such as `2026-02` for `2026-03`. - **`count`** `int` How many more records there are, which is negative for fewer. - **`values`** `Mapping[str, Mapping[str, float | None]]` How much each numeric statistic rose, as `{"sum": {"Hours": 4.0}}`, or None where either period has no value. A period without records counts as 0 for `sum` and `distinct_count`, and has no value for the others. Sync example: ```python from pyquickbase import Period rows = tasks.summarize( Period("Due Date", "month"), sum="Hours", compare_periods=True, ) for row in rows: if row.change: print(row.group["Due Date"], row.change.previous) print(row.change.count, row.change.values["sum"]) ``` Async example: ```python from pyquickbase import Period rows = await tasks.summarize( Period("Due Date", "month"), sum="Hours", compare_periods=True, ) for row in rows: if row.change: print(row.group["Due Date"], row.change.previous) print(row.change.count, row.change.values["sum"]) ``` Output: ```text 2027-02 2027-01 0 {'Hours': 4.0} ``` ### `Period` Group by a date or timestamp field's day, week, month, quarter, or year. Groups are labeled `2026-03-14`, `2026-W11` (an ISO week), `2026-03`, `2026-Q1`, and `2026`, so they sort in order. ```python Period( field: str | int | F, unit: PeriodUnit, tz: datetime.tzinfo = datetime.timezone.utc, ) ``` **Parameters** - **`field`** `str | int | F`, required The date or timestamp field, by label, ID, or field object. - **`unit`** `PeriodUnit`, required `day`, `week`, `month`, `quarter`, or `year`. - **`tz`** `datetime.tzinfo`, default `datetime.timezone.utc` The time zone that decides a timestamp's date. It has no effect on a date field. **Raises** - [`QueryError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QueryError): `unit` isn't one of these, or `field` isn't a field. Sync example: ```python from pyquickbase import Period rows = tasks.summarize(Period("Due Date", "month")) for row in rows: print(row.group["Due Date"], row.count) ``` Async example: ```python from pyquickbase import Period rows = await tasks.summarize(Period("Due Date", "month")) for row in rows: print(row.group["Due Date"], row.count) ``` Output: ```text 2027-01 1 2027-02 1 None 1 ``` ### `PeriodUnit` How much time each group of a [`Period`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Period) covers. ```python PeriodUnit = Literal[ "day", "week", "month", "quarter", "year" ] ``` ### `StatisticName` A value `summarize()` works out from one field's values in each group. ```python StatisticName = Literal[ "sum", "avg", "median", "std_dev", "min", "max", "distinct_count", ] ``` ### `SummaryGroups` What [`Table.summarize()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#summarize) can group by. ```python SummaryGroups = ( str | int | F | Period | Sequence[str | int | F | Period] ) ```