Notion has recently released the official API. With some restrictions, it allows coders to add, update and delete blocks from their pages. There is an official JavaScript client, but nothing for Python. This post describes how to update Notion pages with Python. It will use REST requests to call the API.
Prepare a page with Notion API integration.
User needs to share every page she wants to use with the Notion API to an integration. These are the steps she needs to take:
- Create an integration in https://www.notion.so/my-integrations
- Copy the Integration Token for use in Python
- Share a Notion page to this integration
More details are in the official documentation.
Create the Notion class
After creating the integration and sharing a page to it, the developer can then start preparing for request. She can first combine shared request properties, like header and Integration Token(notion_token) into a single class. This will make it easier to implement new requests later.
class Notion:
def __init__(self, notion_token):
self.headers = {
'Notion-Version': '2021-05-13',
'Authorization': 'Bearer ' + notion_token
}
self.base_url = "https://api.notion.com/v1"
Find a page
Then, the developer might want to find a page in her workspace. She can achieve this using the /search
path. In that function, she can define a query
parameter to find a page by its title. Or don’t use any parameters to get all of the pages that are shared to the integration.
def search_page(self, page_title: str = None):
url = self.base_url + "/search"
body = {}
if page_title is not None:
body["query"] = page_title
response = requests.request("POST", url, headers=self.headers, params=body)
return self.response_or_error(response)
Response results
objects will contain page id’s and properties.
{
"object":"list",
"results":[
{
"object":"page",
"id":"778d046c-26fa-4600-b736-959892fe9d46",
"properties":{
"title":{
"id":"title",
Page properties are not page content. Page content is an array of blocks of different types. Block types can be one of text, bulleted lists, images and more.
Now that the developer has the the page id, she can add some blocks to it.
Add a block
Developer can add blocks via the PATCH
/blocks/{block_id}/children
method. This function expects an array of blocks in the dictionary’s children
field.
def append_child_blocks(self, parent_id: str, children: []):
url = self.base_url + f"/blocks/{parent_id}/children"
response = requests.request(
"PATCH",
url,
headers=self.headers,
json={"children": children}
)
return response
Not many developers want to add an empty block. How to create a block with some text?
Add a text block
One commonly used block is the text block. The type
for this block is paragraph
. Since the Append block children request expects blocks, the developer needs to construct the text object manually.
def text_append(self, parent_id: str, text: str):
text_block = {
"type": "paragraph",
"paragraph": {
"text": [{
"type": "text",
"text": {
"content": text,
}
}]
}
}
After this, the append_child_blocks
method can be used to append this block
return self.append_child_blocks(parent_id, [text_block])
Update a block
Some blocks can be updated via the PATCH
/blocks/{block_id}
method. In there, the developer can define a dictionary with new block values.
def update_block(self, block_id: str, content: dict):
url = self.base_url + f"/blocks/{block_id}"
response = requests.request("PATCH", url, headers=self.headers, json=content)
return self.response_or_error(response)
Update is currently are limited to text
and checked
value in to_do
blocks. Only the values that are present in the request will be updated. This means the rest of the block stays as it is.
Update text
To update the text, the developer first needs the current block id. Then, she can construct the block with new text values, and call the previously defined update_block
method.
def text_set(self, block_id: str, new_text: str):
block = self.get_block(block_id)
type = block["type"]
block[type]["text"][0]["text"]["content"] = new_text
return self.update_block(block_id, block)
Add an image
Similarly to adding text, the image will be added via the append_child_blocks
method. Currently, uploading image binary is not supported. This means that the developer needs to link to the .png
or other image file that is already uploaded to the internet. This link will be made in external
> url
field.
def image_add(self, parent_id: str, image_url: str):
append_children = [
{
"type": "image",
"image": {
"type": "external",
"external": {
"url": image_url
}
}
}
]
return self.append_child_blocks(parent_id, append_children)
Update image
Notion API does not allow updating images. Therefore the developer needs to remove the old block manually, and then add a new image block with the new image URL.
def delete_block(self, block_id: str):
url = self.base_url + f"/blocks/{block_id}"
response = requests.request("DELETE", url, headers=self.headers)
return response
delete_block(old_image_block_id)
image_add(parent_id, "https://new.image.url/image.png")
Other block types
See all of the block types currently available. Notion promises more support coming soon.
Single method to return error or Notion response
Notion API returns errors for all of the requests in the same format. Therefore the developer can create a single function that returns either the response json object or error message.
def response_or_error(response, key: str = None):
response_json = response.json()
if "message" in response_json:
message = response_json["message"]
return {
"code": response.status_code,
"error": message
}
json_response = response.json()
if key is not None:
return json_response[key]
return json_response
Summary
It is great that Notion allows its users to access their pages via the API. Most functionality, like adding and updating text/image or database blocks is available. However, the note taking company has only released a JavaScript library. Python developers need to make the requests manually, according to the API description.
Source code: Github
Use the official Notion API with Python. Add text and images with the requests library.https://t.co/fMAcLl98Y6
— Tõnis Tiganik (@tonisives) November 30, 2021