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.MAXSAVEDRESP = 5000¶
max length of resp. content, saved for inspection
- pygrister.api.SAVEBINARYRESP = False¶
if binary resp. content should be saved for inspection
- 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.
- pygrister.api.Apiresp¶
the return type of all api call functions
alias of
tuple
[int
,Any
]
- 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, request_options: dict | None = None, custom_configurator: Configurator | None = None)¶
- apicalls: int¶
total number of API calls
- ok: bool¶
if an HTTPError occurred
- req_url: str¶
last request url
- req_body: str¶
last request body
- req_headers: dict¶
last request headers
- req_method: str¶
last request method
- resp_content: str | bytes¶
last response content
- resp_code: str¶
last response status code
- resp_reason: str¶
last response status reason
- resp_headers: dict¶
last reponse headers
- session¶
Requests session object, or None
- in_converter¶
converters for input data
- out_converter¶
converters for output data
- request_options¶
other options to pass to request
- 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.
- close_session() None ¶
Close an open session, if any.
- apicall(url: str, method: str = 'GET', headers: dict | None = None, params: dict | None = None, json: dict | None = None, filename: str = '', upload_files: list | None = None) tuple[int, Any] ¶
The engine responsible for actually calling the Apis.
- inspect() str ¶
Collect info on the last api call that was responded to by the server.
Intended for debug: add a
print(self.inspect())
right after the call to inspect. Works even if the server returned a “bad” status code (aka HTTPError). Does not work if the call itself was not successful (eg., timed out).
- 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.
- 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 alist[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.
- 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 alist[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 (inspectGristApi.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.
- 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.
- 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(team_id: str = '') tuple[int, Any] ¶
Implement DELETE
/orgs/{orgId}
.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
.
- 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
.
- delete_doc_history(keep: int = 0, doc_id: str = '', team_id: str = '') tuple[int, Any] ¶
Implement POST
/docs/{docId}/states/remove
.If successful, response will be
None
.
- 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
.
- reload_doc(doc_id: str = '', team_id: str = '') tuple[int, Any] ¶
Implement POST
/docs/{docId}/force-reload
.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.
- 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: str, 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
.
- download_excel(filename: str, table_id: str, header: str = 'label', doc_id: str = '', team_id: str = '') tuple[int, Any] ¶
Implement GET
/docs/{docId}/download/xlsx
.If successful, response will be
None
.
- download_csv(filename: str, table_id: str, header: str = 'label', doc_id: str = '', team_id: str = '') tuple[int, Any] ¶
Implement GET
/docs/{docId}/download/csv
.If successful, response will be
None
.
- download_schema(table_id: str, header: str = 'label', filename: str = '', 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_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 alist[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 beNone
.
- 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: str, 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, 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: str, doc_id: str = '', team_id: str = '') tuple[int, Any] ¶
Implement POST
/docs/{docId}/attachments/archive
.filename
: must be a file name without extension of a tar file. If successful, response will be a summarydict
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: str, 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: str = '', format: str = 'tar', doc_id: str = '', team_id: str = '') tuple[int, Any] ¶
Implement GET
/docs/{docId}/attachments/archive
.filename
: must be a file name without extension and defaults todoc_<doc_id>_attachments.<format>
. If successful, response will beNone
.
- 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
orinternal
.
- 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 beNone
. PassingFalse
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.
- get_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.
- 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(doc_id: str = '', team_id: str = '') tuple[int, Any] ¶
Implement DELETE
/docs/{docId}/webhooks/queue
.If successful, response will be
None
.
- 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.
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.