Pygrister configuration.

In everyday use, you will need Pygrister to push/pull data from a single document and very little else. Working with multiple documents, workspaces and sites is rarely needed. Pygrister comes with a configuration system to register your defaults for common operations, while still allowing you to switch gears at any time.

For example, to retrieve data from a table, you would call the Grist API endpoint as

https://<myteam>.getgrist.com/api/docs/<docID>/tables/<tableID>/records

thus providing every time also your team and document IDs. In Pygrister, you’ll have those values already stored in the configuration, so it’s just a matter of calling

grist = GristApi()
grist.list_records('Table1')

However, when you need to specify a different team/document, Pygrister allows you this more verbose call too:

grist.list_records('Table1', doc_id='mydoc', team_id='myteam')

Where configuration is stored.

Static configuration.

Pygrister configuration is a set of key/value pairs stored

  • first, in a config.py file situated in the installation directory, along with the other Python modules. This is intended to provide sensible default values for each configuration key: you should never modify this file directly;

  • then, in a ~/.gristapi/config.json file that you can leave in your home directory to override the defaults with your own preferences. You are free to put there just the keys for which you want to modify the default value;

  • finally, you may supply your values also as environment variables.

Note that a value declared in one place will override those in the lower layers. For instance, if you do nothing, the value for the GRIST_TEAM_SITE key will default to docs; if you write something like "GRIST_TEAM_SITE": "myteam" in your json file, then it will be myteam; then, if you also set a GRIST_TEAM_SITE env variable, that will be the final value.

You don’t need to provide either a ~/.gristapi/config.json file or env variables: you can choose one or the others, or a mix of the two. Environment variables can be prepared in the shell before launching your Python program, or simply added at runtime, before instantiating the GristApi class. Nothing stops you from doing something like

os.environ['GRIST_TEAM_SITE'] = 'mysite'
grist = GristApi()

in your code. However, this is not really necessary since Pygrister provides 3 more ways of overriding configuration at runtime.

Runtime configuration.

At runtime, you may pass an optional config parameter to the GristApi constructor, to override any “static” configuration previously defined:

grist = GristApi(config={'GRIST_TEAM_SITE': 'mysite'})

Second, at any time you may call either GristApi.reconfig or GristApi.update_config to change the configuration (the difference between the two is explained below):

grist.update_config(config={'GRIST_TEAM_SITE': 'mysite'})

This will affect all API calls from now on.

Finally, specific API call functions may provide optional parameters to temporarily override the configuration. For instance, this

st_code, res = grist.list_records('mytable')

will fetch records from a table in your current working document (as per config). If you need to quickly pull data from another document just once, there’s no need to change your configuration: a simple

st_code, res = grist.list_records('another_table', doc_id='another_doc')

will do the trick.

The function api.get_config returns the current “static” configuration, i.e. taking into account only the json files and environment variables. At runtime, you may look at GristApi.configurator.config to know the “real”, actual configuration in use (the function GristApi.inspect will also output the configuration among other things).

Changing the configuration at runtime.

As mentioned earlier, you may call GristApi.reconfig or GristApi.update_config to change the configuration at runtime.

The difference between the two is that reconfig will re-build your configuration from scratch (ie., from the config file and/or environment variables), then apply your modifications, if any; update_config will apply your changes on top of the existing (runtime) configuration, incrementally.

>>> g = GristApi()
>>> g.update_config({'GRIST_TEAM_SITE': 'mysite'})
>>> g.update_config({'GRIST_SAFEMODE': 'Y'})
>>> # incremental modifications are applied as expected
>>> g._config['GRIST_TEAM_SITE'], g._config['GRIST_SAFEMODE']
('mysite', 'Y')
>>> g.reconfig({'GRIST_SAFEMODE': 'N'})
>>> # reconfig re-builts configuration, so here team site is "docs" again
>>> g._config['GRIST_TEAM_SITE'], g._config['GRIST_SAFEMODE']
('docs', 'N')

Just call reconfig without arguments to revert to the original “static” configuration.

Custom configurators.

This is an advanced topic and you probably will never need it. A “configurator” class deals with the Pygrister configuration behind the scenes. The default configurator is config.Configurator, and the GristApi class will load it when instantiated.

You may want to write your own, different configurator, deriving from config.Configurator. Then, you can pass it to the GristApi constructor as the custom_configurator argument:

class MyConfigurator(Configurator):
    pass # do your own thing here

my_configurator = MyConfigurator(config={...})
grist = GristApi(custom_configurator=my_configurator)

Important: you cannot pass both the config and custom_configurator arguments to the GristApi class constructor: a GristApiNotConfigured exception will be raised. If you choose to pass a custom configurator, you should load its own configuration in advance, as shown in the example above.

The internal configurator object is exposed as the GristApi.configurator attribute. Thus, you may also change configurator at runtime:

grist = GristApi() # load the default configurator
old_configurator = grist.configurator
new_configurator = MyConfigurator(config={...})
grist.configurator = new_configurator # change configurator
grist.configurator = old_configurator # swap back

If you keep the instance of the default configurator, you can then alternate between the two, as above. Of course, if the new configurator holds a different set of config keys, this turns out to be yet another way of changing configuration at runtime.

Now that you know about config.Configurator, you should also know that GristApi.reconfig is just an alias for GristApi.configurator.reconfig, and GristApi.update_config is really GristApi.configurator.update_config.

(All that being said - why would you want to write a custom configurator, after all? For instance, you may want a different way of storing the “static” configurations keys: just ovveride Configurator.get_config and provide your own logic.)

Configuration keys.

This is a list of all the config keys currently defined and their default values, as defined in the config.py module:

{
    'GRIST_API_KEY': '<your_api_key_here>',
    'GRIST_SELF_MANAGED': 'N',
    'GRIST_SELF_MANAGED_HOME': 'http://localhost:8484',
    'GRIST_SELF_MANAGED_SINGLE_ORG': 'Y',
    'GRIST_SERVER_PROTOCOL': 'https://',
    'GRIST_API_SERVER': 'getgrist.com',
    'GRIST_API_ROOT': 'api',
    'GRIST_TEAM_SITE': 'docs',
    'GRIST_WORKSPACE_ID': '<your_ws_id_here>',
    'GRIST_DOC_ID': '<your_doc_id_here>',
    'GRIST_RAISE_ERROR': 'Y',
    'GRIST_SAFEMODE': 'N',
}

Please note: configuration values must be non-empty strings. If you don’t need a config key, just leave the default value as it is: do not override it with an empty string!

GRIST_API_KEY is your secret API key. You may want to provide it only as an environment variable, for added security.

GRIST_SELF_MANAGED, GRIST_SELF_MANAGED_HOME and GRIST_SELF_MANAGED_SINGLE_ORG are intended for self-hosted Grist, and detailed separately below.

GRIST_TEAM_SITE is your team ID. The docs default points to your personal site (the “@my-name” one).

GRIST_SERVER_PROTOCOL, GRIST_API_SERVER and GRIST_API_ROOT are the remaining components of the SaaS Grist Api url: you should never override the default values unless you know what you are doing.

GRIST_WORKSPACE_ID is your workspace ID: in fact, very few APIs make use of this value, and you may not need it at all.

GRIST_DOC_ID should be set to the ID of the document you work with the most. If your workflow involves constant switching between various documents, you may be better off leaving the default value here, and provide the actual IDs at runtime.

GRIST_RAISE_ERROR: if set to Y (the default), Pygrister will raise an exception if something went wrong with the API call. This will be discussed later on.

GRIST_SAFEMODE: if Pygrister is in safe mode (set this value to Y), no writing API calls will be allowed.

Note: extensions and subclasses may add other config keys as needed. Pygrister will incorporate them in the design explained here. For instance, our test suite adds a GRIST_TEST_RUN_USER_TESTS key, to allow running user creation tests: this is, in fact, a “user-defined” key that is needed and processed only by the test suite.

Support for the self-hosted Grist.

The Grist API works the same way for both the regular SaaS Grist and the self-managed version - and so does Pygrister.

To learn about the self-hosted version of Grist read the Grist documentation.

If you want to use Pygrister with a self-hosted Grist instance, you need to set up a few more configuration options.

First, set GRIST_SELF_MANAGED to Y. Then, you need to set GRIST_SELF_MANAGED_HOME to the “home page” url of your Grist server, eg. https://grist.mysite.com. The suggested default http://localhost:8484 is the usual access point of a test instance running locally.

Please note: if you are serving Grist from a public host, then Pygrister’s GRIST_SELF_MANAGED_HOME must be set to the same url of the APP_HOME_URL variable that you will provide to the Grist environment.

Finally, if you are running the single-team flavour of Grist, you need to set GRIST_SELF_MANAGED_SINGLE_ORG to Y (the default). The name of the team must then be specified in GRIST_TEAM_SITE (which you should never change at runtime, of course).

Again, remember that you will still need to provide a GRIST_SINGLE_ORG variable to the Grist environment, set to the same team name as in Pygrister’s GRIST_TEAM_SITE.

(A little duplication here is inevitable, since Pygrister and Grist will usually run in completely separate environments, and they can’t access each other’s variables.)

When GRIST_SELF_MANAGED is set Y and the self-hosted Grist support is enabled in Pygrister, the configuration keys GRIST_SERVER_PROTOCOL and GRIST_API_SERVER will be ignored, and GRIST_SELF_MANAGED_HOME will be used instead. The remaining configuration keys will work as usual.

App-specific configuration.

Having multiple config json files for different applications/workflows is not supported. However, this is hardly a problem: just provide your custom json file and load it at runtime:

with open('myconfig.json', 'r') as f:
    myconfig = json.loads(f.read())

grist = GristApi(config=myconfig)

If you change things, and then you need to revert to your starting config, then you just have to call

grist.reconfig(config=myconfig)

“Cross-site” access.

We call it a cross-site access when you try reaching an object belonging to a team site “from” a different team site, that is, calling https://mysite.getgrist.com/api/... to reach something that does not belong to mysite.

The general rule, here, is that all the /docs APIs do not allow cross-site operations, while other endpoints are fine with it. For example, trying to reach a call to https://<site>.getgrist.com/api/docs/<doc_id> will result in an HTTP 404 if <doc_id> does not belong to <site>. On the other hand, something like https://<site>.getgrist.com/api/workspaces/<ws_id> will work, even if the workspace is not in <site>.

In terms of Pygrister’s own interface, there’s little we can do about this. Most of the time, you will work with a single team site, so you’ll do the right thing anyway. If your workflow involves switching between sites, be aware that the resource you’re trying to contact must belong to your “current” team site (as per configuration). For instance, this will not work:

doc1 = '<doc1_ID>' # belongs to "myteam1"
doc2 = '<doc2_ID>' # belongs to "myteam2"
g = GristApi(config={'GRIST_TEAM_SITE': 'myteam1'})
g.see_doc(doc1) # ok
g.see_doc(doc2) # HTTP 404

In such cases, it is always better to pass the arguments explicitly, to avoid confusion:

g.see_doc(doc1, team_id='myteam1')
g.see_doc(doc2, team_id='myteam2')