An unofficial wrapper for Notion's API, aiming to simplify the dynamic nature of interacting with Notion.
Project description
notion-api
Disclaimer: This is an unofficial package and has no affiliation with Notion.so
A wrapper for Notion's API, aiming to simplify the dynamic nature of interacting with Notion.
README contains examples of the main functionality, including: creating Pages/Databases/Blocks, adding/removing/editing properties, retrieving property values, and database queries.
Some more in-depth walkthroughs can be be found in examples/
.
This library is not complete - new features will continue to be added, and current features may change.
Links: Notion API Updates |
API Reference |
Notion API Changelog |
Notion.so Releases |
If you haven't already, you'll need to setup a Notion Integration and share specific pages/databases with the integration to interact with the API.
Information on integration types and setup can be found here.
Install
pip install -U notion-api
Usage
import notion
# client will check for 'NOTION_TOKEN' in environment variables.
homepage = notion.Page('773b08ff38b44521b44b115827e850f2')
parent_db = notion.Database(homepage.parent_id)
# client will also look for env var `TZ` to set the default timezone.
# If not found, attempts to find the default timezone, or "UTC".
Indexing a page will search for page property values, and indexing a Database will search for property objects.
A full list can be retrieved for both using;
retrieve()
method for a Page, with the optionalfilter_properties
parameter.retrieve
attribute for a Database.
homepage['dependencies']
# {
# "id": "WYYq",
# "type": "relation",
# "relation": [
# {
# "id": "7bcbc8e6-e237-434b-bd0d-6b56e044200b"
# }
# ],
# "has_more": false
# }
parent_db['dependencies']
# {
# "id": "WYYq",
# "name": "dependencies",
# "type": "relation",
# "relation": {
# "database_id": "f5984a7e-2257-4ab0-9d0a-23ea12324031",
# "type": "dual_property",
# "dual_property": {
# "synced_property_name": "blocked",
# "synced_property_id": "wx%7DQ"
# }
# }
# }
See usage of retrieving values from a page in examples/retrieving-property-items.md
Below is a brief example to retrieve the dependencies
property above from homepage
.
from notion import propertyitems
related_id: list[str] = propertyitems.relation(homepage.dependencies)
>>> related_id
["7bcbc8e6-e237-434b-bd0d-6b56e044200b"]
Both Page's and Database's have setters for title/icon/cover.
homepage.title = "new page"
homepage.cover = "https://www.notion.so/images/page-cover/webb1.jpg"
homepage.icon = "https://www.notion.so/icons/alien-pixel_purple.svg"
Creating Pages/Databases/Blocks
Create objects by passing an existing Page/Database instance to the create
classmethods.
The current version of the Notion api does not allow pages/databases to be created to the parent workspace
.
new_database = notion.Database.create(
parent_instance=testpage,
database_title="Example Database",
name_column="page", # This is the column containing page names. Defaults to "Name".
is_inline=True, # can also toggle inline with setters.
description="Database description can go here.",
)
new_page = notion.Page.create(new_database, page_title="A new database row")
Blocks are also created using classmethods. They require a parent instance of either Page
or Block
to append the new block too.
The newly created block is returned as an instance of Block
, which can be used as the parent instance to another nested block.
By default, blocks are appended to the bottom of the parent block.
To append the block somewhere else other than the bottom of the parent block, use the after
parameter and set its value to the ID of the block that the new block should be appended after. The block_id used in the after
paramater must still be a child to the parent instance.
from notion import properties as prop
original_synced_block = notion.Block.original_synced_block(homepage)
# Adding content to the synced block
notion.Block.paragraph(original_synced_block, [prop.RichText("This is a synced block.")])
# Referencing the synced block in a new page.
notion.Block.duplicate_synced_block(new_page, original_synced_block.id)
There are few extensions to the Block
class that have specific functions unique to their block-type.
Below is an example using CodeBlock
. The others are TableBlock
, EquationBlock
, RichTextBlock
, and ToDoBlock
. You can see usage for them in examples/block_extensions.md
.
code_block = notion.CodeBlock("84c5721d8a954667902a757f0033f9e0")
git_graph = r"""
%%{init:{'logLevel':'debug','theme':'default', 'darkMode': true, 'gitGraph':{'rotateCommitLabel':true}, 'themeVariables': {'commitLabelColor':'#ffffff','commitLabelBackground':'#000000', 'tagLabelFontSize': '10px'}}}%%
gitGraph LR:
%% @backgroundColor(#000000)
commit
branch develop
commit tag:"v1.0.0"
commit
checkout main
commit type: HIGHLIGHT
commit
merge develop
commit
branch featureA
commit
"""
code_block.language = prop.CodeBlockLang.mermaid
code_block.code = git_graph
code_block.caption = "Example from https://mermaid.js.org/syntax/gitgraph.html"
Example Function: Using notion.Workspace()
to retrieve a user, and appending blocks in a page to mention user/date.
def inline_mention(page: notion.Page, message: str, user_name: str) -> None:
mentionblock = notion.Block.paragraph(
page,
[
prop.Mention.user(
notion.Workspace().retrieve_user(user_name=user_name),
annotations=prop.Annotations(
code=True, bold=True, color=prop.BlockColor.purple
),
),
prop.RichText(" - "),
prop.Mention.date(
datetime.now().astimezone(page.tz).isoformat(),
annotations=prop.Annotations(
code=True,
bold=True,
italic=True,
underline=True,
color=prop.BlockColor.gray,
),
),
prop.RichText(":"),
]
)
# First method returned the newly created block that we append to here:
notion.Block.paragraph(mentionblock, [prop.RichText(message)])
notion.Block.divider(page)
homepage = notion.Page("0b9eccfa890e4c3390175ee10c664a35")
inline_mention(page=homepage, message="example", user_name="AYVI")
Add, Set, & Delete: Page property values | Database property objects
The first argument for all database property methods is the name of the property,
If a property of that name does not exist, then a new property will be created.
If a property of that name already exists, but it's a different type than the method used - then the API will overwrite this and change the property object to the new type.
The original parameters will be saved if you decide to switch back (i.e. if you change a formula column to a select column, upon changing it back to a formula column, the original formula expression will still be there).
new_database.formula_column("page_id", expression="id()")
new_database.delete_property("url")
new_database.multiselect_column(
"New Options Column",
options=[
prop.Option("Option A", prop.PropertyColor.red),
prop.Option("Option B", prop.PropertyColor.green),
prop.Option("Option C", prop.PropertyColor.blue),
],
)
# if an option does not already exist, a new one will be created with a random color.
# this is not true for `status` column types, which can only be edited via UI.
new_page.set_multiselect("options", ["option-a", "option-b"])
Database Queries
A single notion.query.PropertyFilter
is equivalent to filtering one property type in Notion.
To build nested filters, use notion.query.CompoundFilter
and group property filters chained together by _and(...)
/ _or(...)
.
The database method query()
will return the raw response from the API.
The method query_pages()
will extract the page ID for each object in the array of results, and return a list of notion.Page
objects.
[!IMPORTANT]
In v0.6.0, the methodsquery
andquery_pages
were deprecated and 2 new methods,query_all()
&query_all_pages()
were added. The new methods would iterate through all results, up to themax_page_size
parameter, instead of returning anext_cursor
key and the 100 page max from the original endpoints.In v0.7.0 the original deprecated methods were renamed to
_query
and_query_pages
(left incase anyone wanted the original endpoints), and the new query methods were renamed to take their place:query_all
->query
,query_all_pages
->query_pages
import os
from datetime import datetime, timedelta
from pytz import timezone
from notion import query
TZ = timezone(os.getenv("TZ", "UTC"))
TODAY = datetime.combine(datetime.today(), datetime.min.time()).astimezone(TZ)
TOMORROW = TODAY + timedelta(1)
query_filter = query.CompoundFilter()._and(
query.PropertyFilter.date("date", "created_time", "on_or_after", TODAY.isoformat()),
query.PropertyFilter.date("date", "created_time", "before", TOMORROW.isoformat()),
query.CompoundFilter()._or(
query.PropertyFilter.text("name", "title", "contains", "your page title"),
query.PropertyFilter.text("name", "title", "contains", "your other page title")
),
)
query_sort = query.SortFilter(
[
query.PropertyValueSort.ascending("your property name"),
query.EntryTimestampSort.created_time_descending(),
]
)
query_result = new_database.query(
filter=query_filter,
sort=query_sort,
filter_property_values=["name", "options"],
)
Exceptions & Validating Responses
Errors in Notion requests return an object with the keys: 'object', 'status', 'code', and 'message'. Exceptions are raised by matching the error code and returning the message. For example:
homepage._patch_properties(payload={'an_incorrect_key':'value'})
# Example error object for line above..
# {
# 'object': 'error',
# 'status': 400,
# 'code': 'validation_error',
# 'message': 'body failed validation: body.an_incorrect_key should be not present, instead was `"value"`.'
# }
Traceback (most recent call last):
File "c:\path\to\file\_.py", line 6, in <module>
homepage._patch_properties(payload={'an_incorrect_key':'value'})
File "c:\...\notion\exceptions\validate.py", line 48, in validate_response
raise NotionValidationError(message)
notion.exceptions.errors.NotionValidationError: body failed validation: body.an_incorrect_key should be not present, instead was `"value"`.
Error 400: The request body does not match the schema for the expected parameters.
Possible errors are:
NotionConflictError
NotionDatabaseConnectionUnavailable
NotionGatewayTimeout
NotionInvalidGrant
NotionInternalServerError
NotionInvalidJson
NotionInvalidRequest
NotionInvalidRequestUrl
NotionMissingVersion
NotionObjectNotFound
NotionRateLimited
NotionRestrictedResource
NotionServiceUnavailable
NotionUnauthorized
NotionValidationError
[!TIP] A common error to look out for is
NotionObjectNotFound
.
This error is often raised because your bot has not been added as a connection to the page.
By default, a bot will have access to the children of any Parent object it has access too. Be sure to double check this connection when moving pages.
If you're working on a page that your token has access to via its parent page/database, but you never explicitly granted access to the child page - and you later move that child page out, then it will lose access.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for notion_api-0.7.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | d24b35950fd34748146558a381f672f3544f7c7e19042ac1274bcdd12bc5a59a |
|
MD5 | 97484bd592a443b810d091dacfc50df3 |
|
BLAKE2b-256 | 499b5e677913db60fcd8d0033173fa0ff260e0dce4eeb8f2fa8a3b5010b3790e |