Categories
Notion Python

Add text and images to Notion via the official API and Python

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:

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 a text 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