Various and sundry.¶
Inspecting and troubleshooting the API call.¶
It goes without saying, Pygrister relies on the excellent Python Requests library to do the actual talking with the Grist API. Every time a request is sent, and a response is retrieved, Pygrister saves the relevant details of both for subsequent inspection.
The underlying Requests’
PreparedRequest
object (which is your API call, ready to be posted) will be accessible as
GristApi.apicaller.request. Once the response is retrieved, the
Response
object will be available as GristApi.apicaller.response.
(If you are wondering about the apicaller bit above, read the section
devoted to custom Api callers.)
The convenience function GristApi.inspect will collect and output the
relevant data of both PreparedRequest and Response for you.
You may call it if anything goes wrong: pass an optional sep argument to
set a custom separator between the various elements (eg, pass sep=' --- '
instead of the default \n to produce a one-liner dump suitable for logging).
You may also pass a max_content argument to limit the size of both
request’s and response’s body.
The inspect data always refer to the last API call that was posted, either
successfully or not. You should call inspect and/or access the Requests’
objects before making the next API call.
If the API call returned a “bad” status code (eg, Http 404, Http 500 etc.),
the response object will still be available. If, however, the call was not even
responded to by the server (eg, timed out), then GristApi.apicaller.response
will be None.
Errors vs Status codes.¶
When you receive a response, but the Http status code is “bad” (that is, 300 or more), the underlying Requests library may optionally throw and exception - and Pygrister offers you the same choice.
If you set the GRIST_RAISE_ERROR config key to Y (the default), then
Pygrister will raise a requests.HTTPError if the response is not ok.
Otherwise, Pygrister will simply return the bad response as if nothing.
Keep in mind that both status code and response body will always be retrieved:
upon success, they will be returned by the API call function as usual; if
an HTTPError occurred and you choose to raise it, you can still access the
retrieved data via the GristApi.apicaller.response object.
Since the response is delivered in any case, it is really just a matter of taste.
If you prefer “to ask for forgiveness”, set the config key to Y (the default)
and prepare for the possible exception:
>>> from requests import HTTPError
>>> from pygrister.api import GristApi
>>> grist = GristApi()
>>> try:
... st_code, res = grist.list_records('mytable')
... except HTTPError:
... # there's no "st_code" and "res" but data have been retrieved anyway
... st_code = grist.apicaller.response.status_code
... res = grist.apicaller.response.text # or .json()
If you prefer to “look before you leap” instead, set the config key to N
and check the resulting status code:
>>> grist.reconfig({'GRIST_RAISE_ERROR': 'N'})
>>> st_code, res = grist.list_records('mytable')
>>> if st_code >= 300:
... # now the function is allowed to return even if an error occurred
... print(st_code, res)
Finally note that, if an HTTPError occurred, the body of the retrieved response will of course not conform to the “regular” Pygrister format for a successful request:
>>> grist.reconfig({'GRIST_RAISE_ERROR': 'N'})
>>> grist.list_records('bogus_table') # usually, response here is a list
(404, {'error': 'Table not found "bogus_table"'})
>>> grist.add_records('Table1', ['bogus_records']) # usually, resp. is None
(400, {'error': 'Invalid payload', 'details':
{'userError': 'Error: body.records[0] is not a NewRecord; (...)}})
In such cases, Pygrister will always return the original Grist API response without modification.
The ok attribute.¶
As a shortcut, the GristApi.ok attribute is set to True if the last
request was both received and successful (status code < 300), to False
otherwise. This is useful especially if you choose not to raise errors:
instead of inspecting the status code, you may then write something like
>>> grist.reconfig({'GRIST_RAISE_ERROR': 'N'})
>>> st_code, res = grist.list_records('mytable')
>>> if grist.ok: # equivalent to "if st_code < 300"
... do_something()
Note, however, that this pattern only works if the response was retrieved in
the first place. If the server eg. timed out, Requests (and Pygrister) will
throw the appropriate exception (requests.Timeout for example) well before
you have a status code and a response body to manipulate. If you catch the
exception and keep the GristApi instance alive, however, you will find
that GristApi.ok has been set to False by default: this doesn’t mean
a “bad” status code occurred (because, in fact, there is no status code),
but it can be helpful in log parsing and post-mortem inspection.
To sum up, GristApi.ok
is
Falsewhen a response was not retrieved at all (but you have to catch the subsequent Requests exception first);is
Falsewhen a response was received, with a “bad” status code (if you set yourGRIST_RAISE_ERRORconfig key toY, then you still have to catch arequests.HTTPErrorthough);is
Falsewhen Pygrister is in “dry run mode”, see below;is
Truewhen a response was received and the status code is “good”.
Dry run.¶
Set GristApi.apicaller.dry_run = True to enter “dry run mode”, where
you go as far as to prepare the request, but you never actually post it.
Set it back to False to return to normal functioning.
While in dry run mode:
a
GristApi.apicaller.requestobject will always be prepared;GristApi.apicaller.responsewill always beNonebecause the request will not be posted;GristApi.okwill beFalse;any api call will return a fake response, with Http 418 status code and a warning message as the response body;
even if the fake response has a “bad” status code,
requests.HTTPErrorwill never be raised (even if yourGRIST_RAISE_ERRORconfig key says otherwise).
The latter 3 conditions may sound a little odd, but we wanted dry run mode to fake a response anyway, instead of throwing an exception. And yes, the returned status code is the infamous and unused 418 I’m a teapot, so that you will know that this is not serious business after all
>>> grist = GristApi()
>>> grist.apicaller.dry_run = True
>>> grist.see_team()
(418, {'No Content': 'Pygrister teapot is running dry!'})
Safe mode.¶
If you set the GRIST_SAFEMODE configuration key to Y, all API call
functions attempting a write operation will be blocked: Pygrister will throw
a GristApiInSafeMode exception instead.
This is meant as a higher-level block than the “dry run” mentioned above. However, safe mode actually works by temporary switching to dry run mode, so that, if you catch the exception, you can still inspect the prepared request afterwards:
>>> from pygrister.exceptions import GristApiInSafeMode
>>> grist = GristApi({'GRIST_SAFEMODE': 'Y'})
>>> try:
... grist.add_workspace('bogus')
... except GristApiInSafeMode:
... pass
...
>>> grist.apicaller.request
<PreparedRequest [POST]>
Please note that the two run_sql* functions are still allowed in safe mode,
because the underlying API only accepts SELECT statements anyway.
Additional arguments for the request.¶
You may pass optional arguments, not otherwise used by Pygrister, to the underlying
Requests call.
Simply set GristApi.apicaller.request_options to a dictionary:
>>> grist = GristApi()
>>> grist.apicaller.request_options = {'timeout': 5}
The request_options will then be injected into all subsequent Pygrister API
calls. The code above, for example, will set a timeout limit from now on.
Using Requests sessions in Pygrister.¶
Requests supports using sessions to persist connection data, and so does Pygrister.
Working with sessions is straightforward:
>>> grist = GristApi({...})
>>> grist.open_session() # open a new session
>>> grist.session # this is how you know you are in a session
<requests.sessions.Session object at ...>
>>> # ...Pygrister api calls are now "inside" the session...
>>> grist.close_session() # close the session
>>> grist.session # "session" attribute is now None
>>>
As long as you are in a session, all subsequent api calls will re-use the same
underlying connection, resulting in much faster interaction. From the
second api call on, if you inspect the request headers (grist.req_headers),
you will notice a new 'Cookie' element added by Requests to persist the
connection.
In Pygrister, session have no other use than for boosting performance, and they
are transparent to the rest of the api. Inside a session, you will use the
GristApi class just the same: start a session, and then forget about it.
You may use sessions for performance, when you need to post several api calls in a row (like, in a script). However, keep in mind that Requests (and Pygrister) sessions are supplied “as it is” - your server may be configured to expire a session after a while, for instance.