Pygrister API reference.

Pygrister: a Python client for the Grist API.

Grist is a relational spreadsheet with tons of batteries included. The Grist API allows you to programmatically retrieve/update your data stored on Grist, and manipulate most of the basic Grist objects, such as workspaces, documents, user permissions and so on.

Pygrister is a basic Grist client, wrapping all the documented APIs. Pygrister keeps track of some configuration for you, remembering your team site, workspace, working document, so that you don’t have to type in the boring stuff every time. Apart from that and little else, Pygrister is rather low-level: it will call the api and retrieve the response “as is”. If the api call is malformed, you will simply receive a bad HTTP status code.

Pygrister will not attempt to convert sent and received data types: however, it will execute custom converter functions, if provided.

Basic usage goes as follows:

from pygrister.api import GristApi

grist = GristApi()
# list users/permissions for the current document
status_code, response = grist.list_doc_users()
# fetch all rows in a table
status_code, response = grist.see_records('Table1') 
# add a column to a table
cols = [{'id': 'age', 'fields': {'label':'age', 'type': 'Int'}}]
status_code, response = grist.add_cols('Table1', cols) 

You should read the documentation first, to learn about the basic Pygrister concepts, patterns and configurations. However, the api call functions themselves are not documented in Pygrister: see the Grist API reference documentation for details about each api signature, and browse the Pygrister test suite for more usage examples.

pygrister.api.get_config() dict[str, str]

the global, “static” configuration

pygrister.api.check_safemode(funct)

If Pygrister is in safemode, no writing API call will pass through.

class pygrister.api.Paginator(provider, start, items, lenname, res_transform, **query_args)

A simple iterable object to wrap Api calls that needs pagination.

The main GristApi class.

class pygrister.api.GristApi(config: dict[str, str] | None = None, in_converter: dict | None = None, out_converter: dict | None = None, custom_configurator: Configurator | None = None, custom_apicaller: ApiCaller | None = None)
in_converter

converters for input data

out_converter

converters for output data

reconfig(config: dict[str, str] | None = None) None

Reload the configuration options.

A shortcut for self.configurator.recongif(config).

update_config(config: dict[str, str]) None

Edit the configuration options.

A shortcut for self.configurator.update_config(config).

make_server(team_name: str = '') str

Construct the “server” part of the API url, up to “/api”.

A shortcut for self.configurator.make_server(team_name).

open_session() None

Open a Requests sessions for all subsequent Api calls.

A shortcut for self.apicaller.open_session().

close_session() None

Close an open session, if any.

A shortcut for self.apicaller.close_session().

property ok: bool

False if a HTTP error occurred in the response.

If no response was retrieved, ok will be False by default. A shortcut for self.apicaller.ok.

inspect(sep: str = '\n', max_content: int = 1000) str

Collect info on the last api call that was requested (and possibly responded to) by the server.

Use sep to set a custom separator between elements, and max_content to limit the response content size.

A shortcut for self.caller.inspect().

list_service_accounts() tuple[int, Any]

Implement GET /service-accounts.

If successful, response will be a list[dict] of account details.

add_service_account(expire: str, label: str = '', description: str = '') tuple[int, Any]

Implement POST /service-accounts.

If successful, response will be a tuple of service id and key.

see_service_account(service_id: int) tuple[int, Any]

Implement GET /service-accounts/{saId}.

If successful, response will be a dict of service details.

update_service_account(service_id: int, expire: str = '', label: str = '', description: str = '') tuple[int, Any]

Implement PATCH /service-accounts/{saId}.

If successful, response will be None.

delete_service_account(service_id: int) tuple[int, Any]

Implement DELETE /service-accounts/{saId}.

If successful, response will be None.

update_service_account_key(service_id: int) tuple[int, Any]

Implement POST /service-accounts/{saId}/apikey.

If successful, response will be the new key as str.

delete_service_account_key(service_id: int) tuple[int, Any]

Implement DELETE /service-accounts/{saId}/apikey.

If successful, response will be None.

see_user(user_id: int) tuple[int, Any]

Implement GET /scim/v2/Users/{userId}.

If successful, response will be a dict of user details. If scim is not enabled, will return Http 501.

see_myself() tuple[int, Any]

Implement GET /scim/v2/Me.

If successful, response will be a dict of logged-in user’s details. If scim is not enabled, will return Http 501.

see_profile() tuple[int, Any]

Implement GET /profile/user.

If successful, response will be a dict of current user profile data.

update_profile_name(name: str) tuple[int, Any]

Implement POST /profile/user/name.

If successful, response will be None.

update_profile_locale(locale: str) tuple[int, Any]

Implement POST /profile/user/locale.

If successful, response will be None.

see_apikey() tuple[int, Any]

Implement GET /profile/apikey.

If successful, response will be the key as str.

new_apikey(force: bool = True) tuple[int, Any]

Implement POST /profile/apikey.

If successful, response will be the key as str.

delete_apikey() tuple[int, Any]

Implement DELETE /profile/apikey.

If successful, response will be None.

see_session() tuple[int, Any]

Implement GET /session/access/active.

If successful, response will be a dict of session info.

see_session_users() tuple[int, Any]

Implement GET /session/access/all.

If successful, response will be a dict of session and users info.

update_session_user(email: str, team_id: str = '') tuple[int, Any]

Implement POST /session/access/active.

If successful, response will be None.

list_users(start: int = 1, chunk: int = 10, filter: str = '') Paginator

Implement GET /scim/v2/Users.

This is a paginated api: return an iterable object which, in turn, will retrieve chunk users at a time, as a list[dict].

list_users_raw(start: int = 1, chunk: int = 10, filter: str = '') tuple[int, Any]

Implement GET /scim/v2/Users.

If successful, response will be a dict of user data. If scim is not enabled, will return Http 501.

add_user(username: str, emails: list[str], formatted_name: str = '', display_name: str = '', lang: str = 'en', locale: str = 'en', photos: list[str] | None = None, schemas: list[str] | None = None) tuple[int, Any]

Implement POST /scim/v2/Users.

Note: schemas defaults to ['urn:ietf:params:scim:schemas:core:2.0:User']

If successful, response will be the user id as an int. If scim is not enabled, will return Http 501.

update_user_override(user_id: int, username: str, emails: list[str], formatted_name: str = '', display_name: str = '', lang: str = 'en', locale: str = 'en', photos: list[str] | None = None, schemas: list[str] | None = None) tuple[int, Any]

Implement PUT /scim/v2/Users/{userId}.

Note: schemas defaults to ['urn:ietf:params:scim:schemas:core:2.0:User']

If successful, response will be None. If scim is not enabled, will return Http 501.

update_user(user_id: int, operations: list[dict], schemas: list[str] | None = None) tuple[int, Any]

Implement PATCH /scim/v2/Users/{userId}.

Note: schemas defaults to ['urn:ietf:params:scim:api:messages:2.0:PatchOp']

If successful, response will be None. If scim is not enabled, will return Http 501.

enable_user(user_id: int, enable: bool = True)

Implement POST /users/{userId}/enable and POST /users/{userId}/disable (accessible to admins only).

If successful, response will be None.

delete_user(user_id: int)

Implement DELETE /scim/v2/Users/{userId}.

If successful, response will be None. If scim is not enabled, will return Http 501.

delete_myself(user: str, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement DELETE /users/{userId}.

Note: since this is the only /users endpoint implemented by Grist right now, and since it is of little help (you can only delete your own account), we choose not to implement this one, and leave it here as a stub.

search_users(start: int = 1, chunk: int = 10, sort: str = '', asc: bool = True, filter: str = '', attrib: list[str] | None = None, no_attrib: list[str] | None = None, schemas: list[str] | None = None) Paginator

Implement POST /scim/v2/Users/.search.

Note: schemas defaults to ['urn:ietf:params:scim:api:messages:2.0:BulkRequest']

This is a paginated api: return an iterable object which, in turn, will retrieve chunk users at a time, as a list[dict].

search_users_raw(start: int = 1, chunk: int = 10, sort: str = '', asc: bool = True, filter: str = '', attrib: list[str] | None = None, no_attrib: list[str] | None = None, schemas: list[str] | None = None) tuple[int, Any]

Implement POST /scim/v2/Users/.search.

Note: schemas defaults to ['urn:ietf:params:scim:api:messages:2.0:BulkRequest']

If successful, response will be a dict of user data. If scim is not enabled, will return Http 501.

bulk_users(operations: list[dict], schemas: list[str] | None = None) tuple[int, Any]

Implement POST /scim/v2/Bulk.

Note: schemas defaults to ['urn:ietf:params:scim:api:messages:2.0:BulkRequest']

If successful, response will be a list[int] of status codes for each operation (inspect GristApi.resp_content for details, if any operations resulted in a bad status code). If scim is not enabled, will return Http 501.

see_scim_schemas() tuple[int, Any]

Implement GET /scim/v2/Schemas.

If successful, response will a dict of scim schemas. If scim is not enabled, will return Http 501.

see_scim_config() tuple[int, Any]

Implement GET /scim/v2/ServiceProviderConfig.

If successful, response will a dict of scim provider configuration. If scim is not enabled, will return Http 501.

see_scim_resources() tuple[int, Any]

Implement GET /scim/v2/ResourceTypes.

If successful, response will a dict of scim resources. If scim is not enabled, will return Http 501.

see_group(group_id: int) tuple[int, Any]

Implement GET /scim/v2/Groups/{groupId}.

If successful, response will be a dict of group details. If scim is not enabled, will return Http 501.

list_groups(start: int = 1, chunk: int = 10, filter: str = '') Paginator

Implement GET /scim/v2/Groups.

This is a paginated api: return an iterable object which, in turn, will retrieve chunk groups at a time, as a list[dict].

list_groups_raw(start: int = 1, chunk: int = 10, filter: str = '') tuple[int, Any]

Implement GET /scim/v2/Groups.

If successful, response will be a dict of groups data. If scim is not enabled, will return Http 501.

add_group(name: str, members: list | None = None, schemas: list[str] | None = None) tuple[int, Any]

Implement POST /scim/v2/Groups.

Note: schemas defaults to ['urn:ietf:params:scim:schemas:core:2.0:Group']

If successful, response will be the group id as an int. If scim is not enabled, will return Http 501.

update_group_override(group_id: int, name: str, members: list | None = None, schemas: list[str] | None = None) tuple[int, Any]

Implement PUT /scim/v2/Groups/{userId}.

Note: schemas defaults to ['urn:ietf:params:scim:schemas:core:2.0:Group']

If successful, response will be None. If scim is not enabled, will return Http 501.

update_group(group_id: int, operations: list[dict], schemas: list[str] | None = None) tuple[int, Any]

Implement PATCH /scim/v2/Groups/{userId}.

Note: schemas defaults to ['urn:ietf:params:scim:api:messages:2.0:PatchOp']

If successful, response will be None. If scim is not enabled, will return Http 501.

delete_group(group_id: int)

Implement DELETE /scim/v2/Groups/{groupId}.

If successful, response will be None. If scim is not enabled, will return Http 501.

search_groups(start: int = 1, chunk: int = 10, sort: str = '', asc: bool = True, filter: str = '', attrib: list[str] | None = None, no_attrib: list[str] | None = None, schemas: list[str] | None = None) Paginator

Implement POST /scim/v2/Groups/.search.

Note: schemas defaults to ['urn:ietf:params:scim:api:messages:2.0:SearchRequest']

This is a paginated api: return an iterable object which, in turn, will retrieve chunk groups at a time, as a list[dict].

search_groups_raw(start: int = 1, chunk: int = 10, sort: str = '', asc: bool = True, filter: str = '', attrib: list[str] | None = None, no_attrib: list[str] | None = None, schemas: list[str] | None = None) tuple[int, Any]

Implement POST /scim/v2/Users/.search.

Note: schemas defaults to ['urn:ietf:params:scim:api:messages:2.0:SearchRequest']

If successful, response will be a dict of group data. If scim is not enabled, will return Http 501.

see_role(role_id: int) tuple[int, Any]

Implement GET /scim/v2/Roles/{roleId}.

If successful, response will be a dict of group details. If scim is not enabled, will return Http 501.

list_roles(start: int = 1, chunk: int = 10, filter: str = '') Paginator

Implement GET /scim/v2/Roles.

This is a paginated api: return an iterable object which, in turn, will retrieve chunk groups at a time, as a list[dict].

list_roles_raw(start: int = 1, chunk: int = 10, filter: str = '') tuple[int, Any]

Implement GET /scim/v2/Roles.

If successful, response will be a dict of groups data. If scim is not enabled, will return Http 501.

update_role_override(role_id: int, members: list | None = None, schemas: list[str] | None = None) tuple[int, Any]

Implement PUT /scim/v2/Roles/{rolesId}.

Note: schemas defaults to ['urn:ietf:params:scim:schemas:Grist:1.0:Role']

If successful, response will be None. If scim is not enabled, will return Http 501.

update_role(role_id: int, operations: list[dict], schemas: list[str] | None = None) tuple[int, Any]

Implement PATCH /scim/v2/Roles/{roleId}.

Note: schemas defaults to ['urn:ietf:params:scim:api:messages:2.0:PatchOp']

If successful, response will be None. If scim is not enabled, will return Http 501.

list_team_sites() tuple[int, Any]

Implement GET /orgs.

If successful, response will be a list[dict] of site details.

see_team(team_id: str = '') tuple[int, Any]

Implement GET /orgs/{orgId}.

If successful, response will be a dict of site details.

see_team_usage(team_id: str = '') tuple[int, Any]

Implement GET /orgs/{orgId}/usage.

If successful, response will be a dict of usage details.

update_team(new_name: str, team_id: str = '') tuple[int, Any]

Implement PATCH /orgs/{orgId}.

If successful, response will be None. Note that renaming a team will not change the subdomain too!

delete_team_old(team_id: str = '') tuple[int, Any]

Implement DELETE /orgs/{orgId}.

If successful, response will be None. Note: disabled in Grist 1.7.4, will return Http 410 instead.

delete_team(team_id: str = '') tuple[int, Any]

Implement DELETE /orgs/{orgId}/{name}.

If successful, response will be None.

list_team_users(team_id: str = '') tuple[int, Any]

Implement GET /orgs/{orgId}/access.

If successful, response will be a list[dict] of users.

update_team_users(users: dict[str, str], team_id: str = '') tuple[int, Any]

Implement PATCH /orgs/{orgId}/access.

If successful, response will be None.

list_workspaces(team_id: str = '') tuple[int, Any]

Implement GET /{orgId}/workspaces.

If successful, response will be a list[dict] of workspaces.

add_workspace(name: str, team_id: str = '') tuple[int, Any]

Implement POST /{orgId}/workspaces.

If successful, response will be the workspace id as an int.

see_workspace(ws_id: int = 0) tuple[int, Any]

Implement GET /workspaces/{workspaceId}.

If successful, response will be a dict of workspace details.

update_workspace(new_name: str, ws_id: int = 0) tuple[int, Any]

Implement PATCH /workspaces/{workspaceId}.

If successful, response will be None.

delete_workspace(ws_id: int = 0) tuple[int, Any]

Implement DELETE /workspaces/{workspaceId}.

If successful, response will be None.

trash_workspace(remove: bool = True, permanent: bool = False, ws_id: int = 0) tuple[int, Any]

Implement POST /workspaces/{workspaceId}/remove and POST /workspaces/{workspaceId}/unremove.

If remove and permanent, ws will be deleted instead. If remove=False, permanent is ignored and ws will be recovered.

If successful, response will be None.

list_workspace_users(ws_id: int = 0) tuple[int, Any]

Implement GET /workspaces/{workspaceId}/access.

If successful, response will be a list[dict] of users.

update_workspace_users(users: dict[str, str], ws_id: int = 0) tuple[int, Any]

Implement PATCH /workspaces/{workspaceId}/access.

If successful, response will be None.

add_doc(name: str, pinned: bool = False, ws_id: int = 0) tuple[int, Any]

Implement POST /workspaces/{workspaceId}/docs.

If successful, response will be the doc id as a str.

see_doc(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}.

If successful, response will be a dict of doc details.

update_doc(new_name: str = '', pinned: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PATCH /docs/{docId}.

If successful, response will be None.

delete_doc(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement DELETE /docs/{docId}.

If successful, response will be None.

trash_doc(remove: bool = True, permanent: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/remove and POST /docs/{docId}/unremove.

If remove and permanent, doc will be deleted instead. If remove=False, permanent is ignored and doc will be recovered.

If successful, response will be None.

list_doc_history(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/states.

If successful, response will be a list[dict] of history states.

compare_doc_history(left: str = '', right: str = '', max_rows: int = 10, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/compare.

left, right, if left empty, default to HEAD. If successful, response will be a dict of comparision results.

delete_doc_history(keep: int = 1, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/states/remove.

If successful, response will be None.

fork_doc(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/fork.

If successful, response will be a dict of fork identifiers.

compare_docs(other: str, details: bool = False, max_rows: int = 10, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/compare/{docId2}.

If successful, response will be a dict of comparision results.

list_proposals(outgoing: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/proposals.

Note: if outgoing=False, list change proposals to doc_id from its forks, which should make more sense in Pygrister. If successful, response will be a list of proposals.

add_proposal(retracted: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/propose.

Note that doc_id must be a fork for this to work. If successful, response will be the proposal Id as an int.

apply_proposal(proposal_id: int, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/proposals/{proposalId}/apply.

If successful, response will be a dict of applied changes.

move_doc(ws_id: int, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PATCH /docs/{docId}/move.

If successful, response will be None.

copy_doc(target_ws: int, target_name: str, template: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/copy.

If successful, response will be the str id of the copied doc.

replace_doc(source_doc: str = '', source_snapshot: str = '', reset_tutorial: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/replace.

If successful, response will be None.

reload_doc(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/force-reload.

If successful, response will be None.

flush_doc(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/flush.

If successful, response will be a bool of the flush outcome.

assign_doc(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/assign.

If successful, response will be a bool of the outcome.

enable_doc(enable: bool = True, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/enable and POST /docs/{docId}/disable (accessible to admins only).

If successful, response will be None.

pin_doc(pin: bool = True, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PATCH /docs/{docId}/pin and PATCH /docs/{docId}/unpin.

If successful, response will be None.

set_recovery_mode(mode: bool = True, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/recover.

If successful, response will be None.

list_doc_users(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/access.

If successful, response will be a list[dict] of users.

list_viewas_users(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/usersForViewAs.

If successful, response will be a dict with ``list``s of users divided by category.

update_doc_users(users: dict[str, str], max: str = 'owners', doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PATCH /docs/{docId}/access.

If successful, response will be None.

download_sqlite(filename: Path, nohistory: bool = False, template: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/download.

If successful, response will be None.

upload_sqlite(filename: Path, target_docname: str, ws_id: int = 0) tuple[int, Any]

Implement POST /workspaces/{workspaceId}/import.

If successful, response will be the imported doc Id as str.

download_table(filename: Path, table_id: str, header: str = 'label', format: str = 'csv', doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/download/csv|tsv|dsv|xlsx.

format must be one of csv, tsv, dsv, xlsx. If successful, response will be None.

download_excel(filename: Path, table_id: str, header: str = 'label', doc_id: str = '', team_id: str = '') tuple[int, Any]

Deprecated, will be removed soon. Use download_table instead.

download_csv(filename: Path, table_id: str, header: str = 'label', doc_id: str = '', team_id: str = '') tuple[int, Any]

Deprecated, will be removed soon. Use download_table instead.

download_schema(table_id: str, header: str = 'label', filename: Path | None = None, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/download/table-schema.

If successful, schema will be returned as json; pass the filename param to have it downloaded as a json file instead.

list_snapshots(raw: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/snapshots.

If successful, response will be a list[dict] of snapshot data.

delete_snapshots(snapshot_ids: list[str], doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/snapshots/remove.

If successful, response will be None.

see_timing(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/timing.

If successful, response will be a dict of formula timing data.

start_timing(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/timing/start.

If successful, response will be None.

stop_timing(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/timing/stop.

If successful, response will be a dict of formula timing data.

list_records(table_id: str, filter: dict | None = None, sort: str = '', limit: int = 0, hidden: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/tables/{tableId}/records.

If a converter is found for this table, data conversion will be attempted. If successful, response will be a list of “Pygrister records with id” (see docs).

add_records(table_id: str, records: list[dict], noparse: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/tables/{tableId}/records.

records: a list of “Pygrister records without id” (see docs). If a converter is found for this table, data conversion will be attempted. If successful, response will be a list[int] of added record ids.

update_records(table_id: str, records: list[dict], noparse: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PATCH /docs/{docId}/tables/{tableId}/records.

records: a list of “Pygrister records with id” (see docs). If a converter is found for this table, data conversion will be attempted. If successful, response will be None.

add_update_records(table_id: str, records: list[dict], noparse: bool = False, onmany: str = 'first', noadd: bool = False, noupdate: bool = False, allow_empty_require: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PUT /docs/{docId}/tables/{tableId}/records.

If a converter is found for this table, data conversion will be attempted. If successful, response will be None.

list_tables(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/tables.

If successful, response will be a list[dict] of tables.

add_tables(tables: list[dict], doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/tables.

If successful, response will be a list[str] of added table ids.

update_tables(tables: list[dict], doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PATCH /docs/{docId}/tables.

If successful, response will be None.

list_cols(table_id: str, hidden: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/tables/{tableId}/columns.

If successful, response will be a list[dict] of columns.

add_cols(table_id: str, cols: list[dict], doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/tables/{tableId}/columns.

If successful, response will be a list[str] of added col ids.

update_cols(table_id: str, cols: list[dict], doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PATCH /docs/{docId}/tables/{tableId}/columns.

If successful, response will be None.

add_update_cols(table_id: str, cols: list[dict], noadd: bool = True, noupdate: bool = True, replaceall: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PUT /docs/{docId}/tables/{tableId}/columns.

If successful, response will be None.

delete_column(table_id: str, col_id: str, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement DELETE /docs/{docId}/tables/{tableId}/columns/{colId}.

If successful, response will be None.

delete_rows(table_id: str, rows: list[int], doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/tables/{tableId}/data/delete.

If successful, response will be None.

list_attachments(filter: dict | None = None, sort: str = '', limit: int = 0, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/attachments.

If successful, response will be a list[dict] of attachments.

upload_attachment(filename: Path, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/attachments for one file only.

This is deprecated and redirects to upload_attachments which you should use instead.

upload_attachments(filenames: list[Path], doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/attachments.

If successful, response will be a list[int] of attachments ids.

upload_restore_attachments(filename: Path, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/attachments/archive.

filename: must be a tar file. If successful, response will be a summary dict of attachments used.

see_attachment(attachment_id: int, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/attachments/{attachmentId}.

If successful, response will be a dict of attachment metadata.

download_attachment(filename: Path, attachment_id: int, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/attachments/{attachmentId}/download.

If successful, response will be None.

download_attachments(filename: Path | None = None, format: str = 'tar', doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/attachments/archive.

filename: must be a file name matching with format and defaults to doc_<doc_id>_attachments.<format>. If successful, response will be None.

delete_unused_attachments(expired_only: bool = False, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/attachments/removeUnused.

If successful, response will be None.

see_attachment_store(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/attachments/store.

If successful, response will be either external or internal.

update_attachment_store(internal_store: bool = True, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/attachments/store.

Pass internal_store=True to set internal storage, False to set external storage. If successful, response will be None. Passing False will return Http 400 if external storage is not configured.

list_store_settings(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/attachments/stores.

If successful, response will be a list of external storage settings.

transfer_attachments(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/attachments/transferAll.

If successful, response will be a dict of transfer details. If external storage is not configured, will return Http 404.

see_transfer_status(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/attachments/transferStatus.

If successful, response will be a dict of transfer details. If external storage is not configured, will return Http 404.

verify_attachment_usage(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/attachments/updateUsed.

If successful, response will be None.

verify_attachment_files(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/attachments/verifyFiles.

If successful, response will be None.

list_webhooks(doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/webhooks.

If successful, response will be a list[dict] of webhooks.

add_webhooks(webhooks: list[dict], doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/webhooks.

If successful, response will be a list[str] of added webhook ids.

update_webhook(webhook_id: str, webhook: dict, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement PATCH /docs/{docId}/webhooks/{webhookId}.

If successful, response will be None.

delete_webhook(webhook_id: str, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement DELETE /docs/{docId}/webhooks/{webhookId}.

If successful, response will be None.

empty_payloads_queue(webhook_id: str = '', doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement DELETE /docs/{docId}/webhooks/queue and DELETE /docs/{docId}/webhooks/queue/{webhookId}.

Clear all pending payloads, or pass webhook_id to clear queue for a specific webhook. If successful, response will be None.

list_templates() tuple[int, Any]

Implement GET /templates.

If successful, response will be a list[dict] of workspaces with templates.

see_template(template_id: str) tuple[int, Any]

Implement GET /templates/{templateId}.

If successful, response will be a dict of template details.

list_widgets() tuple[int, Any]

Implement GET /widgets.

If successful, response will be a list[dict] of widgets data.

see_form(section_id: int, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/forms/{viewSectionId}.

If successful, response will be a dict of form details.

run_sql(sql: str, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement GET /docs/{docId}/sql.

If a converter named “sql” is found, data conversion will be attempted. If successful, response will be a list of “Pygrister records” (see docs) with or without id, depending on the query.

run_sql_with_args(sql: str, qargs: list, timeout: int = 1000, doc_id: str = '', team_id: str = '') tuple[int, Any]

Implement POST /docs/{docId}/sql.

If a converter named “sql” is found, data conversion will be attempted. If successful, response will be a list of “Pygrister records” (see docs) with or without id, depending on the query.

Configuration mechanism.

Pygrister configuration engine.

The Configurator class here is responsible for parsing, applying and changing configuration at runtime. The main GristApi class will create and load its own default instance of Configurator at instantiation time.

However, you may subclass Configurator and provide your own custom mechanism:

>>> from pygrister.api import GristApi
>>> from pygrister.config import Configurator
>>> class MyConfig(Configurator):
...     pass   # do your thing here
...
>>> cfg = MyConfig()
>>> grist = GristApi(custom_configurator=cfg)
pygrister.config.apikey2output(apikey: str) str

Obfuscate the secret Grist API key for output printing.

Api call engine.

Pygrister Api call engine.

The ApiCaller class here is responsible for actually posting a Grist Api call. The main GristApi class will create and load its own default instance of ApiCaller at instantiation time.

However, you may subclass ApiCaller and provide your own custom mechanism:

>>> from pygrister.api import GristApi
>>> from pygrister.apicaller import ApiCaller
>>> class MyCaller(ApiCaller):
...     pass   # do your thing here
...
>>> apicall = MyCaller()
>>> grist = GristApi(custom_apicaller=apicall)
pygrister.apicaller.Apiresp

the return type of all api call functions

alias of tuple[int, Any]

class pygrister.apicaller.ApiCaller(configurator: Configurator | None = None, request_options: dict | None = None)

The engine for posting a call to the Grist Apis.

session

Requests session object, or None

request_options

other options to pass to Requests

apicalls: int

total number of API calls

dry_run: bool

prepare, do not post request

request: PreparedRequest | None

last request posted

response: Response | None

last response retrieved

property ok: bool

False if a HTTP error occurred in the response.

Also, if no response was retrieved, will be False by default.

open_session() None

Open a Requests sessions for all subsequent Api calls.

close_session() None

Close an open session, if any.

response_as_json() str

Return the response content as (unicode) parsable json.

apicall(url: str, method: str = 'GET', headers: dict | None = None, params: dict | None = None, json: dict | None = None, filename: Path | None = None, upload_files: list | dict | None = None) tuple[int, Any]

The engine responsible for actually calling the Apis.

Return a Apiresp-type tuple (status_code, resp_content) where “resp_content” is a Json-decoded Python object (usually a dict, but that depends on the the specific Grist Api return value and status code).

May throw any requests.RequestException that is not HTTP- or Json- related, if a server problem occurred. Will throw requests.HTTPError if the status code is >=300 and the config key GRIST_RAISE_ERROR is set (default). Should never throw requests.JsonDecodeError.

The ok property will be False if errors occurred.

inspect(sep: str = '\n', max_content: int = 1000) str

Collect info on the last api call that was requested (and possibly responded to) by the server.

Use sep to set a custom separator between elements, and max_content to limit request/response body’s content size.

Intended for debug: add a print(self.inspect()) right after the call to inspect. Works even if the server returned a “bad” status code. If server did not respond, only request data will be recorded.

Pygrister exceptions.

Pygrister exception hierarchy.

Exceptions listed here can be raised by Pygrister, and they concern its internal functioning. Note that Grist API calls themselves, however, are delegated to Requests, and will (much more frequently!) throw the exceptions provided by Requests.

exception pygrister.exceptions.GristApiException

The base GristApi exception.

exception pygrister.exceptions.GristApiNotConfigured

A configuration error occurred.

exception pygrister.exceptions.GristApiNotImplemented

This API is not yet implemented by Pygrister.

exception pygrister.exceptions.GristApiInSafeMode

Pygrister is in safe mode, no writing to the db is possible.