Skip to main content

Python client for the Notion API

Project description

Test Python Package

[!IMPORTANT] Upgrading from v1.4? Version 2.0 is a full redesign with a new API — NotionClient replaces the old Page, Database, and Search classes. v1.4.x will continue to receive security fixes only. See the migration table and CHANGELOG for details.

notion-database

Python client for the Notion API — easy to use, 1-to-1 API mapping, AI/MCP friendly

Install

pip install notion-database==2.0.dev1

Quick start

from notion_database import (
    NotionClient,
    PropertyValue,
    PropertySchema,
    BlockContent,
    RichText,
    Filter,
    Sort,
    Icon,
    Cover,
)

client = NotionClient("secret_xxx")

Databases

# Retrieve
db = client.databases.retrieve("database-id")

# Create
db = client.databases.create(
    parent={"type": "page_id", "page_id": "page-id"},
    title=[RichText.text("My Database")],
    properties={
        "Name":   PropertySchema.title(),
        "Status": PropertySchema.select([
            {"name": "Active", "color": "green"},
            {"name": "Done",   "color": "gray"},
        ]),
        "Score":  PropertySchema.number("number"),
        "Due":    PropertySchema.date(),
    },
)

# Query with filter and sort
results = client.databases.query(
    "database-id",
    filter=Filter.select("Status").equals("Active"),
    sorts=[Sort.descending("Score")],
)
pages = results["results"]

# Compound filter
results = client.databases.query(
    "database-id",
    filter=Filter.and_([
        Filter.select("Status").equals("Active"),
        Filter.number("Score").greater_than(80),
    ]),
)

# Auto-paginate (returns all pages at once)
all_pages = client.databases.query_all("database-id")

# Update schema
client.databases.update(
    "database-id",
    title=[RichText.text("Renamed DB")],
)

Pages

# Create
page = client.pages.create(
    parent={"database_id": "database-id"},
    properties={
        "Name":   PropertyValue.title("Hello, Notion 2.0!"),
        "Status": PropertyValue.select("Active"),
        "Score":  PropertyValue.number(95),
        "Due":    PropertyValue.date("2024-12-31"),
        "Done":   PropertyValue.checkbox(False),
    },
    icon=Icon.emoji("🚀"),
    cover=Cover.external("https://example.com/cover.jpg"),
    children=[
        BlockContent.heading_1("Introduction"),
        BlockContent.paragraph("This page was created via notion-database 2.0."),
    ],
)

# Retrieve
page = client.pages.retrieve("page-id")

# Update properties
client.pages.update(
    "page-id",
    properties={
        "Status": PropertyValue.select("Done"),
        "Done":   PropertyValue.checkbox(True),
    },
)

# Archive / restore
client.pages.archive("page-id")
client.pages.archive("page-id", archived=False)

Blocks

# Append content to a page
client.blocks.append_children(
    "page-id",
    children=[
        BlockContent.heading_2("Section"),
        BlockContent.paragraph([
            RichText.text("Normal text, "),
            RichText.text("bold", bold=True),
            RichText.text(", and "),
            RichText.text("italic", italic=True),
        ]),
        BlockContent.bulleted_list_item("First item"),
        BlockContent.bulleted_list_item("Second item"),
        BlockContent.to_do("Finish docs", checked=False),
        BlockContent.code("print('hello')", language="python"),
        BlockContent.divider(),
        BlockContent.image("https://example.com/image.png", caption="Fig 1"),
        BlockContent.column_list([
            [BlockContent.paragraph("Left column")],
            [BlockContent.paragraph("Right column")],
        ]),
    ],
)

# Retrieve children (single page)
response = client.blocks.retrieve_children("page-id")
blocks = response["results"]

# Auto-paginate all children
all_blocks = client.blocks.retrieve_all_children("page-id")

# Delete a block
client.blocks.delete("block-id")

Search

# Search all
results = client.search.search("Project")

# Filter by type
db_results = client.search.search_databases("Project")
page_results = client.search.search_pages("Meeting notes")

# Auto-paginate all results
all_results = client.search.search_all("Q1")

Users

# Current bot
me = client.users.me()

# List all workspace users
all_users = client.users.list_all()

# Retrieve by ID
user = client.users.retrieve("user-id")

Comments

# Retrieve comments on a page
comments = client.comments.retrieve("page-id")

# Post a comment
client.comments.create(
    parent={"page_id": "page-id"},
    rich_text=[RichText.text("Great work!")],
)

Filters reference

# Text / title
Filter.text("Name").equals("Alice")
Filter.text("Name").contains("Al")
Filter.text("Name").starts_with("A")
Filter.text("Name").is_empty()

# Number
Filter.number("Score").greater_than(80)
Filter.number("Score").less_than_or_equal_to(100)

# Checkbox
Filter.checkbox("Done").equals(True)

# Select / status
Filter.select("Status").equals("Active")
Filter.status("Status").does_not_equal("Archived")

# Multi-select
Filter.multi_select("Tags").contains("python")

# Date
Filter.date("Due").before("2025-01-01")
Filter.date("Due").past_week()
Filter.date("Due").next_month()

# Timestamp
Filter.created_time().after("2024-01-01")
Filter.last_edited_time().past_week()

# Compound
Filter.and_([
    Filter.select("Status").equals("Active"),
    Filter.number("Score").greater_than(80),
])
Filter.or_([
    Filter.text("Name").contains("Alice"),
    Filter.text("Name").contains("Bob"),
])

# Nested compound
Filter.and_([
    Filter.checkbox("Done").equals(False),
    Filter.or_([
        Filter.select("Priority").equals("High"),
        Filter.date("Due").before("2025-01-01"),
    ]),
])

# Raw (escape hatch for unsupported types)
Filter.raw({"property": "Formula", "formula": {"string": {"equals": "ok"}}})

Sorts reference

Sort.by_property("Name")                      # ascending by default
Sort.by_property("Score", "descending")
Sort.ascending("Name")                        # alias
Sort.descending("CreatedAt")                  # alias
Sort.by_timestamp("created_time", "descending")
Sort.by_timestamp("last_edited_time")

RichText reference

RichText.text("plain")
RichText.text("bold", bold=True)
RichText.text("italic", italic=True)
RichText.text("underline", underline=True)
RichText.text("strike", strikethrough=True)
RichText.text("code", code=True)
RichText.text("colored", color="red")
RichText.text("link", link="https://example.com")

RichText.mention_page("page-id")
RichText.mention_database("db-id")
RichText.mention_user("user-id")
RichText.mention_date("2024-01-01", end="2024-01-31")
RichText.equation("E=mc^2")

Error handling

from notion_database import (
    NotionAPIError,
    NotionNotFoundError,
    NotionRateLimitError,
    NotionUnauthorizedError,
)
import time

try:
    page = client.pages.retrieve("invalid-id")
except NotionNotFoundError:
    print("Page not found")
except NotionRateLimitError:
    time.sleep(1)
    page = client.pages.retrieve("invalid-id")
except NotionUnauthorizedError:
    print("Check your integration token")
except NotionAPIError as e:
    print(f"[{e.status_code}] {e.code}: {e.message}")

Color constants

from notion_database.const import (
    DEFAULT, GRAY, BROWN, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK, RED,
    GRAY_BACKGROUND, BROWN_BACKGROUND, ORANGE_BACKGROUND, YELLOW_BACKGROUND,
    GREEN_BACKGROUND, BLUE_BACKGROUND, PURPLE_BACKGROUND, PINK_BACKGROUND,
    RED_BACKGROUND,
)

BlockContent.paragraph("Highlighted", color=RED_BACKGROUND)

MCP integration

NotionClient is designed to map directly to Notion API endpoints, making it straightforward to expose as MCP tools. Each sub-client (databases, pages, blocks, search, users, comments) corresponds to one section of the Notion API docs. All parameters are typed and documented, so an AI can introspect the signatures without additional context.

License

LGPLv3

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

notion_database-2.0.dev1.tar.gz (30.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

notion_database-2.0.dev1-py3-none-any.whl (32.2 kB view details)

Uploaded Python 3

File details

Details for the file notion_database-2.0.dev1.tar.gz.

File metadata

  • Download URL: notion_database-2.0.dev1.tar.gz
  • Upload date:
  • Size: 30.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for notion_database-2.0.dev1.tar.gz
Algorithm Hash digest
SHA256 569e51d2074e7eda4e12c80ee7bf3e1bcbbf0fd500d43ff2125e7df67eb2c689
MD5 a904f0e4ab169d9e22d4ae81fa5f8c75
BLAKE2b-256 3647f1ec11a22ec98a0b34e0b9e1ff9db1aeede496dbf1686f25f5952192a552

See more details on using hashes here.

Provenance

The following attestation bundles were made for notion_database-2.0.dev1.tar.gz:

Publisher: publish-to-pypi.yml on minwook-shin/notion-database

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file notion_database-2.0.dev1-py3-none-any.whl.

File metadata

File hashes

Hashes for notion_database-2.0.dev1-py3-none-any.whl
Algorithm Hash digest
SHA256 de15648f2dad1002c7e659ffa5ca181a6a73b23d0e37158d1c954355a0a1eed6
MD5 0fb0d963c3cd8a5778f3ddc8efdfef9d
BLAKE2b-256 6d6faffddb0330ff7ded575c416f524c0c48de4f5628f4755963b0c63b78ba43

See more details on using hashes here.

Provenance

The following attestation bundles were made for notion_database-2.0.dev1-py3-none-any.whl:

Publisher: publish-to-pypi.yml on minwook-shin/notion-database

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page