Notionデータベースは主キー列(タイトル列)が固定されており、CSVインポート・エクスポートやレコードの移動、データベースの複製などを用いたとしても主キー列と他の列を入れ替えることはできません。
そこで、Notion APIを利用し、プログラム(Python)によってコピー元DBからすべての情報を取得。手元で主キー列を並べ替えたうえで移行先DBへとすべての情報を投稿することで、実質的に主キー列を並べ替えた状態でデータベースを複製させることができました。
もし同一DBで主キー列と他の列の変更を行いたい場合、移行先DB=コピー元DBとすることで同一DB内の値変更が可能です。 この場合、一旦DBの情報を手元で取得、並び替えてから再度同じDBへと投稿する流れとなります。
本記事ではテスト投稿も含め、段階的に実施した手順をご紹介します。
1つ1つの解説はいらないので早く主キー列の入れ替えを実施したい、という場合は 本番: すべてのページで移行先のNotionデータベースへ主キーを入れ替えた状態で投稿 の箇所をご覧ください。
ただしコード内で利用している関数については遡ってご確認ください。
なお、Notion APIの利用にはPython用公式パッケージであるnotion-sdk-pyを用いています。
Notion APIに関する関連記事:
目次:
- 作業全体の流れ
- notion-sdk-pyを利用する準備
- Notionプロフィールページからインテグレーションを作成
- Notionの該当ページにインテグレーションを接続し、API利用を許可
- notion-sdk-pyのインストール
- コピー元となるデータベースの情報を取得
- コピー元となるデータベースのIDを取得する
- データベース内のすべての情報を取得(レコード数が100件までの場合)
- 取得するデータベースのレコード数・ページ数(列)が100を超える場合
- APIで取得したデータベースの中身を検証
- (省略可)API経由でデータベースを作成する
- API経由でデータベースを作成するために必要な情報
- 主キー(タイトル列)とその他の情報を並べ替える
- 必要な箇所の値を抜き出す
- 投稿用フォーマットとして並べ替えたpropertiesを作成する
- 移行先DBに並べ替えた情報を投稿していく
- 単一jsonのプロパティでテスト投稿
- 本番: すべてのページで移行先のNotionデータベースへ主キーを並べ替えた状態で投稿
- コピー元DBの情報を取得
- コピー元DBのすべてのページについて、主キー列を入れ替えた辞書inリストを作成
- 移行先DBにすべてのページ分のプロパティを投稿
作業全体の流れ
流れとしては下記。
- コピー元となるデータベースの情報をすべて取得
- 手元で、新規作成するデータベースの情報を並べ替えた情報で作成
- 並べ替えた情報で、Notion上にデータベースを新規作成
ただし、そもそもの前提としてNotionでの主キーの型はテキスト形式のみ受け付ける形となっています。たとえば主キーとしてセレクト・マルチセレクトなどの通常のテキスト以外の形式を設置することはNotionの仕様上できませんのでご注意ください。
notion-sdk-pyを利用する準備
Notionプロフィールページからインテグレーションを作成
APIを利用する準備として、Web上で自身のNotionプロフィール > インテグレーション からAPI・インテグレーションを作成のうえ、該当のNotionページ・DBに接続します。
新規インテグレーションを作成。
次ページで、内部インテグレーションシークレットを保存しておきます。ここで取得したインテグレーションシークレットキーがAPIキー。
下記のようにして環境変数として.bash_profileに保存しておきます。
# Notion API Key
NOTION_TOKEN=your_integration_secret
export NOTION_TOKEN
一時的に利用しすぐ破棄するならjsonファイルとして保存しておくのも良いでしょう。
Notionの該当ページにインテグレーションを接続し、API利用を許可
主キー入れ替えとなるコピー元・移行先のDBがあるNotionページとのインテグレーション接続もそれぞれ忘れずに実施しておきます。
該当ページ右上の三点リーダ > Connections から、先ほど作成したインテグレーションに接続したら完了です。これでAPI利用のNotion側準備が整いました。
notion-sdk-pyのインストール
続いて、プログラム側での準備です。
まずはnotion-sdk-pyをインストール。
# ryeの場合
$ rye add notion-client
# pipの場合
$ pip install notion-client
コードファイル上にてNotionクライアントの準備。
import os
from notion_client import Client
# Secret Integrationキーを利用してNotionクライアントを生成
client = Client(auth=os.environ["NOTION_TOKEN"])
コピー元となるデータベースの情報を取得
以下からは、Notion APIで大量のページをリレーション付きで一括投稿する において作成した関数を利用しています。データベースの情報を取得していきます。
コピー元となるデータベースのIDを取得する
まずは、元となるデータベースのIDを調べます。
# Get DB info from query, title and id
def get_db_names_and_ids_from_query(client, query: str) -> dict:
"""
Search for databases in Notion using a query string and return a dictionary containing database names and IDs.
Args:
client: The Notion client object.
query (str): The search query string.
Returns:
dict: A dictionary containing 'db_title' and 'db_id' for found databases.
If no databases are found, returns an empty dictionary.
"""
results = client.search(query=query).get("results")
# Dict for db_info. {db_title: db_id}
db_names_ids = {}
db_count = 0
for result in results:
# Check if the result is a database
if result["object"] == "database":
db_count += 1
# db_title: db_id
db_names_ids[result["title"][0]["plain_text"]] = result["id"]
if db_count:
print(f"Found {db_count} databases.")
return db_names_ids
else:
print(f"No databases found with query: {query}")
return {}
データベース内のすべての情報を取得(レコード数が100件までの場合)
次に、データベース内のすべてのページ情報(すべての列の情報)を取得します。Notion APIでは、データベース内のレコードは通常ページと区別なく、ページとして扱われます。
# Get Page info (Up to 100) from a specific DB
def get_pages_info_from_db_id(client, database_id: str) -> list:
"""
Retrieves all information from a specified Notion database.
Args:
client: The Notion client object.
database_id (str): The ID of the Notion database to query.
Returns:
list: A list of dictionaries, each containing all information for a page in the database.
"""
response = client.databases.query(
**{
"database_id": database_id,
}
)
return response["results"]
# 実行例: コピー元となるデータベースのページ情報を取得
parent_db_pages = get_all_pages_info_from_db_id(client, PARENT_DB_ID)
get_pages_info_from_db_idを実行すると、JSON形式のリストでページ情報が返ってきます。
取得するデータベースのレコード数・ページ数(列)が100を超える場合
Notion APIは一度のリクエストで取得できるデータベースのページ数(列数)を100までに制限しています。
そのため、ページ数が100を超える場合には以下の関数を実行して、100を超えるページ(列)を取得してください。
import time
# Get All Page info from a specific DB, even though it is more than 100 pages
def get_all_pages_info_from_db_id(self, databaseID: str) -> list:
"""Return the query of all the databases."""
data = self.databases.query(databaseID)
# Retrieve the object, has_more, and next_cursor
database_object = data['object']
has_more = data['has_more']
next_cursor = data['next_cursor']
while has_more == True:
# Sleep for 2 second to avoid rate limit
time.sleep(2)
data_while = self.databases.query(databaseID, start_cursor=next_cursor)
# Append the results to the data
for row in data_while['results']:
data['results'].append(row)
# Update the has_more and next_cursor
has_more = data_while['has_more']
next_cursor = data_while['next_cursor']
# Create a new database object with the updated data
new_database = {
"object": database_object,
"results": data["results"],
"next_cursor": next_cursor,
"has_more": has_more
}
# Return only the results (pages)
return new_database["results"]
100を超えるレコード・ページ(列)を取得するAPI操作について参考:
APIで取得したデータベースの中身を検証
今回はテストとして、下記のような構造のデータベースで実施してみました。
# データベースIDを取得
people_dbs = get_db_names_and_ids_from_query(client, query="People")
# 取得したデータベースのうち、Peopleデータベースのページ情報をすべて取得
people_db_pages = get_pages_info_from_db_id(
client,
database_id=people_dbs['People']
)
関数get_pages_info_from_db_idを実行して返ってきた、people_db_pagesリスト内のアイテム(=1つ1つのjson)には、Notion上データベースにおける横一列の情報が格納されています。
色々な情報が返ってきますが、特に’properties’が必要な情報となってくるでしょう。各プロパティの種類は’type’から判別が可能です。
なお、主キー列のみ少し特殊な種類のプロパティとなります。主キー列のidは’title’で固定となっています。
print(people_db_pages['People'])
---- 出力結果 ----
{'object': 'page',
'id': 'xxxxxxx-xxxxxxxxxx',
'name': 'python',',
'created_time': '2024-08-21T09:56:00.000Z',
'last_edited_time': '2024-08-21T09:56:00.000Z',
'created_by': {'object': 'user',
'id': 'xxxxxxx-xxxxxxxxxx2',
'name': 'python','},
'last_edited_by': {'object': 'user',
'id': 'xxxxxxx-xxxxxxxxxx2',
'name': 'python','},
'cover': None,
'icon': None,
'parent': {'type': 'database_id',
'database_id': 'xxxxxxx-xxxxxxxxxx2',
'name': 'python','},
'archived': False,
'in_trash': False,
# 各プロパティの種類と内容
'properties':
# マルチセレクト列のプロパティ情報
{'Tags': {'id': 'xxxx',
'type': 'multi_select',
'multi_select': [{'id': 'xxxxxxx-xxxxxxxxxx2',
'name': 'python',
'color': 'default'}]},
# URL列のプロパティ情報
'Website': {'id': 'xxxxx', 'type': 'url', 'url': None},
# リッチテキスト列のプロパティ情報
'username': {'id': 'xxxxxxxx',
'type': 'rich_text',
'rich_text': [{'type': 'text',
'text': {'content': 'alice', 'link': None},
'annotations': {'bold': False,
'italic': False,
'strikethrough': False,
'underline': False,
'code': False,
'color': 'default'},
'plain_text': 'alice',
'href': None}]},
# 主キー列のプロパティ情報
'Name': {'id': 'title',
'type': 'title',
'title': [{'type': 'text',
'text': {'content': 'Alice Wonder', 'link': None},
'annotations': {'bold': False,
'italic': False,
'strikethrough': False,
'underline': False,
'code': False,
'color': 'default'},
'plain_text': 'Alice Wonder',
'href': None}]}},
'url': 'https://www.notion.so/your-info',
'public_url': 'https://your-url'}
(省略可)API経由でデータベースを作成する
API経由でデータベースを作成する場合です。
今回はデータベースを複製するのみなので、手動で移行先のデータベースをNotion上に作成するほうが早いでしょう。
ただし、移行元とプロパティの種類・プロパティ名は合わせておくようにしましょう。
参考のため、API経由でデータベースを作成するために必要な情報について残しておきます。
API経由でデータベースを作成するために必要な情報
これをみると、下記の情報が必要となりそうです。
- 各プロパティ情報
- 列としての名称
- プロパティの種類
- セレクト・マルチセレクトなどの場合は選択肢を設置可能
- 新規データベース名
- アイコン(省略可)
- 親となるページのページID
- 前述の関数でページIDを取得しておきましょう。
def create_database(parent_id: str, db_name: str) -> dict:
"""
parent_id(str): ID of the parent page
db_name(str): Title of the database
"""
print(f"\n\nCreate database '{db_name}' in page {parent_id}...")
properties = {
"Name": {"title": {}}, # This is a required property
"Description": {"rich_text": {}},
"In stock": {"checkbox": {}},
"Food group": {
"select": {
"options": [
{"name": "🥦 Vegetable", "color": "green"},
{"name": "🍎 Fruit", "color": "red"},
{"name": "💪 Protein", "color": "yellow"},
]
}
},
"Price": {"number": {"format": "dollar"}},
"Last ordered": {"date": {}},
"Store availability": {
"type": "multi_select",
"multi_select": {
"options": [
{"name": "Duc Loi Market", "color": "blue"},
{"name": "Rainbow Grocery", "color": "gray"},
{"name": "Nijiya Market", "color": "purple"},
{"name": "Gus's Community Market", "color": "yellow"},
]
},
},
"+1": {"people": {}},
"Photo": {"files": {}},
}
title = [{"type": "text", "text": {"content": db_name}}]
icon = {"type": "emoji", "emoji": "🎉"}
parent = {"type": "page_id", "page_id": parent_id}
return notion.databases.create(
parent=parent, title=title, properties=properties, icon=icon
)
移行先データベースを作成したら、作成した移行先DBのIDを関数実行などで取得しておきます。
主キー(タイトル列)とその他の情報を並べ替える
主キーと、入れ替えたい列の情報を入れ替えた状態でjson in リストを作成します。
例として、検証用データベース(People)においてコピー元の主キー: Name と通常のテキスト列: usernameを入れ替えた状態、すなわちusernameを主キーとした状態でjson in リストを作成します。
必要な箇所の値を抜き出す
主キーと入れ替えるということは、それぞれの値に紐づけられるtypeなどの情報が変更となります。
そのため、まずはコピー元の主キー(Name)と入れ替えたい列(username)の値だけを抜き出します。
実際はすべてのページについて抜き出していく必要がありますが、まずは1列だけの情報で試しています。
# コピー元主キー列の値: フルネームを抜き出す
orient_full_name = people_db_pages['page_name']["properties"]["Name"]["title"][0]['text']['content']
# コピー元リッチテキスト列の値: usernameを抜き出す
orient_username = people_db_pages['page_name']["properties"]["username"]["rich_text"][0]['text']['content']
投稿用フォーマットとして並べ替えたpropertiesを作成する
このとき、主キーを除く他プロパティのIDを付けた状態で作成してしまうと、postした時にエラーとなるため注意しましょう。
プロパティのIDは特定のデータベースのプロパティに対して付与されるため、移行先DBのIDとは不一致となります。
主キーのみ、IDは”title”であらかじめ固定されています。
データベースの複製ではなく、同一DBに再投稿して主キー列の入れ替えのみ実施する場合はプロパティIDは同一なので、残しておいて構いません。
以下はテスト用として、1アイテム分のプロパティを表すjsonとなります。
changed_properties = {
# コピー元では主キーだったNameをリッチテキスト列として入稿
'Name': {
'type': 'rich_text',
'rich_text': [{'type': 'text',
'text': {'content': orient_full_name, 'link': None},
}],
},
# コピー元ではリッチテキスト列だったusernameを主キー列として入稿
'username': {'id': 'title',
'type': 'title',
'title': [{'type': 'text',
'text': {'content': orient_username, 'link': None},
}],
},
}
移行先DBに並べ替えた情報を投稿していく
最後に、並び替えを行った情報を移行先DBへPOSTをして投稿します。
以下に、JSON形式のプロパティを引数として受け取り、その情報でデータベースに投稿する関数を作成しました。
# Add a new row with property info to Notion DB
def add_row_with_property_info_to_notion_database(client, database_id, json_properties):
"""
Add a new row with property info to Notion DB
Args:
*client: The Notion client object.
*database_id: The ID of the database to add the row to.
*json_properties: The properties of the new row.
"""
response = client.pages.create(
**{
"parent": { "database_id": database_id },
# Define the properties of the new page
"properties": json_properties
}
)
print("add_rows_with_properties_to_notion_database completed")
print(response) # 追加した内容を表示する
こちらの関数を用いて、指定したプロパティ内容・値で移行先データベースへ情報をPOSTしていきます。
単一jsonのプロパティでテスト投稿
下記はテスト入稿データとして作成した単一のjsonプロパティで投稿する例です。
add_row_with_property_info_to_notion_database(
client,
people_dbs['People_2'],
changed_properties
)
無事に、usernameを主キー列、Nameをリッチテキスト列としてコピー元DBと値を入れ替えた形でデータを投稿し、移行させることができました!
テストデータは不要なので、Notion上からレコードを削除しておきましょう。
本番: すべてのページで移行先のNotionデータベースへ主キーを並べ替えた状態で投稿
それでは最後に本番として、すべてのページ情報を主キー列を入れ替えた状態で移行先DBへと投稿します。一連の流れとして実行していきます。
API等の準備は済んでいるものとして、以下実施します。
コピー元DBの情報を取得
まずは、コピー元DBの情報を取得します。(上記で実行済ですが再掲)
# データベースIDを取得
people_dbs = get_db_names_and_ids_from_query(client, query="people")
# 取得したデータベースのうち、コピー元:Peopleデータベースのページ情報をすべて取得(100件まで)
people_db_pages = get_pages_info_from_db_id(
client,
database_id=people_dbs['People']
)
# コピー元: PeopleDBに100件以上のレコードがあった場合
people_db_pages = get_all_pages_info_from_db_id(
client,
database_id=people_dbs['People']
)
コピー元DBのすべてのページについて、主キー列を入れ替えた辞書inリストを作成
次に、コピー元DBのすべてのページについて、主キー列を入れ替えた状態で辞書inリストを作成します。
# List of properties to upload
pages_properties_to_upload = []
# 取得したページの分だけループを回す
for page in people_db_pages:
orient_full_name = page["properties"]["Name"]["title"][0]['text']['content']
orient_username = page["properties"]["username"]["rich_text"][0]['text']['content']
changed_properties = {
# コピー元では主キーだったNameをリッチテキスト列として入稿
'Name': {
'type': 'rich_text',
'rich_text': [{'type': 'text',
'text': {'content': orient_full_name, 'link': None},
}],
},
# コピー元ではリッチテキスト列だったusernameを主キー列として入稿
'username': {'id': 'title',
'type': 'title',
'title': [{'type': 'text',
'text': {'content': orient_username, 'link': None},
}],
},
}
# リストにプロパティを追加 (辞書inリストの作成)
pages_properties_to_upload.append(changed_properties)
移行先DBにすべてのページ分のプロパティを投稿
最後に、移行先DB(今回の例ではpeople_2)にすべてのページ分のプロパティを投稿します。
add_row_with_property_info_to_notion_database関数の中身はこちら。
import time
for property in pages_properties_to_upload:
try:
add_row_with_property_info_to_notion_database(
client,
ENTREPRENEUR_DB_ID,
property
)
time.sleep(2)
except Exception as e:
print(e)
break
これで、無事にすべてのページについて主キー列とリッチテキスト列を入れ替えた状態で投稿できました!実質的に、主キー列を入れ替えてデータベースを複製(別のデータベースへ投稿)したことになります。
Category: Notion
Tags: Python
この記事の気になる箇所を読み返す:
- 作業全体の流れ
- notion-sdk-pyを利用する準備
- Notionプロフィールページからインテグレーションを作成
- Notionの該当ページにインテグレーションを接続し、API利用を許可
- notion-sdk-pyのインストール
- コピー元となるデータベースの情報を取得
- コピー元となるデータベースのIDを取得する
- データベース内のすべての情報を取得(レコード数が100件までの場合)
- 取得するデータベースのレコード数・ページ数(列)が100を超える場合
- APIで取得したデータベースの中身を検証
- (省略可)API経由でデータベースを作成する
- API経由でデータベースを作成するために必要な情報
- 主キー(タイトル列)とその他の情報を並べ替える
- 必要な箇所の値を抜き出す
- 投稿用フォーマットとして並べ替えたpropertiesを作成する
- 移行先DBに並べ替えた情報を投稿していく
- 単一jsonのプロパティでテスト投稿
- 本番: すべてのページで移行先のNotionデータベースへ主キーを並べ替えた状態で投稿
- コピー元DBの情報を取得
- コピー元DBのすべてのページについて、主キー列を入れ替えた辞書inリストを作成
- 移行先DBにすべてのページ分のプロパティを投稿