Skip to main content
6 toolsOAuth bearer
gforms_example.py
Six endpoints over two collections. Four of them change or read the form, two read what respondents submitted, and the two halves take different OAuth scopes.

Authenticating

This pack takes a Google OAuth bearer token, and configure() is optional when $GOOGLE_ACCESS_TOKEN is set. Which credential provider you hand it depends on whose account the calls run as.
Refer to Google’s provider page for the “GOOGLE” constant the snippets below name, the scopes these 6 tools ask for, and this server’s refresh behaviour.

A token you hold

For a script, or a notebook. EnvTokenProvider re-reads the variable on every call, so a token rotated beside the process is picked up without a restart; StaticTokenProvider takes one you already hold as a string. Neither renews anything, so the calls stop when the token expires.
gforms_script.py

One account, refreshed

For an agent or a server acting as you. OAuth2Client turns a client registration and a stored refresh token into an access token, and renews it before it lapses.
gforms_agent.py
No refresh token yet? Your own account is the one-time consent flow that hands you one.

Many end users

For a product whose users each connect their own account. SubjectProvider builds one credential per user through a factory you write, and use_subject names the user a call acts for. Your users’ accounts is the consent route inside your app; serving many users is the per-subject cache and its eviction.
gforms_server.py
Reading responses is forms.responses.readonly, which forms.body does not cover, so forms_responses_get and forms_responses_list carry it instead of the pack default. A consent screen built with scopes_for over gforms.TOOLS asks for both.

The client

oauth_tool_factory is the whole client: a thin wrapper over httpx that attaches your token and these endpoint constants to each request. google-api-python-client and google-auth do not enter your dependency tree.
credentials is whichever of the three you built in Authenticating. A pack takes it through configure(); a client you build takes the same object as credential_provider, and has no configure() of its own.

Paging through a list

A cursor belongs to the tool that returns it, so it is declared on that tool’s builder call:
gforms_pagination.py
Pagination is declared on forms_responses_list. The other 5 take no cursor.

Tools

Each is a Tool, called with ainvoke as in the snippet above. The name links to its parameters, its response and what it costs.

Creating a form takes two calls

forms.create copies info.title and info.documentTitle and nothing else. Google’s own reference calls the description, the items and the settings disallowed there. Questions arrive in a second call:
gforms_two_calls.py
The create body models the two fields that are honoured rather than the whole Form, so the schema cannot offer a field the endpoint rejects.

One Info, two contracts

documentTitle can be set on create and cannot be modified by a batchUpdate. The form description is the other way round. That is one resource with two field sets, so Info carries Mode markers and each tool declares which operation it is: Writing two near-identical models instead would put every field description in two places, and nothing keeps those in step.

Gotchas

Items are addressed by index, and a create_item at index 2 moves every later item down before the next request in the same batch reads its own location. Order several insertions back-to-front, or state each location against the form as it will be by then. Nothing in the API warns you.
Google’s documented way to change a question is to read the form, edit your copy of the item and write it back through update_item with the IDs unchanged. An item that came back reshaped is an item that cannot be written back, so this tool has no response handler. The responses collection is trimmed, because nothing is written back there.
is_published and is_accepting_responses are both required when you set the publish state. Accepting responses while unpublished is refused by the API, and the schema refuses it locally, so that combination never costs a round trip. Legacy forms have no publishSettings field and this endpoint does not support them.
A response holds answers keyed by questionId, and nothing in it names the question. forms_get is where the ID maps back to the wording, so read the form once and keep the mapping rather than fetching it per response.
Google does not support creating one through the API. FileUploadQuestion is still modelled, because an existing one comes back on forms_get and can be moved or deleted, and because its answers name Drive file IDs a caller can act on.
So is changing who can open it. The Forms API creates and edits; the file itself belongs to Drive. Use gdrive for both.